UNPKG

@finos/legend-application-studio

Version:
265 lines 13.2 kB
/** * 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