@lobb-js/lobb-ext-reports 0.11.1 → 0.13.0
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 +1 -0
- package/dist/index.js +4 -3
- package/dist/lib/components/gridStack.svelte +16 -4
- package/dist/lib/components/pages/reports/components/chart.svelte +42 -43
- package/dist/lib/components/pages/reports/components/report.svelte +22 -14
- package/dist/lib/components/pages/reports/index.svelte +2 -2
- package/dist/lib/components/reportBody.svelte +6 -1
- package/extensions/reports/index.ts +31 -3
- package/extensions/reports/studio/index.ts +5 -3
- package/extensions/reports/studio/lib/components/gridStack.svelte +16 -4
- package/extensions/reports/studio/lib/components/pages/reports/components/chart.svelte +42 -43
- package/extensions/reports/studio/lib/components/pages/reports/components/report.svelte +22 -14
- package/extensions/reports/studio/lib/components/pages/reports/index.svelte +2 -2
- package/extensions/reports/studio/lib/components/reportBody.svelte +6 -1
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import Reports from "./lib/components/pages/reports/index.svelte";
|
|
2
2
|
import SharedReport from "./lib/components/pages/shared_report/index.svelte";
|
|
3
3
|
import QueryAiButton from "./lib/components/dv_fields_buttons/query_ai_button/index.svelte";
|
|
4
|
+
export { default as ReportBody } from "./lib/components/reportBody.svelte";
|
|
4
5
|
export default function extension(utils) {
|
|
5
6
|
return {
|
|
6
7
|
name: "reports",
|
|
7
8
|
components: {
|
|
8
9
|
"dvFields.topRight.reports_charts.query": QueryAiButton,
|
|
9
|
-
"pages.
|
|
10
|
+
"pages.dashboards": Reports,
|
|
10
11
|
// Lives at /studio/public/reports/shared_report — no auth required, the
|
|
11
12
|
// page itself swaps the LobbClient bearer to the share_token in the URL.
|
|
12
13
|
"publicPages.shared_report": SharedReport,
|
|
@@ -14,9 +15,9 @@ export default function extension(utils) {
|
|
|
14
15
|
dashboardNavs: {
|
|
15
16
|
middle: [
|
|
16
17
|
{
|
|
17
|
-
href: "/studio/extensions/reports/
|
|
18
|
+
href: "/studio/extensions/reports/dashboards",
|
|
18
19
|
icon: utils.components.Icons.ChartNoAxesCombined,
|
|
19
|
-
label: "
|
|
20
|
+
label: "Dashboards",
|
|
20
21
|
represents: "reports_dashboards",
|
|
21
22
|
},
|
|
22
23
|
],
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
column?: number;
|
|
10
10
|
cellHeight?: string;
|
|
11
11
|
editable?: boolean;
|
|
12
|
+
allowEdit?: boolean;
|
|
12
13
|
onLayoutChange?: (layout: LayoutItem[]) => void;
|
|
13
14
|
children: Snippet;
|
|
14
15
|
}
|
|
@@ -17,6 +18,7 @@
|
|
|
17
18
|
column = 12,
|
|
18
19
|
cellHeight = "150px",
|
|
19
20
|
editable = $bindable(false),
|
|
21
|
+
allowEdit = false,
|
|
20
22
|
onLayoutChange,
|
|
21
23
|
children,
|
|
22
24
|
}: Props = $props();
|
|
@@ -29,7 +31,10 @@
|
|
|
29
31
|
let containerWidth = $state(0);
|
|
30
32
|
let saveTimeout: ReturnType<typeof setTimeout> | null = null;
|
|
31
33
|
|
|
32
|
-
|
|
34
|
+
// Edit mode only ever turns on when the parent explicitly opts in.
|
|
35
|
+
// Without allowEdit, container width is irrelevant — the grid stays
|
|
36
|
+
// disabled forever.
|
|
37
|
+
let editMode = $derived(allowEdit && containerWidth >= 1200);
|
|
33
38
|
|
|
34
39
|
// Keep editable in sync so parent can read it
|
|
35
40
|
$effect(() => { editable = editMode; });
|
|
@@ -112,8 +117,10 @@
|
|
|
112
117
|
});
|
|
113
118
|
</script>
|
|
114
119
|
|
|
115
|
-
<div class="grid-stack"
|
|
116
|
-
{
|
|
120
|
+
<div class="grid-stack-wrap">
|
|
121
|
+
<div class="grid-stack" bind:this={gridEl}>
|
|
122
|
+
{@render children()}
|
|
123
|
+
</div>
|
|
117
124
|
</div>
|
|
118
125
|
|
|
119
126
|
<style>
|
|
@@ -122,9 +129,14 @@
|
|
|
122
129
|
position: absolute;
|
|
123
130
|
}
|
|
124
131
|
|
|
132
|
+
.grid-stack-wrap {
|
|
133
|
+
overflow-x: hidden;
|
|
134
|
+
width: 100%;
|
|
135
|
+
}
|
|
136
|
+
|
|
125
137
|
:global(.grid-stack) {
|
|
126
138
|
background: transparent;
|
|
127
|
-
|
|
139
|
+
margin: 0 -0.5rem;
|
|
128
140
|
}
|
|
129
141
|
|
|
130
142
|
:global(.grid-stack-placeholder > .placeholder-content) {
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
interface Props extends ExtensionProps {
|
|
16
16
|
chartRecord: any;
|
|
17
|
+
showHeader?: boolean;
|
|
17
18
|
onChartDeleted?: () => Promise<void>;
|
|
18
19
|
onChartEdited?: () => Promise<void>;
|
|
19
20
|
onChartClick?: (event: ChartClickEvent) => void;
|
|
@@ -21,6 +22,7 @@
|
|
|
21
22
|
|
|
22
23
|
const {
|
|
23
24
|
chartRecord,
|
|
25
|
+
showHeader = true,
|
|
24
26
|
onChartDeleted,
|
|
25
27
|
onChartEdited,
|
|
26
28
|
onChartClick,
|
|
@@ -95,43 +97,47 @@
|
|
|
95
97
|
}
|
|
96
98
|
</script>
|
|
97
99
|
|
|
98
|
-
<div
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
100
|
+
<div
|
|
101
|
+
class="grid h-full rounded-md border bg-background {showHeader ? 'grid-rows-[auto_1fr]' : 'grid-rows-[1fr]'}"
|
|
102
|
+
>
|
|
103
|
+
{#if showHeader}
|
|
104
|
+
<div class="flex items-center justify-between border-b p-2 text-sm">
|
|
105
|
+
<div class="flex items-center gap-1.5">
|
|
106
|
+
<span>{chartRecord.title}</span>
|
|
107
|
+
{#if chartRecord.description}
|
|
108
|
+
<Tooltip.Root>
|
|
109
|
+
<Tooltip.Trigger>
|
|
110
|
+
<icons.CircleHelp class="text-muted-foreground" size={14} />
|
|
111
|
+
</Tooltip.Trigger>
|
|
112
|
+
<Tooltip.Content class="max-w-64 text-xs">
|
|
113
|
+
{chartRecord.description}
|
|
114
|
+
</Tooltip.Content>
|
|
115
|
+
</Tooltip.Root>
|
|
116
|
+
{/if}
|
|
117
|
+
</div>
|
|
118
|
+
<div class="flex">
|
|
119
|
+
{#if canUpdate}
|
|
120
|
+
<UpdateDetailViewButton
|
|
121
|
+
collectionName="reports_charts"
|
|
122
|
+
recordId={chartRecord.id}
|
|
123
|
+
variant="ghost"
|
|
124
|
+
class="h-7 w-7 text-muted-foreground hover:bg-transparent"
|
|
125
|
+
Icon={icons.Pencil}
|
|
126
|
+
onSuccessfullSave={handleEditOnSuccessfull}
|
|
127
|
+
></UpdateDetailViewButton>
|
|
128
|
+
{/if}
|
|
129
|
+
{#if canDelete}
|
|
130
|
+
<Button
|
|
131
|
+
class="h-6 w-6 text-muted-foreground hover:bg-transparent"
|
|
132
|
+
variant="ghost"
|
|
133
|
+
size="icon"
|
|
134
|
+
Icon={icons.Trash}
|
|
135
|
+
onclick={handleChartDelete}
|
|
136
|
+
></Button>
|
|
137
|
+
{/if}
|
|
138
|
+
</div>
|
|
133
139
|
</div>
|
|
134
|
-
|
|
140
|
+
{/if}
|
|
135
141
|
<div class="flex min-h-0 items-center justify-center overflow-hidden">
|
|
136
142
|
<svelte:boundary>
|
|
137
143
|
{#snippet failed(error, reset)}
|
|
@@ -183,10 +189,3 @@
|
|
|
183
189
|
</svelte:boundary>
|
|
184
190
|
</div>
|
|
185
191
|
</div>
|
|
186
|
-
|
|
187
|
-
<style>
|
|
188
|
-
.gridContainer {
|
|
189
|
-
display: grid;
|
|
190
|
-
grid-template-rows: auto 1fr;
|
|
191
|
-
}
|
|
192
|
-
</style>
|
|
@@ -32,9 +32,7 @@
|
|
|
32
32
|
saving = true;
|
|
33
33
|
for (const change of changes) {
|
|
34
34
|
await utils.lobb.updateOne("reports_charts", change.id, {
|
|
35
|
-
sort_order: change.order,
|
|
36
|
-
col_span: change.w,
|
|
37
|
-
row_span: change.h,
|
|
35
|
+
data: { sort_order: change.order, col_span: change.w, row_span: change.h },
|
|
38
36
|
});
|
|
39
37
|
}
|
|
40
38
|
saving = false;
|
|
@@ -62,20 +60,30 @@
|
|
|
62
60
|
async function handleShare() {
|
|
63
61
|
sharing = true;
|
|
64
62
|
try {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
63
|
+
const event = {
|
|
64
|
+
reportId: String(reportId),
|
|
65
|
+
report,
|
|
66
|
+
handled: false,
|
|
67
|
+
};
|
|
68
|
+
await utils.emitEvent("reports.share", event);
|
|
69
|
+
if (event.handled) return;
|
|
70
|
+
|
|
71
|
+
const basePermissions: Record<string, any> = {
|
|
68
72
|
reports_dashboards: { read: { filter: { id: Number(reportId) } } },
|
|
69
73
|
reports_charts: { read: { filter: { report_id: Number(reportId) } } },
|
|
70
|
-
// TODO: the above ones are ok. the below ones are specific to only this risk project
|
|
71
|
-
// can you please add them using a different way
|
|
72
|
-
risk_config: true,
|
|
73
|
-
risks: true,
|
|
74
74
|
};
|
|
75
|
+
const shareReadAccess = (utils.ctx.meta?.extensions?.reports?.shareReadAccess ?? {}) as Record<string, any>;
|
|
76
|
+
const extraPermissions: Record<string, any> = {};
|
|
77
|
+
for (const [collectionName, readGrant] of Object.entries(shareReadAccess)) {
|
|
78
|
+
extraPermissions[collectionName] = { read: readGrant };
|
|
79
|
+
}
|
|
80
|
+
const permissions = { ...basePermissions, ...extraPermissions };
|
|
75
81
|
const response = await utils.lobb.createOne("auth_shares", {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
82
|
+
data: {
|
|
83
|
+
label: `Share for: ${report?.name ?? "report"}`,
|
|
84
|
+
permissions: JSON.stringify(permissions),
|
|
85
|
+
expires_in_seconds: 7 * 24 * 60 * 60,
|
|
86
|
+
},
|
|
79
87
|
});
|
|
80
88
|
if (!response.ok) {
|
|
81
89
|
const body = await response.json().catch(() => null);
|
|
@@ -154,7 +162,7 @@
|
|
|
154
162
|
<ReportBody
|
|
155
163
|
{reportId}
|
|
156
164
|
{utils}
|
|
157
|
-
|
|
165
|
+
allowEdit
|
|
158
166
|
onLayoutChange={handleLayoutChange}
|
|
159
167
|
onChartClick={handleChartClick}
|
|
160
168
|
onReportLoaded={handleReportLoaded}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
const { utils, ...props }: ExtensionProps = $props();
|
|
6
6
|
|
|
7
|
-
let extensionPagePath = "/studio/extensions/reports/
|
|
7
|
+
let extensionPagePath = "/studio/extensions/reports/dashboards";
|
|
8
8
|
let reportId: string | null = $derived(utils.page.url.pathname.split("/")[5] ?? null);
|
|
9
9
|
let sideBarData: any = $state(null);
|
|
10
10
|
const {
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
|
|
57
57
|
</script>
|
|
58
58
|
|
|
59
|
-
<Sidebar title="
|
|
59
|
+
<Sidebar title="Dashboards" data={sideBarData}>
|
|
60
60
|
{#snippet belowSearch()}
|
|
61
61
|
<div class="p-2">
|
|
62
62
|
<CreateDetailViewButton
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
interface Props extends ExtensionProps {
|
|
8
8
|
reportId: string | number;
|
|
9
9
|
editable?: boolean;
|
|
10
|
+
allowEdit?: boolean;
|
|
11
|
+
showChartHeaders?: boolean;
|
|
10
12
|
onLayoutChange?: (changes: any[]) => void;
|
|
11
13
|
onChartClick?: (event: any, elements: any[], chart: any) => void;
|
|
12
14
|
onReportLoaded?: (report: any) => void;
|
|
@@ -16,6 +18,8 @@
|
|
|
16
18
|
const {
|
|
17
19
|
reportId,
|
|
18
20
|
editable = false,
|
|
21
|
+
allowEdit = false,
|
|
22
|
+
showChartHeaders = true,
|
|
19
23
|
onLayoutChange,
|
|
20
24
|
onChartClick,
|
|
21
25
|
onReportLoaded,
|
|
@@ -91,7 +95,7 @@
|
|
|
91
95
|
</div>
|
|
92
96
|
{:else}
|
|
93
97
|
{#key charts}
|
|
94
|
-
<GridStackComponent {editable} {onLayoutChange}>
|
|
98
|
+
<GridStackComponent {editable} {allowEdit} {onLayoutChange}>
|
|
95
99
|
{#each [...charts].sort((a, b) => (a.sort_order ?? 0) - (b.sort_order ?? 0)) as chart (chart.id)}
|
|
96
100
|
<div
|
|
97
101
|
class="grid-stack-item"
|
|
@@ -101,6 +105,7 @@
|
|
|
101
105
|
<div class="grid-stack-item-content">
|
|
102
106
|
<Chart
|
|
103
107
|
chartRecord={chart}
|
|
108
|
+
showHeader={showChartHeaders}
|
|
104
109
|
onChartDeleted={async () => await loadCharts()}
|
|
105
110
|
onChartEdited={async () => await loadCharts()}
|
|
106
111
|
{onChartClick}
|
|
@@ -3,12 +3,35 @@ import type { Extension } from "@lobb-js/core";
|
|
|
3
3
|
import packageJson from "../../package.json" with { type: "json" };
|
|
4
4
|
import { collections } from "./collections/index.ts";
|
|
5
5
|
import { migrations } from "./migrations.ts";
|
|
6
|
-
import { meta } from "./meta.ts";
|
|
6
|
+
import { meta as buildMeta } from "./meta.ts";
|
|
7
7
|
import { workflows } from "./workflows.ts";
|
|
8
8
|
import { openapi } from "./openapi.ts";
|
|
9
9
|
import { relations } from "./relations.ts";
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
type ShareReadPermission =
|
|
12
|
+
| true
|
|
13
|
+
| { filter?: Record<string, unknown>; fields?: Record<string, true> };
|
|
14
|
+
|
|
15
|
+
export interface ReportsExtensionConfig {
|
|
16
|
+
/**
|
|
17
|
+
* Domain-specific collections a share recipient needs read access to in
|
|
18
|
+
* addition to `reports_dashboards` + `reports_charts`. Reports often
|
|
19
|
+
* drill down into other collections (e.g. risk charts opening the
|
|
20
|
+
* `risks` table on click) — those collections have to be readable by
|
|
21
|
+
* the share token or the drill-down requests will 403.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* reports({
|
|
25
|
+
* shareReadAccess: {
|
|
26
|
+
* risks: true,
|
|
27
|
+
* risk_config: true,
|
|
28
|
+
* },
|
|
29
|
+
* })
|
|
30
|
+
*/
|
|
31
|
+
shareReadAccess?: Record<string, ShareReadPermission>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export default function extension(config: ReportsExtensionConfig = {}): Extension {
|
|
12
35
|
return {
|
|
13
36
|
version: packageJson.version,
|
|
14
37
|
name: "reports",
|
|
@@ -17,7 +40,12 @@ export default function extension(): Extension {
|
|
|
17
40
|
collections: collections,
|
|
18
41
|
relations: relations,
|
|
19
42
|
migrations: migrations,
|
|
20
|
-
meta
|
|
43
|
+
// Surface the config to the studio via meta so the Share button can
|
|
44
|
+
// include it in the per-report share permissions snapshot.
|
|
45
|
+
meta: async (lobb) => ({
|
|
46
|
+
...(await buildMeta(lobb)),
|
|
47
|
+
shareReadAccess: config.shareReadAccess ?? {},
|
|
48
|
+
}),
|
|
21
49
|
openapi: openapi,
|
|
22
50
|
};
|
|
23
51
|
}
|
|
@@ -3,12 +3,14 @@ import Reports from "./lib/components/pages/reports/index.svelte";
|
|
|
3
3
|
import SharedReport from "./lib/components/pages/shared_report/index.svelte";
|
|
4
4
|
import QueryAiButton from "./lib/components/dv_fields_buttons/query_ai_button/index.svelte";
|
|
5
5
|
|
|
6
|
+
export { default as ReportBody } from "./lib/components/reportBody.svelte";
|
|
7
|
+
|
|
6
8
|
export default function extension(utils: ExtensionUtils): Extension {
|
|
7
9
|
return {
|
|
8
10
|
name: "reports",
|
|
9
11
|
components: {
|
|
10
12
|
"dvFields.topRight.reports_charts.query": QueryAiButton,
|
|
11
|
-
"pages.
|
|
13
|
+
"pages.dashboards": Reports,
|
|
12
14
|
// Lives at /studio/public/reports/shared_report — no auth required, the
|
|
13
15
|
// page itself swaps the LobbClient bearer to the share_token in the URL.
|
|
14
16
|
"publicPages.shared_report": SharedReport,
|
|
@@ -16,9 +18,9 @@ export default function extension(utils: ExtensionUtils): Extension {
|
|
|
16
18
|
dashboardNavs: {
|
|
17
19
|
middle: [
|
|
18
20
|
{
|
|
19
|
-
href: "/studio/extensions/reports/
|
|
21
|
+
href: "/studio/extensions/reports/dashboards",
|
|
20
22
|
icon: utils.components.Icons.ChartNoAxesCombined,
|
|
21
|
-
label: "
|
|
23
|
+
label: "Dashboards",
|
|
22
24
|
represents: "reports_dashboards",
|
|
23
25
|
},
|
|
24
26
|
],
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
column?: number;
|
|
10
10
|
cellHeight?: string;
|
|
11
11
|
editable?: boolean;
|
|
12
|
+
allowEdit?: boolean;
|
|
12
13
|
onLayoutChange?: (layout: LayoutItem[]) => void;
|
|
13
14
|
children: Snippet;
|
|
14
15
|
}
|
|
@@ -17,6 +18,7 @@
|
|
|
17
18
|
column = 12,
|
|
18
19
|
cellHeight = "150px",
|
|
19
20
|
editable = $bindable(false),
|
|
21
|
+
allowEdit = false,
|
|
20
22
|
onLayoutChange,
|
|
21
23
|
children,
|
|
22
24
|
}: Props = $props();
|
|
@@ -29,7 +31,10 @@
|
|
|
29
31
|
let containerWidth = $state(0);
|
|
30
32
|
let saveTimeout: ReturnType<typeof setTimeout> | null = null;
|
|
31
33
|
|
|
32
|
-
|
|
34
|
+
// Edit mode only ever turns on when the parent explicitly opts in.
|
|
35
|
+
// Without allowEdit, container width is irrelevant — the grid stays
|
|
36
|
+
// disabled forever.
|
|
37
|
+
let editMode = $derived(allowEdit && containerWidth >= 1200);
|
|
33
38
|
|
|
34
39
|
// Keep editable in sync so parent can read it
|
|
35
40
|
$effect(() => { editable = editMode; });
|
|
@@ -112,8 +117,10 @@
|
|
|
112
117
|
});
|
|
113
118
|
</script>
|
|
114
119
|
|
|
115
|
-
<div class="grid-stack"
|
|
116
|
-
{
|
|
120
|
+
<div class="grid-stack-wrap">
|
|
121
|
+
<div class="grid-stack" bind:this={gridEl}>
|
|
122
|
+
{@render children()}
|
|
123
|
+
</div>
|
|
117
124
|
</div>
|
|
118
125
|
|
|
119
126
|
<style>
|
|
@@ -122,9 +129,14 @@
|
|
|
122
129
|
position: absolute;
|
|
123
130
|
}
|
|
124
131
|
|
|
132
|
+
.grid-stack-wrap {
|
|
133
|
+
overflow-x: hidden;
|
|
134
|
+
width: 100%;
|
|
135
|
+
}
|
|
136
|
+
|
|
125
137
|
:global(.grid-stack) {
|
|
126
138
|
background: transparent;
|
|
127
|
-
|
|
139
|
+
margin: 0 -0.5rem;
|
|
128
140
|
}
|
|
129
141
|
|
|
130
142
|
:global(.grid-stack-placeholder > .placeholder-content) {
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
interface Props extends ExtensionProps {
|
|
16
16
|
chartRecord: any;
|
|
17
|
+
showHeader?: boolean;
|
|
17
18
|
onChartDeleted?: () => Promise<void>;
|
|
18
19
|
onChartEdited?: () => Promise<void>;
|
|
19
20
|
onChartClick?: (event: ChartClickEvent) => void;
|
|
@@ -21,6 +22,7 @@
|
|
|
21
22
|
|
|
22
23
|
const {
|
|
23
24
|
chartRecord,
|
|
25
|
+
showHeader = true,
|
|
24
26
|
onChartDeleted,
|
|
25
27
|
onChartEdited,
|
|
26
28
|
onChartClick,
|
|
@@ -95,43 +97,47 @@
|
|
|
95
97
|
}
|
|
96
98
|
</script>
|
|
97
99
|
|
|
98
|
-
<div
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
100
|
+
<div
|
|
101
|
+
class="grid h-full rounded-md border bg-background {showHeader ? 'grid-rows-[auto_1fr]' : 'grid-rows-[1fr]'}"
|
|
102
|
+
>
|
|
103
|
+
{#if showHeader}
|
|
104
|
+
<div class="flex items-center justify-between border-b p-2 text-sm">
|
|
105
|
+
<div class="flex items-center gap-1.5">
|
|
106
|
+
<span>{chartRecord.title}</span>
|
|
107
|
+
{#if chartRecord.description}
|
|
108
|
+
<Tooltip.Root>
|
|
109
|
+
<Tooltip.Trigger>
|
|
110
|
+
<icons.CircleHelp class="text-muted-foreground" size={14} />
|
|
111
|
+
</Tooltip.Trigger>
|
|
112
|
+
<Tooltip.Content class="max-w-64 text-xs">
|
|
113
|
+
{chartRecord.description}
|
|
114
|
+
</Tooltip.Content>
|
|
115
|
+
</Tooltip.Root>
|
|
116
|
+
{/if}
|
|
117
|
+
</div>
|
|
118
|
+
<div class="flex">
|
|
119
|
+
{#if canUpdate}
|
|
120
|
+
<UpdateDetailViewButton
|
|
121
|
+
collectionName="reports_charts"
|
|
122
|
+
recordId={chartRecord.id}
|
|
123
|
+
variant="ghost"
|
|
124
|
+
class="h-7 w-7 text-muted-foreground hover:bg-transparent"
|
|
125
|
+
Icon={icons.Pencil}
|
|
126
|
+
onSuccessfullSave={handleEditOnSuccessfull}
|
|
127
|
+
></UpdateDetailViewButton>
|
|
128
|
+
{/if}
|
|
129
|
+
{#if canDelete}
|
|
130
|
+
<Button
|
|
131
|
+
class="h-6 w-6 text-muted-foreground hover:bg-transparent"
|
|
132
|
+
variant="ghost"
|
|
133
|
+
size="icon"
|
|
134
|
+
Icon={icons.Trash}
|
|
135
|
+
onclick={handleChartDelete}
|
|
136
|
+
></Button>
|
|
137
|
+
{/if}
|
|
138
|
+
</div>
|
|
133
139
|
</div>
|
|
134
|
-
|
|
140
|
+
{/if}
|
|
135
141
|
<div class="flex min-h-0 items-center justify-center overflow-hidden">
|
|
136
142
|
<svelte:boundary>
|
|
137
143
|
{#snippet failed(error, reset)}
|
|
@@ -183,10 +189,3 @@
|
|
|
183
189
|
</svelte:boundary>
|
|
184
190
|
</div>
|
|
185
191
|
</div>
|
|
186
|
-
|
|
187
|
-
<style>
|
|
188
|
-
.gridContainer {
|
|
189
|
-
display: grid;
|
|
190
|
-
grid-template-rows: auto 1fr;
|
|
191
|
-
}
|
|
192
|
-
</style>
|
|
@@ -32,9 +32,7 @@
|
|
|
32
32
|
saving = true;
|
|
33
33
|
for (const change of changes) {
|
|
34
34
|
await utils.lobb.updateOne("reports_charts", change.id, {
|
|
35
|
-
sort_order: change.order,
|
|
36
|
-
col_span: change.w,
|
|
37
|
-
row_span: change.h,
|
|
35
|
+
data: { sort_order: change.order, col_span: change.w, row_span: change.h },
|
|
38
36
|
});
|
|
39
37
|
}
|
|
40
38
|
saving = false;
|
|
@@ -62,20 +60,30 @@
|
|
|
62
60
|
async function handleShare() {
|
|
63
61
|
sharing = true;
|
|
64
62
|
try {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
63
|
+
const event = {
|
|
64
|
+
reportId: String(reportId),
|
|
65
|
+
report,
|
|
66
|
+
handled: false,
|
|
67
|
+
};
|
|
68
|
+
await utils.emitEvent("reports.share", event);
|
|
69
|
+
if (event.handled) return;
|
|
70
|
+
|
|
71
|
+
const basePermissions: Record<string, any> = {
|
|
68
72
|
reports_dashboards: { read: { filter: { id: Number(reportId) } } },
|
|
69
73
|
reports_charts: { read: { filter: { report_id: Number(reportId) } } },
|
|
70
|
-
// TODO: the above ones are ok. the below ones are specific to only this risk project
|
|
71
|
-
// can you please add them using a different way
|
|
72
|
-
risk_config: true,
|
|
73
|
-
risks: true,
|
|
74
74
|
};
|
|
75
|
+
const shareReadAccess = (utils.ctx.meta?.extensions?.reports?.shareReadAccess ?? {}) as Record<string, any>;
|
|
76
|
+
const extraPermissions: Record<string, any> = {};
|
|
77
|
+
for (const [collectionName, readGrant] of Object.entries(shareReadAccess)) {
|
|
78
|
+
extraPermissions[collectionName] = { read: readGrant };
|
|
79
|
+
}
|
|
80
|
+
const permissions = { ...basePermissions, ...extraPermissions };
|
|
75
81
|
const response = await utils.lobb.createOne("auth_shares", {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
82
|
+
data: {
|
|
83
|
+
label: `Share for: ${report?.name ?? "report"}`,
|
|
84
|
+
permissions: JSON.stringify(permissions),
|
|
85
|
+
expires_in_seconds: 7 * 24 * 60 * 60,
|
|
86
|
+
},
|
|
79
87
|
});
|
|
80
88
|
if (!response.ok) {
|
|
81
89
|
const body = await response.json().catch(() => null);
|
|
@@ -154,7 +162,7 @@
|
|
|
154
162
|
<ReportBody
|
|
155
163
|
{reportId}
|
|
156
164
|
{utils}
|
|
157
|
-
|
|
165
|
+
allowEdit
|
|
158
166
|
onLayoutChange={handleLayoutChange}
|
|
159
167
|
onChartClick={handleChartClick}
|
|
160
168
|
onReportLoaded={handleReportLoaded}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
const { utils, ...props }: ExtensionProps = $props();
|
|
6
6
|
|
|
7
|
-
let extensionPagePath = "/studio/extensions/reports/
|
|
7
|
+
let extensionPagePath = "/studio/extensions/reports/dashboards";
|
|
8
8
|
let reportId: string | null = $derived(utils.page.url.pathname.split("/")[5] ?? null);
|
|
9
9
|
let sideBarData: any = $state(null);
|
|
10
10
|
const {
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
|
|
57
57
|
</script>
|
|
58
58
|
|
|
59
|
-
<Sidebar title="
|
|
59
|
+
<Sidebar title="Dashboards" data={sideBarData}>
|
|
60
60
|
{#snippet belowSearch()}
|
|
61
61
|
<div class="p-2">
|
|
62
62
|
<CreateDetailViewButton
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
interface Props extends ExtensionProps {
|
|
8
8
|
reportId: string | number;
|
|
9
9
|
editable?: boolean;
|
|
10
|
+
allowEdit?: boolean;
|
|
11
|
+
showChartHeaders?: boolean;
|
|
10
12
|
onLayoutChange?: (changes: any[]) => void;
|
|
11
13
|
onChartClick?: (event: any, elements: any[], chart: any) => void;
|
|
12
14
|
onReportLoaded?: (report: any) => void;
|
|
@@ -16,6 +18,8 @@
|
|
|
16
18
|
const {
|
|
17
19
|
reportId,
|
|
18
20
|
editable = false,
|
|
21
|
+
allowEdit = false,
|
|
22
|
+
showChartHeaders = true,
|
|
19
23
|
onLayoutChange,
|
|
20
24
|
onChartClick,
|
|
21
25
|
onReportLoaded,
|
|
@@ -91,7 +95,7 @@
|
|
|
91
95
|
</div>
|
|
92
96
|
{:else}
|
|
93
97
|
{#key charts}
|
|
94
|
-
<GridStackComponent {editable} {onLayoutChange}>
|
|
98
|
+
<GridStackComponent {editable} {allowEdit} {onLayoutChange}>
|
|
95
99
|
{#each [...charts].sort((a, b) => (a.sort_order ?? 0) - (b.sort_order ?? 0)) as chart (chart.id)}
|
|
96
100
|
<div
|
|
97
101
|
class="grid-stack-item"
|
|
@@ -101,6 +105,7 @@
|
|
|
101
105
|
<div class="grid-stack-item-content">
|
|
102
106
|
<Chart
|
|
103
107
|
chartRecord={chart}
|
|
108
|
+
showHeader={showChartHeaders}
|
|
104
109
|
onChartDeleted={async () => await loadCharts()}
|
|
105
110
|
onChartEdited={async () => await loadCharts()}
|
|
106
111
|
{onChartClick}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lobb-js/lobb-ext-reports",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"license": "UNLICENSED",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"package": "svelte-package --input extensions/reports/studio"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@lobb-js/core": "^0.
|
|
35
|
+
"@lobb-js/core": "^0.33.0",
|
|
36
36
|
"chart.js": "^4.4.8",
|
|
37
37
|
"gridstack": "^12.6.0",
|
|
38
38
|
"hono": "^4.7.0",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@faker-js/faker": "^9.6.0",
|
|
46
|
-
"@lobb-js/studio": "^0.
|
|
46
|
+
"@lobb-js/studio": "^0.32.0",
|
|
47
47
|
"@lucide/svelte": "^0.563.1",
|
|
48
48
|
"@sveltejs/adapter-node": "^5.5.4",
|
|
49
49
|
"@sveltejs/kit": "^2.60.1",
|