Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,32 +1,36 @@
import { Injectable, Signal } from '@angular/core';
import { map } from 'rxjs';
import { toObservable } from '@angular/core/rxjs-interop';
import { transient } from 'projects/core';
import { filter, first, map } from 'rxjs';
import { classLog } from '../../../../../shared/logging';
import { FileUploadResult } from '../../shared/components/file-upload-dialog/file-upload-dialog.models';
import { HttpServiceBase } from '../../shared/services/http-service-base';
import { SysDataService } from '../../shared/services/sys-data.service';
import { Extension, ExtensionInspectResult, ExtensionPreflightItem } from './extension.model';

@Injectable()
export class AppExtensionsService extends HttpServiceBase {
#sysData = transient(SysDataService);
log = classLog({ AppExtensionsService });

/** Get all extensions with live refresh capability */
getAllLive(refresh: Signal<unknown>) {
return this.newHttpResource<{ extensions: Extension[] }>(() => {
// Watch the refresh signal to trigger reloads
refresh();

return {
url: this.apiUrl('admin/appExtensions/extensions'),
params: { appId: this.appId },
method: 'GET',
};
const extensions = this.#sysData.get<Extension>({
refresh,
source: 'System.AppExtensions',
});
return { value: extensions };
}

getAll() {
return this.http.get<{ extensions: Extension[] }>(this.apiUrl('admin/appExtensions/extensions'), {
params: { appId: this.appId },
const resource = this.#sysData.getMany<{ default?: Extension[]; Default?: Extension[] }>({
source: 'System.AppExtensions',
});
return toObservable(resource.value, { injector: this.injector }).pipe(
filter((result): result is { default?: Extension[]; Default?: Extension[] } => result != null),
first(),
map(result => ({ extensions: result.default ?? result.Default ?? [] })),
);
}

/** Update config (mutations still best done via HttpClient per Angular docs) */
Expand Down Expand Up @@ -131,17 +135,31 @@ export class AppExtensionsService extends HttpServiceBase {
}

preflightExtension(name: string, edition?: string) {
const params: { appId: string, name: string, edition?: string } = {
appId: this.appId,
name,
interface InspectStreams {
default?: { foundLock: boolean }[];
files?: ExtensionInspectResult['files'];
summary?: ExtensionInspectResult['summary'][];
contentTypes?: ExtensionInspectResult['contentTypes'];
}

const resource = this.#sysData.getMany<InspectStreams>({
source: 'System.AppExtensionDetails',
streams: '*',
params: { ExtensionName: name, ...(edition && { Edition: edition }) },
});
return {
...resource,
value: () => {
const result = resource.value();
if (!result) return undefined;
return {
foundLock: result.default?.[0]?.foundLock ?? false,
files: result.files ?? [],
summary: result.summary?.[0],
contentTypes: result.contentTypes ?? [],
} as ExtensionInspectResult;
},
};
if (edition) params.edition = edition;

return this.newHttpResource<ExtensionInspectResult>(() => ({
url: this.apiUrl('admin/appExtensions/inspect'),
params,
method: 'GET',
}));
}

deleteExtension(name: string, edition?: string, force = false, withData = false) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,7 @@ export class AppExtensions implements OnInit {
/** Signal to trigger reloading of data */
refresh = signal(0);

#extensionsRaw = this.#extensionsSvc.getAllLive(this.refresh).value;
extensions = computed(() => this.#extensionsRaw()?.extensions ?? []);
extensions = this.#extensionsSvc.getAllLive(this.refresh).value;

ngOnInit() {
// register once
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ export class DeleteExtensionComponent implements OnInit {
preflightResult = this.#extensionsSvc.preflightExtension(this.extensionFolder, this.edition).value;
totalLocalEntities = computed(() => {
const result = this.preflightResult();
if (!result?.data?.contentTypes) return 0;
if (!result?.contentTypes) return 0;

return result.data.contentTypes
return result.contentTypes
.map(ct => ct.localEntities)
.reduce((sum, n) => sum + n, 0);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,11 @@ export interface ExtensionInspectResult {
added: number;
missing: number;
};
data: {
contentTypes: {
name: string;
guid: string; // guid
localEntities: number;
}[];
};
contentTypes: {
name: string;
guid: string; // guid
localEntities: number;
}[];
}

export interface ExtensionPreflightItem {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
@if (data()?.foundLock) {
<h4>Data</h4>
<div>
Content Types: {{ data().data.contentTypes.length }}
Content Types: {{ data().contentTypes.length }}
<br />

@for (ct of data().data.contentTypes; track ct) {
@for (ct of data().contentTypes; track ct) {
<div class="content-type-chip">
{{ ct.name }} ({{ ct.localEntities }})
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
@if (queryTypes()) {
@for (contentType of queryTypes().sort(); track contentType) {
<mat-option [value]="contentType.Guid">
{{ contentType.Name }}
{{ contentType.Title || contentType.Name }}
</mat-option>
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { httpResource } from '@angular/common/http';
import { computed, Injectable, Signal } from '@angular/core';
import { toObservable } from '@angular/core/rxjs-interop';
import { transient } from 'projects/core';
Expand All @@ -12,13 +11,13 @@ import { ScopeDetailsDto } from '../models/scopedetails.dto';

// We should list all the "full" paths here, so it's easier to find when searching for API calls
export const webApiTypeRoot = 'admin/type/';
const webApiTypes = 'admin/type/list';
const webApiTypeSave = 'admin/type/save';
const webApiTypeDelete = 'admin/type/delete';
const webApiTypeImport = 'admin/type/import';
const webApiTypeAddGhost = 'admin/type/addghost';

const dataSourceContentTypeDetails = 'System.ContentTypeDetails';
const dataSourceContentTypes = 'System.ContentTypes';
const dataSourceScopes = 'System.Scopes';

interface ScopeData {
Expand All @@ -29,6 +28,12 @@ interface ScopeData {
TypesOfApp: number;
}

interface ContentTypeDataSourceItem extends Partial<ContentType> {
AttributesCount?: number;
RepositoryType?: string;
Title?: string;
}

@Injectable()
export class ContentTypesService extends HttpServiceBase {
#sysData = transient(SysDataService);
Expand Down Expand Up @@ -71,16 +76,55 @@ export class ContentTypesService extends HttpServiceBase {
});
}
getTypes(scope: Signal<string>) {
return httpResource<ContentType[]>(() => ({
url: this.apiUrl(webApiTypes),
params: { appId: this.appId, scope: scope() }
}), { defaultValue: [] });
const contentTypes = this.#sysData.get<ContentTypeDataSourceItem>({
source: dataSourceContentTypes,
params: computed(() => ({
AppId: this.appId,
Scope: scope(),
})),
noCamel: true,
});
return { value: computed(() => contentTypes().map(item => this.#mapContentType(item))) };
}

retrieveContentTypesPromise(scope: string): Promise<ContentType[]> {
return this.fetchPromise<ContentType[]>(webApiTypes, {
params: { appId: this.appId, scope }
const resource = this.#sysData.getMany<{ Default?: ContentTypeDataSourceItem[] }>({
source: dataSourceContentTypes,
params: {
AppId: this.appId,
Scope: scope,
},
noCamel: true,
});
return firstValueFrom(toObservable(resource.value, { injector: this.injector }).pipe(
filter((result): result is { Default?: ContentTypeDataSourceItem[] } => result != null),
first(),
)).then(result => (result.Default ?? []).map(item => this.#mapContentType(item)));
}

#mapContentType(item: ContentTypeDataSourceItem): ContentType {
const nameId = item.NameId ?? item.StaticName ?? '';
const name = item.Name ?? item.Title ?? nameId;

return {
...item,
Description: item.Description ?? '',
Fields: item.Fields ?? item.AttributesCount ?? 0,
Id: item.Id ?? 0,
Items: item.Items ?? 0,
Label: item.Label ?? item.Title ?? name,
Metadata: item.Metadata ?? [],
Name: name,
Permissions: item.Permissions ?? { Count: 0 },
Scope: item.Scope ?? '',
SharedDefId: item.SharedDefId ?? 0,
StaticName: item.StaticName ?? nameId,
NameId: nameId,
EditInfo: item.EditInfo ?? {
ReadOnly: item.RepositoryType != null && item.RepositoryType !== 'Sql',
},
TitleField: item.TitleField ?? '',
};
}

getScopesPromise(): Promise<ScopeOption[]> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,6 @@ const webApiQueryDelete = 'admin/query/Delete';
export const webApiQueryRun = 'admin/query/RunDev';
export const webApiQueryDebugStream = 'admin/query/DebugStream';
export const webApiQuerySave = 'admin/query/Save';
export const webApiQueryGet = 'admin/query/Get';
export const webApiQueryDataSources = 'admin/query/DataSources';

@Injectable()
export class PipelinesService extends HttpServiceBase {
Expand All @@ -53,7 +51,7 @@ export class PipelinesService extends HttpServiceBase {
const l = this.log.fnIf('getAll');
const resource = this.getAllSig(contentType);
return l.r(toObservable(resource.value, { injector: this.injector }).pipe(
map(streams => streams?.Default ?? []),
map(streams => (streams?.Default ?? []).map(query => this.#withDisplayName(query))),
));
}

Expand All @@ -62,7 +60,7 @@ export class PipelinesService extends HttpServiceBase {
const resource = this.getAllSig(contentType, refresh);
return {
...resource,
value: computed(() => resource.value()?.Default ?? []),
value: computed(() => (resource.value()?.Default ?? []).map(query => this.#withDisplayName(query))),
};
}

Expand All @@ -71,11 +69,20 @@ export class PipelinesService extends HttpServiceBase {
const resource = this.getAllSig(contentType);
const res = {
...resource,
value: computed(() => resource.value()?.Default ?? initial ?? []),
value: computed(() => (resource.value()?.Default ?? initial ?? []).map(query => this.#withDisplayName(query))),
};
return l.r(res);
}

#withDisplayName(query: Query): Query {
const displayName = query.Title || query.Name || `Query ${query.Id}`;
return {
...query,
Name: query.Name || displayName,
Title: query.Title || displayName,
};
}

importQuery(file: File) {
const l = this.log.fnIf('importQuery');
const obs = from(toBase64(file)).pipe(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,38 +1,29 @@
import { httpResource } from '@angular/common/http';
import { Injectable, Signal } from '@angular/core';
import { transient } from 'projects/core';
import { FileUploadResult } from '../../shared/components/file-upload-dialog';
import { HttpServiceBaseSignal } from '../../shared/services/http-service-base-signal';
import { SysDataService } from '../../shared/services/sys-data.service';
import { ViewUsage } from '../models/view-usage.model';
import { View } from '../models/view.model';

const webApiViews = 'admin/view/all';
const webApiViewDelete = 'admin/view/delete';
const webApiViewImport = 'admin/view/import';
// const webApiViewPolymorph = 'admin/view/polymorphism';
const webApiViewUsage = 'admin/view/usage';
const webApiJson = 'admin/view/json';

export const Polymorphism_DS_ID = 'a495b51f-44e7-4335-81db-b8a7e33120f0'; // Polymorphism DataSource internal ID
@Injectable()
export class ViewsService extends HttpServiceBaseSignal {
#sysData = transient(SysDataService);

getAllOnce() {
return httpResource<View[]>(() => {
return ({
url: this.apiUrl(webApiViews),
params: { appId: this.appId }
});
});
const views = this.#sysData.get<View>({ source: 'System.Views', noCamel: true });
return { value: views };
}

getAllLive(refresh: Signal<unknown>) {
return httpResource<View[]>(() => {
refresh();
return ({
url: this.apiUrl(webApiViews),
params: { appId: this.appId }
});
});
const views = this.#sysData.get<View>({ refresh, source: 'System.Views', noCamel: true });
return { value: views };
}

async delete(id: number): Promise<number> {
Expand All @@ -57,9 +48,14 @@ export class ViewsService extends HttpServiceBaseSignal {
}

getUsage(guid: string) {
return this.newHttpResource<ViewUsage[]>(() => ({
url: this.apiUrl(webApiViewUsage),
params: { appId: this.appId, guid: guid }
}));
const usage = this.#sysData.get<ViewUsage>({
source: 'System.ViewUsage',
params: {
AppId: this.appId,
ViewGuid: guid,
},
noCamel: true,
});
return { value: usage };
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { httpResource } from '@angular/common/http';
import { Injectable, Signal } from '@angular/core';
import { transient } from 'projects/core';
import { Observable } from 'rxjs';
Expand All @@ -8,7 +7,6 @@ import { App, PendingApp } from '../models/app.model';

const dataSourceApps = 'System.Apps';
const dataSourceInheritableApps = 'System.InheritableApps';
const webApiAppRootPendingApps = 'admin/app/GetPendingApps';
const webApiAppRootApp = 'admin/app/app';
const webApiAppRootInstallPendingApps = 'admin/app/InstallPendingApps';
const webApiAppRootFlushcache = 'admin/app/flushcache';
Expand All @@ -33,10 +31,12 @@ export class AppsListService extends HttpServiceBaseSignal {
}

getPendingApps() {
return httpResource<PendingApp[]>(() => ({
url: this.apiUrl(webApiAppRootPendingApps),
params: { zoneId: this.zoneId },
}));
const pendingApps = this.#sysData.get<PendingApp>({
source: 'System.AppsPendingInitialization',
params: { ZoneId: this.zoneId },
noCamel: true,
});
return { value: pendingApps };
}

create(name: string, inheritAppId?: number, templateId?: number, folder?: string, displayName?: string) {
Expand Down
Loading