Merge branch 'master' of github.com:backstage/backstage into dekoding/more-resilient-incremental-providers
This commit is contained in:
@@ -169,6 +169,33 @@ export const apis: AnyApiFactory[] = [
|
||||
];
|
||||
```
|
||||
|
||||
### Enabling Site Search
|
||||
|
||||
If you wish to see all of the search events in the [Site Search](https://support.google.com/analytics/answer/1012264)
|
||||
section of Google Analytics, you can enable sending virtual pageviews on every `search` event like so:
|
||||
|
||||
```yaml
|
||||
app:
|
||||
analytics:
|
||||
ga:
|
||||
virtualSearchPageView:
|
||||
mode: only # Defaults to 'disabled'
|
||||
mountPath: /virtual-search # Defaults to '/search'
|
||||
searchQuery: term # Defaults to 'query'
|
||||
categoryQuery: sc # Omitted by default
|
||||
```
|
||||
|
||||
Available `mode`s are:
|
||||
|
||||
- `disabled` - no virtual pageviews are sent, default behavior
|
||||
- `only` - sends virtual pageviews _instead_ of `search` events
|
||||
- `both` - sends both virtual pageviews _and_ `search` events
|
||||
|
||||
Virtual pageviews will be sent to the path specified in the `mountPath`, the search term will be
|
||||
set as the value for query parameter `searchQuery` and category (if provided) will be set as the value for
|
||||
query parameter `categoryQuery`, e.g. the example config above will result in
|
||||
virtual pageviews being sent to `/virtual-search?term=SearchTermHere&sc=CategoryHere`.
|
||||
|
||||
### Debugging and Testing
|
||||
|
||||
In pre-production environments, you may wish to set additional configurations
|
||||
|
||||
+32
@@ -53,6 +53,38 @@ export interface Config {
|
||||
*/
|
||||
identity?: 'disabled' | 'optional' | 'required';
|
||||
|
||||
/**
|
||||
* Controls whether to send virtual pageviews on `search` events.
|
||||
* Can be used to enable Site Search in GA.
|
||||
*/
|
||||
virtualSearchPageView?: {
|
||||
/**
|
||||
* - `disabled`: (Default) no virtual pageviews are sent
|
||||
* - `only`: Sends virtual pageview _instead_ of the `search` event
|
||||
* - `both`: Sends both the `search` event _and_ the virtual pageview
|
||||
* @visibility frontend
|
||||
*/
|
||||
mode?: 'disabled' | 'only' | 'both';
|
||||
/**
|
||||
* Specifies on which path the main Search page is mounted.
|
||||
* Defaults to `/search`.
|
||||
* @visibility frontend
|
||||
*/
|
||||
mountPath?: string;
|
||||
/**
|
||||
* Specifies which query param is used for the term query in the virtual pageview URL.
|
||||
* Defaults to `query`.
|
||||
* @visibility frontend
|
||||
*/
|
||||
searchQuery?: string;
|
||||
/**
|
||||
* Specifies which query param is used for the category query in the virtual pageview URL.
|
||||
* Skipped by default.
|
||||
* @visibility frontend
|
||||
*/
|
||||
categoryQuery?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Whether or not to log analytics debug statements to the console.
|
||||
* Defaults to false.
|
||||
|
||||
+96
@@ -25,6 +25,7 @@ describe('GoogleAnalytics', () => {
|
||||
pluginId: 'some-plugin',
|
||||
routeRef: 'unknown',
|
||||
releaseNum: 1337,
|
||||
searchTypes: 'test category',
|
||||
};
|
||||
const trackingId = 'UA-000000-0';
|
||||
const basicValidConfig = new ConfigReader({
|
||||
@@ -152,6 +153,101 @@ describe('GoogleAnalytics', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('captures virtual pageviews instead of search events', () => {
|
||||
const config = new ConfigReader({
|
||||
app: {
|
||||
analytics: {
|
||||
ga: {
|
||||
trackingId,
|
||||
testMode: true,
|
||||
virtualSearchPageView: { mode: 'only' },
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const api = GoogleAnalytics.fromConfig(config);
|
||||
api.captureEvent({
|
||||
action: 'search',
|
||||
subject: 'test search',
|
||||
context,
|
||||
});
|
||||
|
||||
const [command, data] = ReactGA.testModeAPI.calls[1];
|
||||
expect(command).toBe('send');
|
||||
expect(data).toMatchObject({
|
||||
hitType: 'pageview',
|
||||
page: '/search?query=test+search',
|
||||
});
|
||||
expect(ReactGA.testModeAPI.calls).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('captures virtual pageviews alongside search events', () => {
|
||||
const config = new ConfigReader({
|
||||
app: {
|
||||
analytics: {
|
||||
ga: {
|
||||
trackingId,
|
||||
testMode: true,
|
||||
virtualSearchPageView: { mode: 'both' },
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const api = GoogleAnalytics.fromConfig(config);
|
||||
api.captureEvent({
|
||||
action: 'search',
|
||||
subject: 'test search',
|
||||
context,
|
||||
});
|
||||
|
||||
const [pageviewCommand, pageViewData] = ReactGA.testModeAPI.calls[1];
|
||||
expect(pageviewCommand).toBe('send');
|
||||
expect(pageViewData).toMatchObject({
|
||||
hitType: 'pageview',
|
||||
page: '/search?query=test+search',
|
||||
});
|
||||
const [searchCommand, searchData] = ReactGA.testModeAPI.calls[2];
|
||||
expect(searchCommand).toBe('send');
|
||||
expect(searchData).toMatchObject({
|
||||
hitType: 'event',
|
||||
eventCategory: context.extension,
|
||||
eventAction: 'search',
|
||||
eventLabel: 'test search',
|
||||
});
|
||||
});
|
||||
|
||||
it('captures virtual pageviews on custom route with custom search query and custom category', () => {
|
||||
const config = new ConfigReader({
|
||||
app: {
|
||||
analytics: {
|
||||
ga: {
|
||||
trackingId,
|
||||
testMode: true,
|
||||
virtualSearchPageView: {
|
||||
mode: 'only',
|
||||
mountPath: '/custom',
|
||||
searchQuery: 'term',
|
||||
categoryQuery: 'sc',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const api = GoogleAnalytics.fromConfig(config);
|
||||
api.captureEvent({
|
||||
action: 'search',
|
||||
subject: 'test search',
|
||||
context,
|
||||
});
|
||||
|
||||
const [command, data] = ReactGA.testModeAPI.calls[1];
|
||||
expect(command).toBe('send');
|
||||
expect(data).toMatchObject({
|
||||
hitType: 'pageview',
|
||||
page: '/custom?term=test+search&sc=test+category',
|
||||
});
|
||||
});
|
||||
|
||||
it('captures configured custom dimensions/metrics on events', () => {
|
||||
const api = GoogleAnalytics.fromConfig(advancedConfig);
|
||||
|
||||
|
||||
+30
-1
@@ -18,12 +18,16 @@ import ReactGA from 'react-ga';
|
||||
import {
|
||||
AnalyticsApi,
|
||||
AnalyticsContextValue,
|
||||
AnalyticsEventAttributes,
|
||||
AnalyticsEvent,
|
||||
AnalyticsEventAttributes,
|
||||
IdentityApi,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { Config } from '@backstage/config';
|
||||
import { DeferredCapture } from '../../../util';
|
||||
import {
|
||||
parseVirtualSearchPageViewConfig,
|
||||
VirtualSearchPageViewConfig,
|
||||
} from '../../../util/VirtualSearchPageView';
|
||||
|
||||
type CustomDimensionOrMetricConfig = {
|
||||
type: 'dimension' | 'metric';
|
||||
@@ -40,6 +44,7 @@ export class GoogleAnalytics implements AnalyticsApi {
|
||||
private readonly cdmConfig: CustomDimensionOrMetricConfig[];
|
||||
private customUserIdTransform?: (userEntityRef: string) => Promise<string>;
|
||||
private readonly capture: DeferredCapture;
|
||||
private readonly virtualSearchPageView: VirtualSearchPageViewConfig;
|
||||
|
||||
/**
|
||||
* Instantiate the implementation and initialize ReactGA.
|
||||
@@ -51,6 +56,7 @@ export class GoogleAnalytics implements AnalyticsApi {
|
||||
identity: string;
|
||||
trackingId: string;
|
||||
scriptSrc?: string;
|
||||
virtualSearchPageView: VirtualSearchPageViewConfig;
|
||||
testMode: boolean;
|
||||
debug: boolean;
|
||||
}) {
|
||||
@@ -61,11 +67,13 @@ export class GoogleAnalytics implements AnalyticsApi {
|
||||
identityApi,
|
||||
userIdTransform = 'sha-256',
|
||||
scriptSrc,
|
||||
virtualSearchPageView,
|
||||
testMode,
|
||||
debug,
|
||||
} = options;
|
||||
|
||||
this.cdmConfig = cdmConfig;
|
||||
this.virtualSearchPageView = virtualSearchPageView;
|
||||
|
||||
// Initialize Google Analytics.
|
||||
ReactGA.initialize(trackingId, {
|
||||
@@ -105,6 +113,9 @@ export class GoogleAnalytics implements AnalyticsApi {
|
||||
const scriptSrc = config.getOptionalString('app.analytics.ga.scriptSrc');
|
||||
const identity =
|
||||
config.getOptionalString('app.analytics.ga.identity') || 'disabled';
|
||||
const virtualSearchPageView = parseVirtualSearchPageViewConfig(
|
||||
config.getOptionalConfig('app.analytics.ga.virtualSearchPageView'),
|
||||
);
|
||||
const debug = config.getOptionalBoolean('app.analytics.ga.debug') ?? false;
|
||||
const testMode =
|
||||
config.getOptionalBoolean('app.analytics.ga.testMode') ?? false;
|
||||
@@ -134,6 +145,7 @@ export class GoogleAnalytics implements AnalyticsApi {
|
||||
identity,
|
||||
trackingId,
|
||||
scriptSrc,
|
||||
virtualSearchPageView,
|
||||
cdmConfig,
|
||||
testMode,
|
||||
debug,
|
||||
@@ -154,6 +166,23 @@ export class GoogleAnalytics implements AnalyticsApi {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.virtualSearchPageView.mode !== 'disabled' && action === 'search') {
|
||||
const { mountPath, searchQuery, categoryQuery } =
|
||||
this.virtualSearchPageView;
|
||||
const params = new URLSearchParams();
|
||||
params.set(searchQuery, subject);
|
||||
if (categoryQuery) {
|
||||
params.set(categoryQuery, context.searchTypes?.toString() ?? '');
|
||||
}
|
||||
this.capture.pageview(
|
||||
`${mountPath}?${params.toString()}`,
|
||||
customMetadata,
|
||||
);
|
||||
if (this.virtualSearchPageView.mode === 'only') {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.capture.event({
|
||||
category: context.extension || 'App',
|
||||
action,
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* 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 { Config } from '@backstage/config';
|
||||
|
||||
type VirtualSearchPageViewType = 'disabled' | 'only' | 'both';
|
||||
|
||||
export type VirtualSearchPageViewConfig = {
|
||||
mode: VirtualSearchPageViewType;
|
||||
mountPath: string;
|
||||
searchQuery: string;
|
||||
categoryQuery?: string;
|
||||
};
|
||||
|
||||
function isVirtualSearchPageViewType(
|
||||
value: string | undefined,
|
||||
): value is VirtualSearchPageViewType {
|
||||
return value === 'disabled' || value === 'only' || value === 'both';
|
||||
}
|
||||
|
||||
export function parseVirtualSearchPageViewConfig(
|
||||
config: Config | undefined,
|
||||
): VirtualSearchPageViewConfig {
|
||||
const vspvModeString = config?.getOptionalString('mode');
|
||||
return {
|
||||
mode: isVirtualSearchPageViewType(vspvModeString)
|
||||
? vspvModeString
|
||||
: 'disabled',
|
||||
mountPath: config?.getOptionalString('mountPath') ?? '/search',
|
||||
searchQuery: config?.getOptionalString('searchQuery') ?? 'query',
|
||||
categoryQuery: config?.getOptionalString('categoryQuery'),
|
||||
};
|
||||
}
|
||||
@@ -17,7 +17,7 @@
|
||||
import mockFs from 'mock-fs';
|
||||
import { resolve as resolvePath } from 'path';
|
||||
import fetch from 'node-fetch';
|
||||
import { configServiceRef } from '@backstage/backend-plugin-api';
|
||||
import { coreServices } from '@backstage/backend-plugin-api';
|
||||
import { startTestBackend } from '@backstage/backend-test-utils';
|
||||
import { appPlugin } from './appPlugin';
|
||||
import {
|
||||
@@ -51,7 +51,7 @@ describe('appPlugin', () => {
|
||||
await startTestBackend({
|
||||
services: [
|
||||
[
|
||||
configServiceRef,
|
||||
coreServices.config,
|
||||
new ConfigReader({
|
||||
backend: {
|
||||
listen: { port },
|
||||
|
||||
@@ -16,12 +16,9 @@
|
||||
|
||||
import express from 'express';
|
||||
import {
|
||||
configServiceRef,
|
||||
coreServices,
|
||||
createBackendPlugin,
|
||||
databaseServiceRef,
|
||||
loggerServiceRef,
|
||||
loggerToWinstonLogger,
|
||||
httpRouterServiceRef,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { createRouter } from './router';
|
||||
|
||||
@@ -78,10 +75,10 @@ export const appPlugin = createBackendPlugin({
|
||||
register(env, options: AppPluginOptions) {
|
||||
env.registerInit({
|
||||
deps: {
|
||||
logger: loggerServiceRef,
|
||||
config: configServiceRef,
|
||||
database: databaseServiceRef,
|
||||
httpRouter: httpRouterServiceRef,
|
||||
logger: coreServices.logger,
|
||||
config: coreServices.config,
|
||||
database: coreServices.database,
|
||||
httpRouter: coreServices.httpRouter,
|
||||
},
|
||||
async init({ logger, config, database, httpRouter }) {
|
||||
const {
|
||||
|
||||
@@ -14,6 +14,11 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// @ts-check
|
||||
|
||||
/**
|
||||
* @param {import('knex').Knex} knex
|
||||
*/
|
||||
exports.up = async function up(knex) {
|
||||
await knex.schema.createTable('metadata', table => {
|
||||
table.comment('The table of Bazaar metadata');
|
||||
@@ -59,6 +64,9 @@ exports.up = async function up(knex) {
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {import('knex').Knex} knex
|
||||
*/
|
||||
exports.down = async function down(knex) {
|
||||
await knex.schema.dropTable('metadata');
|
||||
await knex.schema.dropTable('members');
|
||||
|
||||
@@ -14,6 +14,11 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// @ts-check
|
||||
|
||||
/**
|
||||
* @param {import('knex').Knex} knex
|
||||
*/
|
||||
exports.up = async function up(knex) {
|
||||
await knex.schema.alterTable('metadata', table => {
|
||||
table
|
||||
@@ -35,6 +40,9 @@ exports.up = async function up(knex) {
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {import('knex').Knex} knex
|
||||
*/
|
||||
exports.down = async function down(knex) {
|
||||
await knex.schema.alterTable('metadata', table => {
|
||||
table
|
||||
|
||||
@@ -14,6 +14,11 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// @ts-check
|
||||
|
||||
/**
|
||||
* @param {import('knex').Knex} knex
|
||||
*/
|
||||
exports.up = async function up(knex) {
|
||||
if (knex.client.config.client.includes('sqlite3')) {
|
||||
await knex.schema.dropTable('metadata');
|
||||
@@ -92,6 +97,9 @@ exports.up = async function up(knex) {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {import('knex').Knex} knex
|
||||
*/
|
||||
exports.down = async function down(knex) {
|
||||
if (knex.client.config.client.includes('sqlite3')) {
|
||||
await knex.schema.dropTable('metadata');
|
||||
|
||||
@@ -14,12 +14,20 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// @ts-check
|
||||
|
||||
/**
|
||||
* @param {import('knex').Knex} knex
|
||||
*/
|
||||
exports.up = async function up(knex) {
|
||||
await knex.schema.alterTable('members', table => {
|
||||
table.string('user_ref').nullable();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {import('knex').Knex} knex
|
||||
*/
|
||||
exports.down = async function down(knex) {
|
||||
return knex.schema.table('members', table => {
|
||||
table.dropColumn('user_ref');
|
||||
|
||||
@@ -14,9 +14,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// @ts-check
|
||||
|
||||
/**
|
||||
* @param { import("knex").Knex } knex
|
||||
* @returns { Promise<void> }
|
||||
* @param {import('knex').Knex} knex
|
||||
*/
|
||||
exports.up = async function up(knex) {
|
||||
await knex.schema.alterTable('metadata', table => {
|
||||
@@ -25,8 +26,7 @@ exports.up = async function up(knex) {
|
||||
};
|
||||
|
||||
/**
|
||||
* @param { import("knex").Knex } knex
|
||||
* @returns { Promise<void> }
|
||||
* @param {import('knex').Knex} knex
|
||||
*/
|
||||
exports.down = async function down(knex) {
|
||||
await knex.schema.alterTable('metadata', table => {
|
||||
|
||||
+4
-8
@@ -15,11 +15,7 @@
|
||||
*/
|
||||
|
||||
import { getVoidLogger } from '@backstage/backend-common';
|
||||
import {
|
||||
configServiceRef,
|
||||
loggerServiceRef,
|
||||
schedulerServiceRef,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { coreServices } from '@backstage/backend-plugin-api';
|
||||
import {
|
||||
PluginTaskScheduler,
|
||||
TaskScheduleDefinition,
|
||||
@@ -66,9 +62,9 @@ describe('awsS3EntityProviderCatalogModule', () => {
|
||||
await startTestBackend({
|
||||
extensionPoints: [[catalogProcessingExtensionPoint, extensionPoint]],
|
||||
services: [
|
||||
[configServiceRef, config],
|
||||
[loggerServiceRef, getVoidLogger()],
|
||||
[schedulerServiceRef, scheduler],
|
||||
[coreServices.config, config],
|
||||
[coreServices.logger, getVoidLogger()],
|
||||
[coreServices.scheduler, scheduler],
|
||||
],
|
||||
features: [awsS3EntityProviderCatalogModule()],
|
||||
});
|
||||
|
||||
@@ -15,11 +15,9 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
coreServices,
|
||||
createBackendModule,
|
||||
loggerToWinstonLogger,
|
||||
configServiceRef,
|
||||
loggerServiceRef,
|
||||
schedulerServiceRef,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node';
|
||||
import { AwsS3EntityProvider } from '../providers';
|
||||
@@ -35,10 +33,10 @@ export const awsS3EntityProviderCatalogModule = createBackendModule({
|
||||
register(env) {
|
||||
env.registerInit({
|
||||
deps: {
|
||||
config: configServiceRef,
|
||||
config: coreServices.config,
|
||||
catalog: catalogProcessingExtensionPoint,
|
||||
logger: loggerServiceRef,
|
||||
scheduler: schedulerServiceRef,
|
||||
logger: coreServices.logger,
|
||||
scheduler: coreServices.scheduler,
|
||||
},
|
||||
async init({ config, catalog, logger, scheduler }) {
|
||||
catalog.addEntityProvider(
|
||||
|
||||
+4
-8
@@ -15,11 +15,7 @@
|
||||
*/
|
||||
|
||||
import { getVoidLogger } from '@backstage/backend-common';
|
||||
import {
|
||||
configServiceRef,
|
||||
loggerServiceRef,
|
||||
schedulerServiceRef,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { coreServices } from '@backstage/backend-plugin-api';
|
||||
import {
|
||||
PluginTaskScheduler,
|
||||
TaskScheduleDefinition,
|
||||
@@ -69,9 +65,9 @@ describe('azureDevOpsEntityProviderCatalogModule', () => {
|
||||
await startTestBackend({
|
||||
extensionPoints: [[catalogProcessingExtensionPoint, extensionPoint]],
|
||||
services: [
|
||||
[configServiceRef, config],
|
||||
[loggerServiceRef, getVoidLogger()],
|
||||
[schedulerServiceRef, scheduler],
|
||||
[coreServices.config, config],
|
||||
[coreServices.logger, getVoidLogger()],
|
||||
[coreServices.scheduler, scheduler],
|
||||
],
|
||||
features: [azureDevOpsEntityProviderCatalogModule()],
|
||||
});
|
||||
|
||||
+4
-6
@@ -17,9 +17,7 @@
|
||||
import {
|
||||
createBackendModule,
|
||||
loggerToWinstonLogger,
|
||||
configServiceRef,
|
||||
loggerServiceRef,
|
||||
schedulerServiceRef,
|
||||
coreServices,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node';
|
||||
import { AzureDevOpsEntityProvider } from '../providers';
|
||||
@@ -35,10 +33,10 @@ export const azureDevOpsEntityProviderCatalogModule = createBackendModule({
|
||||
register(env) {
|
||||
env.registerInit({
|
||||
deps: {
|
||||
config: configServiceRef,
|
||||
config: coreServices.config,
|
||||
catalog: catalogProcessingExtensionPoint,
|
||||
logger: loggerServiceRef,
|
||||
scheduler: schedulerServiceRef,
|
||||
logger: coreServices.logger,
|
||||
scheduler: coreServices.scheduler,
|
||||
},
|
||||
async init({ config, catalog, logger, scheduler }) {
|
||||
catalog.addEntityProvider(
|
||||
|
||||
+6
-12
@@ -20,13 +20,7 @@ import {
|
||||
PluginEndpointDiscovery,
|
||||
TokenManager,
|
||||
} from '@backstage/backend-common';
|
||||
import {
|
||||
configServiceRef,
|
||||
discoveryServiceRef,
|
||||
loggerServiceRef,
|
||||
schedulerServiceRef,
|
||||
tokenManagerServiceRef,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { coreServices } from '@backstage/backend-plugin-api';
|
||||
import {
|
||||
PluginTaskScheduler,
|
||||
TaskScheduleDefinition,
|
||||
@@ -84,11 +78,11 @@ describe('bitbucketCloudEntityProviderCatalogModule', () => {
|
||||
[eventsExtensionPoint, eventsExtensionPointImpl],
|
||||
],
|
||||
services: [
|
||||
[configServiceRef, config],
|
||||
[discoveryServiceRef, discovery],
|
||||
[loggerServiceRef, getVoidLogger()],
|
||||
[schedulerServiceRef, scheduler],
|
||||
[tokenManagerServiceRef, tokenManager],
|
||||
[coreServices.config, config],
|
||||
[coreServices.discovery, discovery],
|
||||
[coreServices.logger, getVoidLogger()],
|
||||
[coreServices.scheduler, scheduler],
|
||||
[coreServices.tokenManager, tokenManager],
|
||||
],
|
||||
features: [bitbucketCloudEntityProviderCatalogModule()],
|
||||
});
|
||||
|
||||
+5
-8
@@ -15,12 +15,9 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
configServiceRef,
|
||||
coreServices,
|
||||
createBackendModule,
|
||||
loggerServiceRef,
|
||||
loggerToWinstonLogger,
|
||||
schedulerServiceRef,
|
||||
tokenManagerServiceRef,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import {
|
||||
catalogProcessingExtensionPoint,
|
||||
@@ -40,13 +37,13 @@ export const bitbucketCloudEntityProviderCatalogModule = createBackendModule({
|
||||
deps: {
|
||||
catalog: catalogProcessingExtensionPoint,
|
||||
catalogApi: catalogServiceRef,
|
||||
config: configServiceRef,
|
||||
config: coreServices.config,
|
||||
// TODO(pjungermann): How to make this optional for those which only want the provider without event support?
|
||||
// Do we even want to support this?
|
||||
events: eventsExtensionPoint,
|
||||
logger: loggerServiceRef,
|
||||
scheduler: schedulerServiceRef,
|
||||
tokenManager: tokenManagerServiceRef,
|
||||
logger: coreServices.logger,
|
||||
scheduler: coreServices.scheduler,
|
||||
tokenManager: coreServices.tokenManager,
|
||||
},
|
||||
async init({
|
||||
catalog,
|
||||
|
||||
+4
-8
@@ -16,11 +16,7 @@
|
||||
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { getVoidLogger } from '@backstage/backend-common';
|
||||
import {
|
||||
configServiceRef,
|
||||
loggerServiceRef,
|
||||
schedulerServiceRef,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { coreServices } from '@backstage/backend-plugin-api';
|
||||
import {
|
||||
PluginTaskScheduler,
|
||||
TaskScheduleDefinition,
|
||||
@@ -73,9 +69,9 @@ describe('bitbucketServerEntityProviderCatalogModule', () => {
|
||||
await startTestBackend({
|
||||
extensionPoints: [[catalogProcessingExtensionPoint, extensionPoint]],
|
||||
services: [
|
||||
[configServiceRef, config],
|
||||
[loggerServiceRef, getVoidLogger()],
|
||||
[schedulerServiceRef, scheduler],
|
||||
[coreServices.config, config],
|
||||
[coreServices.logger, getVoidLogger()],
|
||||
[coreServices.scheduler, scheduler],
|
||||
],
|
||||
features: [bitbucketServerEntityProviderCatalogModule()],
|
||||
});
|
||||
|
||||
+4
-6
@@ -15,11 +15,9 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
configServiceRef,
|
||||
coreServices,
|
||||
createBackendModule,
|
||||
loggerServiceRef,
|
||||
loggerToWinstonLogger,
|
||||
schedulerServiceRef,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node';
|
||||
import { BitbucketServerEntityProvider } from '../providers';
|
||||
@@ -34,9 +32,9 @@ export const bitbucketServerEntityProviderCatalogModule = createBackendModule({
|
||||
env.registerInit({
|
||||
deps: {
|
||||
catalog: catalogProcessingExtensionPoint,
|
||||
config: configServiceRef,
|
||||
logger: loggerServiceRef,
|
||||
scheduler: schedulerServiceRef,
|
||||
config: coreServices.config,
|
||||
logger: coreServices.logger,
|
||||
scheduler: coreServices.scheduler,
|
||||
},
|
||||
async init({ catalog, config, logger, scheduler }) {
|
||||
const winstonLogger = loggerToWinstonLogger(logger);
|
||||
|
||||
+4
-8
@@ -15,11 +15,7 @@
|
||||
*/
|
||||
|
||||
import { getVoidLogger } from '@backstage/backend-common';
|
||||
import {
|
||||
configServiceRef,
|
||||
loggerServiceRef,
|
||||
schedulerServiceRef,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { coreServices } from '@backstage/backend-plugin-api';
|
||||
import {
|
||||
PluginTaskScheduler,
|
||||
TaskScheduleDefinition,
|
||||
@@ -79,9 +75,9 @@ describe('gerritEntityProviderCatalogModule', () => {
|
||||
await startTestBackend({
|
||||
extensionPoints: [[catalogProcessingExtensionPoint, extensionPoint]],
|
||||
services: [
|
||||
[configServiceRef, config],
|
||||
[loggerServiceRef, getVoidLogger()],
|
||||
[schedulerServiceRef, scheduler],
|
||||
[coreServices.config, config],
|
||||
[coreServices.logger, getVoidLogger()],
|
||||
[coreServices.scheduler, scheduler],
|
||||
],
|
||||
features: [gerritEntityProviderCatalogModule()],
|
||||
});
|
||||
|
||||
+4
-6
@@ -15,11 +15,9 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
configServiceRef,
|
||||
coreServices,
|
||||
createBackendModule,
|
||||
loggerServiceRef,
|
||||
loggerToWinstonLogger,
|
||||
schedulerServiceRef,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node';
|
||||
import { GerritEntityProvider } from '../providers/GerritEntityProvider';
|
||||
@@ -34,9 +32,9 @@ export const gerritEntityProviderCatalogModule = createBackendModule({
|
||||
env.registerInit({
|
||||
deps: {
|
||||
catalog: catalogProcessingExtensionPoint,
|
||||
config: configServiceRef,
|
||||
logger: loggerServiceRef,
|
||||
scheduler: schedulerServiceRef,
|
||||
config: coreServices.config,
|
||||
logger: coreServices.logger,
|
||||
scheduler: coreServices.scheduler,
|
||||
},
|
||||
async init({ catalog, config, logger, scheduler }) {
|
||||
const winstonLogger = loggerToWinstonLogger(logger);
|
||||
|
||||
+4
-8
@@ -15,11 +15,7 @@
|
||||
*/
|
||||
|
||||
import { getVoidLogger } from '@backstage/backend-common';
|
||||
import {
|
||||
configServiceRef,
|
||||
loggerServiceRef,
|
||||
schedulerServiceRef,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { coreServices } from '@backstage/backend-plugin-api';
|
||||
import {
|
||||
PluginTaskScheduler,
|
||||
TaskScheduleDefinition,
|
||||
@@ -66,9 +62,9 @@ describe('githubEntityProviderCatalogModule', () => {
|
||||
await startTestBackend({
|
||||
extensionPoints: [[catalogProcessingExtensionPoint, extensionPoint]],
|
||||
services: [
|
||||
[configServiceRef, config],
|
||||
[loggerServiceRef, getVoidLogger()],
|
||||
[schedulerServiceRef, scheduler],
|
||||
[coreServices.config, config],
|
||||
[coreServices.logger, getVoidLogger()],
|
||||
[coreServices.scheduler, scheduler],
|
||||
],
|
||||
features: [githubEntityProviderCatalogModule()],
|
||||
});
|
||||
|
||||
+4
-6
@@ -17,9 +17,7 @@
|
||||
import {
|
||||
createBackendModule,
|
||||
loggerToWinstonLogger,
|
||||
configServiceRef,
|
||||
loggerServiceRef,
|
||||
schedulerServiceRef,
|
||||
coreServices,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node';
|
||||
import { GithubEntityProvider } from '../providers/GithubEntityProvider';
|
||||
@@ -36,9 +34,9 @@ export const githubEntityProviderCatalogModule = createBackendModule({
|
||||
env.registerInit({
|
||||
deps: {
|
||||
catalog: catalogProcessingExtensionPoint,
|
||||
config: configServiceRef,
|
||||
logger: loggerServiceRef,
|
||||
scheduler: schedulerServiceRef,
|
||||
config: coreServices.config,
|
||||
logger: coreServices.logger,
|
||||
scheduler: coreServices.scheduler,
|
||||
},
|
||||
async init({ catalog, config, logger, scheduler }) {
|
||||
catalog.addEntityProvider(
|
||||
|
||||
+4
-8
@@ -15,11 +15,7 @@
|
||||
*/
|
||||
|
||||
import { getVoidLogger } from '@backstage/backend-common';
|
||||
import {
|
||||
configServiceRef,
|
||||
loggerServiceRef,
|
||||
schedulerServiceRef,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { coreServices } from '@backstage/backend-plugin-api';
|
||||
import {
|
||||
PluginTaskScheduler,
|
||||
TaskScheduleDefinition,
|
||||
@@ -78,9 +74,9 @@ describe('gitlabDiscoveryEntityProviderCatalogModule', () => {
|
||||
await startTestBackend({
|
||||
extensionPoints: [[catalogProcessingExtensionPoint, extensionPoint]],
|
||||
services: [
|
||||
[configServiceRef, config],
|
||||
[loggerServiceRef, getVoidLogger()],
|
||||
[schedulerServiceRef, scheduler],
|
||||
[coreServices.config, config],
|
||||
[coreServices.logger, getVoidLogger()],
|
||||
[coreServices.scheduler, scheduler],
|
||||
],
|
||||
features: [gitlabDiscoveryEntityProviderCatalogModule()],
|
||||
});
|
||||
|
||||
+4
-6
@@ -17,9 +17,7 @@
|
||||
import {
|
||||
createBackendModule,
|
||||
loggerToWinstonLogger,
|
||||
configServiceRef,
|
||||
loggerServiceRef,
|
||||
schedulerServiceRef,
|
||||
coreServices,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node';
|
||||
import { GitlabDiscoveryEntityProvider } from '../providers';
|
||||
@@ -35,10 +33,10 @@ export const gitlabDiscoveryEntityProviderCatalogModule = createBackendModule({
|
||||
register(env) {
|
||||
env.registerInit({
|
||||
deps: {
|
||||
config: configServiceRef,
|
||||
config: coreServices.config,
|
||||
catalog: catalogProcessingExtensionPoint,
|
||||
logger: loggerServiceRef,
|
||||
scheduler: schedulerServiceRef,
|
||||
logger: coreServices.logger,
|
||||
scheduler: coreServices.scheduler,
|
||||
},
|
||||
async init({ config, catalog, logger, scheduler }) {
|
||||
catalog.addEntityProvider(
|
||||
|
||||
+8
-6
@@ -14,6 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// @ts-check
|
||||
|
||||
/**
|
||||
* @param { import("knex").Knex } knex
|
||||
*/
|
||||
@@ -25,7 +27,7 @@ exports.up = async function up(knex) {
|
||||
table.comment('Tracks ingestion streams for very large data sets');
|
||||
|
||||
table
|
||||
.uuid('id', { primary: true })
|
||||
.uuid('id')
|
||||
.notNullable()
|
||||
.comment('Auto-generated ID of the ingestion');
|
||||
|
||||
@@ -85,7 +87,7 @@ exports.up = async function up(knex) {
|
||||
});
|
||||
|
||||
await knex.schema.alterTable('ingestions', t => {
|
||||
t.primary('id');
|
||||
t.primary(['id']);
|
||||
t.index('provider_name', 'ingestion_provider_name_idx');
|
||||
t.unique(['provider_name', 'completion_ticket'], {
|
||||
indexName: 'ingestion_composite_index',
|
||||
@@ -100,7 +102,7 @@ exports.up = async function up(knex) {
|
||||
table.comment('tracks each step of an iterative ingestion');
|
||||
|
||||
table
|
||||
.uuid('id', { primary: true })
|
||||
.uuid('id')
|
||||
.notNullable()
|
||||
.comment('Auto-generated ID of the ingestion mark');
|
||||
|
||||
@@ -128,7 +130,7 @@ exports.up = async function up(knex) {
|
||||
});
|
||||
|
||||
await knex.schema.alterTable('ingestion_marks', t => {
|
||||
t.primary('id');
|
||||
t.primary(['id']);
|
||||
t.index('ingestion_id', 'ingestion_mark_ingestion_id_idx');
|
||||
});
|
||||
|
||||
@@ -141,7 +143,7 @@ exports.up = async function up(knex) {
|
||||
);
|
||||
|
||||
table
|
||||
.uuid('id', { primary: true })
|
||||
.uuid('id')
|
||||
.notNullable()
|
||||
.comment('Auto-generated ID of the marked entity');
|
||||
|
||||
@@ -162,7 +164,7 @@ exports.up = async function up(knex) {
|
||||
});
|
||||
|
||||
await knex.schema.alterTable('ingestion_mark_entities', t => {
|
||||
t.primary('id');
|
||||
t.primary(['id']);
|
||||
t.index('ingestion_mark_id', 'ingestion_mark_entity_ingestion_mark_id_idx');
|
||||
});
|
||||
};
|
||||
|
||||
+6
-12
@@ -15,13 +15,7 @@
|
||||
*/
|
||||
|
||||
import { getVoidLogger } from '@backstage/backend-common';
|
||||
import {
|
||||
configServiceRef,
|
||||
databaseServiceRef,
|
||||
httpRouterServiceRef,
|
||||
loggerServiceRef,
|
||||
schedulerServiceRef,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { coreServices } from '@backstage/backend-plugin-api';
|
||||
import { startTestBackend } from '@backstage/backend-test-utils';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node';
|
||||
@@ -56,11 +50,11 @@ describe('bitbucketServerEntityProviderCatalogModule', () => {
|
||||
[catalogProcessingExtensionPoint, { addEntityProvider }],
|
||||
],
|
||||
services: [
|
||||
[configServiceRef, new ConfigReader({})],
|
||||
[databaseServiceRef, database],
|
||||
[httpRouterServiceRef, httpRouter],
|
||||
[loggerServiceRef, getVoidLogger()],
|
||||
[schedulerServiceRef, scheduler],
|
||||
[coreServices.config, new ConfigReader({})],
|
||||
[coreServices.database, database],
|
||||
[coreServices.httpRouter, httpRouter],
|
||||
[coreServices.logger, getVoidLogger()],
|
||||
[coreServices.scheduler, scheduler],
|
||||
],
|
||||
features: [
|
||||
incrementalIngestionEntityProviderCatalogModule({
|
||||
|
||||
+6
-10
@@ -15,12 +15,8 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
configServiceRef,
|
||||
coreServices,
|
||||
createBackendModule,
|
||||
databaseServiceRef,
|
||||
httpRouterServiceRef,
|
||||
loggerServiceRef,
|
||||
schedulerServiceRef,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node';
|
||||
import {
|
||||
@@ -50,11 +46,11 @@ export const incrementalIngestionEntityProviderCatalogModule =
|
||||
env.registerInit({
|
||||
deps: {
|
||||
catalog: catalogProcessingExtensionPoint,
|
||||
config: configServiceRef,
|
||||
database: databaseServiceRef,
|
||||
httpRouter: httpRouterServiceRef,
|
||||
logger: loggerServiceRef,
|
||||
scheduler: schedulerServiceRef,
|
||||
config: coreServices.config,
|
||||
database: coreServices.database,
|
||||
httpRouter: coreServices.httpRouter,
|
||||
logger: coreServices.logger,
|
||||
scheduler: coreServices.scheduler,
|
||||
},
|
||||
async init({
|
||||
catalog,
|
||||
|
||||
@@ -27,7 +27,7 @@ import {
|
||||
tokenManagerFactory,
|
||||
urlReaderFactory,
|
||||
} from '@backstage/backend-app-api';
|
||||
import { configServiceRef } from '@backstage/backend-plugin-api';
|
||||
import { coreServices } from '@backstage/backend-plugin-api';
|
||||
import { startTestBackend } from '@backstage/backend-test-utils';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { catalogPlugin } from '@backstage/plugin-catalog-backend';
|
||||
@@ -62,7 +62,7 @@ async function main() {
|
||||
|
||||
await startTestBackend({
|
||||
services: [
|
||||
[configServiceRef, new ConfigReader(config)],
|
||||
[coreServices.config, new ConfigReader(config)],
|
||||
databaseFactory(),
|
||||
discoveryFactory(),
|
||||
httpRouterFactory(),
|
||||
|
||||
+4
-8
@@ -15,11 +15,7 @@
|
||||
*/
|
||||
|
||||
import { getVoidLogger } from '@backstage/backend-common';
|
||||
import {
|
||||
configServiceRef,
|
||||
loggerServiceRef,
|
||||
schedulerServiceRef,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { coreServices } from '@backstage/backend-plugin-api';
|
||||
import {
|
||||
PluginTaskScheduler,
|
||||
TaskScheduleDefinition,
|
||||
@@ -71,9 +67,9 @@ describe('awsS3EntityProviderCatalogModule', () => {
|
||||
await startTestBackend({
|
||||
extensionPoints: [[catalogProcessingExtensionPoint, extensionPoint]],
|
||||
services: [
|
||||
[configServiceRef, config],
|
||||
[loggerServiceRef, getVoidLogger()],
|
||||
[schedulerServiceRef, scheduler],
|
||||
[coreServices.config, config],
|
||||
[coreServices.logger, getVoidLogger()],
|
||||
[coreServices.scheduler, scheduler],
|
||||
],
|
||||
features: [microsoftGraphOrgEntityProviderCatalogModule()],
|
||||
});
|
||||
|
||||
+4
-6
@@ -15,11 +15,9 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
configServiceRef,
|
||||
coreServices,
|
||||
createBackendModule,
|
||||
loggerServiceRef,
|
||||
loggerToWinstonLogger,
|
||||
schedulerServiceRef,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node';
|
||||
import {
|
||||
@@ -72,9 +70,9 @@ export const microsoftGraphOrgEntityProviderCatalogModule = createBackendModule(
|
||||
env.registerInit({
|
||||
deps: {
|
||||
catalog: catalogProcessingExtensionPoint,
|
||||
config: configServiceRef,
|
||||
logger: loggerServiceRef,
|
||||
scheduler: schedulerServiceRef,
|
||||
config: coreServices.config,
|
||||
logger: coreServices.logger,
|
||||
scheduler: coreServices.scheduler,
|
||||
},
|
||||
async init({ catalog, config, logger, scheduler }) {
|
||||
catalog.addEntityProvider(
|
||||
|
||||
@@ -27,7 +27,7 @@ exports.up = async function up(knex) {
|
||||
//
|
||||
.createTable('locations', table => {
|
||||
table.comment(
|
||||
'Registered locations that shall be contiuously scanned for catalog item updates',
|
||||
'Registered locations that shall be continuously scanned for catalog item updates',
|
||||
);
|
||||
table
|
||||
.uuid('id')
|
||||
@@ -59,7 +59,7 @@ exports.up = async function up(knex) {
|
||||
'An opaque string that changes for each update operation to any part of the entity, including metadata.',
|
||||
);
|
||||
table
|
||||
.string('generation')
|
||||
.integer('generation')
|
||||
.notNullable()
|
||||
.unsigned()
|
||||
.comment(
|
||||
|
||||
@@ -31,6 +31,7 @@ exports.up = async function up(knex) {
|
||||
}
|
||||
await knex.schema.alterTable('entities', table => {
|
||||
table.dropUnique([], 'entities_unique_name');
|
||||
table.dropForeign(['location_id']);
|
||||
});
|
||||
// Setup temporary tables
|
||||
await knex.schema.renameTable('entities_search', 'tmp_entities_search');
|
||||
@@ -56,7 +57,7 @@ exports.up = async function up(knex) {
|
||||
'An opaque string that changes for each update operation to any part of the entity, including metadata.',
|
||||
);
|
||||
table
|
||||
.string('generation')
|
||||
.integer('generation')
|
||||
.notNullable()
|
||||
.unsigned()
|
||||
.comment(
|
||||
|
||||
@@ -23,7 +23,7 @@ exports.up = async function up(knex) {
|
||||
// Sqlite does not support alter column.
|
||||
if (!knex.client.config.client.includes('sqlite3')) {
|
||||
await knex.schema.alterTable('entities_search', table => {
|
||||
table.text('value').nullable().alter({ alterType: true });
|
||||
table.string('value').nullable().alter({ alterType: true });
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -24,8 +24,8 @@ exports.up = async function up(knex) {
|
||||
.where({ namespace: null })
|
||||
.update({ namespace: 'default' });
|
||||
await knex('entities_search').update({
|
||||
key: knex.raw('LOWER(key)'),
|
||||
value: knex.raw('LOWER(value)'),
|
||||
key: knex.raw('LOWER(??)', ['key']),
|
||||
value: knex.raw('LOWER(??)', ['value']),
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -21,19 +21,20 @@
|
||||
*/
|
||||
exports.up = async function up(knex) {
|
||||
await knex.schema.alterTable('entities', table => {
|
||||
table.text('full_name').nullable();
|
||||
table.string('full_name').nullable();
|
||||
});
|
||||
|
||||
await knex('entities').update({
|
||||
full_name: knex.raw(
|
||||
"LOWER(kind) || ':' || LOWER(COALESCE(namespace, 'default')) || '/' || LOWER(name)",
|
||||
"LOWER(??) || ':' || LOWER(COALESCE(??, 'default')) || '/' || LOWER(??)",
|
||||
['kind', 'namespace', 'name'],
|
||||
),
|
||||
});
|
||||
|
||||
// SQLite does not support alter column
|
||||
if (!knex.client.config.client.includes('sqlite3')) {
|
||||
await knex.schema.alterTable('entities', table => {
|
||||
table.text('full_name').notNullable().alter({ alterNullable: true });
|
||||
table.string('full_name').notNullable().alter({ alterNullable: true });
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,8 @@ exports.up = async function up(knex) {
|
||||
// apiVersion and kind should not contain any JSON unsafe chars, and both
|
||||
// metadata and spec are already valid serialized JSON
|
||||
data: knex.raw(
|
||||
`'{"apiVersion":"' || api_version || '","kind":"' || kind || '","metadata":' || metadata || COALESCE(',"spec":' || spec, '') || '}'`,
|
||||
`'{"apiVersion":"' || ?? || '","kind":"' || ?? || '","metadata":' || ?? || COALESCE(',"spec":' || ??, '') || '}'`,
|
||||
['api_version', 'kind', 'metadata', 'spec'],
|
||||
),
|
||||
});
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ exports.up = async function up(knex) {
|
||||
// sqlite doesn't support dropPrimary so we recreate it properly instead
|
||||
await knex.schema.dropTable('entities_relations');
|
||||
await knex.schema.createTable('entities_relations', table => {
|
||||
table.comment('All relations between entities in the catalog');
|
||||
table.comment('All relations between entities');
|
||||
table
|
||||
.uuid('originating_entity_id')
|
||||
.references('id')
|
||||
@@ -61,7 +61,7 @@ exports.down = async function down(knex) {
|
||||
if (knex.client.config.client.includes('sqlite3')) {
|
||||
await knex.schema.dropTable('entities_relations');
|
||||
await knex.schema.createTable('entities_relations', table => {
|
||||
table.comment('All relations between entities in the catalog');
|
||||
table.comment('All relations between entities');
|
||||
table
|
||||
.uuid('originating_entity_id')
|
||||
.references('id')
|
||||
|
||||
@@ -20,39 +20,30 @@
|
||||
* @param {import('knex').Knex} knex
|
||||
*/
|
||||
exports.up = async function up(knex) {
|
||||
const isMySQL = knex.client.config.client.includes('mysql');
|
||||
await knex.schema.createTable('refresh_state', table => {
|
||||
table.comment(
|
||||
'Location refresh states. Every individual location (that was ever directly or indirectly discovered) and entity has an entry in this table. It therefore represents the entire live set of things that the refresh loop considers.',
|
||||
);
|
||||
table.comment('Location refresh states');
|
||||
table
|
||||
.text('entity_id')
|
||||
.string('entity_id')
|
||||
.primary()
|
||||
.notNullable()
|
||||
.comment(
|
||||
'Primary ID, which will also be used as the uid of the resulting entity',
|
||||
);
|
||||
.comment('Primary ID, also used as the uid of the entity');
|
||||
table
|
||||
.text('entity_ref')
|
||||
.string('entity_ref')
|
||||
.notNullable()
|
||||
.comment('A reference to the entity that the refresh state is tied to');
|
||||
.comment('A reference to the entity for this refresh state');
|
||||
table
|
||||
.text('unprocessed_entity')
|
||||
.notNullable()
|
||||
.comment(
|
||||
'The unprocessed entity (in its source form, before being run through all of the processors) as JSON',
|
||||
);
|
||||
.comment('The unprocessed entity (in original form) as JSON');
|
||||
table
|
||||
.text('processed_entity')
|
||||
.nullable()
|
||||
.comment(
|
||||
'The processed entity (after running through all processors, but before being stitched together with state and relations) as JSON',
|
||||
);
|
||||
.comment('The processed entity (not yet stitched) as JSON');
|
||||
table
|
||||
.text('cache')
|
||||
.nullable()
|
||||
.comment(
|
||||
'Cache information tied to the refreshing of this entity, such as etag information or actual response caching',
|
||||
);
|
||||
.comment('Cache information tied to refreshes of this entity');
|
||||
table
|
||||
.text('errors')
|
||||
.notNullable()
|
||||
@@ -64,7 +55,7 @@ exports.up = async function up(knex) {
|
||||
table
|
||||
.dateTime('last_discovery_at') // TODO: timezone or change to epoch-millis or similar
|
||||
.notNullable()
|
||||
.comment('The last timestamp of which this entity was discovered');
|
||||
.comment('The last timestamp that this entity was discovered');
|
||||
table.unique(['entity_ref'], {
|
||||
indexName: 'refresh_state_entity_ref_uniq',
|
||||
});
|
||||
@@ -74,31 +65,23 @@ exports.up = async function up(knex) {
|
||||
});
|
||||
|
||||
await knex.schema.createTable('final_entities', table => {
|
||||
table.comment(
|
||||
'This table contains the final entity result after processing and stitching',
|
||||
);
|
||||
table.comment('Final entities after processing and stitching');
|
||||
table
|
||||
.text('entity_id')
|
||||
.string('entity_id')
|
||||
.primary()
|
||||
.notNullable()
|
||||
.references('entity_id')
|
||||
.inTable('refresh_state')
|
||||
.onDelete('CASCADE')
|
||||
.comment(
|
||||
'Entity ID which corresponds to the ID in the refresh_state table',
|
||||
);
|
||||
.comment('Entity ID -> refresh_state table');
|
||||
table
|
||||
.text('hash')
|
||||
.string('hash')
|
||||
.notNullable()
|
||||
.comment(
|
||||
'Stable hash of the entity data, to be used for caching and avoiding redundant work',
|
||||
);
|
||||
.comment('Stable hash of the entity data');
|
||||
table
|
||||
.text('stitch_ticket')
|
||||
.notNullable()
|
||||
.comment(
|
||||
'A random value representing a unique stitch attempt ticket, that gets updated each time that a stitching attempt is made on the entity',
|
||||
);
|
||||
.comment('Random value representing a unique stitch attempt ticket');
|
||||
table
|
||||
.text('final_entity')
|
||||
.nullable()
|
||||
@@ -107,29 +90,24 @@ exports.up = async function up(knex) {
|
||||
});
|
||||
|
||||
await knex.schema.createTable('refresh_state_references', table => {
|
||||
table.comment(
|
||||
'Holds edges between refresh state rows. Every time when an entity is processed and emits another entity, an edge will be stored to represent that fact. This is used to detect orphans and ultimately deletions.',
|
||||
);
|
||||
const textColumn = isMySQL
|
||||
? table.string.bind(table)
|
||||
: table.text.bind(table);
|
||||
|
||||
table.comment('Edges between refresh state rows');
|
||||
table
|
||||
.increments('id')
|
||||
.comment('Primary key to distinguish unique lines from each other');
|
||||
table
|
||||
.text('source_key')
|
||||
textColumn('source_key')
|
||||
.nullable()
|
||||
.comment(
|
||||
'When the reference source is not an entity, this is an opaque identifier for that source.',
|
||||
);
|
||||
table
|
||||
.text('source_entity_ref')
|
||||
.comment('Opaque identifier for non-entity sources');
|
||||
textColumn('source_entity_ref')
|
||||
.nullable()
|
||||
.references('entity_ref')
|
||||
.inTable('refresh_state')
|
||||
.onDelete('CASCADE')
|
||||
.comment(
|
||||
'When the reference source is an entity, this is the EntityRef of the source entity.',
|
||||
);
|
||||
table
|
||||
.text('target_entity_ref')
|
||||
.comment('EntityRef of entity sources');
|
||||
textColumn('target_entity_ref')
|
||||
.notNullable()
|
||||
.references('entity_ref')
|
||||
.inTable('refresh_state')
|
||||
@@ -147,36 +125,34 @@ exports.up = async function up(knex) {
|
||||
});
|
||||
|
||||
await knex.schema.createTable('relations', table => {
|
||||
table.comment('All relations between entities in the catalog');
|
||||
table.comment('All relations between entities');
|
||||
table
|
||||
.text('originating_entity_id')
|
||||
.string('originating_entity_id')
|
||||
.references('entity_id')
|
||||
.inTable('refresh_state')
|
||||
.onDelete('CASCADE')
|
||||
.notNullable()
|
||||
.comment('The entity that provided the relation');
|
||||
table
|
||||
.text('source_entity_ref')
|
||||
.string('source_entity_ref')
|
||||
.notNullable()
|
||||
.comment('The entity reference of the source entity of the relation');
|
||||
.comment('Entity reference of the source entity of the relation');
|
||||
table
|
||||
.text('type')
|
||||
.string('type')
|
||||
.notNullable()
|
||||
.comment('The type of the relation between the entities');
|
||||
table
|
||||
.text('target_entity_ref')
|
||||
.string('target_entity_ref')
|
||||
.notNullable()
|
||||
.comment('The entity reference of the target entity of the relation');
|
||||
.comment('Entity reference of the target entity of the relation');
|
||||
table.index('source_entity_ref', 'relations_source_entity_ref_idx');
|
||||
table.index('originating_entity_id', 'relations_source_entity_id_idx');
|
||||
});
|
||||
|
||||
await knex.schema.createTable('search', table => {
|
||||
table.comment(
|
||||
'Flattened key-values from the entities, used for quick filtering',
|
||||
);
|
||||
table.comment('Flattened key-values from the entities, for filtering');
|
||||
table
|
||||
.text('entity_id')
|
||||
.string('entity_id')
|
||||
.references('entity_id')
|
||||
.inTable('refresh_state')
|
||||
.onDelete('CASCADE')
|
||||
|
||||
@@ -24,9 +24,7 @@ exports.up = async function up(knex) {
|
||||
table
|
||||
.text('location_key')
|
||||
.nullable()
|
||||
.comment(
|
||||
'An opaque key that uniquely identifies the location of an entity in order to support conflict resolution',
|
||||
);
|
||||
.comment('Opaque conflict resolution key');
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ exports.up = async function up(knex) {
|
||||
table
|
||||
.text('unprocessed_hash')
|
||||
.nullable()
|
||||
.comment('A hash of the unprocessed contents, used to detect changes');
|
||||
.comment('A hash of the unprocessed contents');
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// @ts-check
|
||||
|
||||
/**
|
||||
* @param { import("knex").Knex } knex
|
||||
*/
|
||||
@@ -23,14 +25,14 @@ exports.up = async function up(knex) {
|
||||
'This table contains relations between entities and keys to trigger refreshes with',
|
||||
);
|
||||
table
|
||||
.text('entity_id')
|
||||
.string('entity_id')
|
||||
.notNullable()
|
||||
.references('entity_id')
|
||||
.inTable('refresh_state')
|
||||
.onDelete('CASCADE')
|
||||
.comment('A reference to the entity that the refresh key is tied to');
|
||||
table
|
||||
.text('key')
|
||||
.string('key')
|
||||
.notNullable()
|
||||
.comment(
|
||||
'A reference to a key which should be used to trigger a refresh on this entity',
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// @ts-check
|
||||
|
||||
/**
|
||||
* @param { import("knex").Knex } knex
|
||||
*/
|
||||
|
||||
@@ -36,7 +36,7 @@ import { generateStableHash } from './util';
|
||||
describe('Default Processing Database', () => {
|
||||
const defaultLogger = getVoidLogger();
|
||||
const databases = TestDatabases.create({
|
||||
ids: ['POSTGRES_13', 'POSTGRES_9', 'SQLITE_3'],
|
||||
ids: ['MYSQL_8', 'POSTGRES_13', 'POSTGRES_9', 'SQLITE_3'],
|
||||
});
|
||||
|
||||
async function createDatabase(
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
import { Entity, stringifyEntityRef } from '@backstage/catalog-model';
|
||||
import { ConflictError, isError, NotFoundError } from '@backstage/errors';
|
||||
import { ConflictError, NotFoundError } from '@backstage/errors';
|
||||
import { Knex } from 'knex';
|
||||
import lodash from 'lodash';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
@@ -82,6 +82,7 @@ export class DefaultProcessingDatabase implements ProcessingDatabase {
|
||||
refreshKeys,
|
||||
locationKey,
|
||||
} = options;
|
||||
const configClient = tx.client.config.client;
|
||||
const refreshResult = await tx<DbRefreshStateRow>('refresh_state')
|
||||
.update({
|
||||
processed_entity: JSON.stringify(processedEntity),
|
||||
@@ -114,10 +115,7 @@ export class DefaultProcessingDatabase implements ProcessingDatabase {
|
||||
// Delete old relations
|
||||
// NOTE(freben): knex implemented support for returning() on update queries for sqlite, but at the current time of writing (Sep 2022) not for delete() queries.
|
||||
let previousRelationRows: DbRelationsRow[];
|
||||
if (
|
||||
tx.client.config.client.includes('sqlite3') ||
|
||||
tx.client.config.client.includes('mysql')
|
||||
) {
|
||||
if (configClient.includes('sqlite3') || configClient.includes('mysql')) {
|
||||
previousRelationRows = await tx<DbRelationsRow>('relations')
|
||||
.select('*')
|
||||
.where({ originating_entity_id: id });
|
||||
@@ -663,11 +661,11 @@ export class DefaultProcessingDatabase implements ProcessingDatabase {
|
||||
last_discovery_at: tx.fn.now(),
|
||||
});
|
||||
|
||||
// TODO(Rugvip): only tested towards Postgres and SQLite
|
||||
// TODO(Rugvip): only tested towards MySQL, Postgres and SQLite.
|
||||
// We have to do this because the only way to detect if there was a conflict with
|
||||
// SQLite is to catch the error, while Postgres needs to ignore the conflict to not
|
||||
// break the ongoing transaction.
|
||||
if (!tx.client.config.client.includes('sqlite3')) {
|
||||
if (tx.client.config.client.includes('pg')) {
|
||||
query = query.onConflict('entity_ref').ignore() as any; // type here does not match runtime
|
||||
}
|
||||
|
||||
@@ -675,14 +673,15 @@ export class DefaultProcessingDatabase implements ProcessingDatabase {
|
||||
const result: { rowCount?: number; length?: number } = await query;
|
||||
return result.rowCount === 1 || result.length === 1;
|
||||
} catch (error) {
|
||||
// SQLite reached this rather than the rowCount check above
|
||||
if (
|
||||
isError(error) &&
|
||||
error.message.includes('UNIQUE constraint failed')
|
||||
) {
|
||||
// SQLite, or MySQL reached this rather than the rowCount check above
|
||||
if (!isDatabaseConflictError(error)) {
|
||||
throw error;
|
||||
} else {
|
||||
this.options.logger.debug(
|
||||
`Unable to insert a new refresh state row, ${error}`,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ import { DefaultLocationStore } from './DefaultLocationStore';
|
||||
|
||||
describe('DefaultLocationStore', () => {
|
||||
const databases = TestDatabases.create({
|
||||
ids: ['POSTGRES_13', 'POSTGRES_9', 'SQLITE_3'],
|
||||
ids: ['MYSQL_8', 'POSTGRES_13', 'POSTGRES_9', 'SQLITE_3'],
|
||||
});
|
||||
|
||||
async function createLocationStore(databaseId: TestDatabaseId) {
|
||||
|
||||
@@ -14,15 +14,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import {
|
||||
configServiceRef,
|
||||
createBackendPlugin,
|
||||
databaseServiceRef,
|
||||
loggerServiceRef,
|
||||
coreServices,
|
||||
loggerToWinstonLogger,
|
||||
permissionsServiceRef,
|
||||
urlReaderServiceRef,
|
||||
httpRouterServiceRef,
|
||||
lifecycleServiceRef,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { CatalogBuilder } from './CatalogBuilder';
|
||||
import {
|
||||
@@ -73,13 +67,13 @@ export const catalogPlugin = createBackendPlugin({
|
||||
|
||||
env.registerInit({
|
||||
deps: {
|
||||
logger: loggerServiceRef,
|
||||
config: configServiceRef,
|
||||
reader: urlReaderServiceRef,
|
||||
permissions: permissionsServiceRef,
|
||||
database: databaseServiceRef,
|
||||
httpRouter: httpRouterServiceRef,
|
||||
lifecycle: lifecycleServiceRef,
|
||||
logger: coreServices.logger,
|
||||
config: coreServices.config,
|
||||
reader: coreServices.urlReader,
|
||||
permissions: coreServices.permissions,
|
||||
database: coreServices.database,
|
||||
httpRouter: coreServices.httpRouter,
|
||||
lifecycle: coreServices.lifecycle,
|
||||
},
|
||||
async init({
|
||||
logger,
|
||||
|
||||
@@ -31,7 +31,7 @@ import { DefaultEntitiesCatalog } from './DefaultEntitiesCatalog';
|
||||
|
||||
describe('DefaultEntitiesCatalog', () => {
|
||||
const databases = TestDatabases.create({
|
||||
ids: ['POSTGRES_13', 'POSTGRES_9', 'SQLITE_3'],
|
||||
ids: ['MYSQL_8', 'POSTGRES_13', 'POSTGRES_9', 'SQLITE_3'],
|
||||
});
|
||||
const stitch = jest.fn();
|
||||
const stitcher: Stitcher = { stitch } as any;
|
||||
|
||||
@@ -277,6 +277,8 @@ export class DefaultEntitiesCatalog implements EntitiesCatalog {
|
||||
}
|
||||
|
||||
async removeEntityByUid(uid: string): Promise<void> {
|
||||
const dbConfig = this.database.client.config;
|
||||
|
||||
// Clear the hashed state of the immediate parents of the deleted entity.
|
||||
// This makes sure that when they get reprocessed, their output is written
|
||||
// down again. The reason for wanting to do this, is that if the user
|
||||
@@ -285,21 +287,53 @@ export class DefaultEntitiesCatalog implements EntitiesCatalog {
|
||||
// means it'll never try to write down the children again (it assumes that
|
||||
// they already exist). This means that without the code below, the database
|
||||
// never "heals" from accidental deletes.
|
||||
await this.database<DbRefreshStateRow>('refresh_state')
|
||||
.update({
|
||||
result_hash: 'child-was-deleted',
|
||||
next_update_at: this.database.fn.now(),
|
||||
})
|
||||
.whereIn('entity_ref', function parents(builder) {
|
||||
return builder
|
||||
.from<DbRefreshStateRow>('refresh_state')
|
||||
.innerJoin<DbRefreshStateReferencesRow>('refresh_state_references', {
|
||||
'refresh_state_references.target_entity_ref':
|
||||
'refresh_state.entity_ref',
|
||||
})
|
||||
.where('refresh_state.entity_id', '=', uid)
|
||||
.select('refresh_state_references.source_entity_ref');
|
||||
});
|
||||
if (dbConfig.client.includes('mysql')) {
|
||||
// MySQL doesn't support the syntax we need to do this in a single query,
|
||||
// http://dev.mysql.com/doc/refman/5.6/en/update.html
|
||||
const results = await this.database<DbRefreshStateRow>('refresh_state')
|
||||
.select('entity_id')
|
||||
.whereIn('entity_ref', function parents(builder) {
|
||||
return builder
|
||||
.from<DbRefreshStateRow>('refresh_state')
|
||||
.innerJoin<DbRefreshStateReferencesRow>(
|
||||
'refresh_state_references',
|
||||
{
|
||||
'refresh_state_references.target_entity_ref':
|
||||
'refresh_state.entity_ref',
|
||||
},
|
||||
)
|
||||
.where('refresh_state.entity_id', '=', uid)
|
||||
.select('refresh_state_references.source_entity_ref');
|
||||
});
|
||||
await this.database<DbRefreshStateRow>('refresh_state')
|
||||
.update({
|
||||
result_hash: 'child-was-deleted',
|
||||
next_update_at: this.database.fn.now(),
|
||||
})
|
||||
.whereIn(
|
||||
'entity_id',
|
||||
results.map(key => key.entity_id),
|
||||
);
|
||||
} else {
|
||||
await this.database<DbRefreshStateRow>('refresh_state')
|
||||
.update({
|
||||
result_hash: 'child-was-deleted',
|
||||
next_update_at: this.database.fn.now(),
|
||||
})
|
||||
.whereIn('entity_ref', function parents(builder) {
|
||||
return builder
|
||||
.from<DbRefreshStateRow>('refresh_state')
|
||||
.innerJoin<DbRefreshStateReferencesRow>(
|
||||
'refresh_state_references',
|
||||
{
|
||||
'refresh_state_references.target_entity_ref':
|
||||
'refresh_state.entity_ref',
|
||||
},
|
||||
)
|
||||
.where('refresh_state.entity_id', '=', uid)
|
||||
.select('refresh_state_references.source_entity_ref');
|
||||
});
|
||||
}
|
||||
|
||||
// Stitch the entities that the deleted one had relations to. If we do not
|
||||
// do this, the entities in the other end of the relations will still look
|
||||
@@ -324,7 +358,6 @@ export class DefaultEntitiesCatalog implements EntitiesCatalog {
|
||||
.select({ ref: 'relations.source_entity_ref' }),
|
||||
);
|
||||
|
||||
// Perform the actual deletion
|
||||
await this.database<DbRefreshStateRow>('refresh_state')
|
||||
.where('entity_id', uid)
|
||||
.delete();
|
||||
|
||||
@@ -36,7 +36,7 @@ import { DefaultRefreshService } from './DefaultRefreshService';
|
||||
describe('Refresh integration', () => {
|
||||
const defaultLogger = getVoidLogger();
|
||||
const databases = TestDatabases.create({
|
||||
ids: ['POSTGRES_13', 'POSTGRES_9', 'SQLITE_3'],
|
||||
ids: ['MYSQL_8', 'POSTGRES_13', 'POSTGRES_9', 'SQLITE_3'],
|
||||
});
|
||||
|
||||
async function createDatabase(
|
||||
|
||||
@@ -29,7 +29,7 @@ import { Stitcher } from './Stitcher';
|
||||
|
||||
describe('Stitcher', () => {
|
||||
const databases = TestDatabases.create({
|
||||
ids: ['POSTGRES_13', 'POSTGRES_9', 'SQLITE_3'],
|
||||
ids: ['MYSQL_8', 'POSTGRES_13', 'POSTGRES_9', 'SQLITE_3'],
|
||||
});
|
||||
const logger = getVoidLogger();
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ import { PluginEndpointDiscovery } from '@backstage/backend-common';
|
||||
import {
|
||||
createBackendModule,
|
||||
createServiceFactory,
|
||||
discoveryServiceRef,
|
||||
coreServices,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { startTestBackend } from '@backstage/backend-test-utils';
|
||||
import { CatalogClient } from '@backstage/catalog-client';
|
||||
@@ -29,7 +29,7 @@ describe('catalogServiceRef', () => {
|
||||
expect.assertions(1);
|
||||
|
||||
const mockDiscoveryFactory = createServiceFactory({
|
||||
service: discoveryServiceRef,
|
||||
service: coreServices.discovery,
|
||||
deps: {},
|
||||
factory: async ({}) => {
|
||||
return async () => jest.fn() as unknown as PluginEndpointDiscovery;
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
import {
|
||||
createServiceFactory,
|
||||
createServiceRef,
|
||||
discoveryServiceRef,
|
||||
coreServices,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { CatalogApi, CatalogClient } from '@backstage/catalog-client';
|
||||
|
||||
@@ -31,7 +31,7 @@ export const catalogServiceRef = createServiceRef<CatalogApi>({
|
||||
createServiceFactory({
|
||||
service,
|
||||
deps: {
|
||||
discoveryApi: discoveryServiceRef,
|
||||
discoveryApi: coreServices.discovery,
|
||||
},
|
||||
async factory() {
|
||||
return async ({ discoveryApi }) => {
|
||||
|
||||
@@ -73,7 +73,7 @@ export const CatalogEntityPage: () => JSX.Element;
|
||||
// @public (undocumented)
|
||||
export const CatalogIndexPage: (props: DefaultCatalogPageProps) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
// @public @deprecated (undocumented)
|
||||
export function CatalogKindHeader(props: CatalogKindHeaderProps): JSX.Element;
|
||||
|
||||
// @public
|
||||
|
||||
@@ -59,7 +59,10 @@ export interface CatalogKindHeaderProps {
|
||||
initialFilter?: string;
|
||||
}
|
||||
|
||||
/** @public */
|
||||
/**
|
||||
* @public
|
||||
* @deprecated This component has been deprecated in favour of the EntityKindPicker in the list of filters. If you wish to keep this component long term make sure to raise an issue at github.com/backstage/backstage
|
||||
*/
|
||||
export function CatalogKindHeader(props: CatalogKindHeaderProps) {
|
||||
const { initialFilter = 'component', allowedKinds } = props;
|
||||
const classes = useStyles();
|
||||
|
||||
@@ -39,7 +39,6 @@ import {
|
||||
import React, { ReactNode } from 'react';
|
||||
import { createComponentRouteRef } from '../../routes';
|
||||
import { CatalogTable, CatalogTableRow } from '../CatalogTable';
|
||||
import { CatalogKindHeader } from '../CatalogKindHeader';
|
||||
import { useCatalogPluginOptions } from '../../options';
|
||||
|
||||
/**
|
||||
@@ -73,17 +72,15 @@ export function DefaultCatalogPage(props: DefaultCatalogPageProps) {
|
||||
|
||||
return (
|
||||
<PageWithHeader title={`${orgName} Catalog`} themeId="home">
|
||||
<EntityListProvider>
|
||||
<Content>
|
||||
<ContentHeader
|
||||
titleComponent={<CatalogKindHeader initialFilter={initialKind} />}
|
||||
>
|
||||
<CreateButton
|
||||
title={createButtonTitle}
|
||||
to={createComponentLink && createComponentLink()}
|
||||
/>
|
||||
<SupportButton>All your software catalog entities</SupportButton>
|
||||
</ContentHeader>
|
||||
<Content>
|
||||
<ContentHeader title="">
|
||||
<CreateButton
|
||||
title={createButtonTitle}
|
||||
to={createComponentLink && createComponentLink()}
|
||||
/>
|
||||
<SupportButton>All your software catalog entities</SupportButton>
|
||||
</ContentHeader>
|
||||
<EntityListProvider>
|
||||
<CatalogFilterLayout>
|
||||
<CatalogFilterLayout.Filters>
|
||||
<EntityKindPicker initialFilter={initialKind} />
|
||||
@@ -103,8 +100,8 @@ export function DefaultCatalogPage(props: DefaultCatalogPageProps) {
|
||||
/>
|
||||
</CatalogFilterLayout.Content>
|
||||
</CatalogFilterLayout>
|
||||
</Content>
|
||||
</EntityListProvider>
|
||||
</EntityListProvider>
|
||||
</Content>
|
||||
</PageWithHeader>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -17,8 +17,10 @@ import { Entity as Entity_2 } from '@backstage/plugin-cost-insights-common';
|
||||
import { ForwardRefExoticComponent } from 'react';
|
||||
import { Group as Group_2 } from '@backstage/plugin-cost-insights-common';
|
||||
import { Maybe as Maybe_2 } from '@backstage/plugin-cost-insights-common';
|
||||
import { Metric as Metric_2 } from '@backstage/plugin-cost-insights-common';
|
||||
import { MetricData as MetricData_2 } from '@backstage/plugin-cost-insights-common';
|
||||
import { PaletteOptions } from '@material-ui/core/styles/createPalette';
|
||||
import { Product as Product_2 } from '@backstage/plugin-cost-insights-common';
|
||||
import { Project as Project_2 } from '@backstage/plugin-cost-insights-common';
|
||||
import { PropsWithChildren } from 'react';
|
||||
import { ReactNode } from 'react';
|
||||
@@ -233,8 +235,8 @@ export type ChartData = {
|
||||
// @public (undocumented)
|
||||
export type ConfigContextProps = {
|
||||
baseCurrency: Intl.NumberFormat;
|
||||
metrics: Metric[];
|
||||
products: Product[];
|
||||
metrics: Metric_2[];
|
||||
products: Product_2[];
|
||||
icons: Icon[];
|
||||
engineerCost: number;
|
||||
engineerThreshold: number;
|
||||
@@ -426,21 +428,21 @@ export class ExampleCostInsightsClient implements CostInsightsApi {
|
||||
getCatalogEntityDailyCost(
|
||||
entityRef: string,
|
||||
intervals: string,
|
||||
): Promise<Cost>;
|
||||
): Promise<Cost_2>;
|
||||
// (undocumented)
|
||||
getDailyMetricData(metric: string, intervals: string): Promise<MetricData>;
|
||||
getDailyMetricData(metric: string, intervals: string): Promise<MetricData_2>;
|
||||
// (undocumented)
|
||||
getGroupDailyCost(group: string, intervals: string): Promise<Cost>;
|
||||
getGroupDailyCost(group: string, intervals: string): Promise<Cost_2>;
|
||||
// (undocumented)
|
||||
getGroupProjects(group: string): Promise<Project[]>;
|
||||
getGroupProjects(group: string): Promise<Project_2[]>;
|
||||
// (undocumented)
|
||||
getLastCompleteBillingDate(): Promise<string>;
|
||||
// (undocumented)
|
||||
getProductInsights(options: ProductInsightsOptions): Promise<Entity>;
|
||||
getProductInsights(options: ProductInsightsOptions): Promise<Entity_2>;
|
||||
// (undocumented)
|
||||
getProjectDailyCost(project: string, intervals: string): Promise<Cost>;
|
||||
getProjectDailyCost(project: string, intervals: string): Promise<Cost_2>;
|
||||
// (undocumented)
|
||||
getUserGroups(userId: string): Promise<Group[]>;
|
||||
getUserGroups(userId: string): Promise<Group_2[]>;
|
||||
}
|
||||
|
||||
// @public @deprecated (undocumented)
|
||||
|
||||
@@ -24,9 +24,11 @@ import {
|
||||
AlertOptions,
|
||||
AlertStatus,
|
||||
AlertSnoozeFormData,
|
||||
} from '../../types';
|
||||
import {
|
||||
ChangeStatistic,
|
||||
Entity,
|
||||
} from '../../types';
|
||||
} from '@backstage/plugin-cost-insights-common';
|
||||
import {
|
||||
KubernetesMigrationDismissForm,
|
||||
KubernetesMigrationDismissFormData,
|
||||
|
||||
@@ -19,15 +19,17 @@ import { DateTime } from 'luxon';
|
||||
import { CostInsightsApi, ProductInsightsOptions } from '../api';
|
||||
import {
|
||||
Alert,
|
||||
Cost,
|
||||
DEFAULT_DATE_FORMAT,
|
||||
ProjectGrowthData,
|
||||
UnlabeledDataflowData,
|
||||
} from '../types';
|
||||
import {
|
||||
Entity,
|
||||
Group,
|
||||
MetricData,
|
||||
Project,
|
||||
ProjectGrowthData,
|
||||
UnlabeledDataflowData,
|
||||
} from '../types';
|
||||
Cost,
|
||||
} from '@backstage/plugin-cost-insights-common';
|
||||
import { KubernetesMigrationAlert } from './alerts';
|
||||
import { ProjectGrowthAlert, UnlabeledDataflowAlert } from '../alerts';
|
||||
import {
|
||||
|
||||
+1
-1
@@ -19,9 +19,9 @@ import { BarChart } from '../../../components';
|
||||
import {
|
||||
BarChartOptions,
|
||||
CostInsightsTheme,
|
||||
Entity,
|
||||
ResourceData,
|
||||
} from '../../../types';
|
||||
import { Entity } from '@backstage/plugin-cost-insights-common';
|
||||
import { useTheme } from '@material-ui/core';
|
||||
|
||||
type MigrationBarChartProps = {
|
||||
|
||||
+2
-1
@@ -17,7 +17,8 @@
|
||||
import React from 'react';
|
||||
import { Box, useTheme } from '@material-ui/core';
|
||||
import { CostGrowth, LegendItem } from '../../../components';
|
||||
import { ChangeStatistic, CostInsightsTheme, Duration } from '../../../types';
|
||||
import { CostInsightsTheme, Duration } from '../../../types';
|
||||
import { ChangeStatistic } from '@backstage/plugin-cost-insights-common';
|
||||
import { monthOf } from '../../../utils/formatters';
|
||||
|
||||
export type MigrationBarChartLegendProps = {
|
||||
|
||||
@@ -28,7 +28,8 @@ import {
|
||||
FormGroup,
|
||||
Typography,
|
||||
} from '@material-ui/core';
|
||||
import { AlertFormProps, Entity } from '../../types';
|
||||
import { AlertFormProps } from '../../types';
|
||||
import { Entity } from '@backstage/plugin-cost-insights-common';
|
||||
import { KubernetesMigrationAlert } from '../alerts';
|
||||
import { findAlways } from '../../utils/assert';
|
||||
|
||||
|
||||
@@ -37,8 +37,8 @@ import {
|
||||
AlertDismissReason,
|
||||
AlertDismissOptions,
|
||||
AlertDismissFormData,
|
||||
Maybe,
|
||||
} from '../types';
|
||||
import { Maybe } from '@backstage/plugin-cost-insights-common';
|
||||
import { useAlertDialogStyles as useStyles } from '../utils/styles';
|
||||
|
||||
export type AlertDismissFormProps = AlertFormProps<Alert, AlertDismissFormData>;
|
||||
|
||||
@@ -35,10 +35,10 @@ import {
|
||||
AlertFormProps,
|
||||
Duration,
|
||||
DEFAULT_DATE_FORMAT,
|
||||
Maybe,
|
||||
AlertSnoozeFormData,
|
||||
AlertSnoozeOptions,
|
||||
} from '../types';
|
||||
import { Maybe } from '@backstage/plugin-cost-insights-common';
|
||||
import { useAlertDialogStyles as useStyles } from '../utils/styles';
|
||||
import { intervalsOf } from '../utils/duration';
|
||||
|
||||
|
||||
@@ -22,7 +22,8 @@ import React, {
|
||||
useState,
|
||||
} from 'react';
|
||||
import { Config as BackstageConfig } from '@backstage/config';
|
||||
import { Currency, EngineerThreshold, Icon, Metric, Product } from '../types';
|
||||
import { Currency, EngineerThreshold, Icon } from '../types';
|
||||
import { Metric, Product } from '@backstage/plugin-cost-insights-common';
|
||||
import { getIcon } from '../utils/navigation';
|
||||
import { validateCurrencies, validateMetrics } from '../utils/config';
|
||||
import { createCurrencyFormat, defaultCurrencies } from '../utils/currency';
|
||||
|
||||
@@ -23,7 +23,8 @@ import React, {
|
||||
useState,
|
||||
} from 'react';
|
||||
import { Alert } from '@material-ui/lab';
|
||||
import { Maybe, PageFilters, ProductFilters } from '../types';
|
||||
import { PageFilters, ProductFilters } from '../types';
|
||||
import { Maybe } from '@backstage/plugin-cost-insights-common';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
import {
|
||||
stringify,
|
||||
|
||||
@@ -23,7 +23,7 @@ import React, {
|
||||
import { Alert } from '@material-ui/lab';
|
||||
import { costInsightsApiRef } from '../api';
|
||||
import { MapLoadingToProps, useLoading } from './useLoading';
|
||||
import { Group, Maybe } from '../types';
|
||||
import { Group, Maybe } from '@backstage/plugin-cost-insights-common';
|
||||
import { DefaultLoadingAction } from '../utils/loading';
|
||||
import { useApi, identityApiRef } from '@backstage/core-plugin-api';
|
||||
import { DEFAULT_NAMESPACE, parseEntityRef } from '@backstage/catalog-model';
|
||||
|
||||
@@ -25,7 +25,7 @@ import { Alert } from '@material-ui/lab';
|
||||
import { costInsightsApiRef } from '../api';
|
||||
import { MapLoadingToProps, useLoading } from './useLoading';
|
||||
import { DefaultLoadingAction } from '../utils/loading';
|
||||
import { Maybe } from '../types';
|
||||
import { Maybe } from '@backstage/plugin-cost-insights-common';
|
||||
import { useApi } from '@backstage/core-plugin-api';
|
||||
|
||||
type BillingDateProviderLoadingProps = {
|
||||
|
||||
@@ -20,7 +20,7 @@ import React, {
|
||||
useContext,
|
||||
PropsWithChildren,
|
||||
} from 'react';
|
||||
import { Maybe } from '../types';
|
||||
import { Maybe } from '@backstage/plugin-cost-insights-common';
|
||||
|
||||
export type ScrollTo = Maybe<string>;
|
||||
|
||||
|
||||
@@ -14,7 +14,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { DateAggregation, Entity } from '../types';
|
||||
import {
|
||||
DateAggregation,
|
||||
Entity,
|
||||
} from '@backstage/plugin-cost-insights-common';
|
||||
|
||||
export const MockAggregatedDailyCosts: DateAggregation[] = [
|
||||
{
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Entity, Product } from '../types';
|
||||
import { Entity, Product } from '@backstage/plugin-cost-insights-common';
|
||||
import { findAlways } from '../utils/assert';
|
||||
|
||||
type mockEntityRenderer<T> = (entity: T) => T;
|
||||
|
||||
@@ -16,14 +16,13 @@
|
||||
|
||||
import { DateTime } from 'luxon';
|
||||
import regression, { DataPoint } from 'regression';
|
||||
import { Duration, DEFAULT_DATE_FORMAT } from '../types';
|
||||
import {
|
||||
ChangeStatistic,
|
||||
Duration,
|
||||
Entity,
|
||||
Trendline,
|
||||
DateAggregation,
|
||||
DEFAULT_DATE_FORMAT,
|
||||
} from '../types';
|
||||
} from '@backstage/plugin-cost-insights-common';
|
||||
import { inclusiveEndDateOf, inclusiveStartDateOf } from '../utils/duration';
|
||||
import {
|
||||
MockComputeEngineInsights,
|
||||
|
||||
@@ -14,7 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Alert, AlertForm, AlertStatus, Maybe } from '../types';
|
||||
import { Alert, AlertForm, AlertStatus } from '../types';
|
||||
import { Maybe } from '@backstage/plugin-cost-insights-common';
|
||||
import { AlertAcceptForm, AlertDismissForm, AlertSnoozeForm } from '../forms';
|
||||
|
||||
const createAlertHandler = (status?: AlertStatus) => (alert: Alert) =>
|
||||
|
||||
@@ -20,8 +20,8 @@ import {
|
||||
ChangeThreshold,
|
||||
EngineerThreshold,
|
||||
Duration,
|
||||
Cost,
|
||||
} from '../types';
|
||||
import { Cost } from '@backstage/plugin-cost-insights-common';
|
||||
import { MockAggregatedDailyCosts, trendlineOf, changeOf } from '../testUtils';
|
||||
|
||||
const GrowthMap = {
|
||||
|
||||
@@ -14,15 +14,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ChangeThreshold, GrowthType, Duration } from '../types';
|
||||
import {
|
||||
Cost,
|
||||
ChangeStatistic,
|
||||
ChangeThreshold,
|
||||
GrowthType,
|
||||
MetricData,
|
||||
Duration,
|
||||
DateAggregation,
|
||||
} from '../types';
|
||||
} from '@backstage/plugin-cost-insights-common';
|
||||
import { DateTime, Duration as LuxonDuration } from 'luxon';
|
||||
import { inclusiveStartDateOf } from './duration';
|
||||
import { notEmpty } from './assert';
|
||||
|
||||
@@ -14,7 +14,11 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { DateAggregation, Trendline, ChartData } from '../types';
|
||||
import {
|
||||
DateAggregation,
|
||||
Trendline,
|
||||
} from '@backstage/plugin-cost-insights-common';
|
||||
import { ChartData } from '../types';
|
||||
|
||||
export function trendFrom(trendline: Trendline, date: number): number {
|
||||
return trendline.slope * (date / 1000) + trendline.intercept;
|
||||
|
||||
@@ -14,7 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Metric, Currency } from '../types';
|
||||
import { Currency } from '../types';
|
||||
import { Metric } from '@backstage/plugin-cost-insights-common';
|
||||
|
||||
export function validateMetrics(metrics: Metric[]) {
|
||||
const defaults = metrics.filter(metric => metric.default);
|
||||
|
||||
@@ -14,7 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Duration, Group, PageFilters } from '../types';
|
||||
import { Duration, PageFilters } from '../types';
|
||||
import { Group } from '@backstage/plugin-cost-insights-common';
|
||||
|
||||
export function getDefaultPageFilters(groups: Group[]): PageFilters {
|
||||
return {
|
||||
|
||||
@@ -16,7 +16,8 @@
|
||||
|
||||
import { DateTime, Duration as LuxonDuration } from 'luxon';
|
||||
import pluralize from 'pluralize';
|
||||
import { ChangeStatistic, Duration } from '../types';
|
||||
import { Duration } from '../types';
|
||||
import { ChangeStatistic } from '@backstage/plugin-cost-insights-common';
|
||||
import { inclusiveEndDateOf, inclusiveStartDateOf } from './duration';
|
||||
import { notEmpty } from './assert';
|
||||
|
||||
|
||||
@@ -16,7 +16,8 @@
|
||||
|
||||
import { TooltipProps } from 'recharts';
|
||||
import { Payload } from 'recharts/types/component/DefaultTooltipContent';
|
||||
import { AlertCost, DataKey, Entity, ResourceData } from '../types';
|
||||
import { AlertCost, DataKey, ResourceData } from '../types';
|
||||
import { Entity } from '@backstage/plugin-cost-insights-common';
|
||||
import {
|
||||
currencyFormatter,
|
||||
dateFormatter,
|
||||
|
||||
@@ -16,7 +16,8 @@
|
||||
|
||||
import qs from 'qs';
|
||||
import * as yup from 'yup';
|
||||
import { Duration, Group, PageFilters } from '../types';
|
||||
import { Duration, PageFilters } from '../types';
|
||||
import { Group } from '@backstage/plugin-cost-insights-common';
|
||||
import { getDefaultPageFilters } from '../utils/filters';
|
||||
import { ConfigContextProps } from '../hooks/useConfig';
|
||||
|
||||
|
||||
@@ -14,7 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Duration, Entity, Loading, Maybe, Product } from '../types';
|
||||
import { Duration, Loading } from '../types';
|
||||
import { Entity, Maybe, Product } from '@backstage/plugin-cost-insights-common';
|
||||
import { DEFAULT_DURATION } from './duration';
|
||||
|
||||
export type ProductState = {
|
||||
|
||||
@@ -13,7 +13,8 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { DateAggregation, ResourceData } from '../types';
|
||||
import { ResourceData } from '../types';
|
||||
import { DateAggregation } from '@backstage/plugin-cost-insights-common';
|
||||
import { ProductState } from './loading';
|
||||
|
||||
export const aggregationSort = (
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { DateAggregation } from '../types';
|
||||
import { DateAggregation } from '@backstage/plugin-cost-insights-common';
|
||||
|
||||
export const aggregationSum = (aggregation: DateAggregation[]) =>
|
||||
aggregation.reduce((total, curAgg) => total + curAgg.amount, 0);
|
||||
|
||||
+4
-8
@@ -15,11 +15,7 @@
|
||||
*/
|
||||
|
||||
import { getVoidLogger } from '@backstage/backend-common';
|
||||
import {
|
||||
configServiceRef,
|
||||
loggerServiceRef,
|
||||
schedulerServiceRef,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { coreServices } from '@backstage/backend-plugin-api';
|
||||
import { startTestBackend } from '@backstage/backend-test-utils';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { eventsExtensionPoint } from '@backstage/plugin-events-node';
|
||||
@@ -68,9 +64,9 @@ describe('awsSqsEventsModule', () => {
|
||||
await startTestBackend({
|
||||
extensionPoints: [[eventsExtensionPoint, extensionPoint]],
|
||||
services: [
|
||||
[configServiceRef, config],
|
||||
[loggerServiceRef, getVoidLogger()],
|
||||
[schedulerServiceRef, scheduler],
|
||||
[coreServices.config, config],
|
||||
[coreServices.logger, getVoidLogger()],
|
||||
[coreServices.scheduler, scheduler],
|
||||
],
|
||||
features: [awsSqsConsumingEventPublisherEventsModule()],
|
||||
});
|
||||
|
||||
+4
-6
@@ -15,11 +15,9 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
configServiceRef,
|
||||
coreServices,
|
||||
createBackendModule,
|
||||
loggerServiceRef,
|
||||
loggerToWinstonLogger,
|
||||
schedulerServiceRef,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { eventsExtensionPoint } from '@backstage/plugin-events-node';
|
||||
import { AwsSqsConsumingEventPublisher } from '../publisher/AwsSqsConsumingEventPublisher';
|
||||
@@ -35,10 +33,10 @@ export const awsSqsConsumingEventPublisherEventsModule = createBackendModule({
|
||||
register(env) {
|
||||
env.registerInit({
|
||||
deps: {
|
||||
config: configServiceRef,
|
||||
config: coreServices.config,
|
||||
events: eventsExtensionPoint,
|
||||
logger: loggerServiceRef,
|
||||
scheduler: schedulerServiceRef,
|
||||
logger: coreServices.logger,
|
||||
scheduler: coreServices.scheduler,
|
||||
},
|
||||
async init({ config, events, logger, scheduler }) {
|
||||
const winstonLogger = loggerToWinstonLogger(logger);
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { configServiceRef } from '@backstage/backend-plugin-api';
|
||||
import { coreServices } from '@backstage/backend-plugin-api';
|
||||
import { startTestBackend } from '@backstage/backend-test-utils';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import {
|
||||
@@ -59,7 +59,7 @@ describe('githubWebhookEventsModule', () => {
|
||||
|
||||
await startTestBackend({
|
||||
extensionPoints: [[eventsExtensionPoint, extensionPoint]],
|
||||
services: [[configServiceRef, config]],
|
||||
services: [[coreServices.config, config]],
|
||||
features: [githubWebhookEventsModule()],
|
||||
});
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
configServiceRef,
|
||||
coreServices,
|
||||
createBackendModule,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { eventsExtensionPoint } from '@backstage/plugin-events-node';
|
||||
@@ -34,7 +34,7 @@ export const githubWebhookEventsModule = createBackendModule({
|
||||
register(env) {
|
||||
env.registerInit({
|
||||
deps: {
|
||||
config: configServiceRef,
|
||||
config: coreServices.config,
|
||||
events: eventsExtensionPoint,
|
||||
},
|
||||
async init({ config, events }) {
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { configServiceRef } from '@backstage/backend-plugin-api';
|
||||
import { coreServices } from '@backstage/backend-plugin-api';
|
||||
import { startTestBackend } from '@backstage/backend-test-utils';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import {
|
||||
@@ -54,7 +54,7 @@ describe('gitlabWebhookEventsModule', () => {
|
||||
|
||||
await startTestBackend({
|
||||
extensionPoints: [[eventsExtensionPoint, extensionPoint]],
|
||||
services: [[configServiceRef, config]],
|
||||
services: [[coreServices.config, config]],
|
||||
features: [gitlabWebhookEventsModule()],
|
||||
});
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
configServiceRef,
|
||||
coreServices,
|
||||
createBackendModule,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { eventsExtensionPoint } from '@backstage/plugin-events-node';
|
||||
@@ -36,7 +36,7 @@ export const gitlabWebhookEventsModule = createBackendModule({
|
||||
register(env) {
|
||||
env.registerInit({
|
||||
deps: {
|
||||
config: configServiceRef,
|
||||
config: coreServices.config,
|
||||
events: eventsExtensionPoint,
|
||||
},
|
||||
async init({ config, events }) {
|
||||
|
||||
@@ -17,10 +17,8 @@
|
||||
import { errorHandler, getVoidLogger } from '@backstage/backend-common';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import {
|
||||
configServiceRef,
|
||||
coreServices,
|
||||
createBackendModule,
|
||||
httpRouterServiceRef,
|
||||
loggerServiceRef,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { startTestBackend } from '@backstage/backend-test-utils';
|
||||
import { eventsExtensionPoint } from '@backstage/plugin-events-node';
|
||||
@@ -73,9 +71,9 @@ describe('eventPlugin', () => {
|
||||
await startTestBackend({
|
||||
extensionPoints: [],
|
||||
services: [
|
||||
[configServiceRef, config],
|
||||
[httpRouterServiceRef, httpRouter],
|
||||
[loggerServiceRef, getVoidLogger()],
|
||||
[coreServices.config, config],
|
||||
[coreServices.httpRouter, httpRouter],
|
||||
[coreServices.logger, getVoidLogger()],
|
||||
],
|
||||
features: [eventsPlugin(), testModule()],
|
||||
});
|
||||
|
||||
@@ -15,10 +15,8 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
configServiceRef,
|
||||
createBackendPlugin,
|
||||
httpRouterServiceRef,
|
||||
loggerServiceRef,
|
||||
coreServices,
|
||||
loggerToWinstonLogger,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import {
|
||||
@@ -89,9 +87,9 @@ export const eventsPlugin = createBackendPlugin({
|
||||
|
||||
env.registerInit({
|
||||
deps: {
|
||||
config: configServiceRef,
|
||||
logger: loggerServiceRef,
|
||||
router: httpRouterServiceRef,
|
||||
config: coreServices.config,
|
||||
logger: coreServices.logger,
|
||||
router: coreServices.httpRouter,
|
||||
},
|
||||
async init({ config, logger, router }) {
|
||||
const winstonLogger = loggerToWinstonLogger(logger);
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
"@material-ui/core": "^4.12.2",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@material-ui/lab": "4.0.0-alpha.57",
|
||||
"@react-hookz/web": "^19.0.0"
|
||||
"@react-hookz/web": "^20.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.13.1 || ^17.0.0",
|
||||
|
||||
@@ -55,7 +55,7 @@ describe('Features', () => {
|
||||
|
||||
expect(getByTestId(TEST_IDS.info.info)).toMatchInlineSnapshot(`
|
||||
<div
|
||||
class="MuiBox-root MuiBox-root-10"
|
||||
class="MuiBox-root MuiBox-root-11"
|
||||
data-testid="grm--info"
|
||||
>
|
||||
<h6
|
||||
@@ -72,7 +72,7 @@ describe('Features', () => {
|
||||
: The source control system where releases reside in a practical sense. Read more about
|
||||
|
||||
<a
|
||||
class="MuiTypography-root MuiLink-root MuiLink-underlineHover Link-externalLink-12 MuiTypography-colorPrimary"
|
||||
class="MuiTypography-root MuiLink-root MuiLink-underlineHover Link-externalLink-13 MuiTypography-colorPrimary"
|
||||
href="https://docs.github.com/en/github/administering-a-repository/managing-releases-in-a-repository"
|
||||
rel="noopener"
|
||||
target="_blank"
|
||||
@@ -80,7 +80,7 @@ describe('Features', () => {
|
||||
>
|
||||
Git releases
|
||||
<span
|
||||
class="Link-visuallyHidden-11"
|
||||
class="MuiTypography-root Link-visuallyHidden-12 MuiTypography-body1"
|
||||
>
|
||||
, Opens in a new window
|
||||
</span>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
import React from 'react';
|
||||
import * as deployments from '../../__fixtures__/2-deployments.json';
|
||||
import { renderInTestApp } from '@backstage/test-utils';
|
||||
import { renderInTestApp, textContentMatcher } from '@backstage/test-utils';
|
||||
import { DeploymentDrawer } from './DeploymentDrawer';
|
||||
|
||||
describe('DeploymentDrawer', () => {
|
||||
@@ -33,9 +33,13 @@ describe('DeploymentDrawer', () => {
|
||||
expect(getByText('YAML')).toBeInTheDocument();
|
||||
expect(getByText('Strategy')).toBeInTheDocument();
|
||||
expect(getByText('Rolling Update:')).toBeInTheDocument();
|
||||
expect(getByText('Max Surge: 25%')).toBeInTheDocument();
|
||||
expect(getByText('Max Unavailable: 25%')).toBeInTheDocument();
|
||||
expect(getByText('Type: RollingUpdate')).toBeInTheDocument();
|
||||
expect(getByText(textContentMatcher('Max Surge: 25%'))).toBeInTheDocument();
|
||||
expect(
|
||||
getByText(textContentMatcher('Max Unavailable: 25%')),
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
getByText(textContentMatcher('Type: RollingUpdate')),
|
||||
).toBeInTheDocument();
|
||||
expect(getByText('Min Ready Seconds')).toBeInTheDocument();
|
||||
expect(getByText('???')).toBeInTheDocument();
|
||||
expect(getByText('Progress Deadline Seconds')).toBeInTheDocument();
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import * as ingresses from './__fixtures__/2-ingresses.json';
|
||||
import { wrapInTestApp } from '@backstage/test-utils';
|
||||
import { textContentMatcher, wrapInTestApp } from '@backstage/test-utils';
|
||||
import { IngressDrawer } from './IngressDrawer';
|
||||
|
||||
describe('IngressDrawer', () => {
|
||||
@@ -28,11 +28,17 @@ describe('IngressDrawer', () => {
|
||||
),
|
||||
);
|
||||
|
||||
expect(getAllByText('awesome-service')).toHaveLength(2);
|
||||
expect(getAllByText('awesome-service')).toHaveLength(4);
|
||||
expect(getByText('YAML')).toBeInTheDocument();
|
||||
expect(getByText('Rules')).toBeInTheDocument();
|
||||
expect(getByText('Host: api.awesome-host.io')).toBeInTheDocument();
|
||||
expect(getAllByText('Service Port: 80')).toHaveLength(2);
|
||||
expect(getAllByText('Service Name: awesome-service')).toHaveLength(2);
|
||||
expect(
|
||||
getByText(textContentMatcher('Host: api.awesome-host.io')),
|
||||
).toBeInTheDocument();
|
||||
expect(getAllByText(textContentMatcher('Service Port: 80'))).toHaveLength(
|
||||
2,
|
||||
);
|
||||
expect(
|
||||
getAllByText(textContentMatcher('Service Name: awesome-service')),
|
||||
).toHaveLength(2);
|
||||
});
|
||||
});
|
||||
|
||||
+15
-7
@@ -18,7 +18,7 @@ import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import * as pod from './__fixtures__/pod.json';
|
||||
import * as crashingPod from './__fixtures__/crashing-pod.json';
|
||||
import { wrapInTestApp } from '@backstage/test-utils';
|
||||
import { textContentMatcher, wrapInTestApp } from '@backstage/test-utils';
|
||||
import { PodDrawer } from './PodDrawer';
|
||||
|
||||
describe('PodDrawer', () => {
|
||||
@@ -46,8 +46,10 @@ describe('PodDrawer', () => {
|
||||
expect(getAllByText('True')).toHaveLength(4);
|
||||
expect(getByText('Exposed Ports')).toBeInTheDocument();
|
||||
expect(getByText('Nginx:')).toBeInTheDocument();
|
||||
expect(getByText('Container Port: 80')).toBeInTheDocument();
|
||||
expect(getByText('Protocol: TCP')).toBeInTheDocument();
|
||||
expect(
|
||||
getByText(textContentMatcher('Container Port: 80')),
|
||||
).toBeInTheDocument();
|
||||
expect(getByText(textContentMatcher('Protocol: TCP'))).toBeInTheDocument();
|
||||
});
|
||||
it('should render crashing pod', async () => {
|
||||
const { getByText, getAllByText } = render(
|
||||
@@ -80,12 +82,18 @@ describe('PodDrawer', () => {
|
||||
getAllByText('containers with unready status: [side-car other-side-car]'),
|
||||
).toHaveLength(2);
|
||||
expect(getByText('Exposed Ports')).toBeInTheDocument();
|
||||
expect(getAllByText('Protocol: TCP')).toHaveLength(3);
|
||||
expect(getAllByText(textContentMatcher('Protocol: TCP'))).toHaveLength(3);
|
||||
expect(getByText('Nginx:')).toBeInTheDocument();
|
||||
expect(getByText('Container Port: 80')).toBeInTheDocument();
|
||||
expect(
|
||||
getByText(textContentMatcher('Container Port: 80')),
|
||||
).toBeInTheDocument();
|
||||
expect(getByText('Side Car:')).toBeInTheDocument();
|
||||
expect(getByText('Container Port: 81')).toBeInTheDocument();
|
||||
expect(
|
||||
getByText(textContentMatcher('Container Port: 81')),
|
||||
).toBeInTheDocument();
|
||||
expect(getByText('Other Side Car:')).toBeInTheDocument();
|
||||
expect(getByText('Container Port: 82')).toBeInTheDocument();
|
||||
expect(
|
||||
getByText(textContentMatcher('Container Port: 82')),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import * as services from './__fixtures__/2-services.json';
|
||||
import { wrapInTestApp } from '@backstage/test-utils';
|
||||
import { textContentMatcher, wrapInTestApp } from '@backstage/test-utils';
|
||||
import { ServiceDrawer } from './ServiceDrawer';
|
||||
|
||||
describe('ServiceDrawer', () => {
|
||||
@@ -33,7 +33,11 @@ describe('ServiceDrawer', () => {
|
||||
expect(getByText('YAML')).toBeInTheDocument();
|
||||
expect(getByText('Cluster IP')).toBeInTheDocument();
|
||||
expect(getByText('Ports')).toBeInTheDocument();
|
||||
expect(getByText('Target Port: 1997')).toBeInTheDocument();
|
||||
expect(getByText('App: awesome-service')).toBeInTheDocument();
|
||||
expect(
|
||||
getByText(textContentMatcher('Target Port: 1997')),
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
getByText(textContentMatcher('App: awesome-service')),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
+12
-6
@@ -16,7 +16,7 @@
|
||||
|
||||
import React from 'react';
|
||||
import * as statefulsets from '../../__fixtures__/2-statefulsets.json';
|
||||
import { renderInTestApp } from '@backstage/test-utils';
|
||||
import { renderInTestApp, textContentMatcher } from '@backstage/test-utils';
|
||||
import { StatefulSetDrawer } from './StatefulSetDrawer';
|
||||
|
||||
describe('StatefulSetDrawer', () => {
|
||||
@@ -28,19 +28,25 @@ describe('StatefulSetDrawer', () => {
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(getAllByText('dice-roller')).toHaveLength(3);
|
||||
expect(getAllByText('dice-roller')).toHaveLength(4);
|
||||
expect(getByText('StatefulSet')).toBeInTheDocument();
|
||||
expect(getByText('YAML')).toBeInTheDocument();
|
||||
expect(getByText('Type: RollingUpdate')).toBeInTheDocument();
|
||||
expect(
|
||||
getByText(textContentMatcher('Type: RollingUpdate')),
|
||||
).toBeInTheDocument();
|
||||
expect(getByText('Rolling Update:')).toBeInTheDocument();
|
||||
expect(getByText('Max Surge: 25%')).toBeInTheDocument();
|
||||
expect(getByText('Max Unavailable: 25%')).toBeInTheDocument();
|
||||
expect(getByText(textContentMatcher('Max Surge: 25%'))).toBeInTheDocument();
|
||||
expect(
|
||||
getByText(textContentMatcher('Max Unavailable: 25%')),
|
||||
).toBeInTheDocument();
|
||||
expect(getByText('Pod Management Policy')).toBeInTheDocument();
|
||||
expect(getByText('Parallel')).toBeInTheDocument();
|
||||
expect(getByText('Service Name')).toBeInTheDocument();
|
||||
expect(getByText('Selector')).toBeInTheDocument();
|
||||
expect(getByText('Match Labels:')).toBeInTheDocument();
|
||||
expect(getByText('App: dice-roller')).toBeInTheDocument();
|
||||
expect(
|
||||
getByText(textContentMatcher('App: dice-roller')),
|
||||
).toBeInTheDocument();
|
||||
expect(getByText('Revision History Limit')).toBeInTheDocument();
|
||||
expect(getByText('10')).toBeInTheDocument();
|
||||
expect(getByText('namespace: default')).toBeInTheDocument();
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user