@datawheel/bespoke 0.1.17 → 0.1.18

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.js CHANGED
@@ -18,7 +18,7 @@ import { useDispatch, useSelector } from 'react-redux';
18
18
  import { Stack, Text, Badge, Group, Tooltip, ActionIcon, Center, useMantineTheme, Modal, Button, Flex, Title, Select, MultiSelect, MantineProvider, Divider, Burger, Navbar, ScrollArea, Avatar, Box, AppShell, UnstyledButton, ThemeIcon, Skeleton, Container, TextInput, Loader, LoadingOverlay, Alert, Autocomplete, Menu, Card, Space, Code, SegmentedControl, Textarea, Overlay, Grid, Input, Popover, Checkbox, Radio, Switch, Drawer, Header, Tabs, FileInput, SimpleGrid, Image, Accordion, NumberInput, CopyButton, Col } from '@mantine/core';
19
19
  import { dataConcat } from 'd3plus-viz';
20
20
  import * as d3plus from 'd3plus-react';
21
- import { IconInfoCircle, IconBoxMargin, IconTable, IconMathFunction, IconUsers, IconLogout, IconSearch, IconTrash, IconUserCircle, IconDatabase, IconServer, IconPencil, IconAlertCircle, IconCircleCheck, IconPlayerPlay, IconCirclePlus, IconCircleX, IconFileAnalytics, IconPlus, IconCircleDashed, IconSettings, IconAlignLeft, IconAlignCenter, IconAlignRight, IconMenu, IconRefresh, IconPolaroid, IconCircleMinus, IconEyeOff, IconLogin, IconWorld, IconCamera, IconShare, IconVariable, IconArrowRightCircle, IconUpload, IconPalette, IconCode, IconExternalLink, IconDownload, IconTemplate, IconChartBar, IconLink, IconClipboardCheck, IconClipboardCopy, IconEye, IconRss, IconGlobe } from '@tabler/icons';
21
+ import { IconInfoCircle, IconBoxMargin, IconTable, IconMathFunction, IconUsers, IconLogout, IconSearch, IconTrash, IconUserCircle, IconDatabase, IconServer, IconPencil, IconAlertCircle, IconCircleCheck, IconPlayerPlay, IconCirclePlus, IconCircleX, IconFileAnalytics, IconPlus, IconCircleDashed, IconRefreshAlert, IconSettings, IconAlignLeft, IconAlignCenter, IconAlignRight, IconMenu, IconRefresh, IconPolaroid, IconCircleMinus, IconEyeOff, IconLogin, IconWorld, IconCamera, IconShare, IconVariable, IconArrowRightCircle, IconUpload, IconPalette, IconCode, IconExternalLink, IconDownload, IconTemplate, IconChartBar, IconLink, IconClipboardCheck, IconClipboardCopy, IconEye, IconRss, IconGlobe } from '@tabler/icons';
22
22
  import { useDisclosure, useDebouncedValue, useMediaQuery, useHotkeys, useUncontrolled, getHotkeyHandler } from '@mantine/hooks';
23
23
  import Link from 'next/link';
24
24
  import Router, { useRouter } from 'next/router';
@@ -57,8 +57,8 @@ var init_esm_shims = __esm({
57
57
  });
58
58
 
59
59
  // api/http/lib.ts
60
- function http(axios6, config) {
61
- return axios6.request(config).then((response) => {
60
+ function http(axios7, config) {
61
+ return axios7.request(config).then((response) => {
62
62
  const { status, data } = response;
63
63
  return "error" in data ? { ok: false, status, error: data.error } : { ok: true, status, data: data.data };
64
64
  }, (err) => {
@@ -69,25 +69,25 @@ function http(axios6, config) {
69
69
  return { ok: false, status: 500, error: err.message };
70
70
  });
71
71
  }
72
- function httpGET(axios6, request, transformParams) {
72
+ function httpGET(axios7, request, transformParams) {
73
73
  const config = typeof request === "string" ? { url: request } : request;
74
- return (params) => http(axios6, {
74
+ return (params) => http(axios7, {
75
75
  ...config,
76
76
  method: "GET",
77
77
  params: transformParams ? transformParams(params) : params
78
78
  });
79
79
  }
80
- function httpPOST(axios6, request, transformPayload) {
80
+ function httpPOST(axios7, request, transformPayload) {
81
81
  const config = typeof request === "string" ? { url: request } : request;
82
- return (payload) => http(axios6, {
82
+ return (payload) => http(axios7, {
83
83
  ...config,
84
84
  method: "POST",
85
85
  data: transformPayload ? transformPayload(payload) : payload
86
86
  });
87
87
  }
88
- function httpDELETE(axios6, request, transformPayload) {
88
+ function httpDELETE(axios7, request, transformPayload) {
89
89
  const config = typeof request === "string" ? { url: request } : request;
90
- return (payload) => http(axios6, {
90
+ return (payload) => http(axios7, {
91
91
  ...config,
92
92
  method: "DELETE",
93
93
  data: transformPayload ? transformPayload(payload) : payload
@@ -100,8 +100,8 @@ var init_lib = __esm({
100
100
  });
101
101
 
102
102
  // api/http/image/imageSave.ts
103
- function httpImageSaveFactory(axios6, provider) {
104
- return (params) => http(axios6, {
103
+ function httpImageSaveFactory(axios7, provider) {
104
+ return (params) => http(axios7, {
105
105
  method: "GET",
106
106
  url: `search/images/${provider}`,
107
107
  params: { prompt: params.prompt, provider }
@@ -115,8 +115,8 @@ var init_imageSave = __esm({
115
115
  });
116
116
 
117
117
  // api/http/image/imageSearch.ts
118
- function httpImageSearchFactory(axios6, provider) {
119
- return (params) => http(axios6, {
118
+ function httpImageSearchFactory(axios7, provider) {
119
+ return (params) => http(axios7, {
120
120
  method: "GET",
121
121
  url: `search/images/${provider}`,
122
122
  params: { prompt: params.prompt, provider }
@@ -129,56 +129,57 @@ var init_imageSearch = __esm({
129
129
  }
130
130
  });
131
131
  function apiFactory(baseURL) {
132
- const axios6 = axios.create({ baseURL });
132
+ const axios7 = axios.create({ baseURL });
133
133
  return {
134
- createBlock: httpPOST(axios6, "create/block"),
135
- createDimension: httpPOST(axios6, "create/dimension"),
136
- createFormatter: httpPOST(axios6, "create/formatter"),
137
- createReport: httpPOST(axios6, "create/report"),
138
- createSection: httpPOST(axios6, "create/section"),
139
- createVariant: httpPOST(axios6, "create/variant"),
140
- deleteBlock: httpDELETE(axios6, "delete/block", transformDeletePayload),
141
- deleteDimension: httpDELETE(axios6, "delete/dimension", transformDeletePayload),
142
- deleteFormatter: httpDELETE(axios6, "delete/formatter", transformDeletePayload),
143
- deleteReport: httpDELETE(axios6, "delete/report", transformDeletePayload),
144
- deleteSection: httpDELETE(axios6, "delete/section", transformDeletePayload),
145
- deleteVariant: httpDELETE(axios6, "delete/variant", transformDeletePayload),
146
- readBlock: httpGET(axios6, "read/block"),
147
- readDimension: httpGET(axios6, "read/dimension"),
148
- readFormatter: httpGET(axios6, "read/formatter"),
149
- readReport: httpGET(axios6, "read/report"),
150
- readSection: httpGET(axios6, "read/section"),
151
- readVariant: httpGET(axios6, "read/variant"),
152
- updateBlock: httpPOST(axios6, "update/block"),
153
- updateDimension: httpPOST(axios6, "update/dimension"),
154
- updateFormatter: httpPOST(axios6, "update/formatter"),
155
- updateReport: httpPOST(axios6, "update/report"),
156
- updateSection: httpPOST(axios6, "update/section"),
157
- updateVariant: httpPOST(axios6, "update/variant"),
158
- searchReport: httpGET(axios6, "search/reports"),
159
- validateVariantSlug: httpGET(axios6, "validate/variant"),
160
- readMember: httpGET(axios6, "read/members", transformReadMembers),
161
- readMemberImage: httpGET(axios6, {
134
+ createBlock: httpPOST(axios7, "create/block"),
135
+ createDimension: httpPOST(axios7, "create/dimension"),
136
+ createFormatter: httpPOST(axios7, "create/formatter"),
137
+ createReport: httpPOST(axios7, "create/report"),
138
+ createSection: httpPOST(axios7, "create/section"),
139
+ createVariant: httpPOST(axios7, "create/variant"),
140
+ deleteBlock: httpDELETE(axios7, "delete/block", transformDeletePayload),
141
+ deleteDimension: httpDELETE(axios7, "delete/dimension", transformDeletePayload),
142
+ deleteFormatter: httpDELETE(axios7, "delete/formatter", transformDeletePayload),
143
+ deleteReport: httpDELETE(axios7, "delete/report", transformDeletePayload),
144
+ deleteSection: httpDELETE(axios7, "delete/section", transformDeletePayload),
145
+ deleteVariant: httpDELETE(axios7, "delete/variant", transformDeletePayload),
146
+ readBlock: httpGET(axios7, "read/block"),
147
+ readDimension: httpGET(axios7, "read/dimension"),
148
+ readFormatter: httpGET(axios7, "read/formatter"),
149
+ readReport: httpGET(axios7, "read/report"),
150
+ readSection: httpGET(axios7, "read/section"),
151
+ readVariant: httpGET(axios7, "read/variant"),
152
+ updateBlock: httpPOST(axios7, "update/block"),
153
+ updateDimension: httpPOST(axios7, "update/dimension"),
154
+ updateFormatter: httpPOST(axios7, "update/formatter"),
155
+ updateReport: httpPOST(axios7, "update/report"),
156
+ updateSection: httpPOST(axios7, "update/section"),
157
+ updateVariant: httpPOST(axios7, "update/variant"),
158
+ searchReport: httpGET(axios7, "search/reports"),
159
+ validateVariantSlug: httpGET(axios7, "validate/variant"),
160
+ readMember: httpGET(axios7, "read/members", transformReadMembers),
161
+ readMemberImage: httpGET(axios7, {
162
162
  url: "member/image.png",
163
163
  responseType: "blob"
164
164
  }),
165
- searchMember: httpGET(axios6, "search/members"),
166
- updateMember: httpGET(axios6, "update/members"),
167
- imageLocalSearch: httpImageSearchFactory(axios6, "local"),
168
- imageLocalSave: httpImageSaveFactory(axios6, "local"),
169
- imageFlickrSearch: httpImageSearchFactory(axios6, "flickr"),
170
- imageFlickrSave: httpImageSaveFactory(axios6, "flickr"),
171
- imageUnsplashSearch: httpImageSearchFactory(axios6, "unsplash"),
172
- imageUnsplashSave: httpImageSaveFactory(axios6, "unsplash"),
173
- imageUploadSave: httpImageSaveFactory(axios6, "upload"),
174
- readMetadata: httpGET(axios6, "read/metadata"),
175
- regenerateSearch: httpPOST(axios6, "search/regenerate"),
176
- urlProxy: httpGET(axios6, "url/proxy"),
177
- searchRole: httpGET(axios6, "auth/search/roles"),
178
- searchUser: httpGET(axios6, "auth/search/users"),
179
- readUser: httpGET(axios6, "auth/read/user"),
180
- updateUser: httpPOST(axios6, "auth/update/user"),
181
- addNewReportToCurrentUser: httpPOST(axios6, "auth/update/me")
165
+ searchMember: httpGET(axios7, "search/members"),
166
+ updateMember: httpGET(axios7, "update/members"),
167
+ imageLocalSearch: httpImageSearchFactory(axios7, "local"),
168
+ imageLocalSave: httpImageSaveFactory(axios7, "local"),
169
+ imageFlickrSearch: httpImageSearchFactory(axios7, "flickr"),
170
+ imageFlickrSave: httpImageSaveFactory(axios7, "flickr"),
171
+ imageUnsplashSearch: httpImageSearchFactory(axios7, "unsplash"),
172
+ imageUnsplashSave: httpImageSaveFactory(axios7, "unsplash"),
173
+ imageUploadSave: httpImageSaveFactory(axios7, "upload"),
174
+ readMetadata: httpGET(axios7, "read/metadata"),
175
+ regenerateSearch: httpPOST(axios7, "search/regenerate"),
176
+ urlProxy: httpGET(axios7, "url/proxy"),
177
+ searchRole: httpGET(axios7, "auth/search/roles"),
178
+ searchUser: httpGET(axios7, "auth/search/users"),
179
+ readUser: httpGET(axios7, "auth/read/user"),
180
+ updateUser: httpPOST(axios7, "auth/update/user"),
181
+ addNewReportToCurrentUser: httpPOST(axios7, "auth/update/me"),
182
+ revalidate: httpGET(axios7, "revalidate/report")
182
183
  };
183
184
  }
184
185
  var transformDeletePayload, transformReadMembers;
@@ -3067,7 +3068,7 @@ function ExploreTile({ profile, profilePrefix }) {
3067
3068
  {
3068
3069
  src: `/api/cms/member/image.png?member=${m.id}&size=thumb`,
3069
3070
  height: 160,
3070
- alt: "Norway"
3071
+ alt: m.name
3071
3072
  },
3072
3073
  m.id
3073
3074
  ));
@@ -5948,8 +5949,9 @@ function HeaderLayout(props) {
5948
5949
  ] }) });
5949
5950
  }
5950
5951
  function CMSHeader(props) {
5951
- const { report: currentReport } = props;
5952
+ const { report: currentReport, profilePrefix } = props;
5952
5953
  const { id } = currentReport;
5954
+ const { newConfirmation } = useDialog();
5953
5955
  const location = useManagerLocation();
5954
5956
  const dispatch = useAppDispatch();
5955
5957
  const resource = useContext(ResourceContext);
@@ -5995,6 +5997,37 @@ function CMSHeader(props) {
5995
5997
  });
5996
5998
  setPreviewMembers(newPreviews);
5997
5999
  };
6000
+ const maybeRevalidate = useCallback(async () => {
6001
+ try {
6002
+ await newConfirmation({
6003
+ title: "Are you sure?",
6004
+ message: "Revalidate action will force to regenerate all the cached instances of this report type.",
6005
+ confirmText: "Revalidate now!"
6006
+ });
6007
+ const revalidateUrl = `/api/cms/revalidate/report?profilePrefix=${profilePrefix}&reportId=${id}`;
6008
+ await axios.get(revalidateUrl).then((response) => {
6009
+ const resp = response.data.data.data;
6010
+ const message = `Revalidated ${resp.revalidated} pages of report ID ${resp.reportId}, like ${resp.samples.join(", ")}.`;
6011
+ showNotification({
6012
+ autoClose: false,
6013
+ color: "green",
6014
+ id: "revalidate-results",
6015
+ message,
6016
+ title: "Revalidate Results"
6017
+ });
6018
+ }).catch((error) => {
6019
+ showNotification({
6020
+ autoClose: false,
6021
+ color: "red",
6022
+ id: "revalidate-error",
6023
+ message: error.message,
6024
+ title: "Revalidate Error"
6025
+ });
6026
+ });
6027
+ } catch {
6028
+ console.debug("Revalidate edition.");
6029
+ }
6030
+ }, []);
5998
6031
  useEffect(() => {
5999
6032
  setPreviewMembers([]);
6000
6033
  }, [dimensions]);
@@ -6059,20 +6092,23 @@ function CMSHeader(props) {
6059
6092
  }
6060
6093
  )
6061
6094
  ] }),
6062
- right: /* @__PURE__ */ jsxs(ActionIcon, { color: "blue", variant: "filled", children: [
6063
- /* @__PURE__ */ jsx(IconSettings, { onClick: handlers.open, size: 20 }),
6064
- /* @__PURE__ */ jsx(
6065
- Drawer,
6066
- {
6067
- opened,
6068
- onClose: handlers.close,
6069
- title: /* @__PURE__ */ jsx(IconTitle, { icon: /* @__PURE__ */ jsx(IconFileAnalytics, { size: 24 }), children: `Editing Report: ${currentReport ? currentReport.name : ""}` }),
6070
- size: "50%",
6071
- padding: "xl",
6072
- position: "right",
6073
- children: /* @__PURE__ */ jsx(ReportEditor, { id, onClose: handlers.close })
6074
- }
6075
- )
6095
+ right: /* @__PURE__ */ jsxs(Group, { children: [
6096
+ /* @__PURE__ */ jsx(ActionIcon, { onClick: maybeRevalidate, color: "blue", variant: "filled", children: /* @__PURE__ */ jsx(IconRefreshAlert, { size: 20 }) }),
6097
+ /* @__PURE__ */ jsxs(ActionIcon, { color: "blue", variant: "filled", children: [
6098
+ /* @__PURE__ */ jsx(IconSettings, { onClick: handlers.open, size: 20 }),
6099
+ /* @__PURE__ */ jsx(
6100
+ Drawer,
6101
+ {
6102
+ opened,
6103
+ onClose: handlers.close,
6104
+ title: /* @__PURE__ */ jsx(IconTitle, { icon: /* @__PURE__ */ jsx(IconFileAnalytics, { size: 24 }), children: `Editing Report: ${currentReport ? currentReport.name : ""}` }),
6105
+ size: "50%",
6106
+ padding: "xl",
6107
+ position: "right",
6108
+ children: /* @__PURE__ */ jsx(ReportEditor, { id, onClose: handlers.close })
6109
+ }
6110
+ )
6111
+ ] })
6076
6112
  ] })
6077
6113
  }
6078
6114
  );
@@ -8348,7 +8384,8 @@ init_ordering();
8348
8384
  init_store2();
8349
8385
  init_actions();
8350
8386
  function BuilderEditor(props) {
8351
- const reportRef = useReportRef(props.id);
8387
+ const { profilePrefix, id } = props;
8388
+ const reportRef = useReportRef(id);
8352
8389
  return useMemo(() => {
8353
8390
  if (reportRef.isError) {
8354
8391
  return /* @__PURE__ */ jsx(Center, { style: { height: 300 }, children: /* @__PURE__ */ jsx(Alert, { icon: /* @__PURE__ */ jsx(IconCircleX, { size: 20 }), title: `Error ${reportRef.error.code}`, color: "red", children: reportRef.error.message }) });
@@ -8361,13 +8398,14 @@ function BuilderEditor(props) {
8361
8398
  {
8362
8399
  report: reportRef.data,
8363
8400
  locale: props.locale,
8364
- isLoading: reportRef.isFetching
8401
+ isLoading: reportRef.isFetching,
8402
+ profilePrefix
8365
8403
  }
8366
8404
  );
8367
8405
  }, [reportRef.status]);
8368
8406
  }
8369
8407
  function InteractiveReport(props) {
8370
- const { report, isLoading } = props;
8408
+ const { report, isLoading, profilePrefix } = props;
8371
8409
  const theme = useMantineTheme();
8372
8410
  const dispatch = useAppDispatch();
8373
8411
  const resource = useContext(ResourceContext);
@@ -8448,7 +8486,7 @@ function InteractiveReport(props) {
8448
8486
  }
8449
8487
  ), [sectionOrder, activeSection, maybeActivate]);
8450
8488
  return /* @__PURE__ */ jsxs(Stack, { styles: { backgroundColor: theme.colors.gray[2] }, children: [
8451
- /* @__PURE__ */ jsx(CMSHeader, { report }),
8489
+ /* @__PURE__ */ jsx(CMSHeader, { report, profilePrefix }),
8452
8490
  /* @__PURE__ */ jsx(LoadingOverlay, { visible: isLoading }),
8453
8491
  report.sections.length === 0 && /* @__PURE__ */ jsx(CreateSectionButton, { index: 0, report }),
8454
8492
  /* @__PURE__ */ jsx(DragDropContext, { onDragEnd, children: /* @__PURE__ */ jsx(Droppable, { droppableId: "sections", children: droppableChild }) })
@@ -10693,7 +10731,8 @@ function BespokeManager(options) {
10693
10731
  const {
10694
10732
  title = "Bespoke CMS",
10695
10733
  pathSegment = "bespoke",
10696
- locale = localeDefault3
10734
+ locale = localeDefault3,
10735
+ profilePrefix
10697
10736
  } = options;
10698
10737
  const notifications = {
10699
10738
  position: "bottom-center",
@@ -10707,12 +10746,12 @@ function BespokeManager(options) {
10707
10746
  function BespokeManagerPage() {
10708
10747
  return /* @__PURE__ */ jsxs(ResourceProvider, { pathSegment, children: [
10709
10748
  /* @__PURE__ */ jsx(Head, { children: /* @__PURE__ */ jsx("title", { children: title }) }),
10710
- /* @__PURE__ */ jsx(MantineProvider, { children: /* @__PURE__ */ jsx(NotificationsProvider, { ...notifications, children: /* @__PURE__ */ jsx(DialogProvider, { children: /* @__PURE__ */ jsx(BespokeManagerShell, { locale }) }) }) })
10749
+ /* @__PURE__ */ jsx(MantineProvider, { children: /* @__PURE__ */ jsx(NotificationsProvider, { ...notifications, children: /* @__PURE__ */ jsx(DialogProvider, { children: /* @__PURE__ */ jsx(BespokeManagerShell, { locale, profilePrefix }) }) }) })
10711
10750
  ] });
10712
10751
  }
10713
10752
  }
10714
10753
  function BespokeManagerShell(props) {
10715
- const { locale } = props;
10754
+ const { locale, profilePrefix } = props;
10716
10755
  const [opened, { toggle: toggleOpened }] = useDisclosure(true);
10717
10756
  const { user, error, isLoading } = useUser();
10718
10757
  const location = useManagerLocation();
@@ -10828,7 +10867,7 @@ function BespokeManagerShell(props) {
10828
10867
  if (location.page === "welcome") {
10829
10868
  return /* @__PURE__ */ jsx(HelloView, {});
10830
10869
  }
10831
- if (user && Array.isArray(user.bespoke_roles) && user.bespoke_roles.includes(item.roleRequired)) {
10870
+ if (user && Array.isArray(user.bespoke_roles) && user.bespoke_roles.includes(item?.roleRequired)) {
10832
10871
  if (location.page === "metadata") {
10833
10872
  return /* @__PURE__ */ jsx(MetadataEditor, {});
10834
10873
  }
@@ -10838,7 +10877,7 @@ function BespokeManagerShell(props) {
10838
10877
  if (location.page === "reports") {
10839
10878
  if (location.params.length > 0) {
10840
10879
  const reportId = Number.parseInt(location.params[0]);
10841
- return /* @__PURE__ */ jsx(BuilderEditor, { id: reportId, locale });
10880
+ return /* @__PURE__ */ jsx(BuilderEditor, { id: reportId, locale, profilePrefix });
10842
10881
  }
10843
10882
  return /* @__PURE__ */ jsx(ReportPicker, {});
10844
10883
  }
@@ -10923,11 +10962,14 @@ function useInitialState(pathSegmentsKey) {
10923
10962
  }, []);
10924
10963
  return loaded;
10925
10964
  }
10926
- function BespokeRenderer({ pathSegmentsKey = "bespoke" }) {
10965
+ function BespokeRenderer({ pathSegmentsKey = "bespoke", buildTime }) {
10927
10966
  const stateLoaded = useInitialState(pathSegmentsKey);
10928
10967
  if (!stateLoaded)
10929
10968
  return /* @__PURE__ */ jsx("div", { children: "Loading..." });
10930
- return /* @__PURE__ */ jsx(ResourceProvider, { pathSegment: "bespoke", children: /* @__PURE__ */ jsx(Report_default, {}) });
10969
+ return /* @__PURE__ */ jsxs(ResourceProvider, { pathSegment: "bespoke", children: [
10970
+ /* @__PURE__ */ jsx(Report_default, {}),
10971
+ /* @__PURE__ */ jsx("small", { children: buildTime })
10972
+ ] });
10931
10973
  }
10932
10974
 
10933
10975
  export { Explore_default as BespokeExplore, ExploreModal_default as BespokeExploreModal, LoginButton_default as BespokeLoginBtn, BespokeManager, BespokeRenderer, Report_default as BespokeReport, Search_default as BespokeSearch, UserProvider_default as BespokeUserProvider, withPageRoleAuthRequired as BespokeWithPageRoleAuthRequired, CMS_ROLES, DialogProvider, actions_exports as actions, storeWrapper, useAppDispatch as useBespokeDispatch, useAppSelector as useBespokeSelector, useUser_default as useBespokeUser, useDialog };
package/dist/server.js CHANGED
@@ -22,6 +22,7 @@ import fs from 'fs';
22
22
  import auth0 from 'auth0';
23
23
  import NextCors from 'nextjs-cors';
24
24
  import formidable from 'formidable';
25
+ import glob from 'fast-glob';
25
26
  import { schema, normalize } from 'normalizr';
26
27
  import { createSlice, configureStore } from '@reduxjs/toolkit';
27
28
  import { HYDRATE, createWrapper } from 'next-redux-wrapper';
@@ -3366,7 +3367,7 @@ function dbReadMetadataFactory(db) {
3366
3367
 
3367
3368
  // api/db/regenerateSearch.ts
3368
3369
  function dbRegenerateSearchFactory(db) {
3369
- return async (_params) => {
3370
+ return async () => {
3370
3371
  await search_default2(db, true);
3371
3372
  return { error: false };
3372
3373
  };
@@ -3481,6 +3482,26 @@ function dbUrlProxyFactory() {
3481
3482
  }
3482
3483
  };
3483
3484
  }
3485
+
3486
+ // api/db/revalidate.ts
3487
+ function dbRevalidateFactory(db) {
3488
+ return async (request) => {
3489
+ const { reportId, qty, samples } = request;
3490
+ try {
3491
+ return {
3492
+ ok: true,
3493
+ status: 200,
3494
+ data: {
3495
+ reportId,
3496
+ revalidated: qty,
3497
+ samples
3498
+ }
3499
+ };
3500
+ } catch (err) {
3501
+ throw new BackendError(err.response?.status || 500, err.message);
3502
+ }
3503
+ };
3504
+ }
3484
3505
  var auth0ConfigObject = {
3485
3506
  domain: new URL(process.env.AUTH0_ISSUER_BASE_URL || "").host,
3486
3507
  clientId: process.env.AUTH0_CLIENT_ID,
@@ -3620,7 +3641,8 @@ function apiFactory(dbModels) {
3620
3641
  searchUser: resultWrapper(dbSearchUser()),
3621
3642
  readUser: resultWrapper(dbReadUser()),
3622
3643
  updateUser: resultWrapper(dbUpdateUser()),
3623
- addNewReportToCurrentUser: resultWrapper(dbAddNewReportToCurrentUser())
3644
+ addNewReportToCurrentUser: resultWrapper(dbAddNewReportToCurrentUser()),
3645
+ revalidate: resultWrapper(dbRevalidateFactory())
3624
3646
  };
3625
3647
  }
3626
3648
  async function parseBody(req) {
@@ -3751,6 +3773,48 @@ function endpointUrlProxyFactory(operations) {
3751
3773
  });
3752
3774
  });
3753
3775
  }
3776
+ function endpointRevalidateFactory(operations) {
3777
+ const { readMetadata: readMetadata2, revalidate } = operations;
3778
+ return endpoint("GET", "revalidate/report", async (req, res) => {
3779
+ const { reportId, profilePrefix } = req.query;
3780
+ if (!reportId || !profilePrefix) {
3781
+ throw new BackendError(400, "Missing URL parameter in revalidate factory: reportId or profilePrefix");
3782
+ }
3783
+ try {
3784
+ const reportIdInt = parseInt(`${reportId}`, 10);
3785
+ const profilePrefixClean = `${profilePrefix}`.split("/").filter((p) => p !== "").join("/");
3786
+ const initialPathSelector = [
3787
+ ".next",
3788
+ "server",
3789
+ "pages"
3790
+ ];
3791
+ const pathSelector = [
3792
+ ...initialPathSelector,
3793
+ profilePrefixClean
3794
+ ];
3795
+ const paths = [];
3796
+ const report = await readMetadata2({}).then((metadata) => metadata.ok ? metadata.data.data.find((r) => r.id === reportIdInt) : []);
3797
+ if (report && report.mode === REPORT_MODES.UNILATERAL) {
3798
+ report.dimensions.forEach((d) => {
3799
+ d.variants.forEach((v) => {
3800
+ paths.push([
3801
+ ...pathSelector,
3802
+ v.slug,
3803
+ "*.html"
3804
+ ].join("/"));
3805
+ });
3806
+ });
3807
+ }
3808
+ const files = await glob(paths);
3809
+ const urls = files.map((file) => `${file.replace(initialPathSelector.join("/"), "").replace(".html", "")}`);
3810
+ await Promise.all(urls.map((path) => res.revalidate(path)));
3811
+ return revalidate({ reportId: reportIdInt, qty: urls.length, samples: urls.slice(0, 5) });
3812
+ } catch (err) {
3813
+ console.log(err);
3814
+ throw new BackendError(500, "Error revalidating");
3815
+ }
3816
+ });
3817
+ }
3754
3818
 
3755
3819
  // api/endpoints/users/auth0.ts
3756
3820
  function endpointGetRolesFactory(operations) {
@@ -3874,7 +3938,8 @@ function getEndpointMap(db) {
3874
3938
  endpointGetUsersFactory(api),
3875
3939
  endpointGetUserDataFactory(api),
3876
3940
  endpointUpdateUserDataFactory(api),
3877
- endpointAddNewReportToCurrentUserFactory(api)
3941
+ endpointAddNewReportToCurrentUserFactory(api),
3942
+ endpointRevalidateFactory(api)
3878
3943
  ].map(({ handler, method, path, roleRequired }) => [endpointKey(method, path), { handler, roleRequired }]));
3879
3944
  }
3880
3945
  var endpointMap = getDB().then(getEndpointMap);
@@ -4074,7 +4139,8 @@ function apiFactory2(baseURL) {
4074
4139
  searchUser: httpGET(axios6, "auth/search/users"),
4075
4140
  readUser: httpGET(axios6, "auth/read/user"),
4076
4141
  updateUser: httpPOST(axios6, "auth/update/user"),
4077
- addNewReportToCurrentUser: httpPOST(axios6, "auth/update/me")
4142
+ addNewReportToCurrentUser: httpPOST(axios6, "auth/update/me"),
4143
+ revalidate: httpGET(axios6, "revalidate/report")
4078
4144
  };
4079
4145
  }
4080
4146
  var transformDeletePayload = (id) => ({ id });
@@ -5425,6 +5491,7 @@ function BespokeRendererStaticProps(options) {
5425
5491
  const { dispatch } = store;
5426
5492
  return async (context) => {
5427
5493
  await dispatch(useDatabaseApi);
5494
+ const buildTime = (/* @__PURE__ */ new Date()).toISOString();
5428
5495
  console.log("StaticProps", context);
5429
5496
  const {
5430
5497
  locale = localeDefault7,
@@ -5438,7 +5505,9 @@ function BespokeRendererStaticProps(options) {
5438
5505
  dispatch(readEntity("report", { include: true }))
5439
5506
  ]);
5440
5507
  return {
5441
- props: {}
5508
+ props: {
5509
+ buildTime
5510
+ }
5442
5511
  };
5443
5512
  }
5444
5513
  const members = await dispatch(
@@ -5468,7 +5537,9 @@ function BespokeRendererStaticProps(options) {
5468
5537
  })
5469
5538
  ));
5470
5539
  return {
5471
- props: {}
5540
+ props: {
5541
+ buildTime
5542
+ }
5472
5543
  };
5473
5544
  };
5474
5545
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@datawheel/bespoke",
3
- "version": "0.1.17",
3
+ "version": "0.1.18",
4
4
  "description": "Content management system for creating automated data reports",
5
5
  "exports": {
6
6
  ".": {
@@ -70,6 +70,7 @@
70
70
  "d3plus-react": "^1.1.1",
71
71
  "d3plus-text": "^1.0.2",
72
72
  "d3plus-viz": "^1.2.2",
73
+ "fast-glob": "^3.2.12",
73
74
  "file-loader": "^6.2.0",
74
75
  "file-saver": "^1.3.3",
75
76
  "flickr-sdk": "^6.0.0",