@friggframework/core 2.0.0-next.97 → 2.0.0-next.98
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/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@friggframework/core",
|
|
3
3
|
"prettier": "@friggframework/prettier-config",
|
|
4
|
-
"version": "2.0.0-next.
|
|
4
|
+
"version": "2.0.0-next.98",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"@aws-sdk/client-apigatewaymanagementapi": "^3.588.0",
|
|
7
7
|
"@aws-sdk/client-kms": "^3.588.0",
|
|
@@ -38,9 +38,9 @@
|
|
|
38
38
|
}
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
|
-
"@friggframework/eslint-config": "2.0.0-next.
|
|
42
|
-
"@friggframework/prettier-config": "2.0.0-next.
|
|
43
|
-
"@friggframework/test": "2.0.0-next.
|
|
41
|
+
"@friggframework/eslint-config": "2.0.0-next.98",
|
|
42
|
+
"@friggframework/prettier-config": "2.0.0-next.98",
|
|
43
|
+
"@friggframework/test": "2.0.0-next.98",
|
|
44
44
|
"@prisma/client": "^6.19.3",
|
|
45
45
|
"@types/lodash": "4.17.15",
|
|
46
46
|
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
|
@@ -80,5 +80,5 @@
|
|
|
80
80
|
"publishConfig": {
|
|
81
81
|
"access": "public"
|
|
82
82
|
},
|
|
83
|
-
"gitHead": "
|
|
83
|
+
"gitHead": "da2c1977216b9f939121b18d8461cc4e8a5b2b94"
|
|
84
84
|
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Reporting
|
|
2
|
+
|
|
3
|
+
Read-only, deployment-wide reporting endpoints for `@friggframework/core`.
|
|
4
|
+
Gated by a dedicated reporting API key — **not** the per-user auth used by the
|
|
5
|
+
rest of the Management API.
|
|
6
|
+
|
|
7
|
+
## Auth
|
|
8
|
+
|
|
9
|
+
Send the reporting key in the `x-frigg-reporting-api-key` header. It is validated
|
|
10
|
+
against the `REPORTING_API_KEY` environment variable. Missing or wrong key → `401`.
|
|
11
|
+
|
|
12
|
+
## Endpoints
|
|
13
|
+
|
|
14
|
+
| Method | Path | Description |
|
|
15
|
+
| --- | --- | --- |
|
|
16
|
+
| `GET` | `/api/v2/reports` | Index of available reports. |
|
|
17
|
+
| `GET` | `/api/v2/reports/integrations` | Integrations report (see below). |
|
|
18
|
+
|
|
19
|
+
### `GET /api/v2/reports/integrations`
|
|
20
|
+
|
|
21
|
+
Optional query params (all strings): `status` (an `IntegrationStatus`), `type`
|
|
22
|
+
(`config.type` slug), `userId`.
|
|
23
|
+
|
|
24
|
+
## Response shape
|
|
25
|
+
|
|
26
|
+
```jsonc
|
|
27
|
+
{
|
|
28
|
+
"schemaVersion": 1,
|
|
29
|
+
"service": "frigg-core-api",
|
|
30
|
+
"generatedAt": "2026-06-29T20:45:05.142Z",
|
|
31
|
+
"filters": { "status": null, "type": null, "userId": null },
|
|
32
|
+
"metrics": {
|
|
33
|
+
"total": 12,
|
|
34
|
+
"byStatus": { "ENABLED": 9, "ERROR": 2, "NEEDS_CONFIG": 1, "PROCESSING": 0, "DISABLED": 0 },
|
|
35
|
+
"byType": [
|
|
36
|
+
{
|
|
37
|
+
"type": "hubspot",
|
|
38
|
+
"label": "HubSpot CRM",
|
|
39
|
+
"total": 5,
|
|
40
|
+
"byStatus": { "ENABLED": 4, "ERROR": 1, "NEEDS_CONFIG": 0, "PROCESSING": 0, "DISABLED": 0 }
|
|
41
|
+
}
|
|
42
|
+
],
|
|
43
|
+
"typeLabels": { "hubspot": "HubSpot CRM" },
|
|
44
|
+
"integrations": [ /* lightweight per-integration rows */ ]
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Field reference
|
|
50
|
+
|
|
51
|
+
- `schemaVersion` — contract version; branch on it. Additive changes do **not** bump it.
|
|
52
|
+
- `filters` — echoes the applied filters (nulls when omitted).
|
|
53
|
+
- `metrics.total` — count of integrations matching the filters.
|
|
54
|
+
- `metrics.byStatus` — counts keyed by `IntegrationStatus` value.
|
|
55
|
+
- `metrics.byType[]` — per `type` breakdown: `{ type, label, total, byStatus }`.
|
|
56
|
+
- `type` — the `config.type` slug. Integrations with no type bucket as `"unknown"`.
|
|
57
|
+
- `label` — human-readable name from the integration class's
|
|
58
|
+
`Definition.display.label`. Falls back to the `type` slug when no registered
|
|
59
|
+
class supplies a label (e.g. an integration that was removed, or run on an
|
|
60
|
+
older app that doesn't register it). Additive — `type` is unchanged.
|
|
61
|
+
- `metrics.typeLabels` — map of `type` slug → human-readable label, so callers can
|
|
62
|
+
label `integrations[].type` rows without bloating each row. Contains only types
|
|
63
|
+
whose registered class supplies a non-default `display.label` (classes still
|
|
64
|
+
carrying the IntegrationBase default `'Integration Name'` are excluded).
|
|
65
|
+
- `metrics.integrations[]` — lightweight per-integration rows (`id`, `type`,
|
|
66
|
+
`status`, `userId`, `version`, `moduleCount`, `errorCount`, `mappedRecordCount`,
|
|
67
|
+
`createdAt`, `updatedAt`). These rows carry `type` only — resolve display names
|
|
68
|
+
via `metrics.typeLabels`.
|
|
69
|
+
|
|
70
|
+
## How labels are sourced
|
|
71
|
+
|
|
72
|
+
`createReportingRouter()` loads the app's registered integration classes via
|
|
73
|
+
`loadAppDefinition()` and builds a `{ slug → label }` map from each class's
|
|
74
|
+
`Definition.display.label`. Loading is wrapped in try/catch, so reporting still
|
|
75
|
+
works (labels fall back to slugs) if the app definition can't be loaded. The
|
|
76
|
+
`ListIntegrationsReport` use case stays storage-agnostic — it just reads the
|
|
77
|
+
injected `typeLabels` map, so all database adapters (PostgreSQL, MongoDB,
|
|
78
|
+
DocumentDB) get labels with zero adapter changes.
|
|
79
|
+
|
|
80
|
+
## Caveats
|
|
81
|
+
|
|
82
|
+
- The response is sensitive (exposes integration types, counts, user IDs, versions).
|
|
83
|
+
Treat the reporting key as an admin secret.
|
|
84
|
+
- Read-only — no mutation endpoints.
|
|
85
|
+
- Labels only appear once the deployment runs a `@friggframework/core` version that
|
|
86
|
+
includes this field and the app registers the integration classes.
|
|
@@ -4,11 +4,40 @@ const {
|
|
|
4
4
|
createReportingRepository,
|
|
5
5
|
} = require('./repositories/reporting-repository-factory');
|
|
6
6
|
const { ListIntegrationsReport } = require('./use-cases/list-integrations-report');
|
|
7
|
+
const { loadAppDefinition } = require('../handlers/app-definition-loader');
|
|
8
|
+
|
|
9
|
+
// IntegrationBase.Definition default — skip it so the slug is used instead.
|
|
10
|
+
const PLACEHOLDER_DISPLAY_NAME = 'Integration Name';
|
|
11
|
+
|
|
12
|
+
// Map each integration's config.type slug to its human-readable display label.
|
|
13
|
+
// Wrapped so reporting still works if the app definition fails to load.
|
|
14
|
+
function buildTypeLabels() {
|
|
15
|
+
try {
|
|
16
|
+
const { integrations = [] } = loadAppDefinition();
|
|
17
|
+
const labels = {};
|
|
18
|
+
for (const IntegrationClass of integrations) {
|
|
19
|
+
const def = IntegrationClass?.Definition;
|
|
20
|
+
if (!def?.name) continue;
|
|
21
|
+
const label = def.display?.label;
|
|
22
|
+
if (label && label !== PLACEHOLDER_DISPLAY_NAME) {
|
|
23
|
+
labels[def.name] = label;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return labels;
|
|
27
|
+
} catch (error) {
|
|
28
|
+
console.error(
|
|
29
|
+
'Reporting: failed to load integration labels:',
|
|
30
|
+
error.message
|
|
31
|
+
);
|
|
32
|
+
return {};
|
|
33
|
+
}
|
|
34
|
+
}
|
|
7
35
|
|
|
8
36
|
function createReportingRouter() {
|
|
9
37
|
const reportingRepository = createReportingRepository();
|
|
10
38
|
const listIntegrationsReport = new ListIntegrationsReport({
|
|
11
39
|
reportingRepository,
|
|
40
|
+
typeLabels: buildTypeLabels(),
|
|
12
41
|
});
|
|
13
42
|
|
|
14
43
|
const router = express.Router();
|
|
@@ -14,11 +14,12 @@ const KNOWN_STATUSES = [
|
|
|
14
14
|
];
|
|
15
15
|
|
|
16
16
|
class ListIntegrationsReport {
|
|
17
|
-
constructor({ reportingRepository } = {}) {
|
|
17
|
+
constructor({ reportingRepository, typeLabels = {} } = {}) {
|
|
18
18
|
if (!reportingRepository) {
|
|
19
19
|
throw new Error('reportingRepository is required');
|
|
20
20
|
}
|
|
21
21
|
this.reportingRepository = reportingRepository;
|
|
22
|
+
this.typeLabels = typeLabels;
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
async execute(query = {}) {
|
|
@@ -64,6 +65,7 @@ class ListIntegrationsReport {
|
|
|
64
65
|
if (!byTypeMap.has(integration.type)) {
|
|
65
66
|
byTypeMap.set(integration.type, {
|
|
66
67
|
type: integration.type,
|
|
68
|
+
label: this.typeLabels[integration.type] || integration.type,
|
|
67
69
|
total: 0,
|
|
68
70
|
byStatus: emptyStatusCounts(),
|
|
69
71
|
});
|
|
@@ -86,6 +88,7 @@ class ListIntegrationsReport {
|
|
|
86
88
|
total: integrations.length,
|
|
87
89
|
byStatus,
|
|
88
90
|
byType: Array.from(byTypeMap.values()),
|
|
91
|
+
typeLabels: { ...this.typeLabels },
|
|
89
92
|
integrations,
|
|
90
93
|
},
|
|
91
94
|
};
|