import { BuiltinMetricDefinition, BuiltinMetricDefinitions, CustomMetricDefinition, DataProviderAlertEvent, DataProviderBuildEvent, DataProviderCustomEvent, DataProviderDeploymentEvent, DataProviderEventTypes, DataProviderEventsConfiguration, DataProviderFlagEvent, DataProviderIncidentEvent, DataProviderLifecycleEvent, DataProviderMetricsConfiguration, DataProviderPullRequestEvent, DataProviderPushEvent, DataProviderResult, DataProviderVulnerabilityEvent, } from '@atlassian/forge-graphql-types'; export class DataProviderError extends Error {} /* Builder class for constructing a response object for compass:dataProvider Forge modules. */ export class DataProviderResponse { events: DataProviderEventsConfiguration = {}; metrics: DataProviderMetricsConfiguration = { builtIn: {}, custom: [], }; constructor( public externalSourceId: string, definitions: { eventTypes: DataProviderEventTypes[]; builtInMetricDefinitions: BuiltinMetricDefinition; customMetricDefinitions: CustomMetricDefinition[]; }, ) { for (const eventType of definitions?.eventTypes ?? []) { this.events[eventType] = { initialValues: null }; } for (const metricDef of definitions?.builtInMetricDefinitions ?? []) { if (typeof metricDef === 'object') { this.metrics.builtIn[metricDef.name] = { initialValue: null, derived: metricDef?.derived, }; } else { this.metrics.builtIn[metricDef] = { initialValue: null, }; } } this.metrics.custom = definitions?.customMetricDefinitions ?? []; } /* Add an initialValue to a built-in Metric initialized in the constructor. */ public addBuiltInMetricValue( metricName: BuiltinMetricDefinitions, value: number | null, ): DataProviderResponse { if (this.metrics.builtIn[metricName] === undefined) { throw new DataProviderError( `${metricName} was not initialized for this DataProviderResponse.`, ); } if (this.metrics.builtIn[metricName].derived) { throw new DataProviderError( `Cannot initialize value for ${metricName} because it is a derived metric`, ); } this.metrics.builtIn[metricName] = { initialValue: value, }; return this; } /* Add an initialValue to a custom Metric initialized in the constructor. */ public addCustomMetricValue( metricName: string, value: number | null, ): DataProviderResponse { const idx = this.metrics.custom.findIndex((m) => m.name === metricName); if (idx === -1) { throw new DataProviderError( `No custom metric named ${metricName} was initialized for this DataProviderResponse`, ); } else { this.metrics.custom[idx].initialValue = value; } return this; } /* Add build event values to the response. Build event types must be specified in the constructor before build event values can be added. */ public addBuilds(builds: DataProviderBuildEvent[]): DataProviderResponse { if (this.events.builds === undefined) { throw new DataProviderError( `Builds must be initialized as part of the DataProviderResponse constructor.`, ); } this.events.builds = { initialValues: builds.length > 0 ? builds : null }; return this; } /* Add alert event values to the response. Alert event types must be specified in the constructor before alert event values can be added. */ public addAlerts(alerts: DataProviderAlertEvent[]): DataProviderResponse { if (this.events.alerts === undefined) { throw new DataProviderError( `Alerts must be initialized as part of the DataProviderResponse constructor.`, ); } this.events.alerts = { initialValues: alerts.length > 0 ? alerts : null }; return this; } /* Add incident event values to the response. Incident event types must be specified in the constructor before incident event values can be added. */ public addIncidents( incidents: DataProviderIncidentEvent[], ): DataProviderResponse { if (this.events.incidents === undefined) { throw new DataProviderError( `Incidents must be initialized as part of the DataProviderResponse constructor.`, ); } this.events.incidents = { initialValues: incidents.length > 0 ? incidents : null, }; return this; } /* Add lifecycle event values to the response. Lifecycle event types must be specified in the constructor before lifecycle event values can be added. */ public addLifecycles( lifecycles: DataProviderLifecycleEvent[], ): DataProviderResponse { if (this.events.lifecycles === undefined) { throw new DataProviderError( `Lifecycles must be initialized as part of the DataProviderResponse constructor.`, ); } this.events.lifecycles = { initialValues: lifecycles.length > 0 ? lifecycles : null, }; return this; } /* Add deployment event values to the response. Deployment event types must be specified in the constructor before deployment event values can be added. */ public addDeployments( deployments: DataProviderDeploymentEvent[], ): DataProviderResponse { if (this.events.deployments === undefined) { throw new DataProviderError( `Deployments must be initialized as part of the DataProviderResponse constructor.`, ); } this.events.deployments = { initialValues: deployments.length > 0 ? deployments : null, }; return this; } /* Add flag event values to the response. Flag event types must be specified in the constructor before flag event values can be added. */ public addFlags(flags: DataProviderFlagEvent[]): DataProviderResponse { if (this.events.flags === undefined) { throw new DataProviderError( `Flags must be initialized as part of the DataProviderResponse constructor.`, ); } this.events.flags = { initialValues: flags.length > 0 ? flags : null }; return this; } /* Add custom event values to the response. Custom event types must be specified in the constructor before custom event values can be added. */ public addCustomEvents( customEvents: DataProviderCustomEvent[], ): DataProviderResponse { if (this.events.custom === undefined) { throw new DataProviderError( `Custom events must be initialized as part of the DataProviderResponse constructor.`, ); } this.events.custom = { initialValues: customEvents.length > 0 ? customEvents : null, }; return this; } /* Add push event values to the response. Push event types must be specified in the constructor before push event values can be added. */ public addPushEvents( pushEvents: DataProviderPushEvent[], ): DataProviderResponse { if (this.events.pushes === undefined) { throw new DataProviderError( `Push events must be initialized as part of the DataProviderResponse constructor.`, ); } this.events.pushes = { initialValues: pushEvents.length > 0 ? pushEvents : null, }; return this; } /* Add pull request event values to the response. Pull request event types must be specified in the constructor before pull request event values can be added. */ public addPullRequestEvents( pullRequestEvents: DataProviderPullRequestEvent[], ): DataProviderResponse { if (this.events.pullRequests === undefined) { throw new DataProviderError( `Pull request events must be initialized as part of the DataProviderResponse constructor.`, ); } this.events.pullRequests = { initialValues: pullRequestEvents.length > 0 ? pullRequestEvents : null, }; return this; } /* Add vulnerability event values to the response. Vulnerability event types must be specified in the constructor before vulnerability event values can be added. */ public addVulnerabilityEvents( vulnerabilityEvents: DataProviderVulnerabilityEvent[], ): DataProviderResponse { if (this.events.vulnerabilities === undefined) { throw new DataProviderError( `Vulnerability events must be initialized as part of the DataProviderResponse constructor.`, ); } this.events.vulnerabilities = { initialValues: vulnerabilityEvents.length > 0 ? vulnerabilityEvents : null, }; return this; } /* Construct the compass:dataProvider response in the correct format. The result of DataProviderResponse.build() should be returned from the compass:dataProvider module. */ public build(): DataProviderResult { this.validate(); return { externalSourceId: this.externalSourceId, events: Object.keys(this.events).length > 0 ? this.events : undefined, metrics: this.buildMetrics(), }; } private buildMetrics(): DataProviderMetricsConfiguration { this.metrics.builtIn = this.metrics.builtIn && Object.keys(this.metrics.builtIn).length > 0 ? this.metrics.builtIn : undefined; this.metrics.custom = this.metrics.custom && this.metrics.custom.length > 0 ? this.metrics.custom : undefined; return this.metrics; } private validate(): void { if ( typeof this.externalSourceId !== 'string' || this.externalSourceId.length === 0 ) { throw new DataProviderError('externalSourceId must be a string.'); } if (typeof this.events !== 'object') { throw new DataProviderError('events must be an object.'); } if (typeof this.metrics !== 'object') { throw new DataProviderError('metrics must be an object.'); } const validEventTypes = [ 'alerts', 'builds', 'custom', 'deployments', 'flags', 'incidents', 'lifecycles', 'pushes', 'pullRequests', 'vulnerabilities', ]; for (const [eventType, event] of Object.entries(this.events)) { if (!validEventTypes.includes(eventType)) { throw new DataProviderError(`${eventType} is not a valid event type.`); } if (event.initialValues === undefined) { throw new DataProviderError(`${eventType} must define initialValues`); } } if (this.metrics.builtIn) { for (const [metricType, metric] of Object.entries(this.metrics.builtIn)) { if ( metric.initialValue === undefined || (metric.initialValue !== null && typeof metric.initialValue !== 'number') ) { throw new DataProviderError( `${metricType} must define a valid initialValue`, ); } } } if (this.metrics.custom) { for (const metric of this.metrics.custom) { if ( metric.initialValue === undefined || (metric.initialValue !== null && typeof metric.initialValue !== 'number') ) { throw new DataProviderError( `${metric} must define a valid initialValue`, ); } if (metric.name === undefined || typeof metric.name !== 'string') { throw new DataProviderError(`${metric} must have a valid name.`); } } } } }