| Term | Definition |
|---|---|
| CME | Consort Microservice Experience |
| View | an independent part of the application shown in the main content area, reachable via main navigation |
| Palette | The property pane of a view |
| Project | A specific project of a customer that uses microservices |
| Phase | A project can have different Phases. Phases categorize and sequentialize participating microservices |
| Solution Diagram | Diagram containing all phases with its microservices that are part of the project |
| Environment | E.g. development or production |
| Cluster | The kubernetes cluster |
Every app/library may depend on logging and i18n.
showcase project is omitted in this diagram for clearness. It may depend on anything the cme-ui depends.
graph TD;
logging;
i18n;
rxjs-utils;
shared;
testing;
cost-view;
quality-view;
solution-view;
someother-future-view;
core-services;
connector-kubernetes;
connector-metadata;
cme-ui-->core-services;
cme-ui-. lazy .->solution-view;
cme-ui-. lazy .->quality-view;
cme-ui-. lazy .->cost-view;
cme-ui-. lazy .->someother-future-view;
core-services-->connector-kubernetes;
core-services-->connector-metadata;
connector-kubernetes-->connector-base;
connector-metadata-->connector-base;
connector-aws-costs-->connector-base;
connector-cloudwatch-logs-->connector-base;
connector-feature-toggle-service-->connector-base;
connector-gitlab-->connector-base;
connector-jira-->connector-base;
connector-quality-->connector-base;
solution-view-->core-services;
solution-view-->connector-cloudwatch-logs;
solution-view-->connector-feature-toggle-service;
solution-view-->connector-gitlab;
solution-view-->connector-jira;
cost-view-->connector-aws-costs;
cost-view-->core-services;
quality-view-->core-services
quality-view-->connector-quality;
someother-future-view-->core-services;
To view an always up-to-date dependency graph, use command npm run dep-graph.
To create a new view do the follwing:
-
Create a new lazy loaded module lib for the view:
ng generate lib my-new-view --routing --lazy --parentModule=apps/cme-ui/src/app/app.module.ts --tag=view -
Set the
cmeprefix for this lib in its app config of.angular-cli.json -
Create an entry component that uses
cme-view-containerto set up the subheader and palette (optional) -
Add the route to
app-routing.module -
If the view should show up in presentation mode, add the following route data:
{ shouldShowInPresentationMode: true, pageName: 'my-new-view' }
-
Add the menu entry to
navigation.component.ts(this should be automated some time in the future) -
To be able to directly import the json files, we need to define a dynamic module, so that TypeScript knows what's going on. We do this by creating a
typings.d.tsfile in the lib's src folder with this content:declare module '*.json' { const value: any; export default value; }
-
In the lib's root module import
I18nModule.forChild()and add the content of those translation json files as arguments:import * as translationsDe from './i18n/de.json'; import * as translationsEn from './i18n/en.json'; // Translations must be exported to work with aot in prod build! export const en = translationsEn; export const de = translationsDe; ... I18nModule.forChild({ en, de });
Don't forget to export the imported json translations, otherwise translation will not work in production builds!
-
Import
I18nModulein the entry component's module and add a translatable title to thecme-view-container, like[title]="'my-new-view.title' | translate" -
For translations, create a subfolder somewhere in the src folder of the new module library, e.g. called
i18n. Extend theextractTranslations.shto extract the tranlation key of the new lib into thei18nfolder (copy/paste, but don't forget to change the folders). -
Run the
extract-translationsscript and add the translation values to the newly created keys in the main app's language json files and the new view's language json files -
Add the translation key and value for the navigation entry to the language json files of the main app:
"my-new-view": { "menu-entry": "My New View" }
Done. We should definitely automate some steps of this process, e.g. with schematics. :-)
Don't use console.log() or the other console methods for logging. Use the
class LogService in @cme2/logging instead.
We use http://www.ngx-translate.com/ for translations. This is encapsulated in the project i18n.
For information about how to use it, please see here: https://github.com/ngx-translate/core#4-use-the-service-the-pipe-or-the-directive.
Lazy loaded modules like SolutionView bring their own translations. To avoid dependencies from main app to those views, I18nModule.forChild(...) provides functionality to merge additional child translations into the global translations.
The easiest way to add new translations is to first add it to the template where the text shall be shown, e.g. via {{ 'my-component.my text t' | translate }}. Then let the extract-translations script add the new keys and in the end add the correct translation values for the keys:
Run npm run extract-translations to extract the translation keys. They will be added to the correct {en,de}.json files depending on whether the translation key was added to the main app or a view (views are responsible for setting up their language files and add an appropriate script). You should always git commit before doing so, to easily see the changes.
ngx-translate-extract is used under the hood. It won't find all translation keys, especially if hidden in some bindings, but hopefully it helps. Be aware, that unused keys are not deleted. There is an option for this (--clean) but it's way too zealous.
If you use translation keys in the TypeScript code file instead of the template you can use a marker function to mark strings for translation extraction. See https://github.com/biesbjerg/ngx-translate-extract#mark-strings-for-extraction-using-a-marker-function
Unfortunately the provided function _() from ngx-translate-extract package rises some issues during compilation.
That's why we have re-implemented our own function _() in library @cme2/i18n.
Example of how to mark a string in TypeScript code to be a translation key:
import { _ } from '@cme2/i18n';
const thisIsATranslationKey: string = _('category.component.whatever.Key1');
Our microservice backends all have a valid swagger file. We can generate Angular Services to access those REST interfaces.
The code generator expects JAVA to be installed on your machine.
Steps to follow:
-
Generate a new library project according to the naming convention
connector-[name]withng generate lib connector-xxxx --tag=core-connector. Replace tag 'core-connector' with 'view-connector' if it is a connector which is only relevant for a specific cme-ui view. -
Add your new connector to the script
generators/swagger/generateRestClients.sh. -
Run
npm run generate-rest-clients.
The REST client should be generated into your new library.
npm run format will be executed afterwards to fix code styling.
- Import the new module into
CoreServicesModuleif it's a core service, or in your matching view module if it's a view specific thing. You may need to use namespace aliasing because all generated REST modules are called ApiModule.
Example:
import { ApiModule as ConnectorKubernetesModule } from '@cme2/connector-kubernetes';
import { ApiModule as ConnectorMetadataModule } from '@cme2/connector-metadata';
You can set the key cme2.mockUrls in localStorage to specify a different url for one or more specific backend services.
The key names must match the generated typescript class names in connector-xxx/src/api/*.
Example:
{
"JiraBackendService": "http://localhost:8081",
"ProjectService": "http://localhost:8082"
}would use local urls for the two mentioned services, all other services will use the default cluster connection.
You can generate simple mock services by pasting the swagger.yaml into http://editor.swagger.io (important: don't use https, otherwise server generation will fail) and select "Generate Server -> nodejs-server"
We use Pact for consumer driven contracts testing.
The utility function describePact (and fdescribePact / xdescribePact) can be used to define Pact tests. It configures the pact provider and an initial testing module for the Angular TestBed which the caller can then use to define the specific test cases.
Pact tests can be added in any apps or libs. Their file names must end with .pact.ts. Like spec-files, they should be located next to the service under test in the project structure:
my-service-under-test.service.ts
my-service-under-test.service.pact.ts
my-service-under-test.service.spec.ts
Run npm run test:pact or npm run test:pact:watch to execute the Pact test. The json-artifacts created by Pact will be stored in folder /pacts. Pact test and the other unit tests are seperated. npm run test:pact will only run Pact tests, npm test / ng test will only run "normal" tests.
For every new microservice a new Pact provider config has to be added to the array in app/pact/karma-pact.config.js.
import { describePact } from '@cme2/testing';
/**
* This defines a test called 'MetaDataService pact' for microservice 'metadata-service'.
* When run, it will create a cme-ui-metadata-service.json under /pacts.
**/
describePact('MetaDataService pact', 'metadata-service', (provider: PactWeb, pactTestModule: Type<any>) => {
// in here, you can use describes and its like you are defining a standard Jasmine unit test.
// You could also directly define an 'it', without wrapping it in a describe call.
describe('getProjects()', () => {
beforeEach(() => {
// pactTestModule is the testing module prepared by describePact.
// It already imports HttpClientModule and provides our HostnameService.
// Use overrideModule to add all stuff your service under tests needs to be instantiated.
TestBed.overrideModule(pactTestModule, {
add: {
imports: [MetadataServiceModule],
providers: [
ProjectService,
MetaDataService,
{ provide: StorageService, useFactory: () => instance(mock(LocalStorageService)) },
{ provide: LogService, useFactory: () => instance(mock(LogService)) }
]
}
});
});
// notice the async keyword. We use async/await syntax for awaiting the provider setup
it('should return existing projects', async (done: DoneFn) => {
const expectedProjects = [{ id: 'ID', name: 'project-name', team: [], phases: [] }] as Array<MetadataProject>;
// provider is the PactWeb instance prepared by describePact.
// It already is configured to remove all interactions on restart,
// verifies all calls after each test case and is finalized after all tests are done.
//
// see https://github.com/pact-foundation/pact-js/blob/master/README.md for the pact-js API
await provider
.addInteraction({
state: 'provider has some existing projects',
uponReceiving: 'a request to GET projects',
withRequest: {
method: 'GET',
path: '/api/v1/metadata-service/projects'
},
willRespondWith: {
status: 200,
body: Matchers.somethingLike(expectedProjects),
headers: {
'content-type': 'application/json'
}
}
})
.catch(reason => done.fail(reason));
// Here, the provider.addInteraction setup is inlined in the 'it'-definition, so we need to await
// the provider setup.
//
// You can extract the provider.addInteraction part into an own beforeEach or beforeAll if it
// better suites your use case.
const metaDataService = TestBed.get(MetaDataService) as MetaDataService;
metaDataService.getProjects().subscribe(
response => {
expect(response).toEqual(expectedProjects);
done();
},
error => {
done.fail(error);
}
);
});
});
});This project was generated with Angular CLI using the Nrwl Extensions for Angular (Nx).
Nx is an open source toolkit for enterprise Angular applications.
Nx is designed to help you create and build enterprise grade Angular applications. It provides an opinionated approach to application project structure and patterns.
Watch a 5-minute video on how to get started with Nx.
Run ng generate app myapp to generate an application. When using Nx, you can create multiple applications and libraries in the same CLI workspace. Read more here.
Run ng serve --app=myapp for a dev server. Navigate to http://localhost:4200/. The app will automatically reload if you change any of the source files.
Run ng generate component component-name --app=myapp to generate a new component. You can also use ng generate directive|pipe|service|class|guard|interface|enum|module|app|lib.
Run ng generate app myapp --routing.
Run ng generate lib mymodule --routing --lazy --parentModule=apps/cme-ui/src/app/app.module.ts.
Don't forget to set the cme prefix for this lib in its app config of .angular-cli.json.
Run ng generate component mycomp --app=mymodule (yes, its --app although its a library).
Run ng generate lib mylib --nomodule.
Run ng build --app=myapp to build the project. The build artifacts will be stored in the dist/ directory. Use the -prod flag for a production build.
Run ng test to execute the unit tests via Karma.
Run ng e2e to execute the end-to-end tests via Protractor.
Before running the tests make sure you are serving the app via ng serve.
To get more help on the Angular CLI use ng help or go check out the Angular CLI README.
To get more help on the Nx Workspace additions to the Angular CLI see Nx Workspace Guide.
There is a Dockerfile which builds an image with all tools and dependencies necessary for testing and building Angular apps.
It also contains the source code in the path /ng-app (which is the current working directory of the container).
After the files are copied, a npm install will be executed.
The built image can then be started by Jenkins with different commands.
Run it for example with the following commands:
Run linter
docker run --rm consortit-docker-cme-local.jfrog.io/cme-ui npm run lint
Execute Unit Tests
docker run --rm consortit-docker-cme-local.jfrog.io/cme-ui npm run test
Execute End2End Tests
docker run --rm consortit-docker-cme-local.jfrog.io/cme-ui npm run e2e --app=cme-ui
Build the Angular Application for production
docker run --rm consortit-docker-cme-local.jfrog.io/cme-ui npm run build:prod