@finos/legend-application-studio
Version:
Legend Studio application core
265 lines • 13.2 kB
JavaScript
/**
* Copyright (c) 2020-present, Goldman Sachs
*
* 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 { action, computed, flow, makeObservable, observable } from 'mobx';
import { MINIMUM_SERVICE_OWNERS } from '../../../editor-state/element-editor-state/service/ServiceEditorState.js';
import { assertErrorThrown, LogEvent, ActionState, prettyCONSTName, assertNonEmptyString, guaranteeNonNullable, UnsupportedOperationError, assertTrue, URL_SEPARATOR, filterByType, compareSemVerVersions, } from '@finos/legend-shared';
import { LEGEND_STUDIO_APP_EVENT } from '../../../../../__lib__/LegendStudioEvent.js';
import { ServiceExecutionMode, buildLambdaVariableExpressions, VariableExpression, generateMultiplicityString, areMultiplicitiesEqual, Multiplicity, } from '@finos/legend-graph';
import { ServiceRegistrationEnvironmentConfig } from '../../../../../application/LegendStudioApplicationConfig.js';
import { ActionAlertActionType, ActionAlertType, } from '@finos/legend-application';
import { MASTER_SNAPSHOT_ALIAS } from '@finos/legend-server-depot';
export const LATEST_PROJECT_REVISION = 'Latest Project Revision';
export const PROJECT_SEMANTIC_VERSION_PATTERN = /^[0-9]*.[0-9]*.[0-9]*$/;
export const generateServiceManagementUrl = (baseUrl, serviceUrlPattern) => `${baseUrl}${URL_SEPARATOR +
encodeURIComponent(serviceUrlPattern[0] === URL_SEPARATOR
? serviceUrlPattern.substring(1)
: serviceUrlPattern)}`;
const getServiceExecutionMode = (mode) => {
switch (mode) {
case ServiceExecutionMode.FULL_INTERACTIVE:
return ServiceExecutionMode.FULL_INTERACTIVE;
case ServiceExecutionMode.SEMI_INTERACTIVE:
return ServiceExecutionMode.SEMI_INTERACTIVE;
case ServiceExecutionMode.PROD:
return ServiceExecutionMode.PROD;
default:
throw new UnsupportedOperationError(`Encountered unsupported service execution mode '${mode}'`);
}
};
export class ServiceConfigState {
editorStore;
registrationOptions = [];
registrationState = ActionState.create();
serviceEnv;
serviceExecutionMode;
projectVersion;
enableModesWithVersioning;
TEMPORARY__useStoreModel = false;
TEMPORARY__useGenerateLineage = true;
TEMPORARY__useGenerateOpenApi = false;
constructor(editorStore, registrationOptions, enableModesWithVersioning) {
makeObservable(this, {
serviceEnv: observable,
serviceExecutionMode: observable,
projectVersion: observable,
enableModesWithVersioning: observable,
TEMPORARY__useStoreModel: observable,
TEMPORARY__useGenerateLineage: observable,
TEMPORARY__useGenerateOpenApi: observable,
executionModes: computed,
options: computed,
versionOptions: computed,
setServiceEnv: action,
setServiceExecutionMode: action,
setProjectVersion: action,
setUseStoreModelWithFullInteractive: action,
initialize: action,
updateVersion: action,
updateType: action,
updateEnv: action,
});
this.editorStore = editorStore;
this.registrationOptions = registrationOptions;
this.enableModesWithVersioning = enableModesWithVersioning;
this.registrationState.setMessageFormatter(prettyCONSTName);
this.initialize();
}
get options() {
if (this.enableModesWithVersioning) {
return this.registrationOptions;
}
return this.registrationOptions
.map((_envConfig) => {
const envConfig = new ServiceRegistrationEnvironmentConfig();
envConfig.env = _envConfig.env;
envConfig.executionUrl = _envConfig.executionUrl;
envConfig.managementUrl = _envConfig.managementUrl;
// NOTE: For projects that we cannot create a version for, only fully-interactive mode is supported
envConfig.modes = _envConfig.modes.filter((mode) => mode === ServiceExecutionMode.FULL_INTERACTIVE);
return envConfig;
})
.filter((envConfig) => envConfig.modes.length);
}
get executionModes() {
return (this.options.find((e) => e.env === this.serviceEnv)?.modes ?? []).map(getServiceExecutionMode);
}
get versionOptions() {
if (this.enableModesWithVersioning &&
this.serviceExecutionMode !== ServiceExecutionMode.FULL_INTERACTIVE) {
const semanticVersions = this.editorStore.sdlcState.projectPublishedVersions
.filter((version) => version.match(PROJECT_SEMANTIC_VERSION_PATTERN))
.sort((v1, v2) => compareSemVerVersions(v2, v1));
const snapshotVersions = this.editorStore.sdlcState.projectPublishedVersions
.filter((version) => !version.match(PROJECT_SEMANTIC_VERSION_PATTERN))
.sort((v1, v2) => v1.localeCompare(v2));
const options = snapshotVersions
.concat(semanticVersions)
.map((version) => ({
label: version,
value: version,
}));
if (this.serviceEnv && this.serviceEnv.toUpperCase() === 'PROD') {
// NOTE: we disallow registering against snapshot versions in PROD
return semanticVersions.map((version) => ({
label: version,
value: version,
}));
}
else {
if (this.serviceExecutionMode !== ServiceExecutionMode.PROD) {
return [
{
label: LATEST_PROJECT_REVISION,
value: MASTER_SNAPSHOT_ALIAS,
},
...options.filter((option) => option.value !== MASTER_SNAPSHOT_ALIAS),
];
}
}
return options;
}
return undefined;
}
setServiceEnv(val) {
this.serviceEnv = val;
}
setServiceExecutionMode(val) {
this.serviceExecutionMode = val;
}
setProjectVersion(val) {
this.projectVersion = val;
}
setUseStoreModelWithFullInteractive(val) {
this.TEMPORARY__useStoreModel = val;
}
setUseGenerateLineage(val) {
this.TEMPORARY__useGenerateLineage = val;
}
setUseGenerateOpenApi(val) {
this.TEMPORARY__useGenerateOpenApi = val;
}
initialize() {
this.serviceEnv = this.registrationOptions[0]?.env;
this.serviceExecutionMode = this.executionModes[0];
this.updateVersion();
}
updateVersion() {
if (this.serviceExecutionMode === ServiceExecutionMode.SEMI_INTERACTIVE) {
this.projectVersion = MASTER_SNAPSHOT_ALIAS;
}
else if (this.serviceExecutionMode === ServiceExecutionMode.PROD) {
this.projectVersion =
this.editorStore.sdlcState.projectPublishedVersions[0];
}
else {
this.projectVersion = undefined;
}
}
updateType(val) {
this.setServiceExecutionMode(val);
this.updateVersion();
}
updateEnv(val) {
this.setServiceEnv(val);
this.setServiceExecutionMode(this.executionModes[0]);
}
}
export class ServiceRegistrationState extends ServiceConfigState {
service;
activatePostRegistration = true;
constructor(editorStore, service, registrationOptions, enableModesWithVersioning) {
super(editorStore, registrationOptions, enableModesWithVersioning);
makeObservable(this, {
activatePostRegistration: observable,
setActivatePostRegistration: action,
registerService: flow,
});
this.service = service;
}
setActivatePostRegistration(val) {
this.activatePostRegistration = val;
}
*registerService() {
try {
this.registrationState.inProgress();
this.validateServiceForRegistration();
const config = guaranteeNonNullable(this.options.find((info) => info.env === this.serviceEnv));
const serverUrl = config.executionUrl;
const projectConfig = guaranteeNonNullable(this.editorStore.projectConfigurationEditorState.projectConfiguration);
this.registrationState.setMessage(`Registering service...`);
const serviceRegistrationResult = (yield this.editorStore.graphManagerState.graphManager.registerService(this.service, this.editorStore.graphManagerState.graph, projectConfig.groupId, projectConfig.artifactId, this.projectVersion, serverUrl, guaranteeNonNullable(this.serviceExecutionMode), {
TEMPORARY__useStoreModel: this.TEMPORARY__useStoreModel,
TEMPORARY__useGenerateLineage: this.TEMPORARY__useGenerateLineage,
TEMPORARY__useGenerateOpenApi: this.TEMPORARY__useGenerateOpenApi,
}));
if (this.activatePostRegistration) {
this.registrationState.setMessage(`Activating service...`);
yield this.editorStore.graphManagerState.graphManager.activateService(serverUrl, serviceRegistrationResult.serviceInstanceId);
}
assertNonEmptyString(serviceRegistrationResult.pattern, 'Service registration pattern is missing or empty');
this.editorStore.applicationStore.alertService.setActionAlertInfo({
message: `Service with pattern ${serviceRegistrationResult.pattern} registered ${this.activatePostRegistration ? 'and activated ' : ''}`,
prompt: 'You can now launch and monitor the operation of your service',
type: ActionAlertType.STANDARD,
actions: [
{
label: 'Launch Service',
type: ActionAlertActionType.PROCEED,
handler: () => {
this.editorStore.applicationStore.navigationService.navigator.visitAddress(generateServiceManagementUrl(config.managementUrl, serviceRegistrationResult.pattern));
},
default: true,
},
{
label: 'Close',
type: ActionAlertActionType.PROCEED_WITH_CAUTION,
},
],
});
}
catch (error) {
assertErrorThrown(error);
this.editorStore.applicationStore.logService.error(LogEvent.create(LEGEND_STUDIO_APP_EVENT.SERVICE_REGISTRATION_FAILURE), error);
this.editorStore.applicationStore.notificationService.notifyError(error);
}
finally {
this.registrationState.reset();
this.registrationState.setMessage(undefined);
}
}
validateServiceForRegistration() {
this.service.owners.forEach((owner) => assertNonEmptyString(owner, `Service can't have an empty owner name`));
assertTrue(Boolean(this.service.ownership ??
this.service.owners.length >= MINIMUM_SERVICE_OWNERS), `Service needs to have at least 2 owners in order to be registered`);
guaranteeNonNullable(this.serviceEnv, 'Service registration environment can not be empty');
guaranteeNonNullable(this.serviceExecutionMode, 'Service type can not be empty');
if (this.serviceExecutionMode === ServiceExecutionMode.PROD ||
this.serviceExecutionMode === ServiceExecutionMode.SEMI_INTERACTIVE) {
guaranteeNonNullable(this.projectVersion, 'Service version can not be empty in Semi-interactive and Prod service type');
}
// validate service parameter multiplicities
const SUPPORTED_SERVICE_PARAMETER_MULTIPLICITIES = [
Multiplicity.ONE,
Multiplicity.ZERO_MANY,
Multiplicity.ZERO_ONE,
];
const invalidParams = buildLambdaVariableExpressions(this.service.execution.func, this.editorStore.graphManagerState)
.filter(filterByType(VariableExpression))
.filter((p) => !SUPPORTED_SERVICE_PARAMETER_MULTIPLICITIES.some((m) => areMultiplicitiesEqual(m, p.multiplicity)));
assertTrue(invalidParams.length === 0, `Parameter(s)${invalidParams.map((p) => ` ${p.name}: [${generateMultiplicityString(p.multiplicity.lowerBound, p.multiplicity.upperBound)}]`)} has/have unsupported multiplicity. Supported multiplicities include ${SUPPORTED_SERVICE_PARAMETER_MULTIPLICITIES.map((m) => ` [${generateMultiplicityString(m.lowerBound, m.upperBound)}]`)}.`);
}
}
//# sourceMappingURL=ServiceRegistrationState.js.map