UNPKG

@finos/legend-studio

Version:
313 lines (292 loc) 10.5 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, makeAutoObservable } from 'mobx'; import { type ServiceEditorState, MINIMUM_SERVICE_OWNERS, } from '../../../editor-state/element-editor-state/service/ServiceEditorState.js'; import type { EditorStore } from '../../../EditorStore.js'; import { type GeneratorFn, assertErrorThrown, LogEvent, ActionState, prettyCONSTName, assertNonEmptyString, guaranteeNonNullable, UnsupportedOperationError, getNullableFirstElement, assertTrue, } from '@finos/legend-shared'; import { LEGEND_STUDIO_APP_EVENT } from '../../../LegendStudioAppEvent.js'; import { Version } from '@finos/legend-server-sdlc'; import { type ServiceRegistrationResult, ServiceExecutionMode, } from '@finos/legend-graph'; import { ServiceRegistrationEnvInfo } from '../../../../application/LegendStudioApplicationConfig.js'; import { ActionAlertActionType, ActionAlertType, } from '@finos/legend-application'; export const LATEST_PROJECT_REVISION = 'Latest Project Revision'; const getServiceExecutionMode = (mode: string): ServiceExecutionMode => { 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}'`, ); } }; const URL_SEPARATOR = '/'; interface ServiceVersionOption { label: string; value: Version | string; } export enum SERVICE_REGISTRATION_PHASE { REGISTERING_SERVICE = 'REGISTERING_SERVICE', ACTIVATING_SERVICE = 'ACTIVATING_SERVICE', } export class ServiceRegistrationState { editorStore: EditorStore; serviceEditorState: ServiceEditorState; registrationState = ActionState.create(); serviceEnv?: string | undefined; serviceExecutionMode?: ServiceExecutionMode | undefined; projectVersion?: Version | string | undefined; activatePostRegistration = true; constructor( editorStore: EditorStore, serviceEditorState: ServiceEditorState, ) { makeAutoObservable(this, { editorStore: false, executionModes: computed, updateVersion: action, setProjectVersion: action, initialize: action, updateType: action, updateEnv: action, setActivatePostRegistration: action, }); this.editorStore = editorStore; this.serviceEditorState = serviceEditorState; this.initialize(); this.registrationState.setMessageFormatter(prettyCONSTName); } setServiceEnv(val: string | undefined): void { this.serviceEnv = val; } setServiceExecutionMode(val: ServiceExecutionMode | undefined): void { this.serviceExecutionMode = val; } setProjectVersion(val: Version | string | undefined): void { this.projectVersion = val; } setActivatePostRegistration(val: boolean): void { this.activatePostRegistration = val; } initialize(): void { this.serviceEnv = getNullableFirstElement( this.editorStore.applicationStore.config.options .TEMPORARY__serviceRegistrationConfig, )?.env; this.serviceExecutionMode = this.executionModes[0]; this.updateVersion(); } updateVersion(): void { if (this.serviceExecutionMode === ServiceExecutionMode.SEMI_INTERACTIVE) { this.projectVersion = LATEST_PROJECT_REVISION; } else if (this.serviceExecutionMode === ServiceExecutionMode.PROD) { this.projectVersion = this.editorStore.sdlcState.projectVersions[0]; } else { this.projectVersion = undefined; } } updateType(val: ServiceExecutionMode | undefined): void { this.setServiceExecutionMode(val); this.updateVersion(); } updateEnv(val: string | undefined): void { this.setServiceEnv(val); this.setServiceExecutionMode(this.executionModes[0]); } get options(): ServiceRegistrationEnvInfo[] { if (this.editorStore.sdlcServerClient.features.canCreateVersion) { return this.editorStore.applicationStore.config.options .TEMPORARY__serviceRegistrationConfig; } return this.editorStore.applicationStore.config.options.TEMPORARY__serviceRegistrationConfig.map( (_envConfig) => { const envConfig = new ServiceRegistrationEnvInfo(); 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(): ServiceExecutionMode[] { return ( this.options.find((e) => e.env === this.serviceEnv)?.modes ?? [] ).map(getServiceExecutionMode); } get versionOptions(): ServiceVersionOption[] | undefined { if ( this.editorStore.sdlcServerClient.features.canCreateVersion && this.serviceExecutionMode !== ServiceExecutionMode.FULL_INTERACTIVE ) { const options: ServiceVersionOption[] = this.editorStore.sdlcState.projectVersions.map((version) => ({ label: version.id.id, value: version, })); if (this.serviceExecutionMode !== ServiceExecutionMode.PROD) { return [ { label: prettyCONSTName(LATEST_PROJECT_REVISION), value: LATEST_PROJECT_REVISION, }, ...options, ]; } return options; } return undefined; } *registerService(): GeneratorFn<void> { try { this.registrationState.inProgress(); this.validateServiceForRegistration(); const config = guaranteeNonNullable( this.options.find((info) => info.env === this.serviceEnv), ); const serverUrl = config.executionUrl; const versionInput = this.projectVersion instanceof Version ? this.projectVersion.id.id : undefined; const projectConfig = guaranteeNonNullable( this.editorStore.projectConfigurationEditorState.projectConfiguration, ); this.registrationState.setMessage( SERVICE_REGISTRATION_PHASE.REGISTERING_SERVICE, ); const serviceRegistrationResult = (yield this.editorStore.graphManagerState.graphManager.registerService( this.serviceEditorState.service, this.editorStore.graphManagerState.graph, projectConfig.groupId, projectConfig.artifactId, versionInput, serverUrl, guaranteeNonNullable(this.serviceExecutionMode), )) as ServiceRegistrationResult; if (this.activatePostRegistration) { this.registrationState.setMessage( SERVICE_REGISTRATION_PHASE.ACTIVATING_SERVICE, ); yield this.editorStore.graphManagerState.graphManager.activateService( serverUrl, serviceRegistrationResult.serviceInstanceId, ); } assertNonEmptyString( serviceRegistrationResult.pattern, 'Service registration pattern is missing', ); const message = `Service with patten ${ serviceRegistrationResult.pattern } registered ${this.activatePostRegistration ? 'and activated ' : ''}`; const encodedServicePattern = URL_SEPARATOR + encodeURIComponent( serviceRegistrationResult.pattern[0] === URL_SEPARATOR ? serviceRegistrationResult.pattern.substring(1) : serviceRegistrationResult.pattern, ); this.editorStore.setActionAlertInfo({ message, prompt: 'You can now launch and monitor the operation of your service', type: ActionAlertType.STANDARD, onEnter: (): void => this.editorStore.setBlockGlobalHotkeys(true), onClose: (): void => this.editorStore.setBlockGlobalHotkeys(false), actions: [ { label: 'Launch Service', type: ActionAlertActionType.PROCEED, handler: (): void => { this.editorStore.applicationStore.navigator.openNewWindow( `${config.managementUrl}${encodedServicePattern}`, ); }, default: true, }, { label: 'Close', type: ActionAlertActionType.PROCEED_WITH_CAUTION, }, ], }); } catch (error) { assertErrorThrown(error); this.editorStore.applicationStore.log.error( LogEvent.create(LEGEND_STUDIO_APP_EVENT.SERVICE_REGISTRATION_FAILURE), error, ); this.editorStore.applicationStore.notifyError(error); } finally { this.registrationState.reset(); this.registrationState.setMessage(undefined); } } validateServiceForRegistration(): void { this.serviceEditorState.service.owners.forEach((owner) => assertNonEmptyString(owner, `Service can't have an empty owner name`), ); assertTrue( this.serviceEditorState.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', ); } } }