@gridframe/client 1.0.0 → 1.0.1
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/index.d.ts +39 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +141 -0
- package/package.json +14 -6
- package/src/index.test.ts +0 -192
- package/src/index.ts +0 -250
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { type AddDashboardCardRequest, type CardLibraryResponse, type DashboardApiErrorCode, type DashboardBootstrapResponse, type DashboardCardMutationResponse, type DashboardDocument, type PanelCardDataResponse, type PanelCardDataWithSourceResponse, type RemoveDashboardCardRequest, type UpdateDashboardCardRequest, type UpdateDashboardLayoutRequest } from "@gridframe/core";
|
|
2
|
+
type RequestOptions = {
|
|
3
|
+
apiBaseUrl?: string;
|
|
4
|
+
signal?: AbortSignal;
|
|
5
|
+
};
|
|
6
|
+
type DashboardIdentity = RequestOptions & {
|
|
7
|
+
userId: string;
|
|
8
|
+
dashboardId: string;
|
|
9
|
+
};
|
|
10
|
+
type CardIdentity = DashboardIdentity & {
|
|
11
|
+
cardId: string;
|
|
12
|
+
};
|
|
13
|
+
declare class DashboardClientError extends Error {
|
|
14
|
+
readonly status: number;
|
|
15
|
+
readonly code?: DashboardApiErrorCode | "INVALID_RESPONSE";
|
|
16
|
+
constructor({ status, message, code, }: {
|
|
17
|
+
status: number;
|
|
18
|
+
message: string;
|
|
19
|
+
code?: DashboardApiErrorCode | "INVALID_RESPONSE";
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
declare function bootstrapDashboard(options: RequestOptions & {
|
|
23
|
+
userId: string;
|
|
24
|
+
dashboardId?: string;
|
|
25
|
+
}): Promise<DashboardBootstrapResponse>;
|
|
26
|
+
declare function fetchDashboardCardData(options: CardIdentity & {
|
|
27
|
+
includeSource: true;
|
|
28
|
+
}): Promise<PanelCardDataWithSourceResponse>;
|
|
29
|
+
declare function fetchDashboardCardData(options: CardIdentity & {
|
|
30
|
+
includeSource?: false;
|
|
31
|
+
}): Promise<PanelCardDataResponse>;
|
|
32
|
+
declare function updateDashboardLayout(options: DashboardIdentity & UpdateDashboardLayoutRequest): Promise<DashboardDocument>;
|
|
33
|
+
declare function updateDashboardCard(options: CardIdentity & UpdateDashboardCardRequest): Promise<DashboardDocument>;
|
|
34
|
+
declare function listCardLibrary(options: DashboardIdentity): Promise<CardLibraryResponse>;
|
|
35
|
+
declare function addDashboardCard(options: DashboardIdentity & AddDashboardCardRequest): Promise<DashboardCardMutationResponse>;
|
|
36
|
+
declare function removeDashboardCard(options: CardIdentity & RemoveDashboardCardRequest): Promise<DashboardCardMutationResponse>;
|
|
37
|
+
export { DashboardClientError, addDashboardCard, bootstrapDashboard, fetchDashboardCardData, listCardLibrary, removeDashboardCard, updateDashboardCard, updateDashboardLayout, };
|
|
38
|
+
export type { CardIdentity, DashboardIdentity, RequestOptions };
|
|
39
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAQL,KAAK,uBAAuB,EAC5B,KAAK,mBAAmB,EACxB,KAAK,qBAAqB,EAC1B,KAAK,0BAA0B,EAC/B,KAAK,6BAA6B,EAClC,KAAK,iBAAiB,EACtB,KAAK,qBAAqB,EAC1B,KAAK,+BAA+B,EACpC,KAAK,0BAA0B,EAC/B,KAAK,0BAA0B,EAC/B,KAAK,4BAA4B,EAClC,MAAM,iBAAiB,CAAC;AAIzB,KAAK,cAAc,GAAG;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,CAAC;AAEF,KAAK,iBAAiB,GAAG,cAAc,GAAG;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,KAAK,YAAY,GAAG,iBAAiB,GAAG;IACtC,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAMF,cAAM,oBAAqB,SAAQ,KAAK;IACtC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,CAAC,EAAE,qBAAqB,GAAG,kBAAkB,CAAC;gBAE/C,EACV,MAAM,EACN,OAAO,EACP,IAAI,GACL,EAAE;QACD,MAAM,EAAE,MAAM,CAAC;QACf,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,CAAC,EAAE,qBAAqB,GAAG,kBAAkB,CAAC;KACnD;CAMF;AAED,iBAAe,kBAAkB,CAC/B,OAAO,EAAE,cAAc,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,GACjE,OAAO,CAAC,0BAA0B,CAAC,CAMrC;AAED,iBAAS,sBAAsB,CAC7B,OAAO,EAAE,YAAY,GAAG;IAAE,aAAa,EAAE,IAAI,CAAA;CAAE,GAC9C,OAAO,CAAC,+BAA+B,CAAC,CAAC;AAC5C,iBAAS,sBAAsB,CAC7B,OAAO,EAAE,YAAY,GAAG;IAAE,aAAa,CAAC,EAAE,KAAK,CAAA;CAAE,GAChD,OAAO,CAAC,qBAAqB,CAAC,CAAC;AAmBlC,iBAAe,qBAAqB,CAClC,OAAO,EAAE,iBAAiB,GAAG,4BAA4B,GACxD,OAAO,CAAC,iBAAiB,CAAC,CAU5B;AAED,iBAAe,mBAAmB,CAChC,OAAO,EAAE,YAAY,GAAG,0BAA0B,GACjD,OAAO,CAAC,iBAAiB,CAAC,CAU5B;AAED,iBAAe,eAAe,CAC5B,OAAO,EAAE,iBAAiB,GACzB,OAAO,CAAC,mBAAmB,CAAC,CAU9B;AAED,iBAAe,gBAAgB,CAC7B,OAAO,EAAE,iBAAiB,GAAG,uBAAuB,GACnD,OAAO,CAAC,6BAA6B,CAAC,CAaxC;AAED,iBAAe,mBAAmB,CAChC,OAAO,EAAE,YAAY,GAAG,0BAA0B,GACjD,OAAO,CAAC,6BAA6B,CAAC,CAMxC;AA6ED,OAAO,EACL,oBAAoB,EACpB,gBAAgB,EAChB,kBAAkB,EAClB,sBAAsB,EACtB,eAAe,EACf,mBAAmB,EACnB,mBAAmB,EACnB,qBAAqB,GACtB,CAAC;AACF,YAAY,EAAE,YAAY,EAAE,iBAAiB,EAAE,cAAc,EAAE,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DashboardClientError = void 0;
|
|
4
|
+
exports.addDashboardCard = addDashboardCard;
|
|
5
|
+
exports.bootstrapDashboard = bootstrapDashboard;
|
|
6
|
+
exports.fetchDashboardCardData = fetchDashboardCardData;
|
|
7
|
+
exports.listCardLibrary = listCardLibrary;
|
|
8
|
+
exports.removeDashboardCard = removeDashboardCard;
|
|
9
|
+
exports.updateDashboardCard = updateDashboardCard;
|
|
10
|
+
exports.updateDashboardLayout = updateDashboardLayout;
|
|
11
|
+
const core_1 = require("@gridframe/core");
|
|
12
|
+
const DEFAULT_API_BASE_URL = "/api/gridframe";
|
|
13
|
+
class DashboardClientError extends Error {
|
|
14
|
+
status;
|
|
15
|
+
code;
|
|
16
|
+
constructor({ status, message, code, }) {
|
|
17
|
+
super(message);
|
|
18
|
+
this.name = "DashboardClientError";
|
|
19
|
+
this.status = status;
|
|
20
|
+
this.code = code;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
exports.DashboardClientError = DashboardClientError;
|
|
24
|
+
async function bootstrapDashboard(options) {
|
|
25
|
+
return requestJson({
|
|
26
|
+
url: `${userDashboardsUrl(options)}/bootstrap`,
|
|
27
|
+
init: jsonRequest("POST", { dashboardId: options.dashboardId }, options),
|
|
28
|
+
schema: core_1.DashboardBootstrapResponseSchema,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
async function fetchDashboardCardData(options) {
|
|
32
|
+
const query = options.includeSource ? "?includeSource=true" : "";
|
|
33
|
+
return requestJson({
|
|
34
|
+
url: `${dashboardCardUrl(options)}/data${query}`,
|
|
35
|
+
init: {
|
|
36
|
+
method: "GET",
|
|
37
|
+
headers: { Accept: "application/json" },
|
|
38
|
+
signal: options.signal,
|
|
39
|
+
},
|
|
40
|
+
schema: options.includeSource
|
|
41
|
+
? core_1.PanelCardDataWithSourceResponseSchema
|
|
42
|
+
: core_1.PanelCardDataResponseSchema,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
async function updateDashboardLayout(options) {
|
|
46
|
+
return requestJson({
|
|
47
|
+
url: `${dashboardUrl(options)}/layout`,
|
|
48
|
+
init: jsonRequest("PATCH", { revision: options.revision, cards: options.cards }, options),
|
|
49
|
+
schema: core_1.DashboardDocumentSchema,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
async function updateDashboardCard(options) {
|
|
53
|
+
return requestJson({
|
|
54
|
+
url: dashboardCardUrl(options),
|
|
55
|
+
init: jsonRequest("PATCH", { revision: options.revision, name: options.name }, options),
|
|
56
|
+
schema: core_1.DashboardDocumentSchema,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
async function listCardLibrary(options) {
|
|
60
|
+
return requestJson({
|
|
61
|
+
url: `${dashboardUrl(options)}/card-library`,
|
|
62
|
+
init: {
|
|
63
|
+
method: "GET",
|
|
64
|
+
headers: { Accept: "application/json" },
|
|
65
|
+
signal: options.signal,
|
|
66
|
+
},
|
|
67
|
+
schema: core_1.CardLibraryResponseSchema,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
async function addDashboardCard(options) {
|
|
71
|
+
return requestJson({
|
|
72
|
+
url: `${dashboardUrl(options)}/cards`,
|
|
73
|
+
init: jsonRequest("POST", {
|
|
74
|
+
revision: options.revision,
|
|
75
|
+
libraryItemKey: options.libraryItemKey,
|
|
76
|
+
}, options),
|
|
77
|
+
schema: core_1.DashboardCardMutationResponseSchema,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
async function removeDashboardCard(options) {
|
|
81
|
+
return requestJson({
|
|
82
|
+
url: dashboardCardUrl(options),
|
|
83
|
+
init: jsonRequest("DELETE", { revision: options.revision }, options),
|
|
84
|
+
schema: core_1.DashboardCardMutationResponseSchema,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
function jsonRequest(method, body, options) {
|
|
88
|
+
return {
|
|
89
|
+
method,
|
|
90
|
+
headers: {
|
|
91
|
+
Accept: "application/json",
|
|
92
|
+
"Content-Type": "application/json",
|
|
93
|
+
},
|
|
94
|
+
body: JSON.stringify(body),
|
|
95
|
+
signal: options.signal,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
async function requestJson({ url, init, schema, }) {
|
|
99
|
+
const response = await fetch(url, init);
|
|
100
|
+
const body = await readJson(response);
|
|
101
|
+
if (!response.ok) {
|
|
102
|
+
const parsedError = core_1.DashboardApiErrorSchema.safeParse(body);
|
|
103
|
+
throw new DashboardClientError({
|
|
104
|
+
status: response.status,
|
|
105
|
+
code: parsedError.success ? parsedError.data.error.code : undefined,
|
|
106
|
+
message: parsedError.success
|
|
107
|
+
? parsedError.data.error.message
|
|
108
|
+
: `Dashboard API request failed with ${response.status} ${response.statusText}`,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
try {
|
|
112
|
+
return schema.parse(body);
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
throw new DashboardClientError({
|
|
116
|
+
status: response.status,
|
|
117
|
+
code: "INVALID_RESPONSE",
|
|
118
|
+
message: "Dashboard API returned an invalid response",
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
async function readJson(response) {
|
|
123
|
+
try {
|
|
124
|
+
return await response.json();
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
return undefined;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
function dashboardCardUrl(options) {
|
|
131
|
+
return `${dashboardUrl(options)}/cards/${encodeURIComponent(options.cardId)}`;
|
|
132
|
+
}
|
|
133
|
+
function dashboardUrl(options) {
|
|
134
|
+
return `${userDashboardsUrl(options)}/${encodeURIComponent(options.dashboardId)}`;
|
|
135
|
+
}
|
|
136
|
+
function userDashboardsUrl(options) {
|
|
137
|
+
return `${apiBaseUrl(options.apiBaseUrl)}/users/${encodeURIComponent(options.userId)}/dashboards`;
|
|
138
|
+
}
|
|
139
|
+
function apiBaseUrl(value = DEFAULT_API_BASE_URL) {
|
|
140
|
+
return value.replace(/\/+$/, "");
|
|
141
|
+
}
|
package/package.json
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gridframe/client",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"main": "./dist/index.js",
|
|
5
|
+
"types": "./dist/index.d.ts",
|
|
4
6
|
"exports": {
|
|
5
|
-
".":
|
|
7
|
+
".": {
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"import": "./dist/index.js",
|
|
10
|
+
"require": "./dist/index.js",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
}
|
|
6
13
|
},
|
|
7
14
|
"files": [
|
|
8
|
-
"
|
|
15
|
+
"dist",
|
|
9
16
|
"README.md"
|
|
10
17
|
],
|
|
11
18
|
"publishConfig": {
|
|
@@ -15,13 +22,14 @@
|
|
|
15
22
|
"eslint": "^9.39.1",
|
|
16
23
|
"typescript": "5.9.2",
|
|
17
24
|
"vitest": "^4.1.10",
|
|
18
|
-
"@gridframe/
|
|
19
|
-
"@gridframe/
|
|
25
|
+
"@gridframe/typescript-config": "0.0.0",
|
|
26
|
+
"@gridframe/eslint-config": "0.0.0"
|
|
20
27
|
},
|
|
21
28
|
"dependencies": {
|
|
22
|
-
"@gridframe/core": "
|
|
29
|
+
"@gridframe/core": "1.0.1"
|
|
23
30
|
},
|
|
24
31
|
"scripts": {
|
|
32
|
+
"build": "rm -rf dist && tsc",
|
|
25
33
|
"lint": "eslint . --max-warnings 0",
|
|
26
34
|
"test": "vitest run src",
|
|
27
35
|
"check-types": "tsc --noEmit"
|
package/src/index.test.ts
DELETED
|
@@ -1,192 +0,0 @@
|
|
|
1
|
-
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
DashboardClientError,
|
|
5
|
-
bootstrapDashboard,
|
|
6
|
-
fetchDashboardCardData,
|
|
7
|
-
updateDashboardLayout,
|
|
8
|
-
} from "./index";
|
|
9
|
-
|
|
10
|
-
const bootstrapResponse = {
|
|
11
|
-
dashboards: [{ id: "dashboard-1", title: "Operations", isDefault: true }],
|
|
12
|
-
dashboard: {
|
|
13
|
-
id: "dashboard-1",
|
|
14
|
-
revision: "1",
|
|
15
|
-
config: {
|
|
16
|
-
title: "Operations",
|
|
17
|
-
cards: [
|
|
18
|
-
{
|
|
19
|
-
id: "card-1",
|
|
20
|
-
name: "Revenue",
|
|
21
|
-
visualization: "metric",
|
|
22
|
-
query:
|
|
23
|
-
"/api/gridframe/users/user%2F1/dashboards/dashboard-1/cards/card-1/data",
|
|
24
|
-
layout: { x: 0, y: 0, width: 1, height: 2 },
|
|
25
|
-
},
|
|
26
|
-
],
|
|
27
|
-
},
|
|
28
|
-
},
|
|
29
|
-
} as const;
|
|
30
|
-
|
|
31
|
-
afterEach(() => {
|
|
32
|
-
vi.unstubAllGlobals();
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
describe("bootstrapDashboard", () => {
|
|
36
|
-
it("constructs the owner-scoped URL, forwards abort, and validates JSON", async () => {
|
|
37
|
-
const signal = new AbortController().signal;
|
|
38
|
-
const fetchMock = vi.fn().mockResolvedValue(
|
|
39
|
-
new Response(JSON.stringify(bootstrapResponse), {
|
|
40
|
-
status: 200,
|
|
41
|
-
headers: { "Content-Type": "application/json" },
|
|
42
|
-
}),
|
|
43
|
-
);
|
|
44
|
-
vi.stubGlobal("fetch", fetchMock);
|
|
45
|
-
|
|
46
|
-
await expect(
|
|
47
|
-
bootstrapDashboard({
|
|
48
|
-
userId: "user/1",
|
|
49
|
-
dashboardId: "dashboard-1",
|
|
50
|
-
apiBaseUrl: "/custom/",
|
|
51
|
-
signal,
|
|
52
|
-
}),
|
|
53
|
-
).resolves.toEqual(bootstrapResponse);
|
|
54
|
-
expect(fetchMock).toHaveBeenCalledWith(
|
|
55
|
-
"/custom/users/user%2F1/dashboards/bootstrap",
|
|
56
|
-
expect.objectContaining({
|
|
57
|
-
body: JSON.stringify({ dashboardId: "dashboard-1" }),
|
|
58
|
-
method: "POST",
|
|
59
|
-
signal,
|
|
60
|
-
}),
|
|
61
|
-
);
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
it("rejects a malformed successful response", async () => {
|
|
65
|
-
vi.stubGlobal(
|
|
66
|
-
"fetch",
|
|
67
|
-
vi
|
|
68
|
-
.fn()
|
|
69
|
-
.mockResolvedValue(
|
|
70
|
-
new Response(JSON.stringify({ dashboard: null }), { status: 200 }),
|
|
71
|
-
),
|
|
72
|
-
);
|
|
73
|
-
|
|
74
|
-
await expect(bootstrapDashboard({ userId: "user-1" })).rejects.toThrow(
|
|
75
|
-
"Dashboard API returned an invalid response",
|
|
76
|
-
);
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it("throws a typed error for a non-success response", async () => {
|
|
80
|
-
vi.stubGlobal(
|
|
81
|
-
"fetch",
|
|
82
|
-
vi.fn().mockResolvedValue(
|
|
83
|
-
new Response(
|
|
84
|
-
JSON.stringify({
|
|
85
|
-
error: {
|
|
86
|
-
code: "DASHBOARD_NOT_FOUND",
|
|
87
|
-
message: "Dashboard not found",
|
|
88
|
-
},
|
|
89
|
-
}),
|
|
90
|
-
{ status: 404 },
|
|
91
|
-
),
|
|
92
|
-
),
|
|
93
|
-
);
|
|
94
|
-
|
|
95
|
-
const error = await bootstrapDashboard({ userId: "user-1" }).catch(
|
|
96
|
-
(caught: unknown) => caught,
|
|
97
|
-
);
|
|
98
|
-
|
|
99
|
-
expect(error).toBeInstanceOf(DashboardClientError);
|
|
100
|
-
expect(error).toMatchObject({
|
|
101
|
-
status: 404,
|
|
102
|
-
code: "DASHBOARD_NOT_FOUND",
|
|
103
|
-
message: "Dashboard not found",
|
|
104
|
-
});
|
|
105
|
-
});
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
describe("fetchDashboardCardData", () => {
|
|
109
|
-
it("requests source data through the owner-scoped Card endpoint", async () => {
|
|
110
|
-
const response = {
|
|
111
|
-
status: "success",
|
|
112
|
-
data: { visualization: "metric", value: 42 },
|
|
113
|
-
sourceData: {
|
|
114
|
-
columns: [{ key: "value", label: "Value" }],
|
|
115
|
-
rows: [{ value: 42 }],
|
|
116
|
-
},
|
|
117
|
-
} as const;
|
|
118
|
-
const fetchMock = vi
|
|
119
|
-
.fn()
|
|
120
|
-
.mockResolvedValue(
|
|
121
|
-
new Response(JSON.stringify(response), { status: 200 }),
|
|
122
|
-
);
|
|
123
|
-
vi.stubGlobal("fetch", fetchMock);
|
|
124
|
-
|
|
125
|
-
await expect(
|
|
126
|
-
fetchDashboardCardData({
|
|
127
|
-
userId: "user-1",
|
|
128
|
-
dashboardId: "dashboard-1",
|
|
129
|
-
cardId: "card-1",
|
|
130
|
-
includeSource: true,
|
|
131
|
-
}),
|
|
132
|
-
).resolves.toEqual(response);
|
|
133
|
-
expect(fetchMock).toHaveBeenCalledWith(
|
|
134
|
-
"/api/gridframe/users/user-1/dashboards/dashboard-1/cards/card-1/data?includeSource=true",
|
|
135
|
-
expect.objectContaining({ method: "GET" }),
|
|
136
|
-
);
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
it("rejects success without source data when source data was requested", async () => {
|
|
140
|
-
vi.stubGlobal(
|
|
141
|
-
"fetch",
|
|
142
|
-
vi.fn().mockResolvedValue(
|
|
143
|
-
new Response(
|
|
144
|
-
JSON.stringify({
|
|
145
|
-
status: "success",
|
|
146
|
-
data: { visualization: "metric", value: 42 },
|
|
147
|
-
}),
|
|
148
|
-
{ status: 200 },
|
|
149
|
-
),
|
|
150
|
-
),
|
|
151
|
-
);
|
|
152
|
-
|
|
153
|
-
await expect(
|
|
154
|
-
fetchDashboardCardData({
|
|
155
|
-
userId: "user-1",
|
|
156
|
-
dashboardId: "dashboard-1",
|
|
157
|
-
cardId: "card-1",
|
|
158
|
-
includeSource: true,
|
|
159
|
-
}),
|
|
160
|
-
).rejects.toThrow("Dashboard API returned an invalid response");
|
|
161
|
-
});
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
describe("updateDashboardLayout", () => {
|
|
165
|
-
it("submits the complete owner-scoped layout and validates the Dashboard", async () => {
|
|
166
|
-
const fetchMock = vi
|
|
167
|
-
.fn()
|
|
168
|
-
.mockResolvedValue(
|
|
169
|
-
new Response(JSON.stringify(bootstrapResponse.dashboard), {
|
|
170
|
-
status: 200,
|
|
171
|
-
}),
|
|
172
|
-
);
|
|
173
|
-
vi.stubGlobal("fetch", fetchMock);
|
|
174
|
-
const cards = [{ id: "card-1", x: 0, y: 0, width: 1, height: 2 }];
|
|
175
|
-
|
|
176
|
-
await expect(
|
|
177
|
-
updateDashboardLayout({
|
|
178
|
-
userId: "user/1",
|
|
179
|
-
dashboardId: "dashboard-1",
|
|
180
|
-
revision: "1",
|
|
181
|
-
cards,
|
|
182
|
-
}),
|
|
183
|
-
).resolves.toEqual(bootstrapResponse.dashboard);
|
|
184
|
-
expect(fetchMock).toHaveBeenCalledWith(
|
|
185
|
-
"/api/gridframe/users/user%2F1/dashboards/dashboard-1/layout",
|
|
186
|
-
expect.objectContaining({
|
|
187
|
-
method: "PATCH",
|
|
188
|
-
body: JSON.stringify({ revision: "1", cards }),
|
|
189
|
-
}),
|
|
190
|
-
);
|
|
191
|
-
});
|
|
192
|
-
});
|
package/src/index.ts
DELETED
|
@@ -1,250 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
CardLibraryResponseSchema,
|
|
3
|
-
DashboardApiErrorSchema,
|
|
4
|
-
DashboardBootstrapResponseSchema,
|
|
5
|
-
DashboardCardMutationResponseSchema,
|
|
6
|
-
DashboardDocumentSchema,
|
|
7
|
-
PanelCardDataResponseSchema,
|
|
8
|
-
PanelCardDataWithSourceResponseSchema,
|
|
9
|
-
type AddDashboardCardRequest,
|
|
10
|
-
type CardLibraryResponse,
|
|
11
|
-
type DashboardApiErrorCode,
|
|
12
|
-
type DashboardBootstrapResponse,
|
|
13
|
-
type DashboardCardMutationResponse,
|
|
14
|
-
type DashboardDocument,
|
|
15
|
-
type PanelCardDataResponse,
|
|
16
|
-
type PanelCardDataWithSourceResponse,
|
|
17
|
-
type RemoveDashboardCardRequest,
|
|
18
|
-
type UpdateDashboardCardRequest,
|
|
19
|
-
type UpdateDashboardLayoutRequest,
|
|
20
|
-
} from "@gridframe/core";
|
|
21
|
-
|
|
22
|
-
const DEFAULT_API_BASE_URL = "/api/gridframe";
|
|
23
|
-
|
|
24
|
-
type RequestOptions = {
|
|
25
|
-
apiBaseUrl?: string;
|
|
26
|
-
signal?: AbortSignal;
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
type DashboardIdentity = RequestOptions & {
|
|
30
|
-
userId: string;
|
|
31
|
-
dashboardId: string;
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
type CardIdentity = DashboardIdentity & {
|
|
35
|
-
cardId: string;
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
type RuntimeSchema<T> = {
|
|
39
|
-
parse(value: unknown): T;
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
class DashboardClientError extends Error {
|
|
43
|
-
readonly status: number;
|
|
44
|
-
readonly code?: DashboardApiErrorCode | "INVALID_RESPONSE";
|
|
45
|
-
|
|
46
|
-
constructor({
|
|
47
|
-
status,
|
|
48
|
-
message,
|
|
49
|
-
code,
|
|
50
|
-
}: {
|
|
51
|
-
status: number;
|
|
52
|
-
message: string;
|
|
53
|
-
code?: DashboardApiErrorCode | "INVALID_RESPONSE";
|
|
54
|
-
}) {
|
|
55
|
-
super(message);
|
|
56
|
-
this.name = "DashboardClientError";
|
|
57
|
-
this.status = status;
|
|
58
|
-
this.code = code;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
async function bootstrapDashboard(
|
|
63
|
-
options: RequestOptions & { userId: string; dashboardId?: string },
|
|
64
|
-
): Promise<DashboardBootstrapResponse> {
|
|
65
|
-
return requestJson({
|
|
66
|
-
url: `${userDashboardsUrl(options)}/bootstrap`,
|
|
67
|
-
init: jsonRequest("POST", { dashboardId: options.dashboardId }, options),
|
|
68
|
-
schema: DashboardBootstrapResponseSchema,
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function fetchDashboardCardData(
|
|
73
|
-
options: CardIdentity & { includeSource: true },
|
|
74
|
-
): Promise<PanelCardDataWithSourceResponse>;
|
|
75
|
-
function fetchDashboardCardData(
|
|
76
|
-
options: CardIdentity & { includeSource?: false },
|
|
77
|
-
): Promise<PanelCardDataResponse>;
|
|
78
|
-
async function fetchDashboardCardData(
|
|
79
|
-
options: CardIdentity & { includeSource?: boolean },
|
|
80
|
-
): Promise<PanelCardDataResponse> {
|
|
81
|
-
const query = options.includeSource ? "?includeSource=true" : "";
|
|
82
|
-
|
|
83
|
-
return requestJson({
|
|
84
|
-
url: `${dashboardCardUrl(options)}/data${query}`,
|
|
85
|
-
init: {
|
|
86
|
-
method: "GET",
|
|
87
|
-
headers: { Accept: "application/json" },
|
|
88
|
-
signal: options.signal,
|
|
89
|
-
},
|
|
90
|
-
schema: options.includeSource
|
|
91
|
-
? PanelCardDataWithSourceResponseSchema
|
|
92
|
-
: PanelCardDataResponseSchema,
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
async function updateDashboardLayout(
|
|
97
|
-
options: DashboardIdentity & UpdateDashboardLayoutRequest,
|
|
98
|
-
): Promise<DashboardDocument> {
|
|
99
|
-
return requestJson({
|
|
100
|
-
url: `${dashboardUrl(options)}/layout`,
|
|
101
|
-
init: jsonRequest(
|
|
102
|
-
"PATCH",
|
|
103
|
-
{ revision: options.revision, cards: options.cards },
|
|
104
|
-
options,
|
|
105
|
-
),
|
|
106
|
-
schema: DashboardDocumentSchema,
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
async function updateDashboardCard(
|
|
111
|
-
options: CardIdentity & UpdateDashboardCardRequest,
|
|
112
|
-
): Promise<DashboardDocument> {
|
|
113
|
-
return requestJson({
|
|
114
|
-
url: dashboardCardUrl(options),
|
|
115
|
-
init: jsonRequest(
|
|
116
|
-
"PATCH",
|
|
117
|
-
{ revision: options.revision, name: options.name },
|
|
118
|
-
options,
|
|
119
|
-
),
|
|
120
|
-
schema: DashboardDocumentSchema,
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
async function listCardLibrary(
|
|
125
|
-
options: DashboardIdentity,
|
|
126
|
-
): Promise<CardLibraryResponse> {
|
|
127
|
-
return requestJson({
|
|
128
|
-
url: `${dashboardUrl(options)}/card-library`,
|
|
129
|
-
init: {
|
|
130
|
-
method: "GET",
|
|
131
|
-
headers: { Accept: "application/json" },
|
|
132
|
-
signal: options.signal,
|
|
133
|
-
},
|
|
134
|
-
schema: CardLibraryResponseSchema,
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
async function addDashboardCard(
|
|
139
|
-
options: DashboardIdentity & AddDashboardCardRequest,
|
|
140
|
-
): Promise<DashboardCardMutationResponse> {
|
|
141
|
-
return requestJson({
|
|
142
|
-
url: `${dashboardUrl(options)}/cards`,
|
|
143
|
-
init: jsonRequest(
|
|
144
|
-
"POST",
|
|
145
|
-
{
|
|
146
|
-
revision: options.revision,
|
|
147
|
-
libraryItemKey: options.libraryItemKey,
|
|
148
|
-
},
|
|
149
|
-
options,
|
|
150
|
-
),
|
|
151
|
-
schema: DashboardCardMutationResponseSchema,
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
async function removeDashboardCard(
|
|
156
|
-
options: CardIdentity & RemoveDashboardCardRequest,
|
|
157
|
-
): Promise<DashboardCardMutationResponse> {
|
|
158
|
-
return requestJson({
|
|
159
|
-
url: dashboardCardUrl(options),
|
|
160
|
-
init: jsonRequest("DELETE", { revision: options.revision }, options),
|
|
161
|
-
schema: DashboardCardMutationResponseSchema,
|
|
162
|
-
});
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
function jsonRequest(
|
|
166
|
-
method: "POST" | "PATCH" | "DELETE",
|
|
167
|
-
body: unknown,
|
|
168
|
-
options: RequestOptions,
|
|
169
|
-
): RequestInit {
|
|
170
|
-
return {
|
|
171
|
-
method,
|
|
172
|
-
headers: {
|
|
173
|
-
Accept: "application/json",
|
|
174
|
-
"Content-Type": "application/json",
|
|
175
|
-
},
|
|
176
|
-
body: JSON.stringify(body),
|
|
177
|
-
signal: options.signal,
|
|
178
|
-
};
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
async function requestJson<T>({
|
|
182
|
-
url,
|
|
183
|
-
init,
|
|
184
|
-
schema,
|
|
185
|
-
}: {
|
|
186
|
-
url: string;
|
|
187
|
-
init: RequestInit;
|
|
188
|
-
schema: RuntimeSchema<T>;
|
|
189
|
-
}): Promise<T> {
|
|
190
|
-
const response = await fetch(url, init);
|
|
191
|
-
const body = await readJson(response);
|
|
192
|
-
|
|
193
|
-
if (!response.ok) {
|
|
194
|
-
const parsedError = DashboardApiErrorSchema.safeParse(body);
|
|
195
|
-
|
|
196
|
-
throw new DashboardClientError({
|
|
197
|
-
status: response.status,
|
|
198
|
-
code: parsedError.success ? parsedError.data.error.code : undefined,
|
|
199
|
-
message: parsedError.success
|
|
200
|
-
? parsedError.data.error.message
|
|
201
|
-
: `Dashboard API request failed with ${response.status} ${response.statusText}`,
|
|
202
|
-
});
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
try {
|
|
206
|
-
return schema.parse(body);
|
|
207
|
-
} catch {
|
|
208
|
-
throw new DashboardClientError({
|
|
209
|
-
status: response.status,
|
|
210
|
-
code: "INVALID_RESPONSE",
|
|
211
|
-
message: "Dashboard API returned an invalid response",
|
|
212
|
-
});
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
async function readJson(response: Response): Promise<unknown> {
|
|
217
|
-
try {
|
|
218
|
-
return await response.json();
|
|
219
|
-
} catch {
|
|
220
|
-
return undefined;
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
function dashboardCardUrl(options: CardIdentity) {
|
|
225
|
-
return `${dashboardUrl(options)}/cards/${encodeURIComponent(options.cardId)}`;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
function dashboardUrl(options: DashboardIdentity) {
|
|
229
|
-
return `${userDashboardsUrl(options)}/${encodeURIComponent(options.dashboardId)}`;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
function userDashboardsUrl(options: RequestOptions & { userId: string }) {
|
|
233
|
-
return `${apiBaseUrl(options.apiBaseUrl)}/users/${encodeURIComponent(options.userId)}/dashboards`;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
function apiBaseUrl(value = DEFAULT_API_BASE_URL) {
|
|
237
|
-
return value.replace(/\/+$/, "");
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
export {
|
|
241
|
-
DashboardClientError,
|
|
242
|
-
addDashboardCard,
|
|
243
|
-
bootstrapDashboard,
|
|
244
|
-
fetchDashboardCardData,
|
|
245
|
-
listCardLibrary,
|
|
246
|
-
removeDashboardCard,
|
|
247
|
-
updateDashboardCard,
|
|
248
|
-
updateDashboardLayout,
|
|
249
|
-
};
|
|
250
|
-
export type { CardIdentity, DashboardIdentity, RequestOptions };
|