@agent-native/core-corpus 0.210.2 → 0.211.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/corpus/README.md +1 -1
- package/corpus/templates/analytics/.agents/skills/actions/references/action-fields.md +5 -3
- package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +41 -2
- package/corpus/templates/analytics/.agents/skills/data-querying/SKILL.md +4 -2
- package/corpus/templates/analytics/CHANGELOG.md +18 -0
- package/corpus/templates/analytics/actions/build-data-index.ts +11 -2
- package/corpus/templates/analytics/actions/configure-content-collection.ts +29 -4
- package/corpus/templates/analytics/actions/configure-first-party-events-model.ts +9 -7
- package/corpus/templates/analytics/actions/create-analytics-public-key.ts +4 -0
- package/corpus/templates/analytics/actions/estimate-content-signups-cost.ts +72 -0
- package/corpus/templates/analytics/actions/estimate-query-cost.ts +16 -134
- package/corpus/templates/analytics/actions/list-sigma-workbooks.ts +123 -42
- package/corpus/templates/analytics/actions/migrate-sigma-workbook.ts +20 -17
- package/corpus/templates/analytics/actions/revoke-analytics-public-key.ts +4 -0
- package/corpus/templates/analytics/app/components/dashboard/SigmaMigrationDialog.tsx +246 -680
- package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +1 -1
- package/corpus/templates/analytics/app/i18n/en-US.ts +54 -55
- package/corpus/templates/analytics/app/pages/DashboardOverview.tsx +0 -1
- package/corpus/templates/analytics/app/pages/DataSources.tsx +56 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/filter-vars.ts +4 -3
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +26 -8
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/interpolate.ts +3 -1
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/panel-cost-notice.ts +172 -0
- package/corpus/templates/analytics/app/pages/data-sources/SearchConsoleDetail.tsx +441 -0
- package/corpus/templates/analytics/changelog/2026-10-15-a-content-dashboard-lists-every-blog-article-with-its-author.md +6 -0
- package/corpus/templates/analytics/changelog/2026-10-15-data-sources-has-a-google-search-console-card-that-shows-the.md +6 -0
- package/corpus/templates/analytics/changelog/2026-10-16-the-content-dashboard-can-show-signups-per-blog-article.md +6 -0
- package/corpus/templates/analytics/changelog/2026-10-19-creating-or-revoking-a-public-analytics-key-now-asks-you-to-.md +6 -0
- package/corpus/templates/analytics/changelog/2026-10-19-search-sigma-workbooks-by-name-and-open-ones-a-teammate-alre.md +6 -0
- package/corpus/templates/analytics/changelog/2026-10-20-blog-signup-attribution-excludes-signups-before-the-first-vi.md +6 -0
- package/corpus/templates/analytics/changelog/2026-10-20-collapsed-analytics-navigation-starts-at-the-top.md +6 -0
- package/corpus/templates/analytics/changelog/2026-10-20-content-keeps-successful-coverage-dates-and-missing-day-gaps.md +6 -0
- package/corpus/templates/analytics/changelog/2026-10-20-private-github-repositories-can-be-indexed-with-your-connect.md +6 -0
- package/corpus/templates/analytics/changelog/2026-10-20-search-console-preserves-backfill-and-paused-collection.md +6 -0
- package/corpus/templates/analytics/changelog/2026-10-20-search-console-refreshes-historical-coverage-when-properties.md +6 -0
- package/corpus/templates/analytics/changelog/2026-10-20-search-console-setup-keeps-saved-properties-readable-and-sup.md +6 -0
- package/corpus/templates/analytics/seeds/dashboards/content-view.json +182 -0
- package/corpus/templates/analytics/server/db/schema.ts +1 -0
- package/corpus/templates/analytics/server/lib/bi-migration/external-dashboards-store.ts +56 -18
- package/corpus/templates/analytics/server/lib/bi-migration/unfinished-import.ts +64 -0
- package/corpus/templates/analytics/server/lib/builtin-dashboard-pass.ts +17 -2
- package/corpus/templates/analytics/server/lib/content-metrics/collect.ts +2 -0
- package/corpus/templates/analytics/server/lib/content-metrics/config.ts +92 -3
- package/corpus/templates/analytics/server/lib/content-metrics/legacy-content-view.json +172 -0
- package/corpus/templates/analytics/server/lib/content-metrics/migration.ts +2 -0
- package/corpus/templates/analytics/server/lib/content-metrics/steps/daily-metrics.ts +8 -3
- package/corpus/templates/analytics/server/lib/content-metrics/steps/search-console.ts +16 -3
- package/corpus/templates/analytics/server/lib/content-metrics/steps/signups.ts +240 -0
- package/corpus/templates/analytics/server/lib/content-metrics/store.ts +5 -0
- package/corpus/templates/analytics/server/lib/dashboard-catalog.ts +21 -0
- package/corpus/templates/analytics/server/lib/dashboard-panel-verification.ts +300 -5
- package/corpus/templates/analytics/server/lib/dashboard-seeds.ts +2 -0
- package/corpus/templates/analytics/server/lib/dashboards-store.ts +28 -0
- package/corpus/templates/analytics/server/lib/first-party-analytics-backend.ts +128 -24
- package/corpus/templates/analytics/server/lib/google-analytics.ts +56 -16
- package/corpus/templates/analytics/server/lib/index-build-github.ts +5 -0
- package/corpus/templates/analytics/server/lib/provider-credentials.ts +47 -2
- package/corpus/templates/analytics/server/lib/query-cost-estimate.ts +184 -5
- package/corpus/templates/analytics/server/plugins/db.ts +9 -1
- package/corpus/templates/analytics/shared/builtin-dashboard-ids.ts +2 -0
- package/corpus/templates/analytics/shared/search-console-property.ts +78 -0
- package/corpus/templates/assets/.agents/skills/actions/references/action-fields.md +5 -3
- package/corpus/templates/brain/.agents/skills/actions/references/action-fields.md +5 -3
- package/corpus/templates/calendar/.agents/skills/actions/references/action-fields.md +5 -3
- package/corpus/templates/calendar/actions/delete-event.ts +1 -3
- package/corpus/templates/calendar/actions/delete-events.ts +1 -2
- package/corpus/templates/calendar/actions/event-action-helpers.ts +0 -4
- package/corpus/templates/calendar/actions/update-event.ts +2 -6
- package/corpus/templates/chat/.agents/skills/actions/references/action-fields.md +5 -3
- package/corpus/templates/clips/.agents/skills/actions/references/action-fields.md +5 -3
- package/corpus/templates/clips/CHANGELOG.md +11 -24
- package/corpus/templates/clips/actions/create-recording-upload.ts +5 -1
- package/corpus/templates/clips/app/components/player/chapter-list.tsx +7 -6
- package/corpus/templates/clips/app/components/player/comment-composer.tsx +5 -1
- package/corpus/templates/clips/app/components/player/player-controls.tsx +1 -0
- package/corpus/templates/clips/app/components/player/scrubber.tsx +4 -2
- package/corpus/templates/clips/app/i18n/en-US.ts +3 -3
- package/corpus/templates/clips/app/lib/extension-auth.ts +70 -0
- package/corpus/templates/clips/app/root.tsx +22 -10
- package/corpus/templates/clips/app/routes/_app.notifications.tsx +3 -0
- package/corpus/templates/clips/app/routes/import.tsx +115 -110
- package/corpus/templates/clips/changelog/2026-10-16-agent-uploads-can-use-free-clips-storage.md +6 -0
- package/corpus/templates/clips/changelog/2026-10-19-agents-see-a-clip-s-frames-as-soon-as-it-finishes-processing.md +6 -0
- package/corpus/templates/clips/changelog/2026-10-19-chapter-list-times-now-match-the-player-s-clock-and-each-cha.md +6 -0
- package/corpus/templates/clips/changelog/2026-10-19-editing-a-comment-puts-the-cursor-after-its-text-and-reply-o.md +6 -0
- package/corpus/templates/clips/changelog/2026-10-19-youtube-import-followups.md +6 -0
- package/corpus/templates/clips/changelog/2026-10-20-locally-saved-desktop-recordings-now-appear-in-the-recovery-.md +6 -0
- package/corpus/templates/clips/desktop/src/app.tsx +29 -186
- package/corpus/templates/clips/desktop/src/components/RecordingRecovery.tsx +171 -16
- package/corpus/templates/clips/desktop/src/lib/record-first.ts +9 -32
- package/corpus/templates/clips/desktop/src/lib/recording-recovery.ts +30 -1
- package/corpus/templates/clips/server/lib/agent-frame-request.ts +5 -2
- package/corpus/templates/clips/server/lib/recording-upload-ticket.ts +8 -0
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +2 -0
- package/corpus/templates/content/.agents/skills/actions/references/action-fields.md +5 -3
- package/corpus/templates/content/CHANGELOG.md +12 -0
- package/corpus/templates/content/actions/_content-database-personal-view.ts +5 -15
- package/corpus/templates/content/actions/_database-navigation.ts +3 -3
- package/corpus/templates/content/actions/_database-utils.ts +10 -22
- package/corpus/templates/content/actions/_database-view-setup.ts +16 -76
- package/corpus/templates/content/actions/create-comment-ai-suggestion.ts +93 -44
- package/corpus/templates/content/actions/export-document.ts +2 -14
- package/corpus/templates/content/actions/suggest-document-edit.ts +5 -7
- package/corpus/templates/content/actions/update-content-database-view.ts +2 -14
- package/corpus/templates/content/app/components/editor/CommentsSidebar.tsx +86 -30
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +64 -28
- package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +6 -46
- package/corpus/templates/content/app/components/editor/PageDraftRecovery.tsx +72 -7
- package/corpus/templates/content/app/components/editor/TableHoverControls.tsx +292 -103
- package/corpus/templates/content/app/components/editor/ThreadSuggestion.tsx +147 -40
- package/corpus/templates/content/app/components/editor/VisualEditor.tsx +108 -11
- package/corpus/templates/content/app/components/editor/database/ContentTable.tsx +2 -1
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +150 -166
- package/corpus/templates/content/app/components/editor/database/database-cell-comments.tsx +515 -0
- package/corpus/templates/content/app/components/editor/database/database-item-comments.tsx +5 -43
- package/corpus/templates/content/app/components/editor/database/filter-sort.ts +8 -783
- package/corpus/templates/content/app/components/editor/live-editor-session.ts +5 -1
- package/corpus/templates/content/app/components/editor/page-draft-journal.ts +61 -1
- package/corpus/templates/content/app/components/editor/property-comments.tsx +35 -4
- package/corpus/templates/content/app/components/editor/property-date-format.ts +45 -0
- package/corpus/templates/content/app/components/editor/thread-suggestions.ts +30 -0
- package/corpus/templates/content/app/components/editor/useDocumentReconcileRecovery.ts +23 -4
- package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +36 -10
- package/corpus/templates/content/app/global.css +23 -0
- package/corpus/templates/content/app/i18n-data.ts +1 -0
- package/corpus/templates/content/changelog/2026-10-16-opening-a-page-in-a-new-tab-while-another-tab-is-still-savin.md +6 -0
- package/corpus/templates/content/changelog/2026-10-16-typing-steadily-in-one-tab-no-longer-makes-another-open-tab-.md +6 -0
- package/corpus/templates/content/changelog/2026-10-19-database-views-can-filter-the-same-property-more-than-once-n.md +6 -0
- package/corpus/templates/content/changelog/2026-10-19-edits-made-from-a-block-s-menu-or-a-table-s-row-and-column-m.md +6 -0
- package/corpus/templates/content/changelog/2026-10-19-starting-a-comment-puts-the-cursor-in-the-comment-box-ready-.md +6 -0
- package/corpus/templates/content/changelog/2026-10-19-table-row-and-column-options-now-appear-for-the-row-or-colum.md +6 -0
- package/corpus/templates/content/docs/product/capabilities/content.comment.page-owned.md +5 -1
- package/corpus/templates/content/docs/product/capabilities/content.revision.suggestions.md +3 -1
- package/corpus/templates/content/e2e/helpers.ts +10 -0
- package/corpus/templates/content/shared/api.ts +18 -12
- package/corpus/templates/content/shared/database-query.ts +107 -13
- package/corpus/templates/content/shared/suggestion-diff.ts +35 -0
- package/corpus/templates/crm/.agents/skills/actions/references/action-fields.md +5 -3
- package/corpus/templates/design/.agents/skills/actions/references/action-fields.md +5 -3
- package/corpus/templates/design/CHANGELOG.md +2 -0
- package/corpus/templates/design/actions/delete-file.ts +1 -4
- package/corpus/templates/design/app/components/design/LayersPanel.tsx +10 -1
- package/corpus/templates/design/app/components/visual-editor/ReviewCanvasPins.tsx +24 -4
- package/corpus/templates/design/app/pages/design-editor/commands/mode-change.ts +14 -9
- package/corpus/templates/design/app/pages/design-editor/domains/use-editor-modes.ts +23 -2
- package/corpus/templates/design/app/pages/design-editor/tool-state.ts +29 -0
- package/corpus/templates/design/changelog/2026-10-19-reply-and-edit-on-a-comment-in-the-sidebar-or-on-a-canvas-pi.md +6 -0
- package/corpus/templates/design/changelog/2026-10-20-comments-return-to-the-mode-they-came-from-and-layers-search.md +6 -0
- package/corpus/templates/design/e2e/global-setup.ts +185 -85
- package/corpus/templates/dispatch/.agents/skills/actions/references/action-fields.md +5 -3
- package/corpus/templates/dispatch/CHANGELOG.md +4 -0
- package/corpus/templates/dispatch/actions/apply-workspace-connection-setup.ts +4 -0
- package/corpus/templates/dispatch/actions/delete-workspace-connection.ts +4 -0
- package/corpus/templates/dispatch/actions/set-workspace-connection-grant.ts +4 -0
- package/corpus/templates/dispatch/actions/upsert-workspace-connection.ts +4 -0
- package/corpus/templates/dispatch/changelog/2026-10-19-vault-secrets-grants-link-tokens-app-access-and-workspace-co.md +6 -0
- package/corpus/templates/factory/.agents/skills/actions/references/action-fields.md +5 -3
- package/corpus/templates/factory/.agents/skills/review-latest-feedback/SKILL.md +105 -33
- package/corpus/templates/factory/.agents/skills/review-latest-feedback/references/channel-inventory.md +128 -74
- package/corpus/templates/factory/.agents/skills/review-latest-feedback/references/slack-reaction-replies.md +39 -11
- package/corpus/templates/forms/.agents/skills/actions/references/action-fields.md +5 -3
- package/corpus/templates/mail/.agents/skills/actions/references/action-fields.md +5 -3
- package/corpus/templates/mail/.agents/skills/contacts-and-crm/SKILL.md +22 -17
- package/corpus/templates/mail/AGENTS.md +1 -1
- package/corpus/templates/mail/CHANGELOG.md +14 -0
- package/corpus/templates/mail/actions/provider-api-request.ts +3 -3
- package/corpus/templates/mail/app/components/email/IntegrationsSidebar.tsx +211 -54
- package/corpus/templates/mail/app/hooks/use-inbox-threads.ts +26 -2
- package/corpus/templates/mail/app/i18n/en-US.ts +5 -1
- package/corpus/templates/mail/app/lib/gong-connection.ts +27 -0
- package/corpus/templates/mail/app/lib/thread-cache.ts +110 -9
- package/corpus/templates/mail/app/root.tsx +10 -2
- package/corpus/templates/mail/changelog/2026-10-20-connected-gong-settings-now-open-the-shared-integration-mana.md +6 -0
- package/corpus/templates/mail/changelog/2026-10-20-gong-connection-details-now-match-the-call-data-available-in.md +6 -0
- package/corpus/templates/mail/changelog/2026-10-20-improve-gong-setup-with-a-click-to-connect-flow-and-permissi.md +6 -0
- package/corpus/templates/mail/changelog/2026-10-20-mail-lets-you-retry-when-it-cannot-check-your-gong-access.md +6 -0
- package/corpus/templates/mail/changelog/2026-10-20-mail-thread-cache-stays-isolated-when-mailbox-scope-changes.md +6 -0
- package/corpus/templates/mail/changelog/2026-10-20-the-inbox-and-recently-opened-threads-now-open-instantly-fro.md +6 -0
- package/corpus/templates/mail/server/handlers/emails.ts +9 -0
- package/corpus/templates/plan/.agents/skills/actions/references/action-fields.md +5 -3
- package/corpus/templates/plan/CHANGELOG.md +4 -0
- package/corpus/templates/plan/app/pages/PlansPage.tsx +173 -33
- package/corpus/templates/plan/changelog/2026-10-19-opening-a-comment-thread-puts-the-cursor-in-the-reply-box-an.md +6 -0
- package/corpus/templates/slides/.agents/skills/actions/references/action-fields.md +5 -3
- package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +6 -42
- package/corpus/templates/slides/app/components/editor/FloatingToolbar.tsx +3 -7
- package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +5 -14
- package/corpus/templates/slides/app/i18n/en-US.ts +0 -2
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +27 -33
- package/corpus/templates/slides/scripts/edit-fidelity/run.ts +1 -3
- package/corpus/templates/tasks/.agents/skills/actions/references/action-fields.md +5 -3
- package/package.json +1 -1
|
@@ -2450,7 +2450,7 @@ export function Sidebar({ mobile }: { mobile?: boolean } = {}) {
|
|
|
2450
2450
|
collapsed={true}
|
|
2451
2451
|
/>
|
|
2452
2452
|
<nav className="flex min-h-0 flex-1 flex-col items-center overflow-y-auto px-2 py-3">
|
|
2453
|
-
<div className="
|
|
2453
|
+
<div className="flex flex-col items-center gap-3">
|
|
2454
2454
|
{collapsedNavItems.map((item) => {
|
|
2455
2455
|
const Icon = item.icon;
|
|
2456
2456
|
return (
|
|
@@ -321,65 +321,19 @@ export default {
|
|
|
321
321
|
sigmaRetryConnection: "Check the connection and try again.",
|
|
322
322
|
sigmaRetry: "Retry",
|
|
323
323
|
sigmaNoWorkbooks: "No Sigma workbooks are available.",
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
324
|
+
sigmaSearchWorkbooks: "Search workbooks…",
|
|
325
|
+
sigmaNoMatchingWorkbooks: "No matching workbooks",
|
|
326
|
+
sigmaNoMatchingWorkbooksLoadMore:
|
|
327
|
+
"No matching workbooks loaded yet. Load more workbooks to search the rest.",
|
|
328
|
+
sigmaOpenMigratedDashboard: "Open the imported dashboard in a new tab",
|
|
329
|
+
sigmaOwnedBy: "Owned by",
|
|
330
|
+
sigmaMigratingTooltip: "Being migrated by {{name}}",
|
|
331
|
+
sigmaMigrate: "Migrate",
|
|
332
|
+
sigmaMigrateComingSoon: "Coming soon",
|
|
330
333
|
sigmaArchived: "Archived",
|
|
331
|
-
sigmaPreviewTitle: "Workbook contents",
|
|
332
|
-
sigmaPreviewFailed: "Couldn't preview this workbook",
|
|
333
|
-
sigmaRetryPreview: "Try again to load the workbook preview.",
|
|
334
|
-
sigmaImportableCount: "{{count}} importable",
|
|
335
|
-
sigmaUnsupportedCount: "{{count}} unsupported",
|
|
336
|
-
sigmaImportable: "Importable",
|
|
337
|
-
sigmaUnsupported: "Unsupported",
|
|
338
|
-
sigmaNoElements: "No workbook elements were found.",
|
|
339
|
-
sigmaPreviewOnly:
|
|
340
|
-
"Preview only for now: Sigma does not expose a BigQuery project mapping Analytics can verify, so importing is disabled until that mapping is available.",
|
|
341
|
-
sigmaPreviewTruncated: "Showing {{shown}} of {{total}} elements.",
|
|
342
|
-
sigmaImportWorkbook: "Import workbook",
|
|
343
|
-
sigmaImporting: "Importing workbook…",
|
|
344
|
-
sigmaImportInProgress:
|
|
345
|
-
"An import for this workbook is already in progress.",
|
|
346
|
-
sigmaMigrationFailed: "Couldn't import this workbook. Try again.",
|
|
347
|
-
sigmaMigrationResultInvalid:
|
|
348
|
-
"The import response was incomplete. Reload the workbook list to check its status.",
|
|
349
|
-
sigmaImportComplete: "Dashboard created",
|
|
350
|
-
sigmaImportSummary:
|
|
351
|
-
"{{imported}} elements imported; {{unsupported}} unsupported elements retained as review callouts.",
|
|
352
|
-
sigmaAlreadyImported: "This workbook has already been imported.",
|
|
353
|
-
sigmaImportedNoDashboard:
|
|
354
|
-
"The imported dashboard isn't available to open from this account.",
|
|
355
|
-
sigmaReviewRequiredCount: "{{count}} need review",
|
|
356
|
-
sigmaBetaRepresentationWarning:
|
|
357
|
-
"This beta import may represent some visualizations differently from Sigma.",
|
|
358
|
-
sigmaReasonUnsupportedElement:
|
|
359
|
-
"This element type is not supported for import.",
|
|
360
|
-
sigmaReasonUnsupportedSource:
|
|
361
|
-
"This element uses a source that is not supported for import.",
|
|
362
|
-
sigmaReasonSourceMappingRequired:
|
|
363
|
-
"Sigma doesn't expose a BigQuery project mapping Analytics can verify, so this SQL can't be imported yet.",
|
|
364
|
-
sigmaReasonSourceUnavailable:
|
|
365
|
-
"Sigma didn't allow access to this source connection. Check its permissions.",
|
|
366
|
-
sigmaReasonQueryUnavailable:
|
|
367
|
-
"Sigma did not return an importable query for this element.",
|
|
368
|
-
sigmaReasonUnsafeSql: "This query pattern cannot be imported safely.",
|
|
369
|
-
sigmaReasonVisualizationReview:
|
|
370
|
-
"The data can be imported, but verify its visualization after import.",
|
|
371
|
-
sigmaRetryOwnerOnly:
|
|
372
|
-
"Only the person who started this import can retry it.",
|
|
373
|
-
sigmaImportedByTeammate: "Imported by a teammate",
|
|
374
|
-
sigmaFlaggedCount: "{{count}} flagged for review",
|
|
375
334
|
sigmaMoreWorkbooksFailed: "Could not load more workbooks",
|
|
376
335
|
sigmaLoadingMore: "Loading workbooks…",
|
|
377
336
|
sigmaLoadMore: "Load more workbooks",
|
|
378
|
-
sigmaOpenDashboard: "Open dashboard",
|
|
379
|
-
sigmaReviewWithAgent: "Review with agent",
|
|
380
|
-
sigmaReviewContext: "Imported Sigma dashboard",
|
|
381
|
-
sigmaReviewPrompt:
|
|
382
|
-
"Review the imported Sigma dashboard {{name}}. Check the imported panels and unsupported elements, then suggest any corrections.",
|
|
383
337
|
},
|
|
384
338
|
sidebar: {
|
|
385
339
|
collapseSidebar: "Collapse sidebar",
|
|
@@ -938,6 +892,13 @@ export default {
|
|
|
938
892
|
viewOnly: "You have view-only access to this dashboard.",
|
|
939
893
|
saveFailed: "Couldn't save dashboard",
|
|
940
894
|
saveFailedWithMessage: "Couldn't save dashboard: {{message}}",
|
|
895
|
+
panelCostTitle: 'BigQuery cost of "{{title}}"',
|
|
896
|
+
panelCostOverTitle: '"{{title}}" is expensive to refresh',
|
|
897
|
+
panelCostScheduled:
|
|
898
|
+
"~{{size}} scanned · {{perRun}} per refresh · ~{{perMonth}}/month in email reports",
|
|
899
|
+
panelCostUnscheduled:
|
|
900
|
+
"~{{size}} scanned · {{perRun}} per refresh · no email reports",
|
|
901
|
+
panelCostOverHint: "A narrower date range or fewer columns scans less.",
|
|
941
902
|
archived: 'Archived "{{name}}"',
|
|
942
903
|
unhid: 'Unhid "{{name}}"',
|
|
943
904
|
archiveFailed: "Couldn't archive dashboard",
|
|
@@ -1314,6 +1275,44 @@ export default {
|
|
|
1314
1275
|
"Couldn't check for existing entries with this metric.",
|
|
1315
1276
|
},
|
|
1316
1277
|
dataSources: {
|
|
1278
|
+
searchConsole: {
|
|
1279
|
+
name: "Google Search Console",
|
|
1280
|
+
description:
|
|
1281
|
+
"Daily clicks and impressions per page for the Content view.",
|
|
1282
|
+
serviceAccount: "Service account",
|
|
1283
|
+
copyEmail: "Copy email",
|
|
1284
|
+
grantHint:
|
|
1285
|
+
"Add this email as a user on each property in Search Console, under Settings → Users and permissions.",
|
|
1286
|
+
noServiceAccount: "Add a Google service account key first.",
|
|
1287
|
+
openGoogleCloud: "Open Google Cloud",
|
|
1288
|
+
invalidServiceAccount: "The saved service account key can't be read.",
|
|
1289
|
+
properties: "Properties",
|
|
1290
|
+
siteLabel: "Property",
|
|
1291
|
+
pathLabel: "Path prefix",
|
|
1292
|
+
pathInvalid: "Start the path prefix with /.",
|
|
1293
|
+
addProperty: "Add property",
|
|
1294
|
+
removeProperty: "Remove property",
|
|
1295
|
+
save: "Save",
|
|
1296
|
+
collectDaily: "Collect daily",
|
|
1297
|
+
adminOnly: "Only organization owners and admins can change this.",
|
|
1298
|
+
statusCollecting: "Collecting",
|
|
1299
|
+
statusNeedsAccess: "Needs access",
|
|
1300
|
+
statusFailed: "Last run failed",
|
|
1301
|
+
runSucceeded: "Collected {{when}}",
|
|
1302
|
+
runNeedsAccess: "No access as of {{when}}",
|
|
1303
|
+
runRunning: "Collecting since {{when}}",
|
|
1304
|
+
runFailed: "Failed {{when}}",
|
|
1305
|
+
unavailable: "Collection status is unavailable: {{message}}",
|
|
1306
|
+
keyUnreadable:
|
|
1307
|
+
"Couldn't read the saved service account key. Re-save it under Google Cloud.",
|
|
1308
|
+
propertyBareDomain: "For a Domain property, use {{suggestion}}.",
|
|
1309
|
+
propertyInvalidDomain:
|
|
1310
|
+
"Put only the domain after sc-domain:, as in {{suggestion}}.",
|
|
1311
|
+
propertyMissingSlash:
|
|
1312
|
+
"URL-prefix properties end in /. Use {{suggestion}}.",
|
|
1313
|
+
propertyUnrecognized:
|
|
1314
|
+
"Use sc-domain:example.com, or a URL-prefix property ending in /.",
|
|
1315
|
+
},
|
|
1317
1316
|
uploadFile: "Upload file",
|
|
1318
1317
|
googleSheetsExport: "Google Sheets export",
|
|
1319
1318
|
googleSheetsExportDescription:
|
|
@@ -116,6 +116,11 @@ import {
|
|
|
116
116
|
type DataSourceCategory,
|
|
117
117
|
type WalkthroughStep,
|
|
118
118
|
} from "@/lib/data-sources";
|
|
119
|
+
import {
|
|
120
|
+
SearchConsoleDetail,
|
|
121
|
+
searchConsoleState,
|
|
122
|
+
useContentCollectionStatus,
|
|
123
|
+
} from "@/pages/data-sources/SearchConsoleDetail";
|
|
119
124
|
|
|
120
125
|
import {
|
|
121
126
|
ConnectionTestStatus,
|
|
@@ -2214,6 +2219,7 @@ export default function DataSources() {
|
|
|
2214
2219
|
statusData.workspaceConnections?.available === false);
|
|
2215
2220
|
|
|
2216
2221
|
const firstParty = useFirstPartyAnalyticsData();
|
|
2222
|
+
const contentCollection = useContentCollectionStatus();
|
|
2217
2223
|
const googleSheetsConnected =
|
|
2218
2224
|
getGoogleSheetsConnection(statusData)?.grantState === "connected";
|
|
2219
2225
|
|
|
@@ -2377,6 +2383,55 @@ export default function DataSources() {
|
|
|
2377
2383
|
),
|
|
2378
2384
|
};
|
|
2379
2385
|
|
|
2386
|
+
const searchConsoleLabel = {
|
|
2387
|
+
unavailable: {
|
|
2388
|
+
label: t("dataSources.statusUnknown"),
|
|
2389
|
+
className: "text-destructive",
|
|
2390
|
+
},
|
|
2391
|
+
collecting: {
|
|
2392
|
+
label: t("dataSources.searchConsole.statusCollecting"),
|
|
2393
|
+
className: "text-emerald-500",
|
|
2394
|
+
},
|
|
2395
|
+
needs_access: {
|
|
2396
|
+
label: t("dataSources.searchConsole.statusNeedsAccess"),
|
|
2397
|
+
className: "text-amber-600 dark:text-amber-400",
|
|
2398
|
+
},
|
|
2399
|
+
failed: {
|
|
2400
|
+
label: t("dataSources.searchConsole.statusFailed"),
|
|
2401
|
+
className: "text-destructive",
|
|
2402
|
+
},
|
|
2403
|
+
off: {
|
|
2404
|
+
label: t("dataSources.notConfigured"),
|
|
2405
|
+
className: "text-muted-foreground",
|
|
2406
|
+
},
|
|
2407
|
+
}[searchConsoleState(contentCollection.data, contentCollection.error)];
|
|
2408
|
+
const searchConsoleEntry: SourceEntry = {
|
|
2409
|
+
id: "google-search-console",
|
|
2410
|
+
name: t("dataSources.searchConsole.name"),
|
|
2411
|
+
description: t("dataSources.searchConsole.description"),
|
|
2412
|
+
category: "seo",
|
|
2413
|
+
logo: (
|
|
2414
|
+
<SourceLogo
|
|
2415
|
+
id="google-search-console"
|
|
2416
|
+
name={t("dataSources.searchConsole.name")}
|
|
2417
|
+
fallbackIcon={IconSearch}
|
|
2418
|
+
/>
|
|
2419
|
+
),
|
|
2420
|
+
status: contentCollection.isLoading ? undefined : searchConsoleLabel.label,
|
|
2421
|
+
statusClassName: searchConsoleLabel.className,
|
|
2422
|
+
connected:
|
|
2423
|
+
searchConsoleState(contentCollection.data, contentCollection.error) ===
|
|
2424
|
+
"collecting",
|
|
2425
|
+
searchText: [
|
|
2426
|
+
t("dataSources.searchConsole.name"),
|
|
2427
|
+
t("dataSources.searchConsole.description"),
|
|
2428
|
+
"google search console gsc seo clicks impressions",
|
|
2429
|
+
]
|
|
2430
|
+
.join(" ")
|
|
2431
|
+
.toLowerCase(),
|
|
2432
|
+
detail: <SearchConsoleDetail />,
|
|
2433
|
+
};
|
|
2434
|
+
|
|
2380
2435
|
const customApiEntry: SourceEntry = {
|
|
2381
2436
|
id: "custom-api",
|
|
2382
2437
|
name: t("dataSources.customApi.title"),
|
|
@@ -2403,6 +2458,7 @@ export default function DataSources() {
|
|
|
2403
2458
|
firstPartyEntry,
|
|
2404
2459
|
...sourceEntries,
|
|
2405
2460
|
googleSheetsEntry,
|
|
2461
|
+
searchConsoleEntry,
|
|
2406
2462
|
customApiEntry,
|
|
2407
2463
|
];
|
|
2408
2464
|
const activeEntry = entries.find((entry) => entry.id === activeId);
|
|
@@ -52,7 +52,8 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
|
|
52
52
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
export const MAX_FILTER_OPTIONS =
|
|
55
|
+
export const MAX_FILTER_OPTIONS = 500;
|
|
56
|
+
export const MAX_FILTER_PRESET_VALUES = 100;
|
|
56
57
|
// Each preset is a button in the dropdown, so a filter offers a short list.
|
|
57
58
|
export const MAX_FILTER_PRESETS = 20;
|
|
58
59
|
|
|
@@ -110,9 +111,9 @@ export function filterPresetIssues(
|
|
|
110
111
|
if (
|
|
111
112
|
isRecord(preset) &&
|
|
112
113
|
Array.isArray(preset.values) &&
|
|
113
|
-
preset.values.length >
|
|
114
|
+
preset.values.length > MAX_FILTER_PRESET_VALUES
|
|
114
115
|
) {
|
|
115
|
-
return `has more than ${
|
|
116
|
+
return `has more than ${MAX_FILTER_PRESET_VALUES} values`;
|
|
116
117
|
}
|
|
117
118
|
if (
|
|
118
119
|
!isFilterPreset(preset) ||
|
|
@@ -208,6 +208,7 @@ import {
|
|
|
208
208
|
type FetchedDashboard,
|
|
209
209
|
} from "./fetch-dashboard";
|
|
210
210
|
import { interpolate, interpolateDashboardPanelSql } from "./interpolate";
|
|
211
|
+
import { panelCostChange, usePanelCostNotice } from "./panel-cost-notice";
|
|
211
212
|
import { serializePanelSql } from "./panel-sql";
|
|
212
213
|
import { AddPanelPopover, PanelEditorDialog } from "./PanelEditorDialog";
|
|
213
214
|
import { listReportablePanelIds } from "./report-panel-window";
|
|
@@ -1190,6 +1191,7 @@ function SqlDashboardPageContent({
|
|
|
1190
1191
|
otherViewers: otherViewerCount(activeUsers, session?.email),
|
|
1191
1192
|
onUndo: undoLatestSave,
|
|
1192
1193
|
});
|
|
1194
|
+
const panelCost = usePanelCostNotice(dashboardId);
|
|
1193
1195
|
|
|
1194
1196
|
const updateCachedDashboardConfig = useCallback(
|
|
1195
1197
|
(updated: SqlDashboardConfig) => {
|
|
@@ -2039,8 +2041,10 @@ function SqlDashboardPageContent({
|
|
|
2039
2041
|
clampDashboardColumns(current.columns ?? DEFAULT_DASHBOARD_COLUMNS),
|
|
2040
2042
|
),
|
|
2041
2043
|
}));
|
|
2044
|
+
// A removed panel's cost notice would outlive it.
|
|
2045
|
+
panelCost.clear(panelId);
|
|
2042
2046
|
},
|
|
2043
|
-
[persist],
|
|
2047
|
+
[panelCost, persist],
|
|
2044
2048
|
);
|
|
2045
2049
|
|
|
2046
2050
|
const handleResizePanel = useCallback(
|
|
@@ -2073,6 +2077,7 @@ function SqlDashboardPageContent({
|
|
|
2073
2077
|
);
|
|
2074
2078
|
const handleSavePanel = useCallback(
|
|
2075
2079
|
async (panel: SqlPanel, editorBaseline?: SqlPanel | null) => {
|
|
2080
|
+
const cost: { change?: ReturnType<typeof panelCostChange> } = {};
|
|
2076
2081
|
await persistThrow((current) => {
|
|
2077
2082
|
const idx = current.panels.findIndex((p) => p.id === panel.id);
|
|
2078
2083
|
if (editorBaseline && idx < 0) {
|
|
@@ -2086,30 +2091,43 @@ function SqlDashboardPageContent({
|
|
|
2086
2091
|
current.panels[idx],
|
|
2087
2092
|
)
|
|
2088
2093
|
: panel;
|
|
2094
|
+
cost.change = panelCostChange(
|
|
2095
|
+
idx >= 0 ? current.panels[idx] : null,
|
|
2096
|
+
nextPanel,
|
|
2097
|
+
);
|
|
2089
2098
|
const nextPanels =
|
|
2090
2099
|
idx >= 0
|
|
2091
2100
|
? current.panels.map((p, i) => (i === idx ? nextPanel : p))
|
|
2092
2101
|
: [...current.panels, nextPanel];
|
|
2093
2102
|
return { ...current, panels: nextPanels };
|
|
2094
2103
|
});
|
|
2104
|
+
if (cost.change === "notify")
|
|
2105
|
+
void panelCost.notify(panel.id, panel.title);
|
|
2106
|
+
// A panel turned into a section no longer costs what its notice said.
|
|
2107
|
+
if (cost.change === "clear") panelCost.clear(panel.id);
|
|
2095
2108
|
},
|
|
2096
|
-
[persistThrow],
|
|
2109
|
+
[panelCost, persistThrow],
|
|
2097
2110
|
);
|
|
2098
2111
|
const handleSaveSql = useCallback(
|
|
2099
2112
|
async (panelId: string, sql: string) => {
|
|
2113
|
+
const edit: { title?: string } = {};
|
|
2100
2114
|
await persistThrow((current) => {
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2115
|
+
const panel = current.panels.find((p) => p.id === panelId);
|
|
2116
|
+
edit.title =
|
|
2117
|
+
panel && serializePanelSql(panel.sql) !== serializePanelSql(sql)
|
|
2118
|
+
? panel.title
|
|
2119
|
+
: undefined;
|
|
2120
|
+
if (!panel) return current;
|
|
2104
2121
|
return {
|
|
2105
2122
|
...current,
|
|
2106
|
-
panels: current.panels.map((
|
|
2107
|
-
|
|
2123
|
+
panels: current.panels.map((p) =>
|
|
2124
|
+
p.id === panelId ? { ...p, sql } : p,
|
|
2108
2125
|
),
|
|
2109
2126
|
};
|
|
2110
2127
|
});
|
|
2128
|
+
if (edit.title !== undefined) void panelCost.notify(panelId, edit.title);
|
|
2111
2129
|
},
|
|
2112
|
-
[persistThrow],
|
|
2130
|
+
[panelCost, persistThrow],
|
|
2113
2131
|
);
|
|
2114
2132
|
const handleSaveEditedPanel = useCallback(
|
|
2115
2133
|
async (panel: SqlPanel) => {
|
|
@@ -158,7 +158,9 @@ export function interpolate(
|
|
|
158
158
|
return "SELECT __invalid_custom_date_range__";
|
|
159
159
|
}
|
|
160
160
|
if (
|
|
161
|
-
/generate_series|generate_date_array/i.test(
|
|
161
|
+
/generate_series|generate_date_array|\bwith\s+recursive\b/i.test(
|
|
162
|
+
sourceSql,
|
|
163
|
+
) &&
|
|
162
164
|
(Date.parse(`${end}T00:00:00.000Z`) -
|
|
163
165
|
Date.parse(`${start}T00:00:00.000Z`)) /
|
|
164
166
|
DAY_MILLISECONDS +
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { callAction } from "@agent-native/core/client/hooks";
|
|
2
|
+
import { useFormatters, useT } from "@agent-native/core/client/i18n";
|
|
3
|
+
import { useCallback, useEffect, useMemo, useRef } from "react";
|
|
4
|
+
import { toast } from "sonner";
|
|
5
|
+
|
|
6
|
+
import { serializePanelSql } from "./panel-sql";
|
|
7
|
+
import type { SqlPanel } from "./types";
|
|
8
|
+
|
|
9
|
+
const MIB = 1024 ** 2;
|
|
10
|
+
const GIB = 1024 ** 3;
|
|
11
|
+
const TIB = 1024 ** 4;
|
|
12
|
+
const NOTICE_MS = 8000;
|
|
13
|
+
|
|
14
|
+
type Formatters = ReturnType<typeof useFormatters>;
|
|
15
|
+
|
|
16
|
+
export function formatScanSize(bytes: number, formatters: Formatters): string {
|
|
17
|
+
const [value, unit] =
|
|
18
|
+
bytes >= TIB
|
|
19
|
+
? [bytes / TIB, "TiB"]
|
|
20
|
+
: bytes >= GIB
|
|
21
|
+
? [bytes / GIB, "GiB"]
|
|
22
|
+
: [bytes / MIB, "MiB"];
|
|
23
|
+
return `${formatters.formatNumber(value, { maximumFractionDigits: 1 })} ${unit}`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function formatQueryUsd(usd: number, formatters: Formatters): string {
|
|
27
|
+
const money = (value: number) =>
|
|
28
|
+
formatters.formatNumber(value, {
|
|
29
|
+
style: "currency",
|
|
30
|
+
currency: "USD",
|
|
31
|
+
maximumFractionDigits: 2,
|
|
32
|
+
});
|
|
33
|
+
return usd > 0 && usd < 0.01 ? `<${money(0.01)}` : money(usd);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
type CostPanel = Pick<SqlPanel, "chartType" | "source" | "sql">;
|
|
37
|
+
|
|
38
|
+
/** Section and extension panels draw config, so they run no query to price. */
|
|
39
|
+
function runsQuery(panel: CostPanel): boolean {
|
|
40
|
+
return panel.chartType !== "section" && panel.chartType !== "extension";
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* What saving a panel means for its cost notice: "notify" to price a new or
|
|
45
|
+
* changed query, including a section turned into a chart, and "clear" when the
|
|
46
|
+
* panel stopped running a query. Any other chart-type or config edit reads the
|
|
47
|
+
* same bytes and changes nothing.
|
|
48
|
+
*/
|
|
49
|
+
export function panelCostChange(
|
|
50
|
+
before: CostPanel | null,
|
|
51
|
+
next: CostPanel,
|
|
52
|
+
): "notify" | "clear" | null {
|
|
53
|
+
if (!runsQuery(next)) return before && runsQuery(before) ? "clear" : null;
|
|
54
|
+
const changed =
|
|
55
|
+
!before ||
|
|
56
|
+
!runsQuery(before) ||
|
|
57
|
+
before.source !== next.source ||
|
|
58
|
+
serializePanelSql(before.sql) !== serializePanelSql(next.sql);
|
|
59
|
+
return changed ? "notify" : null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function estimatePanel(dashboardId: string, panelId: string) {
|
|
63
|
+
try {
|
|
64
|
+
return await callAction("estimate-query-cost", { dashboardId, panelId });
|
|
65
|
+
} catch {
|
|
66
|
+
// coercion-ok: the notice is informational; a failed estimate leaves the save as it was.
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* After a save that changed a panel's query, shows what one refresh of that
|
|
73
|
+
* panel scans and costs in BigQuery, from the same free dry run as
|
|
74
|
+
* estimate-query-cost. Panels outside BigQuery, and estimates that fail, show
|
|
75
|
+
* nothing and close the panel's earlier notice. An estimate a newer save of the
|
|
76
|
+
* panel has replaced shows nothing either. Over
|
|
77
|
+
* the per-run or per-month threshold the notice stays up until dismissed or
|
|
78
|
+
* until you leave the dashboard. `clear` closes a panel's notice, and drops its
|
|
79
|
+
* pending estimate, when the panel stops running a query.
|
|
80
|
+
*/
|
|
81
|
+
export function usePanelCostNotice(dashboardId: string | undefined) {
|
|
82
|
+
const t = useT();
|
|
83
|
+
const formatters = useFormatters();
|
|
84
|
+
// An estimate that lands after leaving the dashboard must not show there,
|
|
85
|
+
// and its warnings close when you leave.
|
|
86
|
+
const openDashboardRef = useRef(dashboardId);
|
|
87
|
+
const warningsRef = useRef(new Set<string>());
|
|
88
|
+
useEffect(() => {
|
|
89
|
+
openDashboardRef.current = dashboardId;
|
|
90
|
+
const warnings = warningsRef.current;
|
|
91
|
+
return () => {
|
|
92
|
+
for (const id of warnings) toast.dismiss(id);
|
|
93
|
+
warnings.clear();
|
|
94
|
+
};
|
|
95
|
+
}, [dashboardId]);
|
|
96
|
+
// Nor may one that lands after a newer save of the same panel.
|
|
97
|
+
const latestRequestRef = useRef(new Map<string, number>());
|
|
98
|
+
const requestCountRef = useRef(0);
|
|
99
|
+
const closeNotice = useCallback((id: string) => {
|
|
100
|
+
toast.dismiss(id);
|
|
101
|
+
warningsRef.current.delete(id);
|
|
102
|
+
}, []);
|
|
103
|
+
|
|
104
|
+
const notify = useCallback(
|
|
105
|
+
async (panelId: string, title: string) => {
|
|
106
|
+
if (!dashboardId) return;
|
|
107
|
+
const key = `${dashboardId}:${panelId}`;
|
|
108
|
+
const request = ++requestCountRef.current;
|
|
109
|
+
latestRequestRef.current.set(key, request);
|
|
110
|
+
const result = await estimatePanel(dashboardId, panelId);
|
|
111
|
+
if (
|
|
112
|
+
openDashboardRef.current !== dashboardId ||
|
|
113
|
+
latestRequestRef.current.get(key) !== request
|
|
114
|
+
) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const id = `panel-cost:${key}`;
|
|
118
|
+
const estimate =
|
|
119
|
+
result && "panels" in result ? result.panels[0] : undefined;
|
|
120
|
+
if (estimate?.status !== "estimated") {
|
|
121
|
+
// An earlier notice priced the query this save replaced.
|
|
122
|
+
closeNotice(id);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const over =
|
|
126
|
+
estimate.overPerRunThreshold || estimate.overPerMonthThreshold;
|
|
127
|
+
const values = {
|
|
128
|
+
size: formatScanSize(estimate.estimatedBytes, formatters),
|
|
129
|
+
perRun: formatQueryUsd(estimate.usdPerRun, formatters),
|
|
130
|
+
perMonth:
|
|
131
|
+
estimate.usdPerMonth === null
|
|
132
|
+
? ""
|
|
133
|
+
: formatQueryUsd(estimate.usdPerMonth, formatters),
|
|
134
|
+
};
|
|
135
|
+
const cost = t(
|
|
136
|
+
estimate.usdPerMonth === null
|
|
137
|
+
? "sqlDashboard.panelCostUnscheduled"
|
|
138
|
+
: "sqlDashboard.panelCostScheduled",
|
|
139
|
+
values,
|
|
140
|
+
);
|
|
141
|
+
const options = {
|
|
142
|
+
id,
|
|
143
|
+
// The hint goes on its own line under the cost.
|
|
144
|
+
description: over
|
|
145
|
+
? `${cost}\n${t("sqlDashboard.panelCostOverHint")}`
|
|
146
|
+
: cost,
|
|
147
|
+
classNames: { description: "whitespace-pre-line" },
|
|
148
|
+
duration: over ? Number.POSITIVE_INFINITY : NOTICE_MS,
|
|
149
|
+
closeButton: over,
|
|
150
|
+
};
|
|
151
|
+
if (over) {
|
|
152
|
+
warningsRef.current.add(options.id);
|
|
153
|
+
toast.warning(t("sqlDashboard.panelCostOverTitle", { title }), options);
|
|
154
|
+
} else {
|
|
155
|
+
toast.message(t("sqlDashboard.panelCostTitle", { title }), options);
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
[closeNotice, dashboardId, formatters, t],
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
const clear = useCallback(
|
|
162
|
+
(panelId: string) => {
|
|
163
|
+
if (!dashboardId) return;
|
|
164
|
+
const key = `${dashboardId}:${panelId}`;
|
|
165
|
+
latestRequestRef.current.set(key, ++requestCountRef.current);
|
|
166
|
+
closeNotice(`panel-cost:${key}`);
|
|
167
|
+
},
|
|
168
|
+
[closeNotice, dashboardId],
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
return useMemo(() => ({ notify, clear }), [notify, clear]);
|
|
172
|
+
}
|