From 03f9bdf73e5177576b08d18be98756bc4d45cf9e Mon Sep 17 00:00:00 2001 From: Eric P Green Date: Wed, 8 Nov 2023 10:59:45 -0500 Subject: [PATCH] Cloud UI: create page for a project's Scheduled Reports (#3241) * Display a project's dashboards in a table * Refactor `Tag` component to accept a `color` prop * Change `Search` component to white color * Add `ContentContainer` helper layout component * Edit copy * Create `Tab` components using `svelte-headlessui` * Use tabs to navigate between a project's subroutes * Display scheduled reports in a table * Move file * Rename component * Add icon * Add file * Fix import * Remove project hero * Add `ContentContainer` * Fix issues with `dashboards/listing` directory * Add status badge to "Logs" tab * Hide "Reports" tab for now * Fix import * Fix CSS selector * Fix import * Match Figma mocks * Only show Logs tab to admins * Add fallback/localized error for dashboards table * Add URL slug to dashboard table row * Better sort copy * Restore the `ProjectHero` while no project tabs * Better "last refresh" date * Fix up sorting behavior * Move type, add comment * Make the click target bigger * Add tooltip for last refresh date * Create dedicated slot for an empty table * Nit * Make whole cell clickable * Fix svelte-check * Better typing * Decompose object * Move `ProjectTabs` into runtime context * Fix refresh of logs page * Remove old padding * Center the dashboards table * Add "No logs" view * Bugfix * Use `$table` not `get(table)` * Reset table filter when changing projects * Hide sort button for now * Show reports tab * Fix row padding * Rename "InfoCell" -> "CompositeCell" * Center the table * Match dashboards table * Fix padding on other Search instances * Use `luxon` * Add report icon; make entire row clickable * Use `CheckCircleOutline` icon * Clean up header * Add table empty state * Adjust left padding in table header * Creating `listing` subdirectory * Remove redirect from project to dashboard * Fetch reports from runtime * Add error state * Add 0 reports state * Move code * Remove unused props * Move data fetching to `selectors.ts` * Format report `owner` * Account for reports not-yet-run * Use `cronstrue` to translate cron expressions to natural language * Add owner's name; fix status badge * Fix lint --- package-lock.json | 10 ++ web-admin/package.json | 3 +- .../listing/DashboardsTableHeader.svelte | 2 +- .../src/features/projects/ProjectTabs.svelte | 9 +- .../listing/NoReportsCTA.svelte | 25 +++++ .../listing/ReportsError.svelte | 15 +++ .../listing/ReportsTable.svelte | 90 +++++++++++++++ .../listing/ReportsTableActionCell.svelte | 16 +++ .../listing/ReportsTableCompositeCell.svelte | 54 +++++++++ .../listing/ReportsTableEmpty.svelte | 1 + .../listing/ReportsTableHeader.svelte | 103 ++++++++++++++++++ .../features/scheduled-reports/selectors.ts | 18 +++ .../features/scheduled-reports/tableUtils.ts | 20 ++++ .../[project]/-/reports/+page.svelte | 17 +++ .../icons/CancelCircleInverse.svelte | 4 +- .../icons/CheckCircleOutline.svelte | 24 ++++ .../src/components/icons/ReportIcon.svelte | 20 ++++ .../src/components/icons/ThreeDot.svelte | 18 +++ .../entity-management/resource-selectors.ts | 1 + 19 files changed, 441 insertions(+), 9 deletions(-) create mode 100644 web-admin/src/features/scheduled-reports/listing/NoReportsCTA.svelte create mode 100644 web-admin/src/features/scheduled-reports/listing/ReportsError.svelte create mode 100644 web-admin/src/features/scheduled-reports/listing/ReportsTable.svelte create mode 100644 web-admin/src/features/scheduled-reports/listing/ReportsTableActionCell.svelte create mode 100644 web-admin/src/features/scheduled-reports/listing/ReportsTableCompositeCell.svelte create mode 100644 web-admin/src/features/scheduled-reports/listing/ReportsTableEmpty.svelte create mode 100644 web-admin/src/features/scheduled-reports/listing/ReportsTableHeader.svelte create mode 100644 web-admin/src/features/scheduled-reports/selectors.ts create mode 100644 web-admin/src/features/scheduled-reports/tableUtils.ts create mode 100644 web-admin/src/routes/[organization]/[project]/-/reports/+page.svelte create mode 100644 web-common/src/components/icons/CheckCircleOutline.svelte create mode 100644 web-common/src/components/icons/ReportIcon.svelte create mode 100644 web-common/src/components/icons/ThreeDot.svelte diff --git a/package-lock.json b/package-lock.json index 378b7ff9a84..31f5cd4fc73 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23176,6 +23176,15 @@ "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==", "dev": true }, + "node_modules/cronstrue": { + "version": "2.41.0", + "resolved": "https://registry.npmjs.org/cronstrue/-/cronstrue-2.41.0.tgz", + "integrity": "sha512-3ZS3eMJaxMRBGmDauKCKbyIRgVcph6uSpkhSbbZvvJWkelHiSTzGJbBqmu8io7Hspd2F45bQKnC1kzoNvtku2g==", + "dev": true, + "bin": { + "cronstrue": "bin/cli.js" + } + }, "node_modules/cross-fetch": { "version": "3.1.6", "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.6.tgz", @@ -44716,6 +44725,7 @@ "autoprefixer": "^10.4.13", "axios": "^0.27.2", "cookie": "^0.4.1", + "cronstrue": "^2.41.0", "eslint": "^8.16.0", "eslint-config-prettier": "^8.3.0", "eslint-plugin-svelte3": "^4.0.0", diff --git a/web-admin/package.json b/web-admin/package.json index 4555e75c380..2357ce09cf4 100644 --- a/web-admin/package.json +++ b/web-admin/package.json @@ -16,9 +16,9 @@ "@fontsource/fira-mono": "^4.5.0", "@playwright/test": "^1.25.0", "@rgossiaux/svelte-headlessui": "^2.0.0", + "@rilldata/svelte-query": "^4.29.20-0.0.1", "@sveltejs/adapter-static": "^1.0.0", "@sveltejs/kit": "^1.5.0", - "@rilldata/svelte-query": "^4.29.20-0.0.1", "@tanstack/svelte-query": "npm:@rilldata/svelte-query@4.29.20-0.0.1", "@types/cookie": "^0.5.1", "@typescript-eslint/eslint-plugin": "^5.27.0", @@ -26,6 +26,7 @@ "autoprefixer": "^10.4.13", "axios": "^0.27.2", "cookie": "^0.4.1", + "cronstrue": "^2.41.0", "eslint": "^8.16.0", "eslint-config-prettier": "^8.3.0", "eslint-plugin-svelte3": "^4.0.0", diff --git a/web-admin/src/features/dashboards/listing/DashboardsTableHeader.svelte b/web-admin/src/features/dashboards/listing/DashboardsTableHeader.svelte index a2aeb00efe7..33f3e5fb07b 100644 --- a/web-admin/src/features/dashboards/listing/DashboardsTableHeader.svelte +++ b/web-admin/src/features/dashboards/listing/DashboardsTableHeader.svelte @@ -46,7 +46,7 @@ class="pl-2 pr-4 py-2 max-w-[800px] flex items-center gap-x-2 bg-slate-100" > -
+
diff --git a/web-admin/src/features/projects/ProjectTabs.svelte b/web-admin/src/features/projects/ProjectTabs.svelte index 931297b00fc..526bf1d6e30 100644 --- a/web-admin/src/features/projects/ProjectTabs.svelte +++ b/web-admin/src/features/projects/ProjectTabs.svelte @@ -19,11 +19,10 @@ route: `/${organization}/${project}`, label: "Dashboards", }, - // Hide until releasing Scheduled Reports - // { - // route: `/${organization}/${project}/-/reports`, - // label: "Reports", - // }, + { + route: `/${organization}/${project}/-/reports`, + label: "Reports", + }, ]; const adminTabs = [ { diff --git a/web-admin/src/features/scheduled-reports/listing/NoReportsCTA.svelte b/web-admin/src/features/scheduled-reports/listing/NoReportsCTA.svelte new file mode 100644 index 00000000000..a013d36ccf7 --- /dev/null +++ b/web-admin/src/features/scheduled-reports/listing/NoReportsCTA.svelte @@ -0,0 +1,25 @@ + + +
+
+
+ +
+
+
+
+ You don't have any reports yet +
+
+ Learn how to create a report in our + docs +
+
+
diff --git a/web-admin/src/features/scheduled-reports/listing/ReportsError.svelte b/web-admin/src/features/scheduled-reports/listing/ReportsError.svelte new file mode 100644 index 00000000000..6d794cc43c2 --- /dev/null +++ b/web-admin/src/features/scheduled-reports/listing/ReportsError.svelte @@ -0,0 +1,15 @@ + + +
+
+
Error loading reports
+
+ If this error persists, please contact support. +
+
+
diff --git a/web-admin/src/features/scheduled-reports/listing/ReportsTable.svelte b/web-admin/src/features/scheduled-reports/listing/ReportsTable.svelte new file mode 100644 index 00000000000..41ea991722a --- /dev/null +++ b/web-admin/src/features/scheduled-reports/listing/ReportsTable.svelte @@ -0,0 +1,90 @@ + + +{#if $reports.isLoading} +
+ +
+{:else if $reports.isError} + +{:else if $reports.isSuccess} + {#if $reports.data.resources.length === 0} + + {:else} + + + +
+ {/if} +{/if} diff --git a/web-admin/src/features/scheduled-reports/listing/ReportsTableActionCell.svelte b/web-admin/src/features/scheduled-reports/listing/ReportsTableActionCell.svelte new file mode 100644 index 00000000000..924bf3c7d95 --- /dev/null +++ b/web-admin/src/features/scheduled-reports/listing/ReportsTableActionCell.svelte @@ -0,0 +1,16 @@ + + + + + + + diff --git a/web-admin/src/features/scheduled-reports/listing/ReportsTableCompositeCell.svelte b/web-admin/src/features/scheduled-reports/listing/ReportsTableCompositeCell.svelte new file mode 100644 index 00000000000..2395f9970e8 --- /dev/null +++ b/web-admin/src/features/scheduled-reports/listing/ReportsTableCompositeCell.svelte @@ -0,0 +1,54 @@ + + + +
+ +
+ {title} +
+ {#if lastRun} + {#if lastRunErrorMessage} + + {:else} + + {/if} + {/if} +
+
+ {#if !lastRun} + Hasn't run yet + {:else} + Last run {formatDateToCustomString(new Date(lastRun))} + {/if} + + {humanReadableFrequency} + + Created by {owner?.userName || "a project admin"} +
+
diff --git a/web-admin/src/features/scheduled-reports/listing/ReportsTableEmpty.svelte b/web-admin/src/features/scheduled-reports/listing/ReportsTableEmpty.svelte new file mode 100644 index 00000000000..aee2025eb5d --- /dev/null +++ b/web-admin/src/features/scheduled-reports/listing/ReportsTableEmpty.svelte @@ -0,0 +1 @@ + No reports found. diff --git a/web-admin/src/features/scheduled-reports/listing/ReportsTableHeader.svelte b/web-admin/src/features/scheduled-reports/listing/ReportsTableHeader.svelte new file mode 100644 index 00000000000..7aca7d5a172 --- /dev/null +++ b/web-admin/src/features/scheduled-reports/listing/ReportsTableHeader.svelte @@ -0,0 +1,103 @@ + + + + + + +
+ +
+ + +
+ + + + + + {numReports} report{numReports !== 1 ? "s" : ""} + + + + + + + + + diff --git a/web-admin/src/features/scheduled-reports/selectors.ts b/web-admin/src/features/scheduled-reports/selectors.ts new file mode 100644 index 00000000000..0068a982bb1 --- /dev/null +++ b/web-admin/src/features/scheduled-reports/selectors.ts @@ -0,0 +1,18 @@ +import { ResourceKind } from "@rilldata/web-common/features/entity-management/resource-selectors"; +import { + createRuntimeServiceGetResource, + createRuntimeServiceListResources, +} from "@rilldata/web-common/runtime-client"; + +export function useReports(instanceId: string) { + return createRuntimeServiceListResources(instanceId, { + kind: ResourceKind.Report, + }); +} + +export function useReport(instanceId: string, name: string) { + return createRuntimeServiceGetResource(instanceId, { + "name.name": name, + "name.kind": ResourceKind.Report, + }); +} diff --git a/web-admin/src/features/scheduled-reports/tableUtils.ts b/web-admin/src/features/scheduled-reports/tableUtils.ts new file mode 100644 index 00000000000..120ce06f09c --- /dev/null +++ b/web-admin/src/features/scheduled-reports/tableUtils.ts @@ -0,0 +1,20 @@ +export function formatDateToCustomString(date: Date) { + const formattedDate = date.toLocaleString("en-US", { + month: "short", + day: "2-digit", + year: "numeric", + hour: "numeric", + minute: "2-digit", + hour12: true, + }); + + const dateParts = formattedDate.split(", "); + dateParts[0] = dateParts[0] + " " + dateParts[1]; + dateParts.splice(1, 1); + + return dateParts.join(", ").replace(" PM", "pm").replace(" AM", "am"); +} + +export function capitalizeFirstLetter(string: string) { + return string.charAt(0).toUpperCase() + string.slice(1); +} diff --git a/web-admin/src/routes/[organization]/[project]/-/reports/+page.svelte b/web-admin/src/routes/[organization]/[project]/-/reports/+page.svelte new file mode 100644 index 00000000000..0421bd8f15d --- /dev/null +++ b/web-admin/src/routes/[organization]/[project]/-/reports/+page.svelte @@ -0,0 +1,17 @@ + + + + +
+ +
+
+
diff --git a/web-common/src/components/icons/CancelCircleInverse.svelte b/web-common/src/components/icons/CancelCircleInverse.svelte index a99ae2f3075..935f5510b82 100644 --- a/web-common/src/components/icons/CancelCircleInverse.svelte +++ b/web-common/src/components/icons/CancelCircleInverse.svelte @@ -13,12 +13,12 @@ > diff --git a/web-common/src/components/icons/CheckCircleOutline.svelte b/web-common/src/components/icons/CheckCircleOutline.svelte new file mode 100644 index 00000000000..c492fe5d8b0 --- /dev/null +++ b/web-common/src/components/icons/CheckCircleOutline.svelte @@ -0,0 +1,24 @@ + + + + + + diff --git a/web-common/src/components/icons/ReportIcon.svelte b/web-common/src/components/icons/ReportIcon.svelte new file mode 100644 index 00000000000..ba424c2e67f --- /dev/null +++ b/web-common/src/components/icons/ReportIcon.svelte @@ -0,0 +1,20 @@ + + + + + diff --git a/web-common/src/components/icons/ThreeDot.svelte b/web-common/src/components/icons/ThreeDot.svelte new file mode 100644 index 00000000000..fb6e73db706 --- /dev/null +++ b/web-common/src/components/icons/ThreeDot.svelte @@ -0,0 +1,18 @@ + + + + + diff --git a/web-common/src/features/entity-management/resource-selectors.ts b/web-common/src/features/entity-management/resource-selectors.ts index 400a8cfa459..f140a90fd6d 100644 --- a/web-common/src/features/entity-management/resource-selectors.ts +++ b/web-common/src/features/entity-management/resource-selectors.ts @@ -15,6 +15,7 @@ export enum ResourceKind { Source = "rill.runtime.v1.Source", Model = "rill.runtime.v1.Model", MetricsView = "rill.runtime.v1.MetricsView", + Report = "rill.runtime.v1.Report", } export const SingletonProjectParserName = "parser";