From 0e7eed3cd55ac7d569ba498ee09456dcf7b339b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Popovics=20Andr=C3=A1s?= Date: Thu, 7 May 2020 23:23:11 +0200 Subject: [PATCH] Add missing documentation for extension loader callbacks (#1457) * Add missing documentation for extension loader callbacks * Fix messed up pipeline, how it could work until now??? * Travis yml fix * Travis yml fix --- .travis.yml | 25 ++-- .vscode/settings.json | 2 +- docs/extending/README.md | 1 + docs/extending/custom-extension-loaders.md | 112 ++++++++++++++++++ .../extensions-data-loader.guard.ts | 2 +- scripts/ci/job_hooks/before_install.sh | 5 + scripts/install.sh | 7 -- scripts/update-version.sh | 12 +- 8 files changed, 145 insertions(+), 21 deletions(-) create mode 100644 docs/extending/custom-extension-loaders.md delete mode 100755 scripts/install.sh diff --git a/.travis.yml b/.travis.yml index 0abd11575..546c9223c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,7 +26,7 @@ before_install: - sleep 3 - . ./scripts/ci/job_hooks/before_install.sh -install: ./scripts/install.sh +install: echo "no install" stages: - name: Quality and Unit tests @@ -44,17 +44,26 @@ jobs: include: - stage: Quality and Unit tests name: 'Code quality checks' - script: npm run lint - - name: 'Build no animation' - script: npm run build.e2e + script: npm ci && npm run lint + + - stage: Quality and Unit tests + name: 'Build (without animation)' + script: npm ci && npm run build.e2e after_success: ./scripts/ci/utils/artifact-to-s3.sh -a ./dist/app -o "$S3_DBP_FOLDER/alfresco-content-app.tar.bz2" - - name: 'Unit tests aos' - script: - - ng test adf-office-services-ext --watch=false - - name: 'Unit tests ACA' + cache: false + + - stage: Quality and Unit tests + name: 'Unit tests aos' + script: npm ci && ng test adf-office-services-ext --watch=false + cache: false + + - stage: Quality and Unit tests + name: 'Unit tests ACA' script: + - npm ci - ng test app --code-coverage --watch=false - bash <(curl -s https://codecov.io/bash) -X gcov + cache: false - stage: e2e name: Test Suite appNavigation&search diff --git a/.vscode/settings.json b/.vscode/settings.json index a143f058a..e1bb8415a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,7 +3,7 @@ "javascript.preferences.importModuleSpecifier": "relative", "typescript.preferences.quoteStyle": "single", "typescript.preferences.importModuleSpecifier": "relative", - "editor.formatOnSave": true, + "editor.formatOnSave": false, "[json]": { "editor.formatOnSave": false }, diff --git a/docs/extending/README.md b/docs/extending/README.md index abe4ea059..27c1a0e00 100644 --- a/docs/extending/README.md +++ b/docs/extending/README.md @@ -21,5 +21,6 @@ Learn how to extend the features of the Alfresco Content Application. - [Custom icons](/extending/icons) - [Registration](/extending/registration) - [Creating custom evaluators](/extending/creating-custom-evaluators) +- [Custom extension loaders](/extending/custom-extension-loaders.md) - [Tutorials](/extending/tutorials) - [Redistributable libraries](/extending/redistributable-libraries) diff --git a/docs/extending/custom-extension-loaders.md b/docs/extending/custom-extension-loaders.md new file mode 100644 index 000000000..c229dc519 --- /dev/null +++ b/docs/extending/custom-extension-loaders.md @@ -0,0 +1,112 @@ +--- +Title: Custom extension loaders +--- + +# Creating an extension loader + +There can be scenarios when there is a need to be able to preload some data, doing some health check or just kick off some services to be sure that a particular extension/plugin is set up properly and ready to function. + +## Lifecycle + +The extension loader callbacks are invoked when hitting the `root logged-in route` (''), which means they are called (once per application lifetime), when a user wants to access any of the authentication protected routes. + +```ts +export const APP_ROUTES: Routes = [ + { + path: 'login', + component: LoginComponent, + data: { + title: 'APP.SIGN_IN' + } + }, + ...// more unauthenticated routes + { + path: '', + component: AppLayoutComponent, + canActivate: [AuthGuardEcm, ExtensionsDataLoaderGuard], + children: [ + ...// more authenticated routes + { + path: 'trashcan', + loadChildren: './components/trashcan/trashcan.module#AppTrashcanModule' + } + ], + canActivateChild: [AuthGuardEcm] + } +]; +``` + +The Alfresco Content App provided `ExtensionsDataLoaderGuard` router guards executes the registered callback in the order they were registered. + +## Examples + +- Ensuring that a backend service is running and showing the extension provided features only if the service is working correctly. +- Preloading some data after the application is started but before it got rendered in an asynchronous way, which might be needed for an extension to work. + +## Registering a loader for an extension + +For this each extension can register its own loader, which can be either synchronous or asynchronous. The application is going to be rendered only after every extension loader is either finished or any of them errored. +This registering can be done, using Angular's multi-provider capabilities: + +```ts +import { EXTENSION_DATA_LOADERS } from '@alfresco/aca-shared'; + +@NgModule({ + imports: [...], + declarations: [...], + entryComponents: [...], + providers: [ + ... + { + provide: EXTENSION_DATA_LOADERS, + multi: true, + useValue: myExtensionLoader + }, + ... + ], +}) +export class MyExtensionModule { +``` + +1. `MyExtensionModule` is the extension's entry module, which needs to be imported by the extensions.module.ts. +2. `EXTENSION_DATA_LOADERS` is an injection token which can be imported from the `aca-shared` package. +3. `myExtensionLoader` is the function which preloads data / sets the necessary services for the extension. + +### ExtensionLoaderCallback + +The signature of an extension loader callback is the following: + +```ts +type ExtensionLoaderCallback = (route: ActivatedRouteSnapshot) => Observable; +``` + +The callback needs to return an Observable of boolean (of success). The boolean value returned, from application loading/rendering perspective doesn't really matter, it is rather representative, it doesn't block the application to be rendered. + +Because of the following behaviour mentioned above, each extension's loader might implement its own error handling within itself, to provide a better User eXperience by for example notifying the user with an error snackbar about the problem. + +```ts +export const myExtensionLoader = (route: ActivatedRouteSnapshot) => { + if (!isExtensionEnabled()) { + // Do nothing, simply let the next extension's loader callback invoked, if any + // The returned boolean doesn't matter here, doesn't block anything + return of(true); + } + + // The returned boolean doesn't matter here, doesn't block anything + return myExtensionService.checkBackendHealth().pipe( + tap((status) => { + if (!status) { + // If the BE is down, let the user know what to expect + store.dispatch( + new SnackbarErrorAction("Backend error. My Extension's features are disabled.") + ); + } + }) + ); + }; +``` + +## Warning + +Always handle (catch) the errors in your extension loader! Although an uncaught, errored extension loader can't prevent the application to be rendered, but it can short circuit any of the other extension loaders. (This is coming from the nature of how the forkJoin is implemented in the RxJs library.) +This would essentially mean, that if you have 3 extensions, with registered asynchronous loaders for each, in case of one of them errors, the application doesn't wait the other 2 to be finished, but continues to render. Which can result that the other (2) not errored extension won't behave properly, since the application rendering haven't waited until their loaders were finished. diff --git a/projects/aca-shared/src/lib/adf-extensions/extensions-data-loader.guard.ts b/projects/aca-shared/src/lib/adf-extensions/extensions-data-loader.guard.ts index 71e3f09ee..1d1316d73 100644 --- a/projects/aca-shared/src/lib/adf-extensions/extensions-data-loader.guard.ts +++ b/projects/aca-shared/src/lib/adf-extensions/extensions-data-loader.guard.ts @@ -30,7 +30,7 @@ import { map, catchError } from 'rxjs/operators'; export type ExtensionLoaderCallback = ( route: ActivatedRouteSnapshot -) => Observable; +) => Observable; export function DefaultExtensionLoaderFactory() { return []; diff --git a/scripts/ci/job_hooks/before_install.sh b/scripts/ci/job_hooks/before_install.sh index f45c4dc39..f025c4c0c 100755 --- a/scripts/ci/job_hooks/before_install.sh +++ b/scripts/ci/job_hooks/before_install.sh @@ -1,5 +1,10 @@ #!/usr/bin/env bash +# Run the build on mergin to development with always the latest ADF +if [ "${TRAVIS_BRANCH}" == "develop" ] && [ "${TRAVIS_EVENT_TYPE}" == "push" ]; then + ./scripts/update-version.sh -v --$ADF_RELEASE_VERSION +fi + pip install --user awscli export NODE_OPTIONS="--max_old_space_size=30000" diff --git a/scripts/install.sh b/scripts/install.sh deleted file mode 100755 index a5f3af8d7..000000000 --- a/scripts/install.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash - -DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" - -if [ "${TRAVIS_BRANCH}" != "master" ]; then - ./scripts/update-version.sh -v alpha -fi diff --git a/scripts/update-version.sh b/scripts/update-version.sh index 63d859a53..2928509e6 100755 --- a/scripts/update-version.sh +++ b/scripts/update-version.sh @@ -23,12 +23,16 @@ set_version() { } update(){ - for (( j=0; j<${libslength}; j++ )); + eval libsWithVersions=(); + + for (( i=0; i<${libslength}; i++ )); do - EXACT_VERSION="${libs[$j]}@${VERSION}" - echo "====== ${EXACT_VERSION} ======" - npm i -E ${EXACT_VERSION} + EXACT_VERSION="${libs[$i]}@${VERSION}" + eval libsWithVersions=( "${libsWithVersions[@]}" "${EXACT_VERSION}" ) done + + echo "npm i --ignore-scripts -E ${libsWithVersions[*]}" + npm i --ignore-scripts -E ${libsWithVersions[*]} } while [[ $1 == -* ]]; do