@bcgov/plugin-catalog-backend-module-bc-data-catalogue 0.2.6-main.59c9210c → 0.2.6-main.91ee0be1
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/dist/BcDataCatalogueDatasetProcessor.cjs.js +49 -14
- package/dist/BcDataCatalogueDatasetProcessor.cjs.js.map +1 -1
- package/dist/BcDataCatalogueEntityFactory.cjs.js +252 -380
- package/dist/BcDataCatalogueEntityFactory.cjs.js.map +1 -1
- package/dist/BcDataCatalogueNaming.cjs.js +6 -0
- package/dist/BcDataCatalogueNaming.cjs.js.map +1 -1
- package/dist/BcDataCatalogueOpenApiProcessor.cjs.js +64 -0
- package/dist/BcDataCatalogueOpenApiProcessor.cjs.js.map +1 -0
- package/dist/builders/ApiEntityBuilder.cjs.js +89 -0
- package/dist/builders/ApiEntityBuilder.cjs.js.map +1 -0
- package/dist/builders/DatasetEntityBuilder.cjs.js +243 -0
- package/dist/builders/DatasetEntityBuilder.cjs.js.map +1 -0
- package/dist/builders/GroupEntityBuilder.cjs.js +35 -0
- package/dist/builders/GroupEntityBuilder.cjs.js.map +1 -0
- package/dist/builders/OpenApiEntityBuilder.cjs.js +249 -0
- package/dist/builders/OpenApiEntityBuilder.cjs.js.map +1 -0
- package/dist/builders/SystemEntityBuilder.cjs.js +37 -0
- package/dist/builders/SystemEntityBuilder.cjs.js.map +1 -0
- package/dist/builders/UserEntityBuilder.cjs.js +32 -0
- package/dist/builders/UserEntityBuilder.cjs.js.map +1 -0
- package/dist/module.cjs.js +2 -0
- package/dist/module.cjs.js.map +1 -1
- package/package.json +2 -2
|
@@ -2,21 +2,50 @@
|
|
|
2
2
|
|
|
3
3
|
var pluginCatalogCommonBcDataCatalogue = require('@bcgov/plugin-catalog-common-bc-data-catalogue');
|
|
4
4
|
var BcDataCatalogueNaming = require('./BcDataCatalogueNaming.cjs.js');
|
|
5
|
+
var ApiEntityBuilder = require('./builders/ApiEntityBuilder.cjs.js');
|
|
6
|
+
var DatasetEntityBuilder = require('./builders/DatasetEntityBuilder.cjs.js');
|
|
7
|
+
var GroupEntityBuilder = require('./builders/GroupEntityBuilder.cjs.js');
|
|
8
|
+
var OpenApiEntityBuilder = require('./builders/OpenApiEntityBuilder.cjs.js');
|
|
9
|
+
var SystemEntityBuilder = require('./builders/SystemEntityBuilder.cjs.js');
|
|
10
|
+
var UserEntityBuilder = require('./builders/UserEntityBuilder.cjs.js');
|
|
5
11
|
|
|
6
|
-
const MANAGED_BY_LOCATION = "url:https://catalogue.data.gov.bc.ca/api/3/action/package_search";
|
|
7
|
-
const GAP = "GAP";
|
|
8
12
|
class BcDataCatalogueEntityFactory {
|
|
9
13
|
reader;
|
|
10
14
|
logger;
|
|
11
15
|
allowedHosts;
|
|
12
16
|
naming;
|
|
13
17
|
schemaUtils;
|
|
18
|
+
apiEntityBuilder;
|
|
19
|
+
datasetEntityBuilder;
|
|
20
|
+
groupEntityBuilder;
|
|
21
|
+
openApiEntityBuilder;
|
|
22
|
+
systemEntityBuilder;
|
|
23
|
+
userEntityBuilder;
|
|
14
24
|
constructor(options) {
|
|
15
25
|
this.reader = options.reader;
|
|
16
26
|
this.logger = options.logger;
|
|
17
27
|
this.allowedHosts = options.allowedHosts;
|
|
18
28
|
this.naming = new BcDataCatalogueNaming.BcDataCatalogueNaming();
|
|
19
29
|
this.schemaUtils = new pluginCatalogCommonBcDataCatalogue.BcDataCatalogueSchemaUtils();
|
|
30
|
+
this.apiEntityBuilder = new ApiEntityBuilder.ApiEntityBuilder({
|
|
31
|
+
naming: this.naming
|
|
32
|
+
});
|
|
33
|
+
this.datasetEntityBuilder = new DatasetEntityBuilder.DatasetEntityBuilder({
|
|
34
|
+
naming: this.naming,
|
|
35
|
+
schemaUtils: this.schemaUtils
|
|
36
|
+
});
|
|
37
|
+
this.groupEntityBuilder = new GroupEntityBuilder.GroupEntityBuilder({
|
|
38
|
+
naming: this.naming
|
|
39
|
+
});
|
|
40
|
+
this.openApiEntityBuilder = new OpenApiEntityBuilder.OpenApiEntityBuilder({
|
|
41
|
+
naming: this.naming
|
|
42
|
+
});
|
|
43
|
+
this.systemEntityBuilder = new SystemEntityBuilder.SystemEntityBuilder({
|
|
44
|
+
naming: this.naming
|
|
45
|
+
});
|
|
46
|
+
this.userEntityBuilder = new UserEntityBuilder.UserEntityBuilder({
|
|
47
|
+
naming: this.naming
|
|
48
|
+
});
|
|
20
49
|
}
|
|
21
50
|
async createEntities(allPackages) {
|
|
22
51
|
this.logger.info("[BCDC Entity Factory] <createEntities");
|
|
@@ -25,48 +54,35 @@ class BcDataCatalogueEntityFactory {
|
|
|
25
54
|
const bcGovGroupId = this.naming.getGroupId("gov.bc.ca");
|
|
26
55
|
allGroups.set(
|
|
27
56
|
bcGovGroupId,
|
|
28
|
-
this.
|
|
57
|
+
this.groupEntityBuilder.build({
|
|
58
|
+
hostName: "gov.bc.ca",
|
|
59
|
+
displayName: "Governent of British Columbia"
|
|
60
|
+
})
|
|
29
61
|
);
|
|
30
62
|
const allOrganizations = /* @__PURE__ */ new Map();
|
|
31
63
|
allPackages.forEach((pkg) => {
|
|
32
64
|
allOrganizations.set(pkg.organization.id, pkg.organization);
|
|
33
65
|
});
|
|
34
66
|
const allSystems = /* @__PURE__ */ new Map();
|
|
35
|
-
this.logger.info(
|
|
67
|
+
this.logger.info(
|
|
68
|
+
`[BCDC Entity Factory] Organizations ${allOrganizations.size}`
|
|
69
|
+
);
|
|
36
70
|
allOrganizations.forEach((organization) => {
|
|
37
71
|
const organizationGroupId = this.naming.getGroupId(organization.name);
|
|
38
72
|
if (!allGroups.has(organizationGroupId)) {
|
|
39
73
|
allGroups.set(
|
|
40
74
|
organizationGroupId,
|
|
41
|
-
this.
|
|
42
|
-
organization.name,
|
|
43
|
-
organization.title,
|
|
44
|
-
bcGovGroupId
|
|
45
|
-
)
|
|
75
|
+
this.groupEntityBuilder.build({
|
|
76
|
+
hostName: organization.name,
|
|
77
|
+
displayName: organization.title,
|
|
78
|
+
parentGroup: bcGovGroupId
|
|
79
|
+
})
|
|
46
80
|
);
|
|
47
81
|
}
|
|
48
|
-
const systemEntity = {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
owner: organizationGroupId,
|
|
53
|
-
type: "government"
|
|
54
|
-
},
|
|
55
|
-
metadata: {
|
|
56
|
-
name: this.naming.toSafeName(organization.name),
|
|
57
|
-
title: organization.title,
|
|
58
|
-
description: organization.description,
|
|
59
|
-
annotations: {
|
|
60
|
-
"backstage.io/managed-by-location": MANAGED_BY_LOCATION,
|
|
61
|
-
"backstage.io/managed-by-origin-location": MANAGED_BY_LOCATION,
|
|
62
|
-
"bcdata.gov.bc.ca/organization-id": organization.id,
|
|
63
|
-
"bcdata.gov.bc.ca/organization-type": organization.type,
|
|
64
|
-
"bcdata.gov.bc.ca/organization-created": organization.created,
|
|
65
|
-
"bcdata.gov.bc.ca/organization-approval-status": organization.approval_status,
|
|
66
|
-
"bcdata.gov.bc.ca/organization-state": organization.state
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
};
|
|
82
|
+
const systemEntity = this.systemEntityBuilder.build({
|
|
83
|
+
organization,
|
|
84
|
+
ownerGroupId: organizationGroupId
|
|
85
|
+
});
|
|
70
86
|
allSystems.set(this.naming.getSystemId(organization.name), systemEntity);
|
|
71
87
|
});
|
|
72
88
|
const allUsers = /* @__PURE__ */ new Map();
|
|
@@ -75,23 +91,7 @@ class BcDataCatalogueEntityFactory {
|
|
|
75
91
|
const email = contact.email.toLowerCase();
|
|
76
92
|
let user = allUsers.get(email);
|
|
77
93
|
if (user === void 0) {
|
|
78
|
-
user = {
|
|
79
|
-
apiVersion: "backstage.io/v1alpha1",
|
|
80
|
-
kind: "User",
|
|
81
|
-
spec: {
|
|
82
|
-
profile: {
|
|
83
|
-
email
|
|
84
|
-
},
|
|
85
|
-
memberOf: []
|
|
86
|
-
},
|
|
87
|
-
metadata: {
|
|
88
|
-
name: this.naming.toSafeName(email),
|
|
89
|
-
annotations: {
|
|
90
|
-
"backstage.io/managed-by-location": MANAGED_BY_LOCATION,
|
|
91
|
-
"backstage.io/managed-by-origin-location": MANAGED_BY_LOCATION
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
};
|
|
94
|
+
user = this.userEntityBuilder.build({ email });
|
|
95
95
|
allUsers.set(this.naming.getUserId(email), user);
|
|
96
96
|
}
|
|
97
97
|
if (user.spec.profile?.displayName === void 0) {
|
|
@@ -106,7 +106,11 @@ class BcDataCatalogueEntityFactory {
|
|
|
106
106
|
const groupId = this.naming.getGroupId(hostName);
|
|
107
107
|
let group = allGroups.get(groupId);
|
|
108
108
|
if (group === void 0) {
|
|
109
|
-
group = this.
|
|
109
|
+
group = this.groupEntityBuilder.build({
|
|
110
|
+
hostName,
|
|
111
|
+
displayName: hostName,
|
|
112
|
+
parentGroup: bcGovGroupId
|
|
113
|
+
});
|
|
110
114
|
allGroups.set(groupId, group);
|
|
111
115
|
}
|
|
112
116
|
if (!user.spec.memberOf?.includes(groupId)) {
|
|
@@ -117,313 +121,127 @@ class BcDataCatalogueEntityFactory {
|
|
|
117
121
|
});
|
|
118
122
|
const allDatasets = /* @__PURE__ */ new Map();
|
|
119
123
|
const allApis = /* @__PURE__ */ new Map();
|
|
124
|
+
const allOpenApis = /* @__PURE__ */ new Map();
|
|
125
|
+
const openApiDefinitionToId = /* @__PURE__ */ new Map();
|
|
120
126
|
for (const pkg of allPackages) {
|
|
121
127
|
const safeName = this.naming.toSafeName(pkg.name);
|
|
122
128
|
const systemId = this.naming.getSystemId(pkg.organization.name);
|
|
123
129
|
const ownerGroupId = this.naming.getGroupId(pkg.organization.name);
|
|
124
|
-
const learnMoreLinks = [];
|
|
125
|
-
const relatedResources = [];
|
|
126
|
-
const accessMethods = [];
|
|
127
130
|
const apiResources = [];
|
|
128
131
|
const providesApis = [];
|
|
129
|
-
pkg.more_info?.forEach((moreInfo) => {
|
|
130
|
-
if (moreInfo.url.length > 0) {
|
|
131
|
-
const entityLink = {
|
|
132
|
-
url: moreInfo.url,
|
|
133
|
-
title: moreInfo.description || moreInfo.url,
|
|
134
|
-
icon: "externalLink",
|
|
135
|
-
type: "more_info"
|
|
136
|
-
};
|
|
137
|
-
learnMoreLinks.push(entityLink);
|
|
138
|
-
relatedResources.push({
|
|
139
|
-
url: moreInfo.url,
|
|
140
|
-
title: moreInfo.description || moreInfo.url
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
});
|
|
144
132
|
pkg.resources?.forEach((resource) => {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
}
|
|
149
|
-
if (resource.bcdc_type === "geographic") {
|
|
133
|
+
const apiResourceCandidate = this.getApiResourceCandidate(resource);
|
|
134
|
+
if (apiResourceCandidate) {
|
|
135
|
+
apiResources.push(apiResourceCandidate);
|
|
150
136
|
return;
|
|
151
137
|
}
|
|
152
|
-
if (resource.url.length > 0) {
|
|
153
|
-
accessMethods.push(this.toDatasetAccessMethod(resource));
|
|
154
|
-
relatedResources.push({
|
|
155
|
-
url: resource.url,
|
|
156
|
-
title: resource.name
|
|
157
|
-
});
|
|
158
|
-
} else {
|
|
159
|
-
this.logger.info(
|
|
160
|
-
`[BCDC Entity Factory] Missing URL ${resource.bcdc_type} ${resource.format} ${pkg.name}`
|
|
161
|
-
);
|
|
162
|
-
}
|
|
163
138
|
});
|
|
164
|
-
const
|
|
165
|
-
|
|
166
|
-
|
|
139
|
+
const bcdcDatasetUrl = `https://catalogue.data.gov.bc.ca/dataset/${pkg.name}`;
|
|
140
|
+
const datasetEntity = this.datasetEntityBuilder.build({
|
|
141
|
+
pkg,
|
|
142
|
+
safeName,
|
|
143
|
+
ownerGroupId,
|
|
144
|
+
systemId,
|
|
145
|
+
providesApis,
|
|
146
|
+
bcdcDatasetUrl
|
|
167
147
|
});
|
|
168
|
-
const schema = this.schemaUtils.buildDatasetSchema(pkg.resources);
|
|
169
|
-
const tableCount = schema?.tables?.length ?? 0;
|
|
170
|
-
if (tableCount > 0) {
|
|
171
|
-
tags.push("has-schema");
|
|
172
|
-
} else {
|
|
173
|
-
tags.push("has-no-schema");
|
|
174
|
-
}
|
|
175
|
-
if (tableCount >= 2 && tableCount <= 5) {
|
|
176
|
-
tags.push("has-two-to-five-tables");
|
|
177
|
-
} else if (tableCount >= 6 && tableCount <= 10) {
|
|
178
|
-
tags.push("has-six-to-10-tables");
|
|
179
|
-
} else if (tableCount >= 11) {
|
|
180
|
-
tags.push("has-11-or-more-tables");
|
|
181
|
-
}
|
|
182
|
-
const datasetEntity = {
|
|
183
|
-
apiVersion: pluginCatalogCommonBcDataCatalogue.DATASET_API_VERSION,
|
|
184
|
-
kind: pluginCatalogCommonBcDataCatalogue.DATASET_KIND,
|
|
185
|
-
spec: {
|
|
186
|
-
owner: ownerGroupId,
|
|
187
|
-
system: systemId,
|
|
188
|
-
type: GAP + "<type>",
|
|
189
|
-
description: pkg.notes || "No description available",
|
|
190
|
-
status: this.normalizeStatus(pkg.publish_state),
|
|
191
|
-
securityClassification: this.normalizeSecurityClassification(
|
|
192
|
-
pkg.security_class
|
|
193
|
-
),
|
|
194
|
-
connectedServicesDescription: GAP + "<connectedServicesDescription>",
|
|
195
|
-
updateFrequency: GAP + "<updateFrequency>",
|
|
196
|
-
providesApis,
|
|
197
|
-
accessMethods: accessMethods.length > 0 ? accessMethods : void 0,
|
|
198
|
-
schema,
|
|
199
|
-
quality: {
|
|
200
|
-
score: GAP + "<quality.score>",
|
|
201
|
-
validation: GAP + "<quality.validation>",
|
|
202
|
-
controls: [GAP + "<quality.controls>"]
|
|
203
|
-
},
|
|
204
|
-
governance: {
|
|
205
|
-
retention: GAP + "<governance.retention>",
|
|
206
|
-
description: GAP + "<governance.description>"
|
|
207
|
-
},
|
|
208
|
-
about: {
|
|
209
|
-
description: pkg.purpose || "No description available"
|
|
210
|
-
},
|
|
211
|
-
authoritativeDesignation: {
|
|
212
|
-
authoritativeFor: GAP + "<authoritativeDesignation.authoritativeFor>"
|
|
213
|
-
},
|
|
214
|
-
lineage: {
|
|
215
|
-
sourceSystem: GAP + "<lineage.sourceSystem>",
|
|
216
|
-
transformation: pkg.lineage_statement || "No transformation information available",
|
|
217
|
-
refresh: GAP + "<lineage.refresh>"
|
|
218
|
-
},
|
|
219
|
-
versioning: {
|
|
220
|
-
currentVersion: GAP + "<versioning.currentVersion>",
|
|
221
|
-
// pkg.version is not populated for any of the 3000+ datasets
|
|
222
|
-
initialRelease: pkg.record_publish_date,
|
|
223
|
-
lastUpdated: pkg.record_last_modified,
|
|
224
|
-
description: GAP + "<versioning.description>"
|
|
225
|
-
},
|
|
226
|
-
support: {
|
|
227
|
-
description: pkg.organization.description,
|
|
228
|
-
dataCustodian: pkg.organization.title,
|
|
229
|
-
governanceAuthority: GAP + "<support.governanceAuthority>",
|
|
230
|
-
pathways: GAP + "<support.pathways>",
|
|
231
|
-
dataAndSemantics: {
|
|
232
|
-
description: GAP + "<support.dataAndSemantics.description>",
|
|
233
|
-
channel: GAP + "<support.dataAndSemantics.channel>",
|
|
234
|
-
responseTime: GAP + "<support.dataAndSemantics.responseTime>",
|
|
235
|
-
escalation: GAP + "<support.dataAndSemantics.escalation>"
|
|
236
|
-
},
|
|
237
|
-
accessAndIntegration: {
|
|
238
|
-
description: GAP + "<support.accessAndIntegration.description>",
|
|
239
|
-
channel: GAP + "<support.accessAndIntegration.channel>",
|
|
240
|
-
responseTime: GAP + "<support.accessAndIntegration.responseTime>",
|
|
241
|
-
escalation: GAP + "<support.accessAndIntegration.escalation>"
|
|
242
|
-
},
|
|
243
|
-
governanceAndProductionEscalation: {
|
|
244
|
-
description: GAP + "<support.governanceAndProductionEscalation.description>",
|
|
245
|
-
channel: GAP + "<support.governanceAndProductionEscalation.channel>",
|
|
246
|
-
referenceDataset: GAP + "<support.governanceAndProductionEscalation.referenceDataset>",
|
|
247
|
-
responseTime: GAP + "<support.governanceAndProductionEscalation.responseTime>"
|
|
248
|
-
}
|
|
249
|
-
},
|
|
250
|
-
relatedResources: relatedResources.length > 0 ? relatedResources : void 0
|
|
251
|
-
},
|
|
252
|
-
metadata: {
|
|
253
|
-
name: safeName,
|
|
254
|
-
title: pkg.title || pkg.name,
|
|
255
|
-
description: pkg.notes || "No description available",
|
|
256
|
-
annotations: {
|
|
257
|
-
"backstage.io/managed-by-location": MANAGED_BY_LOCATION,
|
|
258
|
-
"backstage.io/managed-by-origin-location": MANAGED_BY_LOCATION,
|
|
259
|
-
"bcdata.gov.bc.ca/package-author": pkg.author || "Unknown",
|
|
260
|
-
"bcdata.gov.bc.ca/package-author_email": pkg.author_email || "Unknown",
|
|
261
|
-
"bcdata.gov.bc.ca/package-creator_user_id": pkg.creator_user_id,
|
|
262
|
-
"bcdata.gov.bc.ca/package-download_audience": pkg.download_audience,
|
|
263
|
-
"bcdata.gov.bc.ca/package-id": pkg.id,
|
|
264
|
-
"bcdata.gov.bc.ca/package-isopen": `${pkg.isopen}`,
|
|
265
|
-
"bcdata.gov.bc.ca/package-license_id": pkg.license_id,
|
|
266
|
-
"bcdata.gov.bc.ca/package-license_title": pkg.license_title || "Unknown",
|
|
267
|
-
"bcdata.gov.bc.ca/package-license_url": pkg.license_url,
|
|
268
|
-
"bcdata.gov.bc.ca/package-maintainer": pkg.maintainer || "Unknown",
|
|
269
|
-
"bcdata.gov.bc.ca/package-maintainer_email": pkg.maintainer_email || "Unknown",
|
|
270
|
-
"bcdata.gov.bc.ca/package-metadata_created": pkg.metadata_created,
|
|
271
|
-
"bcdata.gov.bc.ca/package-metadata_modified": pkg.metadata_modified,
|
|
272
|
-
"bcdata.gov.bc.ca/package-metadata_visibility": pkg.metadata_visibility,
|
|
273
|
-
"bcdata.gov.bc.ca/package-name": pkg.name,
|
|
274
|
-
"bcdata.gov.bc.ca/package-notes": pkg.notes || "Unknown",
|
|
275
|
-
"bcdata.gov.bc.ca/package-owner_org": pkg.owner_org,
|
|
276
|
-
"bcdata.gov.bc.ca/package-private": `${pkg.private}`,
|
|
277
|
-
"bcdata.gov.bc.ca/package-publish_state": pkg.publish_state,
|
|
278
|
-
"bcdata.gov.bc.ca/package-record_create_date": pkg.record_create_date || "Unknown",
|
|
279
|
-
"bcdata.gov.bc.ca/package-record_last_modified": pkg.record_last_modified,
|
|
280
|
-
"bcdata.gov.bc.ca/package-record_publish_date": pkg.record_publish_date,
|
|
281
|
-
"bcdata.gov.bc.ca/package-resource_status": pkg.resource_status,
|
|
282
|
-
"bcdata.gov.bc.ca/package-security_class": pkg.security_class,
|
|
283
|
-
"bcdata.gov.bc.ca/package-state": pkg.state,
|
|
284
|
-
"bcdata.gov.bc.ca/package-title": pkg.title || "Unknown",
|
|
285
|
-
"bcdata.gov.bc.ca/package-type": pkg.type,
|
|
286
|
-
"bcdata.gov.bc.ca/package-url": pkg.url || "Unknown",
|
|
287
|
-
"bcdata.gov.bc.ca/package-version": pkg.version || "Unknown",
|
|
288
|
-
"bcdata.gov.bc.ca/package-view_audience": pkg.view_audience
|
|
289
|
-
},
|
|
290
|
-
links: learnMoreLinks.length > 0 ? learnMoreLinks : void 0,
|
|
291
|
-
tags
|
|
292
|
-
}
|
|
293
|
-
};
|
|
294
148
|
allDatasets.set(this.naming.getComponentId(safeName), datasetEntity);
|
|
295
149
|
let hasOpenApi = false;
|
|
296
|
-
for (const
|
|
150
|
+
for (const candidate of apiResources) {
|
|
151
|
+
const { apiResource, definitionUrl, definitionHost, isOpenApiCandidate } = candidate;
|
|
297
152
|
const name = apiResource.name;
|
|
298
|
-
|
|
299
|
-
if (apiResource.bcdc_type === "webservice" || apiResource.format === "arcgis_rest") {
|
|
300
|
-
const prefix = apiResource.format === "openapi-json" ? "api" : apiResource.format;
|
|
301
|
-
const formatSafeName = this.naming.toSafeName(prefix);
|
|
302
|
-
const baseName = this.naming.toSafeName(`${formatSafeName}-${safeName}`);
|
|
303
|
-
let candidateName = baseName;
|
|
304
|
-
let candidateApiId = this.naming.getApiId(candidateName);
|
|
305
|
-
if (allApis.has(candidateApiId)) {
|
|
306
|
-
const distinguishingSuffix = this.naming.extractDistinguishingSuffix(name);
|
|
307
|
-
const suffixWithSeparator = `-${distinguishingSuffix}`;
|
|
308
|
-
const maxBaseLength = 63 - suffixWithSeparator.length;
|
|
309
|
-
let truncatedBase = baseName;
|
|
310
|
-
if (baseName.length > maxBaseLength) {
|
|
311
|
-
truncatedBase = baseName.slice(0, maxBaseLength).replace(/[-_.]+$/, "");
|
|
312
|
-
}
|
|
313
|
-
candidateName = this.naming.toSafeName(
|
|
314
|
-
`${truncatedBase}${suffixWithSeparator}`
|
|
315
|
-
);
|
|
316
|
-
candidateApiId = this.naming.getApiId(candidateName);
|
|
317
|
-
let counter = 1;
|
|
318
|
-
while (allApis.has(candidateApiId)) {
|
|
319
|
-
const counterSuffix = `-${counter}`;
|
|
320
|
-
const maxBaseWithCounter = 63 - suffixWithSeparator.length - counterSuffix.length;
|
|
321
|
-
let truncatedBaseForCounter = baseName;
|
|
322
|
-
if (baseName.length > maxBaseWithCounter) {
|
|
323
|
-
truncatedBaseForCounter = baseName.slice(0, maxBaseWithCounter).replace(/[-_.]+$/, "");
|
|
324
|
-
}
|
|
325
|
-
candidateName = this.naming.toSafeName(
|
|
326
|
-
`${truncatedBaseForCounter}${suffixWithSeparator}${counterSuffix}`
|
|
327
|
-
);
|
|
328
|
-
candidateApiId = this.naming.getApiId(candidateName);
|
|
329
|
-
counter++;
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
apiSafeName = candidateName;
|
|
333
|
-
}
|
|
334
|
-
const definition = apiResource.url;
|
|
335
|
-
const url = new URL(definition);
|
|
336
|
-
const host = url.host.toLowerCase();
|
|
337
|
-
if (!this.allowedHosts.includes(host)) {
|
|
153
|
+
if (definitionHost && !this.allowedHosts.includes(definitionHost)) {
|
|
338
154
|
this.logger.warn(
|
|
339
|
-
`[BCDC Entity Factory] API definition host is NOT allowed: "${
|
|
155
|
+
`[BCDC Entity Factory] API definition host is NOT allowed: "${definitionHost}"`
|
|
340
156
|
);
|
|
341
157
|
}
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
}
|
|
350
|
-
|
|
158
|
+
let definitionContent = definitionUrl;
|
|
159
|
+
let isOpenApiResource = false;
|
|
160
|
+
if (isOpenApiCandidate) {
|
|
161
|
+
const fetchedDefinition = await this.tryReadDefinition(definitionUrl);
|
|
162
|
+
if (fetchedDefinition !== void 0) {
|
|
163
|
+
definitionContent = fetchedDefinition;
|
|
164
|
+
isOpenApiResource = await pluginCatalogCommonBcDataCatalogue.parseOpenApiDocument(fetchedDefinition) !== void 0;
|
|
165
|
+
}
|
|
166
|
+
if (isOpenApiResource) {
|
|
167
|
+
hasOpenApi = true;
|
|
168
|
+
}
|
|
351
169
|
}
|
|
352
|
-
|
|
353
|
-
if (
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
const
|
|
358
|
-
definitionContent = content;
|
|
359
|
-
} catch (error) {
|
|
170
|
+
const bcdcDatasetResourceUrl = `${bcdcDatasetUrl}/resource/${apiResource.id}`;
|
|
171
|
+
if (isOpenApiResource) {
|
|
172
|
+
const normalizedDefinitionUrl = this.normalizeOpenApiDefinitionUrl(definitionUrl);
|
|
173
|
+
const existingOpenApiId = openApiDefinitionToId.get(normalizedDefinitionUrl);
|
|
174
|
+
if (existingOpenApiId) {
|
|
175
|
+
const existingOpenApi = allOpenApis.get(existingOpenApiId);
|
|
360
176
|
this.logger.warn(
|
|
361
|
-
`[BCDC Entity Factory]
|
|
177
|
+
`[BCDC Entity Factory] Duplicate OpenApi definition detected for "${normalizedDefinitionUrl}". Existing OpenApi: name="${existingOpenApi.metadata.name}", owner="${existingOpenApi.spec.owner}", system="${existingOpenApi.spec.system ?? ""}", bcdc_type="${existingOpenApi.metadata.annotations?.["bcdata.gov.bc.ca/resource-bcdc_type"]}", format="${existingOpenApi.metadata.annotations?.["bcdata.gov.bc.ca/resource-format"]}", resource-url="${existingOpenApi.metadata.annotations?.["bcdata.gov.bc.ca/resource-url"]}", openapi-url="${normalizedDefinitionUrl}". New OpenApi resource: name="${apiResource.name}", owner="${ownerGroupId}", system="${systemId}", bcdc_type="${apiResource.bcdc_type}", format="${apiResource.format}", resource-url="${apiResource.url}", openapi-url="${normalizedDefinitionUrl}". Reusing existing OpenApi entity.`
|
|
362
178
|
);
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
}
|
|
366
|
-
const apiEntity = {
|
|
367
|
-
apiVersion: "backstage.io/v1alpha1",
|
|
368
|
-
kind: "API",
|
|
369
|
-
spec: {
|
|
370
|
-
type: apiResource.format === "openapi-json" ? "openapi" : apiResource.bcdc_type,
|
|
371
|
-
lifecycle: "production",
|
|
372
|
-
owner: ownerGroupId,
|
|
373
|
-
definition: definitionContent,
|
|
374
|
-
system: systemId
|
|
375
|
-
},
|
|
376
|
-
metadata: {
|
|
377
|
-
name: apiSafeName,
|
|
378
|
-
description: apiResource.description || "No description available",
|
|
379
|
-
links: apiEntityLinks,
|
|
380
|
-
tags: [this.naming.toSafeName(apiResource.format)],
|
|
381
|
-
annotations: {
|
|
382
|
-
"backstage.io/managed-by-location": MANAGED_BY_LOCATION,
|
|
383
|
-
"backstage.io/managed-by-origin-location": MANAGED_BY_LOCATION,
|
|
384
|
-
"bcdata.gov.bc.ca/resource-bcdc_type": apiResource.bcdc_type,
|
|
385
|
-
"bcdata.gov.bc.ca/resource-cache_last_updated": apiResource.cache_last_updated || "Undefined",
|
|
386
|
-
"bcdata.gov.bc.ca/resource-cache_url": apiResource.cache_url || "Undefined",
|
|
387
|
-
"bcdata.gov.bc.ca/resource-created": apiResource.created,
|
|
388
|
-
"bcdata.gov.bc.ca/resource-datastore_active": `${apiResource.datastore_active}`,
|
|
389
|
-
"bcdata.gov.bc.ca/resource-description": apiResource.description || "Undefined",
|
|
390
|
-
"bcdata.gov.bc.ca/resource-format": apiResource.format,
|
|
391
|
-
"bcdata.gov.bc.ca/resource-hash": apiResource.hash,
|
|
392
|
-
"bcdata.gov.bc.ca/resource-id": apiResource.id,
|
|
393
|
-
"bcdata.gov.bc.ca/resource-metadata_modified": apiResource.metadata_modified,
|
|
394
|
-
"bcdata.gov.bc.ca/resource-mimetype": apiResource.mimetype || "Undefined",
|
|
395
|
-
"bcdata.gov.bc.ca/resource-name": apiResource.name,
|
|
396
|
-
"bcdata.gov.bc.ca/resource-package_id": apiResource.package_id,
|
|
397
|
-
"bcdata.gov.bc.ca/resource-position": `${apiResource.position}`,
|
|
398
|
-
"bcdata.gov.bc.ca/resource-projection_name": apiResource.projection_name || "Undefined",
|
|
399
|
-
"bcdata.gov.bc.ca/resource-resource_access_method": apiResource.resource_access_method,
|
|
400
|
-
"bcdata.gov.bc.ca/resource-resource_storage_location": apiResource.resource_storage_location,
|
|
401
|
-
"bcdata.gov.bc.ca/resource-resource_type": apiResource.resource_type,
|
|
402
|
-
"bcdata.gov.bc.ca/resource-resource_update_cycle": apiResource.resource_update_cycle,
|
|
403
|
-
"bcdata.gov.bc.ca/resource-size": `${apiResource.size}`,
|
|
404
|
-
"bcdata.gov.bc.ca/resource-spatial_datatype": apiResource.spatial_datatype || "Undefined",
|
|
405
|
-
"bcdata.gov.bc.ca/resource-state": apiResource.state,
|
|
406
|
-
"bcdata.gov.bc.ca/resource-url": apiResource.url,
|
|
407
|
-
"bcdata.gov.bc.ca/resource-url_type": apiResource.url_type || "Undefined"
|
|
179
|
+
if (!datasetEntity.spec.providesApis?.includes(existingOpenApiId)) {
|
|
180
|
+
datasetEntity.spec.providesApis?.push(existingOpenApiId);
|
|
408
181
|
}
|
|
182
|
+
continue;
|
|
409
183
|
}
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
this.logger.warn(
|
|
415
|
-
`[BCDC Entity Factory] Duplicate API name detected: "${apiSafeName}" (ID: ${apiId}). Existing API: name="${existingApi.metadata.name}", Format: "${existingApi.metadata.annotations?.["bcdata.gov.bc.ca/resource-format"]}", resource-id="${existingApi.metadata.annotations?.["bcdata.gov.bc.ca/resource-id"]}", package-id="${existingApi.metadata.annotations?.["bcdata.gov.bc.ca/resource-package_id"]}", url="${existingApi.metadata.annotations?.["bcdata.gov.bc.ca/resource-url"]}". New API: name="${apiEntity.metadata.name}", resource-id="${apiResource.id}", package-id="${apiResource.package_id}", url="${apiResource.url}". The new API will overwrite the existing one.`
|
|
184
|
+
const openApiSafeName = this.buildOpenApiSafeName(
|
|
185
|
+
name,
|
|
186
|
+
definitionHost,
|
|
187
|
+
allOpenApis
|
|
416
188
|
);
|
|
189
|
+
const openApiId = this.naming.getOpenApiId(openApiSafeName);
|
|
190
|
+
const openApiEntity = await this.openApiEntityBuilder.build({
|
|
191
|
+
pkg,
|
|
192
|
+
apiResource,
|
|
193
|
+
ownerGroupId,
|
|
194
|
+
systemId,
|
|
195
|
+
openApiSafeName,
|
|
196
|
+
datasetEntityRef: this.naming.getDatasetId(safeName),
|
|
197
|
+
definitionUrl,
|
|
198
|
+
definition: definitionContent,
|
|
199
|
+
bcdcDatasetResourceUrl
|
|
200
|
+
});
|
|
201
|
+
allOpenApis.set(openApiId, openApiEntity);
|
|
202
|
+
openApiDefinitionToId.set(normalizedDefinitionUrl, openApiId);
|
|
203
|
+
datasetEntity.spec.providesApis?.push(openApiId);
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
const apiSafeName = this.buildApiSafeName(name, definitionHost);
|
|
207
|
+
const apiId = this.naming.getApiId(apiSafeName);
|
|
208
|
+
const existingApi = allApis.get(apiId);
|
|
209
|
+
const expectedApiType = apiResource.bcdc_type;
|
|
210
|
+
if (existingApi) {
|
|
211
|
+
if (existingApi.spec.type !== expectedApiType || existingApi.spec.owner !== ownerGroupId || existingApi.spec.system !== systemId) {
|
|
212
|
+
this.logger.warn(
|
|
213
|
+
`[BCDC Entity Factory] Duplicate API identity detected for "${apiSafeName}" (ID: ${apiId}). Existing API: type="${existingApi.spec.type}", owner="${existingApi.spec.owner}", system="${existingApi.spec.system ?? ""}", bcdc_type="${existingApi.metadata.annotations?.["bcdata.gov.bc.ca/resource-bcdc_type"]}", format="${existingApi.metadata.annotations?.["bcdata.gov.bc.ca/resource-format"]}", resource-id="${existingApi.metadata.annotations?.["bcdata.gov.bc.ca/resource-id"]}", package-id="${existingApi.metadata.annotations?.["bcdata.gov.bc.ca/resource-package_id"]}", url="${existingApi.metadata.annotations?.["bcdata.gov.bc.ca/resource-url"]}". New API: type="${expectedApiType}", owner="${ownerGroupId}", system="${systemId}", bcdc_type="${apiResource.bcdc_type}", format="${apiResource.format}", resource-id="${apiResource.id}", package-id="${apiResource.package_id}", url="${apiResource.url}". Reusing existing API entity.`
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
if (!datasetEntity.spec.providesApis?.includes(apiId)) {
|
|
217
|
+
datasetEntity.spec.providesApis?.push(apiId);
|
|
218
|
+
}
|
|
219
|
+
continue;
|
|
417
220
|
}
|
|
221
|
+
const apiEntity = this.apiEntityBuilder.build({
|
|
222
|
+
apiResource,
|
|
223
|
+
ownerGroupId,
|
|
224
|
+
systemId,
|
|
225
|
+
apiSafeName,
|
|
226
|
+
definition: definitionContent,
|
|
227
|
+
apiType: expectedApiType,
|
|
228
|
+
bcdcDatasetResourceUrl
|
|
229
|
+
});
|
|
418
230
|
allApis.set(apiId, apiEntity);
|
|
419
231
|
datasetEntity.spec.providesApis?.push(apiId);
|
|
420
232
|
}
|
|
421
233
|
if (datasetEntity.spec.providesApis?.length) {
|
|
422
|
-
if (!tags
|
|
423
|
-
tags
|
|
234
|
+
if (!datasetEntity.metadata.tags?.includes("has-api")) {
|
|
235
|
+
datasetEntity.metadata.tags = [
|
|
236
|
+
...datasetEntity.metadata.tags ?? [],
|
|
237
|
+
"has-api"
|
|
238
|
+
];
|
|
424
239
|
}
|
|
425
|
-
if (hasOpenApi && !tags
|
|
426
|
-
tags
|
|
240
|
+
if (hasOpenApi && !datasetEntity.metadata.tags?.includes("has-openapi")) {
|
|
241
|
+
datasetEntity.metadata.tags = [
|
|
242
|
+
...datasetEntity.metadata.tags ?? [],
|
|
243
|
+
"has-openapi"
|
|
244
|
+
];
|
|
427
245
|
}
|
|
428
246
|
}
|
|
429
247
|
}
|
|
@@ -432,78 +250,132 @@ class BcDataCatalogueEntityFactory {
|
|
|
432
250
|
const systemEntities = Array.from(allSystems.values());
|
|
433
251
|
const datasetEntities = Array.from(allDatasets.values());
|
|
434
252
|
const apiEntities = Array.from(allApis.values());
|
|
253
|
+
const openApiEntities = Array.from(allOpenApis.values());
|
|
435
254
|
allEntities.push(...userEntities);
|
|
436
255
|
allEntities.push(...groupEntities);
|
|
437
256
|
allEntities.push(...systemEntities);
|
|
438
257
|
allEntities.push(...datasetEntities);
|
|
439
258
|
allEntities.push(...apiEntities);
|
|
440
|
-
|
|
259
|
+
allEntities.push(...openApiEntities);
|
|
260
|
+
this.logger.info(
|
|
261
|
+
`[BCDC Entity Factory] >createEntities ${allEntities.length}`
|
|
262
|
+
);
|
|
441
263
|
return allEntities;
|
|
442
264
|
}
|
|
443
|
-
|
|
444
|
-
|
|
265
|
+
buildOpenApiSafeName(resourceName, host, allOpenApis) {
|
|
266
|
+
const baseName = host ? this.naming.toSafeName(`${resourceName}-${host}`) : this.naming.toSafeName(resourceName);
|
|
267
|
+
let candidateName = baseName;
|
|
268
|
+
let candidateOpenApiId = this.naming.getOpenApiId(candidateName);
|
|
269
|
+
if (!allOpenApis.has(candidateOpenApiId)) {
|
|
270
|
+
return candidateName;
|
|
271
|
+
}
|
|
272
|
+
const distinguishingSuffix = host && host.length > 0 ? this.naming.toSafeName(host) : this.naming.extractDistinguishingSuffix(resourceName);
|
|
273
|
+
const suffixWithSeparator = `-${distinguishingSuffix}`;
|
|
274
|
+
const maxBaseLength = 63 - suffixWithSeparator.length;
|
|
275
|
+
let truncatedBase = baseName;
|
|
276
|
+
if (baseName.length > maxBaseLength) {
|
|
277
|
+
truncatedBase = baseName.slice(0, maxBaseLength).replace(/[-_.]+$/, "");
|
|
278
|
+
}
|
|
279
|
+
candidateName = this.naming.toSafeName(
|
|
280
|
+
`${truncatedBase}${suffixWithSeparator}`
|
|
281
|
+
);
|
|
282
|
+
candidateOpenApiId = this.naming.getOpenApiId(candidateName);
|
|
283
|
+
let counter = 1;
|
|
284
|
+
while (allOpenApis.has(candidateOpenApiId)) {
|
|
285
|
+
const counterSuffix = `-${counter}`;
|
|
286
|
+
const maxBaseWithCounter = 63 - suffixWithSeparator.length - counterSuffix.length;
|
|
287
|
+
let truncatedBaseForCounter = baseName;
|
|
288
|
+
if (baseName.length > maxBaseWithCounter) {
|
|
289
|
+
truncatedBaseForCounter = baseName.slice(0, maxBaseWithCounter).replace(/[-_.]+$/, "");
|
|
290
|
+
}
|
|
291
|
+
candidateName = this.naming.toSafeName(
|
|
292
|
+
`${truncatedBaseForCounter}${suffixWithSeparator}${counterSuffix}`
|
|
293
|
+
);
|
|
294
|
+
candidateOpenApiId = this.naming.getOpenApiId(candidateName);
|
|
295
|
+
counter++;
|
|
296
|
+
}
|
|
297
|
+
return candidateName;
|
|
298
|
+
}
|
|
299
|
+
buildApiSafeName(resourceName, host) {
|
|
300
|
+
return host ? this.naming.toSafeName(`${resourceName}-${host}`) : this.naming.toSafeName(resourceName);
|
|
445
301
|
}
|
|
446
|
-
|
|
302
|
+
getApiResourceCandidate(resource) {
|
|
303
|
+
const definitionUrl = this.getDefinitionUrl(resource.url);
|
|
304
|
+
if (!definitionUrl) {
|
|
305
|
+
return void 0;
|
|
306
|
+
}
|
|
307
|
+
const definitionHost = this.getUrlHost(definitionUrl);
|
|
308
|
+
const isOpenApiCandidate = resource.format === "openapi-json" || resource.bcdc_type === "webservice" && ["json", "xml", "html"].includes(resource.format) && this.looksLikeOpenApiUrl(resource.url, definitionUrl);
|
|
309
|
+
if (isOpenApiCandidate) {
|
|
310
|
+
return {
|
|
311
|
+
apiResource: resource,
|
|
312
|
+
definitionUrl,
|
|
313
|
+
definitionHost,
|
|
314
|
+
isOpenApiCandidate: true
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
if (resource.bcdc_type !== "webservice") {
|
|
318
|
+
return void 0;
|
|
319
|
+
}
|
|
320
|
+
if (["kml", "wms", "arcgis_rest", "xml"].includes(resource.format)) {
|
|
321
|
+
return void 0;
|
|
322
|
+
}
|
|
447
323
|
return {
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
type: resource.bcdc_type,
|
|
453
|
-
format: resource.format,
|
|
454
|
-
updateFrequency: resource.resource_update_cycle
|
|
324
|
+
apiResource: resource,
|
|
325
|
+
definitionUrl,
|
|
326
|
+
definitionHost,
|
|
327
|
+
isOpenApiCandidate: false
|
|
455
328
|
};
|
|
456
329
|
}
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
330
|
+
getDefinitionUrl(resourceUrl) {
|
|
331
|
+
const trimmedUrl = resourceUrl?.trim();
|
|
332
|
+
if (!trimmedUrl) {
|
|
333
|
+
return void 0;
|
|
334
|
+
}
|
|
335
|
+
try {
|
|
336
|
+
const parsedUrl = new URL(trimmedUrl);
|
|
337
|
+
const nestedUrl = parsedUrl.searchParams.get("url")?.trim();
|
|
338
|
+
if (nestedUrl) {
|
|
339
|
+
return nestedUrl;
|
|
340
|
+
}
|
|
341
|
+
return trimmedUrl;
|
|
342
|
+
} catch {
|
|
343
|
+
return void 0;
|
|
465
344
|
}
|
|
466
345
|
}
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
346
|
+
getUrlHost(url) {
|
|
347
|
+
try {
|
|
348
|
+
return new URL(url).host.toLowerCase();
|
|
349
|
+
} catch {
|
|
350
|
+
return void 0;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
normalizeOpenApiDefinitionUrl(url) {
|
|
354
|
+
return url.trim().toLowerCase();
|
|
355
|
+
}
|
|
356
|
+
looksLikeOpenApiUrl(resourceUrl, definitionUrl) {
|
|
357
|
+
const raw = resourceUrl.toLowerCase();
|
|
358
|
+
const normalized = definitionUrl.toLowerCase();
|
|
359
|
+
if (raw.includes("oas-editor.apps.gov.bc.ca/?url=")) {
|
|
360
|
+
return true;
|
|
361
|
+
}
|
|
362
|
+
return normalized.includes("openapi") || normalized.includes("swagger") || normalized.includes("/api-specs/");
|
|
363
|
+
}
|
|
364
|
+
async tryReadDefinition(url) {
|
|
365
|
+
try {
|
|
366
|
+
const response = await this.reader.readUrl(url);
|
|
367
|
+
return (await response.buffer()).toString();
|
|
368
|
+
} catch (error) {
|
|
369
|
+
this.logger.warn(
|
|
370
|
+
`[BCDC Entity Factory] Failed to fetch API definition from ${url}: ${error}`
|
|
371
|
+
);
|
|
372
|
+
return void 0;
|
|
479
373
|
}
|
|
480
374
|
}
|
|
481
375
|
getEmailHostname(email) {
|
|
482
376
|
const match = email.trim().toLowerCase().match(/@([\w.-]+)/);
|
|
483
377
|
return match ? match[1] : void 0;
|
|
484
378
|
}
|
|
485
|
-
createGroupEntity(hostName, displayName, parentGroup) {
|
|
486
|
-
return {
|
|
487
|
-
apiVersion: "backstage.io/v1alpha1",
|
|
488
|
-
kind: "Group",
|
|
489
|
-
metadata: {
|
|
490
|
-
name: this.naming.toSafeName(hostName),
|
|
491
|
-
annotations: {
|
|
492
|
-
"backstage.io/managed-by-location": MANAGED_BY_LOCATION,
|
|
493
|
-
"backstage.io/managed-by-origin-location": MANAGED_BY_LOCATION
|
|
494
|
-
}
|
|
495
|
-
},
|
|
496
|
-
spec: {
|
|
497
|
-
type: "government",
|
|
498
|
-
profile: {
|
|
499
|
-
displayName
|
|
500
|
-
},
|
|
501
|
-
parent: parentGroup,
|
|
502
|
-
children: [],
|
|
503
|
-
members: []
|
|
504
|
-
}
|
|
505
|
-
};
|
|
506
|
-
}
|
|
507
379
|
}
|
|
508
380
|
|
|
509
381
|
exports.BcDataCatalogueEntityFactory = BcDataCatalogueEntityFactory;
|