@backstage-community/plugin-tech-insights-backend 1.2.0 → 1.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/README.md +7 -1
- package/dist/index.cjs.js +19 -815
- package/dist/index.cjs.js.map +1 -1
- package/dist/plugin/config.cjs.js +59 -0
- package/dist/plugin/config.cjs.js.map +1 -0
- package/dist/plugin/plugin.cjs.js +102 -0
- package/dist/plugin/plugin.cjs.js.map +1 -0
- package/dist/service/fact/FactRetrieverEngine.cjs.js +127 -0
- package/dist/service/fact/FactRetrieverEngine.cjs.js.map +1 -0
- package/dist/service/fact/FactRetrieverRegistry.cjs.js +45 -0
- package/dist/service/fact/FactRetrieverRegistry.cjs.js.map +1 -0
- package/dist/service/fact/createFactRetriever.cjs.js +15 -0
- package/dist/service/fact/createFactRetriever.cjs.js.map +1 -0
- package/dist/service/fact/factRetrievers/entityMetadataFactRetriever.cjs.js +59 -0
- package/dist/service/fact/factRetrievers/entityMetadataFactRetriever.cjs.js.map +1 -0
- package/dist/service/fact/factRetrievers/entityOwnershipFactRetriever.cjs.js +54 -0
- package/dist/service/fact/factRetrievers/entityOwnershipFactRetriever.cjs.js.map +1 -0
- package/dist/service/fact/factRetrievers/techdocsFactRetriever.cjs.js +62 -0
- package/dist/service/fact/factRetrievers/techdocsFactRetriever.cjs.js.map +1 -0
- package/dist/service/fact/factRetrievers/utils.cjs.js +23 -0
- package/dist/service/fact/factRetrievers/utils.cjs.js.map +1 -0
- package/dist/service/persistence/TechInsightsDatabase.cjs.js +161 -0
- package/dist/service/persistence/TechInsightsDatabase.cjs.js.map +1 -0
- package/dist/service/persistence/persistenceContext.cjs.js +23 -0
- package/dist/service/persistence/persistenceContext.cjs.js.map +1 -0
- package/dist/service/router.cjs.js +120 -0
- package/dist/service/router.cjs.js.map +1 -0
- package/dist/service/techInsightsContextBuilder.cjs.js +62 -0
- package/dist/service/techInsightsContextBuilder.cjs.js.map +1 -0
- package/package.json +8 -8
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var catalogClient = require('@backstage/catalog-client');
|
|
4
|
+
var isEmpty = require('lodash/isEmpty');
|
|
5
|
+
|
|
6
|
+
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
|
|
7
|
+
|
|
8
|
+
var isEmpty__default = /*#__PURE__*/_interopDefaultCompat(isEmpty);
|
|
9
|
+
|
|
10
|
+
const entityMetadataFactRetriever = {
|
|
11
|
+
id: "entityMetadataFactRetriever",
|
|
12
|
+
version: "0.0.1",
|
|
13
|
+
title: "Entity Metadata",
|
|
14
|
+
description: "Generates facts which indicate the completeness of entity metadata",
|
|
15
|
+
schema: {
|
|
16
|
+
hasTitle: {
|
|
17
|
+
type: "boolean",
|
|
18
|
+
description: "The entity has a title in metadata"
|
|
19
|
+
},
|
|
20
|
+
hasDescription: {
|
|
21
|
+
type: "boolean",
|
|
22
|
+
description: "The entity has a description in metadata"
|
|
23
|
+
},
|
|
24
|
+
hasTags: {
|
|
25
|
+
type: "boolean",
|
|
26
|
+
description: "The entity has tags in metadata"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
handler: async ({ discovery, entityFilter, auth }) => {
|
|
30
|
+
const { token } = await auth.getPluginRequestToken({
|
|
31
|
+
onBehalfOf: await auth.getOwnServiceCredentials(),
|
|
32
|
+
targetPluginId: "catalog"
|
|
33
|
+
});
|
|
34
|
+
const catalogClient$1 = new catalogClient.CatalogClient({
|
|
35
|
+
discoveryApi: discovery
|
|
36
|
+
});
|
|
37
|
+
const entities = await catalogClient$1.getEntities(
|
|
38
|
+
{ filter: entityFilter },
|
|
39
|
+
{ token }
|
|
40
|
+
);
|
|
41
|
+
return entities.items.map((entity) => {
|
|
42
|
+
return {
|
|
43
|
+
entity: {
|
|
44
|
+
namespace: entity.metadata.namespace,
|
|
45
|
+
kind: entity.kind,
|
|
46
|
+
name: entity.metadata.name
|
|
47
|
+
},
|
|
48
|
+
facts: {
|
|
49
|
+
hasTitle: Boolean(entity.metadata?.title),
|
|
50
|
+
hasDescription: Boolean(entity.metadata?.description),
|
|
51
|
+
hasTags: !isEmpty__default.default(entity.metadata?.tags)
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
exports.entityMetadataFactRetriever = entityMetadataFactRetriever;
|
|
59
|
+
//# sourceMappingURL=entityMetadataFactRetriever.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entityMetadataFactRetriever.cjs.js","sources":["../../../../src/service/fact/factRetrievers/entityMetadataFactRetriever.ts"],"sourcesContent":["/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n FactRetriever,\n FactRetrieverContext,\n} from '@backstage-community/plugin-tech-insights-node';\nimport { CatalogClient } from '@backstage/catalog-client';\nimport { Entity } from '@backstage/catalog-model';\nimport isEmpty from 'lodash/isEmpty';\n\n/**\n * Generates facts which indicate the completeness of entity metadata.\n *\n * @public\n */\nexport const entityMetadataFactRetriever: FactRetriever = {\n id: 'entityMetadataFactRetriever',\n version: '0.0.1',\n title: 'Entity Metadata',\n description:\n 'Generates facts which indicate the completeness of entity metadata',\n schema: {\n hasTitle: {\n type: 'boolean',\n description: 'The entity has a title in metadata',\n },\n hasDescription: {\n type: 'boolean',\n description: 'The entity has a description in metadata',\n },\n hasTags: {\n type: 'boolean',\n description: 'The entity has tags in metadata',\n },\n },\n handler: async ({ discovery, entityFilter, auth }: FactRetrieverContext) => {\n const { token } = await auth.getPluginRequestToken({\n onBehalfOf: await auth.getOwnServiceCredentials(),\n targetPluginId: 'catalog',\n });\n const catalogClient = new CatalogClient({\n discoveryApi: discovery,\n });\n const entities = await catalogClient.getEntities(\n { filter: entityFilter },\n { token },\n );\n\n return entities.items.map((entity: Entity) => {\n return {\n entity: {\n namespace: entity.metadata.namespace!,\n kind: entity.kind,\n name: entity.metadata.name,\n },\n facts: {\n hasTitle: Boolean(entity.metadata?.title),\n hasDescription: Boolean(entity.metadata?.description),\n hasTags: !isEmpty(entity.metadata?.tags),\n },\n };\n });\n },\n};\n"],"names":["catalogClient","CatalogClient","isEmpty"],"mappings":";;;;;;;;;AA6BO,MAAM,2BAA6C,GAAA;AAAA,EACxD,EAAI,EAAA,6BAAA;AAAA,EACJ,OAAS,EAAA,OAAA;AAAA,EACT,KAAO,EAAA,iBAAA;AAAA,EACP,WACE,EAAA,oEAAA;AAAA,EACF,MAAQ,EAAA;AAAA,IACN,QAAU,EAAA;AAAA,MACR,IAAM,EAAA,SAAA;AAAA,MACN,WAAa,EAAA,oCAAA;AAAA,KACf;AAAA,IACA,cAAgB,EAAA;AAAA,MACd,IAAM,EAAA,SAAA;AAAA,MACN,WAAa,EAAA,0CAAA;AAAA,KACf;AAAA,IACA,OAAS,EAAA;AAAA,MACP,IAAM,EAAA,SAAA;AAAA,MACN,WAAa,EAAA,iCAAA;AAAA,KACf;AAAA,GACF;AAAA,EACA,SAAS,OAAO,EAAE,SAAW,EAAA,YAAA,EAAc,MAAiC,KAAA;AAC1E,IAAA,MAAM,EAAE,KAAA,EAAU,GAAA,MAAM,KAAK,qBAAsB,CAAA;AAAA,MACjD,UAAA,EAAY,MAAM,IAAA,CAAK,wBAAyB,EAAA;AAAA,MAChD,cAAgB,EAAA,SAAA;AAAA,KACjB,CAAA,CAAA;AACD,IAAM,MAAAA,eAAA,GAAgB,IAAIC,2BAAc,CAAA;AAAA,MACtC,YAAc,EAAA,SAAA;AAAA,KACf,CAAA,CAAA;AACD,IAAM,MAAA,QAAA,GAAW,MAAMD,eAAc,CAAA,WAAA;AAAA,MACnC,EAAE,QAAQ,YAAa,EAAA;AAAA,MACvB,EAAE,KAAM,EAAA;AAAA,KACV,CAAA;AAEA,IAAA,OAAO,QAAS,CAAA,KAAA,CAAM,GAAI,CAAA,CAAC,MAAmB,KAAA;AAC5C,MAAO,OAAA;AAAA,QACL,MAAQ,EAAA;AAAA,UACN,SAAA,EAAW,OAAO,QAAS,CAAA,SAAA;AAAA,UAC3B,MAAM,MAAO,CAAA,IAAA;AAAA,UACb,IAAA,EAAM,OAAO,QAAS,CAAA,IAAA;AAAA,SACxB;AAAA,QACA,KAAO,EAAA;AAAA,UACL,QAAU,EAAA,OAAA,CAAQ,MAAO,CAAA,QAAA,EAAU,KAAK,CAAA;AAAA,UACxC,cAAgB,EAAA,OAAA,CAAQ,MAAO,CAAA,QAAA,EAAU,WAAW,CAAA;AAAA,UACpD,OAAS,EAAA,CAACE,wBAAQ,CAAA,MAAA,CAAO,UAAU,IAAI,CAAA;AAAA,SACzC;AAAA,OACF,CAAA;AAAA,KACD,CAAA,CAAA;AAAA,GACH;AACF;;;;"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var catalogClient = require('@backstage/catalog-client');
|
|
4
|
+
|
|
5
|
+
const entityOwnershipFactRetriever = {
|
|
6
|
+
id: "entityOwnershipFactRetriever",
|
|
7
|
+
version: "0.0.1",
|
|
8
|
+
title: "Entity Ownership",
|
|
9
|
+
description: "Generates facts which indicate the quality of data in the spec.owner field",
|
|
10
|
+
entityFilter: [
|
|
11
|
+
{ kind: ["component", "domain", "system", "api", "resource", "template"] }
|
|
12
|
+
],
|
|
13
|
+
schema: {
|
|
14
|
+
hasOwner: {
|
|
15
|
+
type: "boolean",
|
|
16
|
+
description: "The spec.owner field is set"
|
|
17
|
+
},
|
|
18
|
+
hasGroupOwner: {
|
|
19
|
+
type: "boolean",
|
|
20
|
+
description: "The spec.owner field is set and refers to a group"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
handler: async ({ discovery, entityFilter, auth }) => {
|
|
24
|
+
const { token } = await auth.getPluginRequestToken({
|
|
25
|
+
onBehalfOf: await auth.getOwnServiceCredentials(),
|
|
26
|
+
targetPluginId: "catalog"
|
|
27
|
+
});
|
|
28
|
+
const catalogClient$1 = new catalogClient.CatalogClient({
|
|
29
|
+
discoveryApi: discovery
|
|
30
|
+
});
|
|
31
|
+
const entities = await catalogClient$1.getEntities(
|
|
32
|
+
{ filter: entityFilter },
|
|
33
|
+
{ token }
|
|
34
|
+
);
|
|
35
|
+
return entities.items.map((entity) => {
|
|
36
|
+
return {
|
|
37
|
+
entity: {
|
|
38
|
+
namespace: entity.metadata.namespace,
|
|
39
|
+
kind: entity.kind,
|
|
40
|
+
name: entity.metadata.name
|
|
41
|
+
},
|
|
42
|
+
facts: {
|
|
43
|
+
hasOwner: Boolean(entity.spec?.owner),
|
|
44
|
+
hasGroupOwner: Boolean(
|
|
45
|
+
entity.spec?.owner && !(entity.spec?.owner).startsWith("user:")
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
exports.entityOwnershipFactRetriever = entityOwnershipFactRetriever;
|
|
54
|
+
//# sourceMappingURL=entityOwnershipFactRetriever.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entityOwnershipFactRetriever.cjs.js","sources":["../../../../src/service/fact/factRetrievers/entityOwnershipFactRetriever.ts"],"sourcesContent":["/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n FactRetriever,\n FactRetrieverContext,\n} from '@backstage-community/plugin-tech-insights-node';\nimport { CatalogClient } from '@backstage/catalog-client';\nimport { Entity } from '@backstage/catalog-model';\n\n/**\n * Generates facts which indicate the quality of data in the spec.owner field.\n *\n * @public\n */\nexport const entityOwnershipFactRetriever: FactRetriever = {\n id: 'entityOwnershipFactRetriever',\n version: '0.0.1',\n title: 'Entity Ownership',\n description:\n 'Generates facts which indicate the quality of data in the spec.owner field',\n entityFilter: [\n { kind: ['component', 'domain', 'system', 'api', 'resource', 'template'] },\n ],\n schema: {\n hasOwner: {\n type: 'boolean',\n description: 'The spec.owner field is set',\n },\n hasGroupOwner: {\n type: 'boolean',\n description: 'The spec.owner field is set and refers to a group',\n },\n },\n handler: async ({ discovery, entityFilter, auth }: FactRetrieverContext) => {\n const { token } = await auth.getPluginRequestToken({\n onBehalfOf: await auth.getOwnServiceCredentials(),\n targetPluginId: 'catalog',\n });\n const catalogClient = new CatalogClient({\n discoveryApi: discovery,\n });\n const entities = await catalogClient.getEntities(\n { filter: entityFilter },\n { token },\n );\n\n return entities.items.map((entity: Entity) => {\n return {\n entity: {\n namespace: entity.metadata.namespace!,\n kind: entity.kind,\n name: entity.metadata.name,\n },\n facts: {\n hasOwner: Boolean(entity.spec?.owner),\n hasGroupOwner: Boolean(\n entity.spec?.owner &&\n !(entity.spec?.owner as string).startsWith('user:'),\n ),\n },\n };\n });\n },\n};\n"],"names":["catalogClient","CatalogClient"],"mappings":";;;;AA4BO,MAAM,4BAA8C,GAAA;AAAA,EACzD,EAAI,EAAA,8BAAA;AAAA,EACJ,OAAS,EAAA,OAAA;AAAA,EACT,KAAO,EAAA,kBAAA;AAAA,EACP,WACE,EAAA,4EAAA;AAAA,EACF,YAAc,EAAA;AAAA,IACZ,EAAE,MAAM,CAAC,WAAA,EAAa,UAAU,QAAU,EAAA,KAAA,EAAO,UAAY,EAAA,UAAU,CAAE,EAAA;AAAA,GAC3E;AAAA,EACA,MAAQ,EAAA;AAAA,IACN,QAAU,EAAA;AAAA,MACR,IAAM,EAAA,SAAA;AAAA,MACN,WAAa,EAAA,6BAAA;AAAA,KACf;AAAA,IACA,aAAe,EAAA;AAAA,MACb,IAAM,EAAA,SAAA;AAAA,MACN,WAAa,EAAA,mDAAA;AAAA,KACf;AAAA,GACF;AAAA,EACA,SAAS,OAAO,EAAE,SAAW,EAAA,YAAA,EAAc,MAAiC,KAAA;AAC1E,IAAA,MAAM,EAAE,KAAA,EAAU,GAAA,MAAM,KAAK,qBAAsB,CAAA;AAAA,MACjD,UAAA,EAAY,MAAM,IAAA,CAAK,wBAAyB,EAAA;AAAA,MAChD,cAAgB,EAAA,SAAA;AAAA,KACjB,CAAA,CAAA;AACD,IAAM,MAAAA,eAAA,GAAgB,IAAIC,2BAAc,CAAA;AAAA,MACtC,YAAc,EAAA,SAAA;AAAA,KACf,CAAA,CAAA;AACD,IAAM,MAAA,QAAA,GAAW,MAAMD,eAAc,CAAA,WAAA;AAAA,MACnC,EAAE,QAAQ,YAAa,EAAA;AAAA,MACvB,EAAE,KAAM,EAAA;AAAA,KACV,CAAA;AAEA,IAAA,OAAO,QAAS,CAAA,KAAA,CAAM,GAAI,CAAA,CAAC,MAAmB,KAAA;AAC5C,MAAO,OAAA;AAAA,QACL,MAAQ,EAAA;AAAA,UACN,SAAA,EAAW,OAAO,QAAS,CAAA,SAAA;AAAA,UAC3B,MAAM,MAAO,CAAA,IAAA;AAAA,UACb,IAAA,EAAM,OAAO,QAAS,CAAA,IAAA;AAAA,SACxB;AAAA,QACA,KAAO,EAAA;AAAA,UACL,QAAU,EAAA,OAAA,CAAQ,MAAO,CAAA,IAAA,EAAM,KAAK,CAAA;AAAA,UACpC,aAAe,EAAA,OAAA;AAAA,YACb,MAAA,CAAO,MAAM,KACX,IAAA,CAAA,CAAE,OAAO,IAAM,EAAA,KAAA,EAAiB,WAAW,OAAO,CAAA;AAAA,WACtD;AAAA,SACF;AAAA,OACF,CAAA;AAAA,KACD,CAAA,CAAA;AAAA,GACH;AACF;;;;"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var catalogClient = require('@backstage/catalog-client');
|
|
4
|
+
var utils = require('./utils.cjs.js');
|
|
5
|
+
|
|
6
|
+
const techdocsAnnotation = "backstage.io/techdocs-ref";
|
|
7
|
+
const techdocsEntityAnnotation = "backstage.io/techdocs-entity";
|
|
8
|
+
const techdocsAnnotationFactName = utils.generateAnnotationFactName(techdocsAnnotation);
|
|
9
|
+
const techdocsEntityAnnotationFactName = utils.generateAnnotationFactName(
|
|
10
|
+
techdocsEntityAnnotation
|
|
11
|
+
);
|
|
12
|
+
const techdocsFactRetriever = {
|
|
13
|
+
id: "techdocsFactRetriever",
|
|
14
|
+
version: "0.1.0",
|
|
15
|
+
title: "Tech Docs",
|
|
16
|
+
description: "Generates facts related to the completeness of techdocs configuration for entities",
|
|
17
|
+
schema: {
|
|
18
|
+
[techdocsAnnotationFactName]: {
|
|
19
|
+
type: "boolean",
|
|
20
|
+
description: "The entity has a TechDocs reference annotation"
|
|
21
|
+
},
|
|
22
|
+
[techdocsEntityAnnotationFactName]: {
|
|
23
|
+
type: "boolean",
|
|
24
|
+
description: "The entity has a TechDocs entity annotation"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
handler: async ({ discovery, entityFilter, auth }) => {
|
|
28
|
+
const { token } = await auth.getPluginRequestToken({
|
|
29
|
+
onBehalfOf: await auth.getOwnServiceCredentials(),
|
|
30
|
+
targetPluginId: "catalog"
|
|
31
|
+
});
|
|
32
|
+
const catalogClient$1 = new catalogClient.CatalogClient({
|
|
33
|
+
discoveryApi: discovery
|
|
34
|
+
});
|
|
35
|
+
const entities = await catalogClient$1.getEntities(
|
|
36
|
+
{ filter: entityFilter },
|
|
37
|
+
{ token }
|
|
38
|
+
);
|
|
39
|
+
return entities.items.map((entity) => {
|
|
40
|
+
return {
|
|
41
|
+
entity: {
|
|
42
|
+
namespace: entity.metadata.namespace,
|
|
43
|
+
kind: entity.kind,
|
|
44
|
+
name: entity.metadata.name
|
|
45
|
+
},
|
|
46
|
+
facts: {
|
|
47
|
+
[techdocsAnnotationFactName]: utils.entityHasAnnotation(
|
|
48
|
+
entity,
|
|
49
|
+
techdocsAnnotation
|
|
50
|
+
),
|
|
51
|
+
[techdocsEntityAnnotationFactName]: utils.entityHasAnnotation(
|
|
52
|
+
entity,
|
|
53
|
+
techdocsEntityAnnotation
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
exports.techdocsFactRetriever = techdocsFactRetriever;
|
|
62
|
+
//# sourceMappingURL=techdocsFactRetriever.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"techdocsFactRetriever.cjs.js","sources":["../../../../src/service/fact/factRetrievers/techdocsFactRetriever.ts"],"sourcesContent":["/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n FactRetriever,\n FactRetrieverContext,\n} from '@backstage-community/plugin-tech-insights-node';\nimport { CatalogClient } from '@backstage/catalog-client';\nimport { Entity } from '@backstage/catalog-model';\nimport { entityHasAnnotation, generateAnnotationFactName } from './utils';\n\nconst techdocsAnnotation = 'backstage.io/techdocs-ref';\nconst techdocsEntityAnnotation = 'backstage.io/techdocs-entity';\nconst techdocsAnnotationFactName =\n generateAnnotationFactName(techdocsAnnotation);\nconst techdocsEntityAnnotationFactName = generateAnnotationFactName(\n techdocsEntityAnnotation,\n);\n\n/**\n * Generates facts related to the completeness of techdocs configuration for entities.\n *\n * @public\n */\nexport const techdocsFactRetriever: FactRetriever = {\n id: 'techdocsFactRetriever',\n version: '0.1.0',\n title: 'Tech Docs',\n description:\n 'Generates facts related to the completeness of techdocs configuration for entities',\n schema: {\n [techdocsAnnotationFactName]: {\n type: 'boolean',\n description: 'The entity has a TechDocs reference annotation',\n },\n [techdocsEntityAnnotationFactName]: {\n type: 'boolean',\n description: 'The entity has a TechDocs entity annotation',\n },\n },\n handler: async ({ discovery, entityFilter, auth }: FactRetrieverContext) => {\n const { token } = await auth.getPluginRequestToken({\n onBehalfOf: await auth.getOwnServiceCredentials(),\n targetPluginId: 'catalog',\n });\n const catalogClient = new CatalogClient({\n discoveryApi: discovery,\n });\n const entities = await catalogClient.getEntities(\n { filter: entityFilter },\n { token },\n );\n\n return entities.items.map((entity: Entity) => {\n return {\n entity: {\n namespace: entity.metadata.namespace!,\n kind: entity.kind,\n name: entity.metadata.name,\n },\n facts: {\n [techdocsAnnotationFactName]: entityHasAnnotation(\n entity,\n techdocsAnnotation,\n ),\n [techdocsEntityAnnotationFactName]: entityHasAnnotation(\n entity,\n techdocsEntityAnnotation,\n ),\n },\n };\n });\n },\n};\n"],"names":["generateAnnotationFactName","catalogClient","CatalogClient","entityHasAnnotation"],"mappings":";;;;;AAwBA,MAAM,kBAAqB,GAAA,2BAAA,CAAA;AAC3B,MAAM,wBAA2B,GAAA,8BAAA,CAAA;AACjC,MAAM,0BAAA,GACJA,iCAA2B,kBAAkB,CAAA,CAAA;AAC/C,MAAM,gCAAmC,GAAAA,gCAAA;AAAA,EACvC,wBAAA;AACF,CAAA,CAAA;AAOO,MAAM,qBAAuC,GAAA;AAAA,EAClD,EAAI,EAAA,uBAAA;AAAA,EACJ,OAAS,EAAA,OAAA;AAAA,EACT,KAAO,EAAA,WAAA;AAAA,EACP,WACE,EAAA,oFAAA;AAAA,EACF,MAAQ,EAAA;AAAA,IACN,CAAC,0BAA0B,GAAG;AAAA,MAC5B,IAAM,EAAA,SAAA;AAAA,MACN,WAAa,EAAA,gDAAA;AAAA,KACf;AAAA,IACA,CAAC,gCAAgC,GAAG;AAAA,MAClC,IAAM,EAAA,SAAA;AAAA,MACN,WAAa,EAAA,6CAAA;AAAA,KACf;AAAA,GACF;AAAA,EACA,SAAS,OAAO,EAAE,SAAW,EAAA,YAAA,EAAc,MAAiC,KAAA;AAC1E,IAAA,MAAM,EAAE,KAAA,EAAU,GAAA,MAAM,KAAK,qBAAsB,CAAA;AAAA,MACjD,UAAA,EAAY,MAAM,IAAA,CAAK,wBAAyB,EAAA;AAAA,MAChD,cAAgB,EAAA,SAAA;AAAA,KACjB,CAAA,CAAA;AACD,IAAM,MAAAC,eAAA,GAAgB,IAAIC,2BAAc,CAAA;AAAA,MACtC,YAAc,EAAA,SAAA;AAAA,KACf,CAAA,CAAA;AACD,IAAM,MAAA,QAAA,GAAW,MAAMD,eAAc,CAAA,WAAA;AAAA,MACnC,EAAE,QAAQ,YAAa,EAAA;AAAA,MACvB,EAAE,KAAM,EAAA;AAAA,KACV,CAAA;AAEA,IAAA,OAAO,QAAS,CAAA,KAAA,CAAM,GAAI,CAAA,CAAC,MAAmB,KAAA;AAC5C,MAAO,OAAA;AAAA,QACL,MAAQ,EAAA;AAAA,UACN,SAAA,EAAW,OAAO,QAAS,CAAA,SAAA;AAAA,UAC3B,MAAM,MAAO,CAAA,IAAA;AAAA,UACb,IAAA,EAAM,OAAO,QAAS,CAAA,IAAA;AAAA,SACxB;AAAA,QACA,KAAO,EAAA;AAAA,UACL,CAAC,0BAA0B,GAAGE,yBAAA;AAAA,YAC5B,MAAA;AAAA,YACA,kBAAA;AAAA,WACF;AAAA,UACA,CAAC,gCAAgC,GAAGA,yBAAA;AAAA,YAClC,MAAA;AAAA,YACA,wBAAA;AAAA,WACF;AAAA,SACF;AAAA,OACF,CAAA;AAAA,KACD,CAAA,CAAA;AAAA,GACH;AACF;;;;"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var camelCase = require('lodash/camelCase');
|
|
4
|
+
var lodash = require('lodash');
|
|
5
|
+
|
|
6
|
+
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
|
|
7
|
+
|
|
8
|
+
var camelCase__default = /*#__PURE__*/_interopDefaultCompat(camelCase);
|
|
9
|
+
|
|
10
|
+
const generateAnnotationFactName = (annotation) => camelCase__default.default(`hasAnnotation-${annotation}`);
|
|
11
|
+
const entityHasAnnotation = (entity, annotation) => Boolean(lodash.get(entity, ["metadata", "annotations", annotation]));
|
|
12
|
+
const isTtl = (lifecycle) => {
|
|
13
|
+
return !!lifecycle.timeToLive;
|
|
14
|
+
};
|
|
15
|
+
const isMaxItems = (lifecycle) => {
|
|
16
|
+
return !!lifecycle.maxItems;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
exports.entityHasAnnotation = entityHasAnnotation;
|
|
20
|
+
exports.generateAnnotationFactName = generateAnnotationFactName;
|
|
21
|
+
exports.isMaxItems = isMaxItems;
|
|
22
|
+
exports.isTtl = isTtl;
|
|
23
|
+
//# sourceMappingURL=utils.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.cjs.js","sources":["../../../../src/service/fact/factRetrievers/utils.ts"],"sourcesContent":["/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport camelCase from 'lodash/camelCase';\nimport { Entity } from '@backstage/catalog-model';\nimport { get } from 'lodash';\nimport {\n FactLifecycle,\n MaxItems,\n TTL,\n} from '@backstage-community/plugin-tech-insights-node';\n\nexport const generateAnnotationFactName = (annotation: string) =>\n camelCase(`hasAnnotation-${annotation}`);\n\nexport const entityHasAnnotation = (entity: Entity, annotation: string) =>\n Boolean(get(entity, ['metadata', 'annotations', annotation]));\n\nexport const isTtl = (lifecycle: FactLifecycle): lifecycle is TTL => {\n return !!(lifecycle as TTL).timeToLive;\n};\n\nexport const isMaxItems = (lifecycle: FactLifecycle): lifecycle is MaxItems => {\n return !!(lifecycle as MaxItems).maxItems;\n};\n"],"names":["camelCase","get"],"mappings":";;;;;;;;;AAwBO,MAAM,6BAA6B,CAAC,UAAA,KACzCA,0BAAU,CAAA,CAAA,cAAA,EAAiB,UAAU,CAAE,CAAA,EAAA;AAElC,MAAM,mBAAsB,GAAA,CAAC,MAAgB,EAAA,UAAA,KAClD,OAAQ,CAAAC,UAAA,CAAI,MAAQ,EAAA,CAAC,UAAY,EAAA,aAAA,EAAe,UAAU,CAAC,CAAC,EAAA;AAEjD,MAAA,KAAA,GAAQ,CAAC,SAA+C,KAAA;AACnE,EAAO,OAAA,CAAC,CAAE,SAAkB,CAAA,UAAA,CAAA;AAC9B,EAAA;AAEa,MAAA,UAAA,GAAa,CAAC,SAAoD,KAAA;AAC7E,EAAO,OAAA,CAAC,CAAE,SAAuB,CAAA,QAAA,CAAA;AACnC;;;;;;;"}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var semver = require('semver');
|
|
4
|
+
var lodash = require('lodash');
|
|
5
|
+
var luxon = require('luxon');
|
|
6
|
+
var catalogModel = require('@backstage/catalog-model');
|
|
7
|
+
var utils = require('../fact/factRetrievers/utils.cjs.js');
|
|
8
|
+
|
|
9
|
+
class TechInsightsDatabase {
|
|
10
|
+
constructor(db, logger) {
|
|
11
|
+
this.db = db;
|
|
12
|
+
this.logger = logger;
|
|
13
|
+
}
|
|
14
|
+
CHUNK_SIZE = 50;
|
|
15
|
+
async getLatestSchemas(ids) {
|
|
16
|
+
const queryBuilder = this.db("fact_schemas");
|
|
17
|
+
if (ids) {
|
|
18
|
+
queryBuilder.whereIn("id", ids);
|
|
19
|
+
}
|
|
20
|
+
const existingSchemas = await queryBuilder.orderBy("id", "desc").select();
|
|
21
|
+
const groupedSchemas = lodash.groupBy(existingSchemas, "id");
|
|
22
|
+
return Object.values(groupedSchemas).map((schemas) => {
|
|
23
|
+
const sorted = semver.rsort(schemas.map((it) => it.version));
|
|
24
|
+
return schemas.find((it) => it.version === sorted[0]);
|
|
25
|
+
}).map((it) => ({
|
|
26
|
+
...lodash.omit(it, "schema"),
|
|
27
|
+
...JSON.parse(it.schema),
|
|
28
|
+
entityFilter: it.entityFilter ? JSON.parse(it.entityFilter) : null
|
|
29
|
+
}));
|
|
30
|
+
}
|
|
31
|
+
async insertFactSchema(schemaDefinition) {
|
|
32
|
+
const { id, version, schema, entityFilter } = schemaDefinition;
|
|
33
|
+
const existingSchemas = await this.db("fact_schemas").where({ id }).and.where({ version }).select();
|
|
34
|
+
if (!existingSchemas || existingSchemas.length === 0) {
|
|
35
|
+
await this.db("fact_schemas").insert({
|
|
36
|
+
id,
|
|
37
|
+
version,
|
|
38
|
+
entityFilter: entityFilter ? JSON.stringify(entityFilter) : void 0,
|
|
39
|
+
schema: JSON.stringify(schema)
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
async insertFacts({
|
|
44
|
+
id,
|
|
45
|
+
facts,
|
|
46
|
+
lifecycle
|
|
47
|
+
}) {
|
|
48
|
+
if (facts.length === 0) return;
|
|
49
|
+
const currentSchema = await this.getLatestSchema(id);
|
|
50
|
+
const factRows = facts.map((it) => {
|
|
51
|
+
const ts = it.timestamp?.toISO();
|
|
52
|
+
return {
|
|
53
|
+
id,
|
|
54
|
+
version: currentSchema.version,
|
|
55
|
+
entity: catalogModel.stringifyEntityRef(it.entity),
|
|
56
|
+
facts: JSON.stringify(it.facts),
|
|
57
|
+
...ts && { timestamp: ts }
|
|
58
|
+
};
|
|
59
|
+
});
|
|
60
|
+
await this.db.transaction(async (tx) => {
|
|
61
|
+
await tx.batchInsert("facts", factRows, this.CHUNK_SIZE);
|
|
62
|
+
if (lifecycle && utils.isTtl(lifecycle)) {
|
|
63
|
+
const expiration = luxon.DateTime.now().minus(lifecycle.timeToLive);
|
|
64
|
+
await this.deleteExpiredFactsByDate(tx, id, expiration);
|
|
65
|
+
}
|
|
66
|
+
if (lifecycle && utils.isMaxItems(lifecycle)) {
|
|
67
|
+
await this.deleteExpiredFactsByNumber(tx, id, lifecycle.maxItems);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
async getLatestFactsByIds(ids, entityTriplet) {
|
|
72
|
+
const results = await this.db("facts").where({ entity: entityTriplet }).and.whereIn("id", ids).join(
|
|
73
|
+
this.db("facts").max("timestamp as maxTimestamp").column("id as subId").where({ entity: entityTriplet }).and.whereIn("id", ids).groupBy("id").as("subQ"),
|
|
74
|
+
{
|
|
75
|
+
"facts.id": "subQ.subId",
|
|
76
|
+
"facts.timestamp": "subQ.maxTimestamp"
|
|
77
|
+
}
|
|
78
|
+
);
|
|
79
|
+
return this.dbFactRowsToTechInsightFacts(results);
|
|
80
|
+
}
|
|
81
|
+
async getEntities() {
|
|
82
|
+
const results = await this.db("facts").distinct("entity");
|
|
83
|
+
return results.map((row) => catalogModel.parseEntityRef(row.entity));
|
|
84
|
+
}
|
|
85
|
+
async getFactsBetweenTimestampsByIds(ids, entityTriplet, startDateTime, endDateTime) {
|
|
86
|
+
const results = await this.db("facts").where({ entity: entityTriplet }).and.whereIn("id", ids).and.whereBetween("timestamp", [
|
|
87
|
+
startDateTime.toISO(),
|
|
88
|
+
endDateTime.toISO()
|
|
89
|
+
]);
|
|
90
|
+
return lodash.groupBy(
|
|
91
|
+
results.map((it) => {
|
|
92
|
+
const { namespace, kind, name } = catalogModel.parseEntityRef(it.entity);
|
|
93
|
+
const timestamp = typeof it.timestamp === "string" ? luxon.DateTime.fromISO(it.timestamp) : luxon.DateTime.fromJSDate(it.timestamp);
|
|
94
|
+
return {
|
|
95
|
+
id: it.id,
|
|
96
|
+
entity: { namespace, kind, name },
|
|
97
|
+
timestamp,
|
|
98
|
+
version: it.version,
|
|
99
|
+
facts: JSON.parse(it.facts)
|
|
100
|
+
};
|
|
101
|
+
}),
|
|
102
|
+
"id"
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
async getLatestSchema(id) {
|
|
106
|
+
const existingSchemas = await this.db("fact_schemas").where({ id }).orderBy("id", "desc").select();
|
|
107
|
+
if (existingSchemas.length < 1) {
|
|
108
|
+
this.logger.warn(`No schema found for ${id}. `);
|
|
109
|
+
throw new Error(`No schema found for ${id}. `);
|
|
110
|
+
}
|
|
111
|
+
const sorted = semver.rsort(existingSchemas.map((it) => it.version));
|
|
112
|
+
return existingSchemas.find((it) => it.version === sorted[0]);
|
|
113
|
+
}
|
|
114
|
+
async deleteExpiredFactsByDate(tx, factRetrieverId, timestamp) {
|
|
115
|
+
await tx("facts").where({ id: factRetrieverId }).and.where("timestamp", "<", timestamp.toISO()).delete();
|
|
116
|
+
}
|
|
117
|
+
async deleteExpiredFactsByNumber(tx, factRetrieverId, maxItems) {
|
|
118
|
+
const deletionFilterQuery = (subTx) => subTx.select(["id", "entity", "timestamp"]).from("facts").where({ id: factRetrieverId }).and.whereIn(
|
|
119
|
+
"entity",
|
|
120
|
+
(db) => db.distinct("entity").where({ id: factRetrieverId })
|
|
121
|
+
).and.leftJoin(
|
|
122
|
+
(joinTable) => joinTable.select("*").from(
|
|
123
|
+
this.db("facts").column(
|
|
124
|
+
{ fid: "id" },
|
|
125
|
+
{ fentity: "entity" },
|
|
126
|
+
{ ftimestamp: "timestamp" }
|
|
127
|
+
).column(
|
|
128
|
+
this.db.raw(
|
|
129
|
+
"row_number() over (partition by id, entity order by timestamp desc) as fact_rank"
|
|
130
|
+
)
|
|
131
|
+
).as("ranks")
|
|
132
|
+
).where("fact_rank", "<=", maxItems).as("filterjoin"),
|
|
133
|
+
(joinClause) => {
|
|
134
|
+
joinClause.on("filterjoin.fid", "facts.id").on("filterjoin.fentity", "facts.entity").on("filterjoin.ftimestamp", "facts.timestamp");
|
|
135
|
+
}
|
|
136
|
+
).whereNull("filterjoin.fid");
|
|
137
|
+
await tx("facts").whereIn(
|
|
138
|
+
["id", "entity", "timestamp"],
|
|
139
|
+
(database) => deletionFilterQuery(database)
|
|
140
|
+
).delete();
|
|
141
|
+
}
|
|
142
|
+
dbFactRowsToTechInsightFacts(rows) {
|
|
143
|
+
return rows.reduce((acc, it) => {
|
|
144
|
+
const { namespace, kind, name } = catalogModel.parseEntityRef(it.entity);
|
|
145
|
+
const timestamp = typeof it.timestamp === "string" ? luxon.DateTime.fromISO(it.timestamp) : luxon.DateTime.fromJSDate(it.timestamp);
|
|
146
|
+
return {
|
|
147
|
+
...acc,
|
|
148
|
+
[it.id]: {
|
|
149
|
+
id: it.id,
|
|
150
|
+
entity: { namespace, kind, name },
|
|
151
|
+
timestamp,
|
|
152
|
+
version: it.version,
|
|
153
|
+
facts: JSON.parse(it.facts)
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
}, {});
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
exports.TechInsightsDatabase = TechInsightsDatabase;
|
|
161
|
+
//# sourceMappingURL=TechInsightsDatabase.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TechInsightsDatabase.cjs.js","sources":["../../../src/service/persistence/TechInsightsDatabase.ts"],"sourcesContent":["/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Knex } from 'knex';\nimport {\n FactLifecycle,\n FactSchemaDefinition,\n FlatTechInsightFact,\n TechInsightFact,\n TechInsightsStore,\n} from '@backstage-community/plugin-tech-insights-node';\nimport { FactSchema } from '@backstage-community/plugin-tech-insights-common';\nimport { rsort } from 'semver';\nimport { groupBy, omit } from 'lodash';\nimport { DateTime } from 'luxon';\nimport {\n CompoundEntityRef,\n parseEntityRef,\n stringifyEntityRef,\n} from '@backstage/catalog-model';\nimport { isMaxItems, isTtl } from '../fact/factRetrievers/utils';\nimport { LoggerService } from '@backstage/backend-plugin-api';\n\ntype Transaction = Knex.Transaction;\n\nexport type RawDbFactRow = {\n id: string;\n version: string;\n timestamp: Date | string;\n entity: string;\n facts: string;\n};\n\ntype RawDbFactSchemaRow = {\n id: string;\n version: string;\n schema: string;\n entityFilter?: string;\n};\n\n/**\n * Default TechInsightsDatabase implementation.\n *\n * @internal\n */\nexport class TechInsightsDatabase implements TechInsightsStore {\n private readonly CHUNK_SIZE = 50;\n\n constructor(\n private readonly db: Knex,\n private readonly logger: LoggerService,\n ) {}\n\n async getLatestSchemas(ids?: string[]): Promise<FactSchema[]> {\n const queryBuilder = this.db<RawDbFactSchemaRow>('fact_schemas');\n if (ids) {\n queryBuilder.whereIn('id', ids);\n }\n const existingSchemas = await queryBuilder.orderBy('id', 'desc').select();\n\n const groupedSchemas = groupBy(existingSchemas, 'id');\n return Object.values(groupedSchemas)\n .map(schemas => {\n const sorted = rsort(schemas.map(it => it.version));\n return schemas.find(it => it.version === sorted[0])!;\n })\n .map((it: RawDbFactSchemaRow) => ({\n ...omit(it, 'schema'),\n ...JSON.parse(it.schema),\n entityFilter: it.entityFilter ? JSON.parse(it.entityFilter) : null,\n }));\n }\n\n async insertFactSchema(schemaDefinition: FactSchemaDefinition) {\n const { id, version, schema, entityFilter } = schemaDefinition;\n const existingSchemas = await this.db<RawDbFactSchemaRow>('fact_schemas')\n .where({ id })\n .and.where({ version })\n .select();\n\n if (!existingSchemas || existingSchemas.length === 0) {\n await this.db<RawDbFactSchemaRow>('fact_schemas').insert({\n id,\n version,\n entityFilter: entityFilter ? JSON.stringify(entityFilter) : undefined,\n schema: JSON.stringify(schema),\n });\n }\n }\n\n async insertFacts({\n id,\n facts,\n lifecycle,\n }: {\n id: string;\n facts: TechInsightFact[];\n lifecycle?: FactLifecycle;\n }): Promise<void> {\n if (facts.length === 0) return;\n const currentSchema = await this.getLatestSchema(id);\n const factRows = facts.map(it => {\n const ts = it.timestamp?.toISO();\n return {\n id,\n version: currentSchema.version,\n entity: stringifyEntityRef(it.entity),\n facts: JSON.stringify(it.facts),\n ...(ts && { timestamp: ts }),\n };\n });\n\n await this.db.transaction(async tx => {\n await tx.batchInsert<RawDbFactRow>('facts', factRows, this.CHUNK_SIZE);\n\n if (lifecycle && isTtl(lifecycle)) {\n const expiration = DateTime.now().minus(lifecycle.timeToLive);\n await this.deleteExpiredFactsByDate(tx, id, expiration);\n }\n if (lifecycle && isMaxItems(lifecycle)) {\n await this.deleteExpiredFactsByNumber(tx, id, lifecycle.maxItems);\n }\n });\n }\n\n async getLatestFactsByIds(\n ids: string[],\n entityTriplet: string,\n ): Promise<{ [factId: string]: FlatTechInsightFact }> {\n const results = await this.db<RawDbFactRow>('facts')\n .where({ entity: entityTriplet })\n .and.whereIn('id', ids)\n .join(\n this.db('facts')\n .max('timestamp as maxTimestamp')\n .column('id as subId')\n .where({ entity: entityTriplet })\n .and.whereIn('id', ids)\n .groupBy('id')\n .as('subQ'),\n {\n 'facts.id': 'subQ.subId',\n 'facts.timestamp': 'subQ.maxTimestamp',\n },\n );\n return this.dbFactRowsToTechInsightFacts(results);\n }\n\n async getEntities(): Promise<CompoundEntityRef[]> {\n const results = await this.db<RawDbFactRow>('facts').distinct('entity');\n return results.map(row => parseEntityRef(row.entity));\n }\n\n async getFactsBetweenTimestampsByIds(\n ids: string[],\n entityTriplet: string,\n startDateTime: DateTime,\n endDateTime: DateTime,\n ): Promise<{\n [factId: string]: FlatTechInsightFact[];\n }> {\n const results = await this.db<RawDbFactRow>('facts')\n .where({ entity: entityTriplet })\n .and.whereIn('id', ids)\n .and.whereBetween('timestamp', [\n startDateTime.toISO(),\n endDateTime.toISO(),\n ]);\n\n return groupBy(\n results.map(it => {\n const { namespace, kind, name } = parseEntityRef(it.entity);\n const timestamp =\n typeof it.timestamp === 'string'\n ? DateTime.fromISO(it.timestamp)\n : DateTime.fromJSDate(it.timestamp);\n return {\n id: it.id,\n entity: { namespace, kind, name },\n timestamp,\n version: it.version,\n facts: JSON.parse(it.facts),\n };\n }),\n 'id',\n );\n }\n\n private async getLatestSchema(id: string): Promise<RawDbFactSchemaRow> {\n const existingSchemas = await this.db<RawDbFactSchemaRow>('fact_schemas')\n .where({ id })\n .orderBy('id', 'desc')\n .select();\n if (existingSchemas.length < 1) {\n this.logger.warn(`No schema found for ${id}. `);\n throw new Error(`No schema found for ${id}. `);\n }\n const sorted = rsort(existingSchemas.map(it => it.version));\n return existingSchemas.find(it => it.version === sorted[0])!;\n }\n\n private async deleteExpiredFactsByDate(\n tx: Transaction,\n factRetrieverId: string,\n timestamp: DateTime,\n ) {\n await tx<RawDbFactRow>('facts')\n .where({ id: factRetrieverId })\n .and.where('timestamp', '<', timestamp.toISO())\n .delete();\n }\n\n private async deleteExpiredFactsByNumber(\n tx: Transaction,\n factRetrieverId: string,\n maxItems: number,\n ) {\n const deletionFilterQuery = (subTx: Knex.QueryBuilder<any, unknown[]>) =>\n subTx\n .select(['id', 'entity', 'timestamp'])\n .from('facts')\n .where({ id: factRetrieverId })\n .and.whereIn('entity', db =>\n db.distinct('entity').where({ id: factRetrieverId }),\n )\n .and.leftJoin(\n joinTable =>\n joinTable\n .select('*')\n .from(\n this.db('facts')\n .column(\n { fid: 'id' },\n { fentity: 'entity' },\n { ftimestamp: 'timestamp' },\n )\n .column(\n this.db.raw(\n 'row_number() over (partition by id, entity order by timestamp desc) as fact_rank',\n ),\n )\n .as('ranks'),\n )\n .where('fact_rank', '<=', maxItems)\n .as('filterjoin'),\n joinClause => {\n joinClause\n .on('filterjoin.fid', 'facts.id')\n .on('filterjoin.fentity', 'facts.entity')\n .on('filterjoin.ftimestamp', 'facts.timestamp');\n },\n )\n .whereNull('filterjoin.fid');\n await tx('facts')\n .whereIn(['id', 'entity', 'timestamp'], database =>\n deletionFilterQuery(database),\n )\n .delete();\n }\n\n private dbFactRowsToTechInsightFacts(rows: RawDbFactRow[]) {\n return rows.reduce((acc, it) => {\n const { namespace, kind, name } = parseEntityRef(it.entity);\n const timestamp =\n typeof it.timestamp === 'string'\n ? DateTime.fromISO(it.timestamp)\n : DateTime.fromJSDate(it.timestamp);\n return {\n ...acc,\n [it.id]: {\n id: it.id,\n entity: { namespace, kind, name },\n timestamp,\n version: it.version,\n facts: JSON.parse(it.facts),\n },\n };\n }, {});\n }\n}\n"],"names":["groupBy","rsort","omit","stringifyEntityRef","isTtl","DateTime","isMaxItems","parseEntityRef"],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var TechInsightsDatabase = require('./TechInsightsDatabase.cjs.js');
|
|
4
|
+
var backendPluginApi = require('@backstage/backend-plugin-api');
|
|
5
|
+
|
|
6
|
+
const migrationsDir = backendPluginApi.resolvePackagePath(
|
|
7
|
+
"@backstage-community/plugin-tech-insights-backend",
|
|
8
|
+
"migrations"
|
|
9
|
+
);
|
|
10
|
+
const initializePersistenceContext = async (database, options) => {
|
|
11
|
+
const client = await database.getClient();
|
|
12
|
+
if (!database.migrations?.skip) {
|
|
13
|
+
await client.migrate.latest({
|
|
14
|
+
directory: migrationsDir
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
return {
|
|
18
|
+
techInsightsStore: new TechInsightsDatabase.TechInsightsDatabase(client, options.logger)
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
exports.initializePersistenceContext = initializePersistenceContext;
|
|
23
|
+
//# sourceMappingURL=persistenceContext.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"persistenceContext.cjs.js","sources":["../../../src/service/persistence/persistenceContext.ts"],"sourcesContent":["/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { TechInsightsDatabase } from './TechInsightsDatabase';\nimport { PersistenceContext } from '@backstage-community/plugin-tech-insights-node';\nimport {\n DatabaseService,\n LoggerService,\n resolvePackagePath,\n} from '@backstage/backend-plugin-api';\n\nconst migrationsDir = resolvePackagePath(\n '@backstage-community/plugin-tech-insights-backend',\n 'migrations',\n);\n\n/**\n * A Container for persistence context initialization options\n *\n * @public\n */\nexport type PersistenceContextOptions = {\n logger: LoggerService;\n};\n\n/**\n * A factory function to construct persistence context for running implementation.\n *\n * @public\n */\nexport const initializePersistenceContext = async (\n database: DatabaseService,\n options: PersistenceContextOptions,\n): Promise<PersistenceContext> => {\n const client = await database.getClient();\n\n if (!database.migrations?.skip) {\n await client.migrate.latest({\n directory: migrationsDir,\n });\n }\n\n return {\n techInsightsStore: new TechInsightsDatabase(client, options.logger),\n };\n};\n"],"names":["resolvePackagePath","TechInsightsDatabase"],"mappings":";;;;;AAuBA,MAAM,aAAgB,GAAAA,mCAAA;AAAA,EACpB,mDAAA;AAAA,EACA,YAAA;AACF,CAAA,CAAA;AAgBa,MAAA,4BAAA,GAA+B,OAC1C,QAAA,EACA,OACgC,KAAA;AAChC,EAAM,MAAA,MAAA,GAAS,MAAM,QAAA,CAAS,SAAU,EAAA,CAAA;AAExC,EAAI,IAAA,CAAC,QAAS,CAAA,UAAA,EAAY,IAAM,EAAA;AAC9B,IAAM,MAAA,MAAA,CAAO,QAAQ,MAAO,CAAA;AAAA,MAC1B,SAAW,EAAA,aAAA;AAAA,KACZ,CAAA,CAAA;AAAA,GACH;AAEA,EAAO,OAAA;AAAA,IACL,iBAAmB,EAAA,IAAIC,yCAAqB,CAAA,MAAA,EAAQ,QAAQ,MAAM,CAAA;AAAA,GACpE,CAAA;AACF;;;;"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var express = require('express');
|
|
4
|
+
var Router = require('express-promise-router');
|
|
5
|
+
var luxon = require('luxon');
|
|
6
|
+
var catalogModel = require('@backstage/catalog-model');
|
|
7
|
+
var errors = require('@backstage/errors');
|
|
8
|
+
var rootHttpRouter = require('@backstage/backend-defaults/rootHttpRouter');
|
|
9
|
+
var pLimit = require('p-limit');
|
|
10
|
+
|
|
11
|
+
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
|
|
12
|
+
|
|
13
|
+
var express__default = /*#__PURE__*/_interopDefaultCompat(express);
|
|
14
|
+
var Router__default = /*#__PURE__*/_interopDefaultCompat(Router);
|
|
15
|
+
var pLimit__default = /*#__PURE__*/_interopDefaultCompat(pLimit);
|
|
16
|
+
|
|
17
|
+
async function createRouter(options) {
|
|
18
|
+
const router = Router__default.default();
|
|
19
|
+
router.use(express__default.default.json());
|
|
20
|
+
const { persistenceContext, factChecker, logger, config } = options;
|
|
21
|
+
const { techInsightsStore } = persistenceContext;
|
|
22
|
+
const factory = rootHttpRouter.MiddlewareFactory.create({ logger, config });
|
|
23
|
+
if (factChecker) {
|
|
24
|
+
logger.info("Fact checker configured. Enabling fact checking endpoints.");
|
|
25
|
+
router.get("/checks", async (_req, res) => {
|
|
26
|
+
return res.json(await factChecker.getChecks());
|
|
27
|
+
});
|
|
28
|
+
router.post("/checks/run/:namespace/:kind/:name", async (req, res) => {
|
|
29
|
+
const { namespace, kind, name } = req.params;
|
|
30
|
+
const { checks } = req.body;
|
|
31
|
+
const entityTriplet = catalogModel.stringifyEntityRef({ namespace, kind, name });
|
|
32
|
+
const checkResult = await factChecker.runChecks(entityTriplet, checks);
|
|
33
|
+
return res.json(checkResult);
|
|
34
|
+
});
|
|
35
|
+
const checksRunConcurrency = config.getOptionalNumber("techInsights.checksRunConcurrency") || 100;
|
|
36
|
+
router.post("/checks/run", async (req, res) => {
|
|
37
|
+
const checks = req.body.checks;
|
|
38
|
+
let entities = req.body.entities;
|
|
39
|
+
if (entities.length === 0) {
|
|
40
|
+
entities = await techInsightsStore.getEntities();
|
|
41
|
+
}
|
|
42
|
+
const limit = pLimit__default.default(checksRunConcurrency);
|
|
43
|
+
const tasks = entities.map(
|
|
44
|
+
async (entity) => limit(async () => {
|
|
45
|
+
const entityTriplet = typeof entity === "string" ? entity : catalogModel.stringifyEntityRef(entity);
|
|
46
|
+
try {
|
|
47
|
+
const results2 = await factChecker.runChecks(entityTriplet, checks);
|
|
48
|
+
return {
|
|
49
|
+
entity: entityTriplet,
|
|
50
|
+
results: results2
|
|
51
|
+
};
|
|
52
|
+
} catch (e) {
|
|
53
|
+
const error = errors.serializeError(e);
|
|
54
|
+
logger.error(`${error.name}: ${error.message}`);
|
|
55
|
+
return {
|
|
56
|
+
entity: entityTriplet,
|
|
57
|
+
error,
|
|
58
|
+
results: []
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
})
|
|
62
|
+
);
|
|
63
|
+
const results = await Promise.all(tasks);
|
|
64
|
+
return res.json(results);
|
|
65
|
+
});
|
|
66
|
+
} else {
|
|
67
|
+
logger.info(
|
|
68
|
+
"Starting tech insights module without fact checking endpoints."
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
router.get("/fact-schemas", async (req, res) => {
|
|
72
|
+
const ids = req.query.ids;
|
|
73
|
+
return res.json(await techInsightsStore.getLatestSchemas(ids));
|
|
74
|
+
});
|
|
75
|
+
router.get("/facts/latest", async (req, res) => {
|
|
76
|
+
const { entity } = req.query;
|
|
77
|
+
const { namespace, kind, name } = catalogModel.parseEntityRef(entity);
|
|
78
|
+
if (!req.query.ids) {
|
|
79
|
+
return res.status(422).json({ error: "Failed to parse ids from request" });
|
|
80
|
+
}
|
|
81
|
+
const ids = [req.query.ids].flat();
|
|
82
|
+
return res.json(
|
|
83
|
+
await techInsightsStore.getLatestFactsByIds(
|
|
84
|
+
ids,
|
|
85
|
+
catalogModel.stringifyEntityRef({ namespace, kind, name })
|
|
86
|
+
)
|
|
87
|
+
);
|
|
88
|
+
});
|
|
89
|
+
router.get("/facts/range", async (req, res) => {
|
|
90
|
+
const { entity } = req.query;
|
|
91
|
+
const { namespace, kind, name } = catalogModel.parseEntityRef(entity);
|
|
92
|
+
if (!req.query.ids) {
|
|
93
|
+
return res.status(422).json({ error: "Failed to parse ids from request" });
|
|
94
|
+
}
|
|
95
|
+
const ids = [req.query.ids].flat();
|
|
96
|
+
const startDatetime = luxon.DateTime.fromISO(req.query.startDatetime);
|
|
97
|
+
const endDatetime = luxon.DateTime.fromISO(req.query.endDatetime);
|
|
98
|
+
if (!startDatetime.isValid || !endDatetime.isValid) {
|
|
99
|
+
return res.status(422).json({
|
|
100
|
+
message: "Failed to parse datetime from request",
|
|
101
|
+
field: !startDatetime.isValid ? "startDateTime" : "endDateTime",
|
|
102
|
+
value: !startDatetime.isValid ? startDatetime : endDatetime
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
const entityTriplet = catalogModel.stringifyEntityRef({ namespace, kind, name });
|
|
106
|
+
return res.json(
|
|
107
|
+
await techInsightsStore.getFactsBetweenTimestampsByIds(
|
|
108
|
+
ids,
|
|
109
|
+
entityTriplet,
|
|
110
|
+
startDatetime,
|
|
111
|
+
endDatetime
|
|
112
|
+
)
|
|
113
|
+
);
|
|
114
|
+
});
|
|
115
|
+
router.use(factory.error());
|
|
116
|
+
return router;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
exports.createRouter = createRouter;
|
|
120
|
+
//# sourceMappingURL=router.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"router.cjs.js","sources":["../../src/service/router.ts"],"sourcesContent":["/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport express from 'express';\nimport Router from 'express-promise-router';\nimport { Config } from '@backstage/config';\nimport {\n FactChecker,\n PersistenceContext,\n TechInsightCheck,\n} from '@backstage-community/plugin-tech-insights-node';\n\nimport { CheckResult } from '@backstage-community/plugin-tech-insights-common';\nimport { DateTime } from 'luxon';\nimport {\n CompoundEntityRef,\n parseEntityRef,\n stringifyEntityRef,\n} from '@backstage/catalog-model';\nimport { serializeError } from '@backstage/errors';\nimport { LoggerService } from '@backstage/backend-plugin-api';\nimport { MiddlewareFactory } from '@backstage/backend-defaults/rootHttpRouter';\nimport pLimit from 'p-limit';\n\n/**\n * @public\n *\n * RouterOptions to construct TechInsights endpoints\n * @typeParam CheckType - Type of the check for the fact checker this builder returns\n * @typeParam CheckResultType - Type of the check result for the fact checker this builder returns\n */\nexport interface RouterOptions<\n CheckType extends TechInsightCheck,\n CheckResultType extends CheckResult,\n> {\n /**\n * Optional FactChecker implementation. If omitted, endpoints are not constructed\n */\n factChecker?: FactChecker<CheckType, CheckResultType>;\n\n /**\n * TechInsights PersistenceContext. Should contain an implementation of TechInsightsStore\n */\n persistenceContext: PersistenceContext;\n\n /**\n * Backstage config object\n */\n config: Config;\n\n /**\n * Implementation of Winston logger\n */\n logger: LoggerService;\n}\n\n/**\n * @public\n *\n * Constructs a tech-insights router.\n *\n * Exposes endpoints to handle facts\n * Exposes optional endpoints to handle checks if a FactChecker implementation is passed in\n *\n * @param options - RouterOptions object\n */\nexport async function createRouter<\n CheckType extends TechInsightCheck,\n CheckResultType extends CheckResult,\n>(options: RouterOptions<CheckType, CheckResultType>): Promise<express.Router> {\n const router = Router();\n router.use(express.json());\n const { persistenceContext, factChecker, logger, config } = options;\n const { techInsightsStore } = persistenceContext;\n\n const factory = MiddlewareFactory.create({ logger, config });\n\n if (factChecker) {\n logger.info('Fact checker configured. Enabling fact checking endpoints.');\n router.get('/checks', async (_req, res) => {\n return res.json(await factChecker.getChecks());\n });\n\n router.post('/checks/run/:namespace/:kind/:name', async (req, res) => {\n const { namespace, kind, name } = req.params;\n const { checks }: { checks: string[] } = req.body;\n const entityTriplet = stringifyEntityRef({ namespace, kind, name });\n const checkResult = await factChecker.runChecks(entityTriplet, checks);\n return res.json(checkResult);\n });\n\n const checksRunConcurrency =\n config.getOptionalNumber('techInsights.checksRunConcurrency') || 100;\n router.post('/checks/run', async (req, res) => {\n const checks: string[] = req.body.checks;\n let entities: CompoundEntityRef[] = req.body.entities;\n if (entities.length === 0) {\n entities = await techInsightsStore.getEntities();\n }\n const limit = pLimit(checksRunConcurrency);\n const tasks = entities.map(async entity =>\n limit(async () => {\n const entityTriplet =\n typeof entity === 'string' ? entity : stringifyEntityRef(entity);\n try {\n const results = await factChecker.runChecks(entityTriplet, checks);\n return {\n entity: entityTriplet,\n results,\n };\n } catch (e: any) {\n const error = serializeError(e);\n logger.error(`${error.name}: ${error.message}`);\n return {\n entity: entityTriplet,\n error: error,\n results: [],\n };\n }\n }),\n );\n const results = await Promise.all(tasks);\n return res.json(results);\n });\n } else {\n logger.info(\n 'Starting tech insights module without fact checking endpoints.',\n );\n }\n\n router.get('/fact-schemas', async (req, res) => {\n const ids = req.query.ids as string[];\n return res.json(await techInsightsStore.getLatestSchemas(ids));\n });\n\n /**\n * /facts/latest?entity=component:default/mycomponent&ids[]=factRetrieverId1&ids[]=factRetrieverId2\n */\n router.get('/facts/latest', async (req, res) => {\n const { entity } = req.query;\n const { namespace, kind, name } = parseEntityRef(entity as string);\n\n if (!req.query.ids) {\n return res\n .status(422)\n .json({ error: 'Failed to parse ids from request' });\n }\n const ids = [req.query.ids].flat() as string[];\n return res.json(\n await techInsightsStore.getLatestFactsByIds(\n ids,\n stringifyEntityRef({ namespace, kind, name }),\n ),\n );\n });\n\n /**\n * /facts/range?entity=component:default/mycomponent&startDateTime=2021-12-24T01:23:45&endDateTime=2021-12-31T23:59:59&ids[]=factRetrieverId1&ids[]=factRetrieverId2\n */\n router.get('/facts/range', async (req, res) => {\n const { entity } = req.query;\n const { namespace, kind, name } = parseEntityRef(entity as string);\n\n if (!req.query.ids) {\n return res\n .status(422)\n .json({ error: 'Failed to parse ids from request' });\n }\n const ids = [req.query.ids].flat() as string[];\n const startDatetime = DateTime.fromISO(req.query.startDatetime as string);\n const endDatetime = DateTime.fromISO(req.query.endDatetime as string);\n if (!startDatetime.isValid || !endDatetime.isValid) {\n return res.status(422).json({\n message: 'Failed to parse datetime from request',\n field: !startDatetime.isValid ? 'startDateTime' : 'endDateTime',\n value: !startDatetime.isValid ? startDatetime : endDatetime,\n });\n }\n const entityTriplet = stringifyEntityRef({ namespace, kind, name });\n return res.json(\n await techInsightsStore.getFactsBetweenTimestampsByIds(\n ids,\n entityTriplet,\n startDatetime,\n endDatetime,\n ),\n );\n });\n\n router.use(factory.error());\n return router;\n}\n"],"names":["Router","express","MiddlewareFactory","stringifyEntityRef","pLimit","results","serializeError","parseEntityRef","DateTime"],"mappings":"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"}
|