diff --git a/lib/content-services/src/lib/services/public-api.ts b/lib/content-services/src/lib/services/public-api.ts index 98705113dd..36d39adc3d 100644 --- a/lib/content-services/src/lib/services/public-api.ts +++ b/lib/content-services/src/lib/services/public-api.ts @@ -17,3 +17,4 @@ export * from './alfresco-api.service'; export { AlfrescoApiFactory } from './alfresco-api.interface'; +export * from './extension-manager.service'; diff --git a/lib/core/src/lib/models/extension-info.model.ts b/lib/core/src/lib/models/extension-info.model.ts new file mode 100644 index 0000000000..c847e5e562 --- /dev/null +++ b/lib/core/src/lib/models/extension-info.model.ts @@ -0,0 +1,37 @@ +/*! + * @license + * Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ActionRef, RouteRef, RuleRef } from '@alfresco/js-api'; + +export class ExtensionInfoModel { + $id: string; + $name: string; + $version: string; + $vendor: string; + $license: string; + $description?: string; + $dependencies?: Array; + $compatibilities?: Array; + + extensionId: string; + rules?: Array; + routes?: Array; + actions?: Array; + features?: { + [key: string]: any; + }; +} diff --git a/lib/core/src/lib/models/public-api.ts b/lib/core/src/lib/models/public-api.ts index 0c6d07fe4f..0b0bad5d72 100644 --- a/lib/core/src/lib/models/public-api.ts +++ b/lib/core/src/lib/models/public-api.ts @@ -22,3 +22,4 @@ export * from './request-pagination.model'; export * from './decimal-number.model'; export * from './general-user.model'; export * from './path.model'; +export * from './extension-info.model'; diff --git a/lib/core/src/lib/services/extension-manager.service.spec.ts b/lib/core/src/lib/services/extension-manager.service.spec.ts new file mode 100644 index 0000000000..96646e393a --- /dev/null +++ b/lib/core/src/lib/services/extension-manager.service.spec.ts @@ -0,0 +1,81 @@ +/*! + * @license + * Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ExtensionManagerService } from './extension-manager.service'; +import { TestBed } from '@angular/core/testing'; +import { CoreTestingModule } from '../testing/core.testing.module'; +import { ExtensionComposition, ExtensionCompositionEntry } from '@alfresco/js-api'; +import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; +import { first } from 'rxjs/operators'; + +describe('ExtensionManagerService', () => { + let extensionManagerService: ExtensionManagerService; + let httpTestingController: HttpTestingController; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [CoreTestingModule, HttpClientTestingModule] + }); + extensionManagerService = TestBed.inject(ExtensionManagerService); + httpTestingController = TestBed.inject(HttpTestingController); + }); + + it('should fetch saved plugin state for provided instance', () => { + const getSpy = spyOn(extensionManagerService.settingsApi, 'getSavedExtensionState').and.returnValue( + Promise.resolve(new ExtensionCompositionEntry()) + ); + const instanceId = 'test-instance-id'; + extensionManagerService.getSavedPluginState(instanceId); + expect(getSpy).toHaveBeenCalledOnceWith('test-instance-id'); + }); + + it('should fetch saved plugin state for provided instance', () => { + const extensionComposition = new ExtensionComposition(); + const instanceId = 'test-instance-id'; + const putSpy = spyOn(extensionManagerService.settingsApi, 'publishExtensionConfig').and.returnValue(Promise.resolve()); + extensionManagerService.publishExtensionConfig(instanceId, extensionComposition); + expect(putSpy).toHaveBeenCalledOnceWith('test-instance-id', extensionComposition); + }); + + it('should fetch extension info from pluginInfo.json of a running instance of ADW', (done) => { + extensionManagerService + .getPluginInfo('test-adw-url') + .pipe(first()) + .subscribe((data) => { + expect(data).toEqual([]); + done(); + }); + const req = httpTestingController.expectOne('test-adw-url/pluginInfo.json'); + expect(req.request.method).toEqual('GET'); + req.flush([]); + httpTestingController.verify(); + }); + + it('should fetch extension defaults from appConfig.json of a running instance of ADW', (done) => { + extensionManagerService + .getDefaultPluginState('test-adw-url') + .pipe(first()) + .subscribe((data) => { + expect(data).toEqual({ plugins: {} }); + done(); + }); + const req = httpTestingController.expectOne('test-adw-url/app.config.json'); + expect(req.request.method).toEqual('GET'); + req.flush({ plugins: {} }); + httpTestingController.verify(); + }); +}); diff --git a/lib/core/src/lib/services/extension-manager.service.ts b/lib/core/src/lib/services/extension-manager.service.ts new file mode 100644 index 0000000000..213af6044e --- /dev/null +++ b/lib/core/src/lib/services/extension-manager.service.ts @@ -0,0 +1,53 @@ +/*! + * @license + * Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Injectable } from '@angular/core'; +import { AlfrescoApiService } from './alfresco-api.service'; +import { from, Observable } from 'rxjs'; +import { AppConfigPluginRef, SettingsApi, ExtensionCompositionEntry, ExtensionComposition } from '@alfresco/js-api'; +import { HttpClient } from '@angular/common/http'; +import { ExtensionInfoModel } from '../models'; + +@Injectable({ + providedIn: 'root' +}) +export class ExtensionManagerService { + constructor(private apiService: AlfrescoApiService, private httpClient: HttpClient) {} + + private _settingsApi: SettingsApi; + + get settingsApi(): SettingsApi { + this._settingsApi = this._settingsApi ?? new SettingsApi(this.apiService.getInstance()); + return this._settingsApi; + } + + getPluginInfo(instanceUrl: string): Observable { + return this.httpClient.get(`${instanceUrl}/pluginInfo.json`); + } + + getSavedPluginState(instanceId: string): Observable { + return from(this.settingsApi.getSavedExtensionState(instanceId)); + } + + getDefaultPluginState(instanceUrl: string): Observable { + return this.httpClient.get(`${instanceUrl}/app.config.json`); + } + + publishExtensionConfig(instanceId: string, pluginConfig: ExtensionComposition): Observable { + return from(this.settingsApi.publishExtensionConfig(instanceId, pluginConfig)); + } +} diff --git a/lib/extensions/src/lib/services/extension.service.ts b/lib/extensions/src/lib/services/extension.service.ts index 990893284e..18745c3693 100644 --- a/lib/extensions/src/lib/services/extension.service.ts +++ b/lib/extensions/src/lib/services/extension.service.ts @@ -25,7 +25,7 @@ import { RuleService } from './rule.service'; import { ExtensionElement } from '../config/extension-element'; import { BehaviorSubject, Observable } from 'rxjs'; import { mergeArrays, mergeObjects } from '../config/extension-utils'; -import { ActionRef, RouteRef, RuleRef } from '@alfresco/js-api'; +import { ActionRef, ExtensionComposition, RouteRef, RuleRef } from '@alfresco/js-api'; /** * The default extensions factory @@ -109,8 +109,9 @@ export class ExtensionService { return config; } - appendConfig(partialConfig: ExtensionConfig) { - this.config = { ...this.config, + appendConfig(partialConfig: ExtensionComposition) { + this.config = { + ...this.config, rules: mergeArrays(this.config.rules, partialConfig.rules), features: mergeObjects(this.config.features, partialConfig.features), routes: mergeArrays(this.config.routes, partialConfig.routes), diff --git a/lib/js-api/src/api/content-rest-api/api/settings.api.ts b/lib/js-api/src/api/content-rest-api/api/settings.api.ts index 32c82b9a4d..5db8ef0849 100644 --- a/lib/js-api/src/api/content-rest-api/api/settings.api.ts +++ b/lib/js-api/src/api/content-rest-api/api/settings.api.ts @@ -17,10 +17,9 @@ import { BaseApi } from './base.api'; import { throwIfNotDefined } from '../../../assert'; -import { ExtensionConfig } from '../model/extensionConfig'; -import { ExtensionConfigEntry } from '../model/extensionConfigEntry'; +import { ExtensionComposition } from '../model/extensionComposition'; +import { ExtensionCompositionEntry } from '../model/extensionCompositionEntry'; export class SettingsApi extends BaseApi { - /** * Gets the published extension configuration from * the database @@ -28,7 +27,7 @@ export class SettingsApi extends BaseApi { * @param instanceId Unique ID for a running instance of ADW * for which configuration is to be fetched */ - getSavedExtensionState(instanceId: string): Promise { + getSavedExtensionState(instanceId: string): Promise { throwIfNotDefined(instanceId, 'instanceId'); const pathParams = { @@ -50,7 +49,7 @@ export class SettingsApi extends BaseApi { * @param extensionConfig Extension configuration that is to * be saved */ - publishExtensionConfig(instanceId: string, extensionConfig: ExtensionConfig): Promise { + publishExtensionConfig(instanceId: string, extensionConfig: ExtensionComposition): Promise { throwIfNotDefined(instanceId, 'instanceId'); throwIfNotDefined(extensionConfig, 'extensionConfig'); diff --git a/lib/js-api/src/api/content-rest-api/docs/SettingsApi.md b/lib/js-api/src/api/content-rest-api/docs/SettingsApi.md index 4cd2b74c70..1a0f7e1247 100644 --- a/lib/js-api/src/api/content-rest-api/docs/SettingsApi.md +++ b/lib/js-api/src/api/content-rest-api/docs/SettingsApi.md @@ -19,9 +19,9 @@ Gets the extension configuration saved on the backend |----------------|--------|----------------------------------------------|-------| | **instanceId** | string | The identifier of a running instance of ADW. | | -**Return type**: [ExtensionConfigEntry](#ExtensionConfigEntry) +**Return type**: [ExtensionCompositionEntry](#ExtensionCompositionEntry) -## publishExtensionConfig +## publishExtensionComposition Saves an extension configuration on the backend @@ -30,7 +30,7 @@ Saves an extension configuration on the backend | Name | Type | Description | |---------------------|-------------------------------------|----------------------------------------------| | **instanceId** | string | The identifier of a running instance of ADW. | -| **extensionConfig** | [ExtensionConfig](#ExtensionConfig) | The extension configuration to be saved. | +| **extensionConfig** | [ExtensionComposition](#ExtensionComposition) | The extension configuration to be saved. | **Example** @@ -40,7 +40,7 @@ import {AlfrescoApi, SitesApi} from '@alfresco/js-api'; const alfrescoApi = new AlfrescoApi(/*..*/); const settingsApi = new SettingsApi(alfrescoApi); -const extensionConfig = new ExtensionConfig(); +const extensionConfig = new ExtensionComposition(); extensionConfig = { appConfig: {}, features: {}, @@ -56,15 +56,15 @@ settingsApi.publishExtensionConfig(``, extensionConfig).then(() => { # Models -## ExtensionConfigEntry +## ExtensionCompositionEntry **Properties** | Name | Type | |-----------|-------------------------------------| -| **entry** | [ExtensionConfig](#ExtensionConfig) | +| **entry** | [ExtensionComposition](#ExtensionComposition) | -## ExtensionConfig +## ExtensionComposition **Properties** diff --git a/lib/js-api/src/api/content-rest-api/model/extensionConfig.ts b/lib/js-api/src/api/content-rest-api/model/extensionComposition.ts similarity index 85% rename from lib/js-api/src/api/content-rest-api/model/extensionConfig.ts rename to lib/js-api/src/api/content-rest-api/model/extensionComposition.ts index 5d751e98c2..ff07d41b08 100644 --- a/lib/js-api/src/api/content-rest-api/model/extensionConfig.ts +++ b/lib/js-api/src/api/content-rest-api/model/extensionComposition.ts @@ -20,12 +20,12 @@ import { RuleRef } from './ruleRef'; import { RouteRef } from './routeRef'; import { ActionRef } from './actionRef'; -export class ExtensionConfig { +export class ExtensionComposition { appConfig: AppConfigPluginRef; - rules?: Array; - routes?: Array; - actions?: Array; + rules?: Array = []; + routes?: Array = []; + actions?: Array = []; features?: { [key: string]: any; - }; + } = {}; } diff --git a/lib/js-api/src/api/content-rest-api/model/extensionCompositionEntry.ts b/lib/js-api/src/api/content-rest-api/model/extensionCompositionEntry.ts new file mode 100644 index 0000000000..89b9170e53 --- /dev/null +++ b/lib/js-api/src/api/content-rest-api/model/extensionCompositionEntry.ts @@ -0,0 +1,22 @@ +/*! + * @license + * Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ExtensionComposition } from './extensionComposition'; + +export class ExtensionCompositionEntry { + entry: ExtensionComposition; +} diff --git a/lib/js-api/src/api/content-rest-api/model/index.ts b/lib/js-api/src/api/content-rest-api/model/index.ts index 8144e8bcae..882da16db4 100644 --- a/lib/js-api/src/api/content-rest-api/model/index.ts +++ b/lib/js-api/src/api/content-rest-api/model/index.ts @@ -81,8 +81,8 @@ export * from './download-status'; export * from './downloadBodyCreate'; export * from './downloadEntry'; export * from './errorError'; -export * from './extensionConfig'; -export * from './extensionConfigEntry'; +export * from './extensionComposition'; +export * from './extensionCompositionEntry'; export * from './favorite'; export * from './favoriteBodyCreate'; export * from './favoriteEntry';