@datawheel/bespoke 0.1.34 → 0.1.36

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.
Files changed (3) hide show
  1. package/dist/index.js +261 -124
  2. package/dist/server.js +43 -17
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -20,7 +20,7 @@ import { Stack, Text, Badge, Group, useMantineTheme, Flex, packSx, Title, Toolti
20
20
  import { dataConcat } from 'd3plus-viz';
21
21
  import * as d3plus from 'd3plus-react';
22
22
  import Router, { useRouter } from 'next/router';
23
- import { IconInfoCircle, IconRefresh, IconSearch, IconAlignLeft, IconAlignCenter, IconAlignRight, IconBoxMargin, IconTable, IconMathFunction, IconUsers, IconLogout, IconTrash, IconUserCircle, IconEdit, IconDatabase, IconServer, IconPencil, IconAlertCircle, IconCircleCheck, IconPlayerPlay, IconAlarmFilled, IconBox, IconLink, IconCircleX, IconFlag, IconCirclePlus, IconFileAnalytics, IconPlus, IconX, IconChevronDown, IconCamera, IconShare, IconCircleDashed, IconListSearch, IconExternalLink, IconSettings, IconFileOff, IconFilesOff, IconHierarchy3, IconMenu, IconApi, IconPolaroid, IconCircleMinus, IconEyeOff, IconChevronLeft, IconChevronRight, IconLogin, IconWorld, IconLock, IconVariable, IconArrowRightCircle, IconDownload, IconTemplate, IconChartBar, IconCode, IconUpload, IconCodePlus, IconClipboardCheck, IconClipboardCopy, IconPalette, IconEye, IconMinimize, IconMaximize, IconRss, IconGlobe } from '@tabler/icons-react';
23
+ import { IconInfoCircle, IconRefresh, IconSearch, IconAlignLeft, IconAlignCenter, IconAlignRight, IconBoxMargin, IconTable, IconMathFunction, IconUsers, IconLogout, IconTrash, IconUserCircle, IconEdit, IconDatabase, IconServer, IconPencil, IconAlertCircle, IconCircleCheck, IconPlayerPlay, IconAlarmFilled, IconBox, IconLink, IconCircleX, IconFlag, IconCirclePlus, IconFileAnalytics, IconPlus, IconX, IconChevronDown, IconCamera, IconShare, IconCircleDashed, IconListSearch, IconExternalLink, IconSettings, IconFileOff, IconFilesOff, IconHierarchy3, IconMenu, IconApi, IconPolaroid, IconCircleMinus, IconEyeOff, IconChevronLeft, IconChevronRight, IconLogin, IconWorld, IconLock, IconVariable, IconArrowRightCircle, IconDownload, IconTemplate, IconChartBar, IconCode, IconUpload, IconCodePlus, IconClipboardCheck, IconClipboardCopy, IconPalette, IconEye, IconMinimize, IconMaximize, IconRss, IconGlobe, IconLinkOff } from '@tabler/icons-react';
24
24
  import { useMediaQuery, useDisclosure, useDebouncedValue, useHotkeys, useFullscreen, getHotkeyHandler } from '@mantine/hooks';
25
25
  import dynamic from 'next/dynamic';
26
26
  import Link from 'next/link';
@@ -1009,6 +1009,15 @@ var init_arrayUtils = __esm({
1009
1009
  keyDiver = (obj, str) => !str ? obj : typeof str === "string" ? str.split(".").reduce((o, i) => o[i], obj) : obj;
1010
1010
  }
1011
1011
  });
1012
+
1013
+ // libs/consts.ts
1014
+ var apiSeparator;
1015
+ var init_consts = __esm({
1016
+ "libs/consts.ts"() {
1017
+ init_esm_shims();
1018
+ apiSeparator = "|||";
1019
+ }
1020
+ });
1012
1021
  function isResult(response) {
1013
1022
  return response.ok !== void 0;
1014
1023
  }
@@ -1089,8 +1098,8 @@ async function runSingleBlock(block, formatterFunctions, blockContext, readMembe
1089
1098
  const allowed = isBlockAllowed(block, blockContext, variables, formatterFunctions);
1090
1099
  let resp = null;
1091
1100
  const unswappedAPI = block.contentByLocale?.logic?.content?.api;
1092
- let apiResponse;
1093
1101
  let duration = null;
1102
+ let responseSize = null;
1094
1103
  let api = null;
1095
1104
  if (!allowed) {
1096
1105
  return {
@@ -1101,21 +1110,35 @@ async function runSingleBlock(block, formatterFunctions, blockContext, readMembe
1101
1110
  }
1102
1111
  if (unswappedAPI) {
1103
1112
  api = swapAPI(unswappedAPI, formatterFunctions, blockContext);
1104
- const isAbsolute = new RegExp("^(?:[a-z+]+:)?//", "i");
1105
- if (!isAbsolute.test(api)) {
1106
- api = `${process.env.NEXT_PUBLIC_REPORTS_BASE_URL}/${api}`.replace(/([^:]\/)\/+/g, "$1");
1113
+ const apis = api.split(apiSeparator).filter((apiItem) => apiItem !== "");
1114
+ const apiPromisesList = [];
1115
+ for (let index = 0; index < apis.length; index++) {
1116
+ let apiUrl = apis[index];
1117
+ const isAbsolute = new RegExp("^(?:[a-z+]+:)?//", "i");
1118
+ if (!isAbsolute.test(api)) {
1119
+ apiUrl = `${process.env.NEXT_PUBLIC_REPORTS_BASE_URL}/${apiUrl}`.replace(/([^:]\/)\/+/g, "$1");
1120
+ }
1121
+ apiPromisesList.push(
1122
+ apiFetch(
1123
+ apiUrl,
1124
+ block.settings,
1125
+ readMemberFn,
1126
+ locale
1127
+ ).then((result) => {
1128
+ if (verbose2)
1129
+ console.log("Variable loaded:", apiUrl, "response time: ", result.requestDuration);
1130
+ return result;
1131
+ }, (e) => {
1132
+ if (verbose2)
1133
+ console.error(`Error fetching ${apiUrl} block ${block.id}: ${e.message}`);
1134
+ return { data: {}, requestDuration: 0 };
1135
+ })
1136
+ );
1107
1137
  }
1108
- apiResponse = await apiFetch(api, block.settings, readMemberFn, locale).then((result) => {
1109
- if (verbose2)
1110
- console.log("Variable loaded:", api, "response time: ", result.requestDuration);
1111
- return result;
1112
- }, (e) => {
1113
- if (verbose2)
1114
- console.error(`Error fetching ${api} block ${block.id}: ${e.message}`);
1115
- return { data: {}, requestDuration: 0 };
1116
- });
1117
- resp = apiResponse.data;
1118
- duration = apiResponse.requestDuration;
1138
+ const apiResponses = await Promise.all(apiPromisesList);
1139
+ resp = apiResponses.length === 1 ? apiResponses[0].data : apiResponses.map((r) => r.data);
1140
+ responseSize = apiResponses.reduce((acc, r) => acc + JSON.stringify(r.data).length / 1024, 0);
1141
+ duration = Math.max(...apiResponses.map((ar) => ar.requestDuration));
1119
1142
  }
1120
1143
  if (block.type === BLOCK_TYPES.SELECTOR) {
1121
1144
  const { config, log: log2, error: error2 } = runSelector_default(
@@ -1157,6 +1180,7 @@ async function runSingleBlock(block, formatterFunctions, blockContext, readMembe
1157
1180
  error: "Configuration did not return an object",
1158
1181
  log,
1159
1182
  duration,
1183
+ responseSize,
1160
1184
  resp,
1161
1185
  api,
1162
1186
  allowed
@@ -1171,6 +1195,7 @@ async function runSingleBlock(block, formatterFunctions, blockContext, readMembe
1171
1195
  log,
1172
1196
  error,
1173
1197
  duration,
1198
+ responseSize,
1174
1199
  resp,
1175
1200
  api,
1176
1201
  allowed
@@ -1192,6 +1217,7 @@ var init_runConsumers = __esm({
1192
1217
  init_getBlockContent();
1193
1218
  init_getRootBlocksForSection();
1194
1219
  init_arrayUtils();
1220
+ init_consts();
1195
1221
  verbose2 = getLogging_default();
1196
1222
  debug = yn4(process.env.NEXT_PUBLIC_REPORTS_DEBUG);
1197
1223
  ORIGIN = process.env.REPORTS_ORIGIN || "";
@@ -1319,10 +1345,17 @@ var init_runConsumers = __esm({
1319
1345
  ).then(({ outputVariables, status }) => {
1320
1346
  if (
1321
1347
  // store output variables for block that:
1322
- block.consumers.length > 0 && status.allowed && Object.keys(outputVariables).length > 0
1348
+ (block.consumers.length > 0 || block.type === BLOCK_TYPES.GENERATOR) && status.allowed && Object.keys(outputVariables).length > 0
1323
1349
  )
1324
1350
  variablesById[bid2] = outputVariables;
1325
- statusById[bid2] = mode === "report" ? { allowed: status.allowed } : status;
1351
+ if (mode === "report") {
1352
+ statusById[bid2] = { allowed: status.allowed };
1353
+ if (block.type === BLOCK_TYPES.GENERATOR) {
1354
+ statusById[bid2]["api"] = status.api;
1355
+ }
1356
+ } else {
1357
+ statusById[bid2] = status;
1358
+ }
1326
1359
  });
1327
1360
  const endBlock = /* @__PURE__ */ new Date();
1328
1361
  const blockTime = (endBlock.getTime() - startBlock.getTime()) / 1e3;
@@ -4123,11 +4156,12 @@ function useInitialState(pathSegmentsKey) {
4123
4156
  }
4124
4157
  if (!isLoading && !user) {
4125
4158
  setPrivateBlocks({ resolved: true, blocks: [] });
4159
+ setLoading(false);
4126
4160
  }
4127
4161
  }, [user, isLoading]);
4128
4162
  useEffect(() => {
4129
4163
  const hasParams = Object.keys(query).some((key) => key !== pathSegmentsKey);
4130
- if (hasParams && privateBlocks.resolved) {
4164
+ if ((hasParams || privateBlocks.blocks.length > 0) && privateBlocks.resolved) {
4131
4165
  const selectorIds = Object.keys(query).map((d) => Number(d.match(/\d+/)));
4132
4166
  const revalidateBlocks = selectorIds.concat(privateBlocks.blocks);
4133
4167
  Promise.all(
@@ -4151,17 +4185,13 @@ function useInitialState(pathSegmentsKey) {
4151
4185
  { variables, status },
4152
4186
  readMemberFn,
4153
4187
  "report"
4154
- ).then((data) => {
4155
- return dispatch(variablesActions.setVariableChange({ ...data, attributes }));
4156
- });
4188
+ );
4157
4189
  })
4158
- ).then(() => {
4159
- console.log("finished revalidate");
4190
+ ).then((d) => {
4191
+ d.map((data) => dispatch(variablesActions.setVariableChange({ ...data, attributes })));
4160
4192
  setLoading(false);
4161
4193
  });
4162
4194
  }
4163
- if (!hasParams && privateBlocks.resolved)
4164
- setLoading(false);
4165
4195
  }, [privateBlocks.resolved]);
4166
4196
  return loading || !privateBlocks.resolved;
4167
4197
  }
@@ -5076,33 +5106,86 @@ var evalType = (value) => {
5076
5106
  return t;
5077
5107
  };
5078
5108
  var sortOrder = ["object", "array", "string", "number"];
5109
+ var maxLevel = 5;
5079
5110
  function ConsoleVariable({
5080
5111
  dimmed = false,
5081
5112
  label,
5082
5113
  root = true,
5083
- value
5114
+ value,
5115
+ parentIsArray = false,
5116
+ level = 0
5084
5117
  }) {
5118
+ const theme = useMantineTheme();
5119
+ if (level === maxLevel)
5120
+ return null;
5121
+ const maxLevelElement = /* @__PURE__ */ jsx(Text, { lineClamp: 1, size: "xs", variant: "dimmed", p: 5, children: "[Max depth hitted for preview]" });
5085
5122
  const t = evalType(value);
5086
5123
  let v = value;
5087
5124
  const textValue = JSON.stringify(value);
5088
5125
  const isLongText = textValue && textValue.length > 50 ? true : false;
5089
- const theme = useMantineTheme();
5126
+ const limit = 10;
5127
+ const accordionStyles = {
5128
+ control: {
5129
+ padding: "5px 0px 5px 5px"
5130
+ },
5131
+ label: {
5132
+ padding: "5px 0px"
5133
+ },
5134
+ content: {
5135
+ paddingTop: 0,
5136
+ paddingBottom: 0,
5137
+ paddingRight: 0
5138
+ }
5139
+ };
5090
5140
  if (t === "object") {
5091
- return /* @__PURE__ */ jsx(Accordion, { className: "cr-variable-accordion", children: /* @__PURE__ */ jsxs(Accordion.Item, { value: "label", children: [
5092
- /* @__PURE__ */ jsx(Accordion.Control, { children: /* @__PURE__ */ jsx(Text, { lineClamp: 1, size: "xs", weight: 700, children: label }) }),
5093
- /* @__PURE__ */ jsx(Accordion.Panel, { children: Object.keys(value).sort((a, b) => {
5141
+ return /* @__PURE__ */ jsx(Accordion, { className: "cr-variable-accordion", variant: "contained", styles: accordionStyles, children: /* @__PURE__ */ jsxs(Accordion.Item, { value: "label", children: [
5142
+ /* @__PURE__ */ jsx(Accordion.Control, { children: /* @__PURE__ */ jsx(Text, { lineClamp: 1, size: "xs", weight: 700, color: dimmed ? "dimmed" : theme.primaryColor, children: root ? `{{${label}}}` : parentIsArray ? `[${label}]` : label }) }),
5143
+ /* @__PURE__ */ jsx(Accordion.Panel, { children: level + 1 < maxLevel ? Object.keys(value).sort((a, b) => {
5094
5144
  const aType = evalType(value[a]);
5095
5145
  const bType = evalType(value[b]);
5096
5146
  if (aType !== bType)
5097
5147
  return sortOrder.indexOf(aType) - sortOrder.indexOf(bType);
5098
5148
  return a.localeCompare(b);
5099
- }).map((k) => /* @__PURE__ */ jsx(ConsoleVariable, { root: false, label: k, value: value[k] }, k)) })
5149
+ }).map(
5150
+ (k) => /* @__PURE__ */ jsx(
5151
+ ConsoleVariable,
5152
+ {
5153
+ root: false,
5154
+ label: k,
5155
+ value: value[k],
5156
+ level: level + 1
5157
+ },
5158
+ k
5159
+ )
5160
+ ) : maxLevelElement })
5100
5161
  ] }) });
5101
5162
  }
5102
5163
  if (t === "array") {
5103
- return /* @__PURE__ */ jsx(Accordion, { className: "cr-variable-accordion", children: /* @__PURE__ */ jsxs(Accordion.Item, { value: "label", children: [
5104
- /* @__PURE__ */ jsx(Accordion.Control, { children: /* @__PURE__ */ jsx(Text, { lineClamp: 1, size: "xs", weight: 700, children: label }) }),
5105
- /* @__PURE__ */ jsx(Accordion.Panel, { children: value.map((k, i) => /* @__PURE__ */ jsx(ConsoleVariable, { root: false, label: i, value: k }, i)) })
5164
+ const isLargeArray = value.length > limit;
5165
+ const renderArray = isLargeArray ? value.slice(0, limit) : value;
5166
+ const otherElements = isLargeArray ? value.length - limit : 0;
5167
+ return /* @__PURE__ */ jsx(Accordion, { className: "cr-variable-accordion", variant: "contained", styles: accordionStyles, children: /* @__PURE__ */ jsxs(Accordion.Item, { value: "label", children: [
5168
+ /* @__PURE__ */ jsx(Accordion.Control, { children: /* @__PURE__ */ jsx(Text, { lineClamp: 1, size: "xs", weight: 700, p: 0, color: dimmed ? "dimmed" : theme.primaryColor, children: root ? `{{${label}}}` : parentIsArray ? `[${label}]` : label }) }),
5169
+ /* @__PURE__ */ jsxs(Accordion.Panel, { children: [
5170
+ level + 1 < maxLevel ? renderArray.map(
5171
+ (k, i) => /* @__PURE__ */ jsx(
5172
+ ConsoleVariable,
5173
+ {
5174
+ root: false,
5175
+ label: i,
5176
+ value: k,
5177
+ level: level + 1,
5178
+ parentIsArray: true
5179
+ },
5180
+ i
5181
+ )
5182
+ ) : maxLevelElement,
5183
+ isLargeArray && /* @__PURE__ */ jsxs(Text, { lineClamp: 1, size: "xs", variant: "dimmed", p: 5, children: [
5184
+ "[+ ",
5185
+ otherElements,
5186
+ " other elements]"
5187
+ ] })
5188
+ ] })
5106
5189
  ] }) });
5107
5190
  }
5108
5191
  if (t === "string")
@@ -5120,13 +5203,14 @@ function ConsoleVariable({
5120
5203
  color: dimmed ? "dimmed" : theme.primaryColor,
5121
5204
  lineClamp: 1,
5122
5205
  size: "xs",
5206
+ pl: 5,
5123
5207
  style: {
5124
5208
  overflow: "visible",
5125
5209
  wordBreak: "keep-all",
5126
5210
  whiteSpace: "nowrap"
5127
5211
  },
5128
5212
  weight: 700,
5129
- children: root ? `{{${label}}}` : label
5213
+ children: root ? `{{${label}}}` : parentIsArray ? `[${label}]` : label
5130
5214
  }
5131
5215
  ),
5132
5216
  /* @__PURE__ */ jsx(
@@ -5136,10 +5220,21 @@ function ConsoleVariable({
5136
5220
  language: "jsx",
5137
5221
  noCopy: true,
5138
5222
  scrollAreaComponent: "div",
5139
- style: {
5140
- flex: "1 1 100%",
5141
- textAlign: "right",
5142
- overflow: "hidden"
5223
+ styles: {
5224
+ "root": {
5225
+ flex: "1 1 100%",
5226
+ textAlign: "right",
5227
+ overflow: "hidden"
5228
+ },
5229
+ "code": {
5230
+ padding: "5px 0 5px 5px"
5231
+ },
5232
+ "scrollArea": {
5233
+ marginLeft: "5px"
5234
+ },
5235
+ "line": {
5236
+ paddingRight: "5px"
5237
+ }
5143
5238
  },
5144
5239
  children: v
5145
5240
  }
@@ -5171,10 +5266,7 @@ function InputMenuItem({
5171
5266
  value: variables[d]
5172
5267
  },
5173
5268
  d
5174
- )) : /* @__PURE__ */ jsx(Text, { color: "dimmed", size: "xs", children: /* @__PURE__ */ jsxs("i", { children: [
5175
- "empty",
5176
- label
5177
- ] }) }) });
5269
+ )) : /* @__PURE__ */ jsx(Text, { color: "dimmed", size: "xs", children: /* @__PURE__ */ jsx("i", { children: `empty ${label}` }) }) });
5178
5270
  }
5179
5271
  var InputMenuItem_default = InputMenuItem;
5180
5272
  function Generator2({ outputVariables, status, hasAPI = false, debug: debug2 }) {
@@ -5755,6 +5847,7 @@ init_getBlockContent();
5755
5847
  init_hooks();
5756
5848
  init_varSwapRecursive();
5757
5849
  init_runConsumers();
5850
+ init_consts();
5758
5851
  var formatOptions = {
5759
5852
  csv: "CSV",
5760
5853
  json: "JSON",
@@ -5770,20 +5863,25 @@ function DataTab(props) {
5770
5863
  const router = useRouter();
5771
5864
  const sourcesOptions = useAppSelector((state) => {
5772
5865
  const dataInformation = [];
5773
- console.log("calculate api blocks");
5774
5866
  blocksIds.map((blockId) => state.records.entities.block[blockId]).filter((block) => [BLOCK_TYPES.GENERATOR, BLOCK_TYPES.VIZ].includes(block.type)).map((block) => {
5867
+ console.log(state.variables.status[block.id]);
5775
5868
  if (block.type === BLOCK_TYPES.GENERATOR) {
5776
5869
  const api = state.variables.variables[block.id]._api || state.variables.status[block.id].api;
5777
5870
  if (api) {
5778
- const ix = dataInformation.length;
5779
- dataInformation.push(
5780
- {
5781
- ix,
5782
- id: block.id,
5783
- title: `Data Source ${ix + 1}`,
5784
- path: api
5871
+ const apis = api.split(apiSeparator);
5872
+ apis.forEach((apiUrl) => {
5873
+ if (apiUrl && apiUrl !== "") {
5874
+ const ix = dataInformation.length;
5875
+ dataInformation.push(
5876
+ {
5877
+ ix,
5878
+ id: block.id,
5879
+ title: `Data Source ${ix + 1}`,
5880
+ path: apiUrl
5881
+ }
5882
+ );
5785
5883
  }
5786
- );
5884
+ });
5787
5885
  }
5788
5886
  } else if (block.type === BLOCK_TYPES.VIZ) {
5789
5887
  const content = getBlockContent(block);
@@ -5800,7 +5898,7 @@ function DataTab(props) {
5800
5898
  }
5801
5899
  };
5802
5900
  const d3Props = d3plusPropify_default(transpiledLogic, formatterFunctions, variables, locale, block.id, {}, globals);
5803
- if (d3Props.config._api) {
5901
+ if (d3Props.config._api && d3Props.config._api !== "") {
5804
5902
  const ix = dataInformation.length;
5805
5903
  dataInformation.push(
5806
5904
  {
@@ -5810,7 +5908,7 @@ function DataTab(props) {
5810
5908
  path: d3Props.config._api
5811
5909
  }
5812
5910
  );
5813
- } else if (typeof d3Props.config.data === "string") {
5911
+ } else if (typeof d3Props.config.data === "string" && d3Props.config.data !== "") {
5814
5912
  const ix = dataInformation.length;
5815
5913
  dataInformation.push(
5816
5914
  {
@@ -5822,7 +5920,7 @@ function DataTab(props) {
5822
5920
  );
5823
5921
  } else if (Array.isArray(d3Props.config.data)) {
5824
5922
  d3Props.config.data.forEach((dataItem) => {
5825
- if (typeof dataItem === "string") {
5923
+ if (typeof dataItem === "string" && dataItem !== "") {
5826
5924
  const ix = dataInformation.length;
5827
5925
  dataInformation.push(
5828
5926
  {
@@ -5944,7 +6042,7 @@ function DataTab(props) {
5944
6042
  )) }) }),
5945
6043
  selectedSource && /* @__PURE__ */ jsx(CopyInput, { title: `Original ${selectedSource.title}:`, url: selectedSource.path }),
5946
6044
  selectedSource && loadingPreview && /* @__PURE__ */ jsx(Fragment, { children: "Loading..." }),
5947
- selectedSource && !loadingPreview && currentPreview.length === 0 && /* @__PURE__ */ jsx(Alert, { icon: /* @__PURE__ */ jsx(IconAlertCircle, { size: "1rem" }), title: "Oops!", children: "Sorry. We couldn't generate the data preview. Try to open the source and check the data directly." }),
6045
+ selectedSource && !loadingPreview && currentPreview.length === 0 && /* @__PURE__ */ jsx(Alert, { icon: /* @__PURE__ */ jsx(IconAlertCircle, { size: "1rem" }), title: "Oops!", children: "Sorry. We couldn't generate the data preview as a table. Try to open the source and check the data directly." }),
5948
6046
  selectedSource && !loadingPreview && currentPreview.length === 0 && /* @__PURE__ */ jsx("a", { href: selectedSource.path, target: "_blank", rel: "noreferrer", children: /* @__PURE__ */ jsx(
5949
6047
  Button,
5950
6048
  {
@@ -7134,14 +7232,17 @@ function Section({ section }) {
7134
7232
  children: /* @__PURE__ */ jsx(ColumnsWrapper, { children: Object.keys(columns).sort((a, b) => orderSort(a, b, "blockcol")).map((columnIndex) => {
7135
7233
  const column = columns[columnIndex];
7136
7234
  const columnSettings = settings.columnSettings ? settings.columnSettings[columnIndex] : {};
7235
+ const columnBlocks = Object.values(column).sort((a, b) => orderSort(a, b, "blockrow"));
7236
+ if (!columnBlocks.some((block) => status[block.id].allowed))
7237
+ return null;
7137
7238
  return /* @__PURE__ */ jsx(
7138
7239
  SectionColumn,
7139
7240
  {
7140
7241
  column,
7141
7242
  columnSettings,
7142
7243
  sx: { flexBasis: `${100 / colsQty}%` },
7143
- children: Object.values(column).sort((a, b) => orderSort(a, b, "blockrow")).map((item) => {
7144
- if (!item.id || !status[item.id].allowed && item.type !== BLOCK_TYPES.NAV)
7244
+ children: columnBlocks.map((item) => {
7245
+ if (!item.id || !status[item.id]?.allowed && item.type !== BLOCK_TYPES.NAV)
7145
7246
  return null;
7146
7247
  const { settings: settings2, type } = blockRecords[item.id];
7147
7248
  const blockWidth = settings2.width && !settings2.width.stretch && settings2.width.unit ? formatters[settings2.width.unit](settings2.width.value) : settings2.display === "inline" ? "auto" : "100%";
@@ -8946,9 +9047,9 @@ function ApiInput({
8946
9047
  defaultValue,
8947
9048
  id,
8948
9049
  onChange,
8949
- onEnterPress
9050
+ onEnterPress,
9051
+ onDelete
8950
9052
  }) {
8951
- const status = useBlockStatus(id);
8952
9053
  const formatterFunctions = useFormatterFunctionsForLocale();
8953
9054
  const blockContext = useBlockContext(id);
8954
9055
  const [preview, setPreview] = useState(() => varSwap_default(defaultValue, formatterFunctions, blockContext));
@@ -8967,21 +9068,9 @@ function ApiInput({
8967
9068
  setPreview(varSwap_default(value, formatterFunctions, blockContext));
8968
9069
  onChange(value);
8969
9070
  };
8970
- const getColor = (duration2) => {
8971
- if (duration2 < 150)
8972
- return "green";
8973
- if (duration2 < 1e3)
8974
- return "yellow";
8975
- return "red";
8976
- };
8977
- const duration = status?.duration;
8978
- return /* @__PURE__ */ jsxs(Group, { noWrap: true, position: "apart", spacing: "xs", children: [
8979
- /* @__PURE__ */ jsx(Text, { lineClamp: 1, size: "xs", children: /* @__PURE__ */ jsx("a", { href: preview, target: "_blank", rel: "noreferrer", children: preview }) }),
8980
- /* @__PURE__ */ jsxs(Group, { noWrap: true, spacing: "xs", children: [
8981
- duration && /* @__PURE__ */ jsxs(Badge, { color: getColor(duration), children: [
8982
- duration,
8983
- "ms"
8984
- ] }),
9071
+ return /* @__PURE__ */ jsxs(Group, { noWrap: true, position: "apart", spacing: "xs", py: 5, children: [
9072
+ /* @__PURE__ */ jsx(Text, { lineClamp: 1, size: "xs", children: /* @__PURE__ */ jsx("a", { href: preview, target: "_blank", rel: "noreferrer", children: preview || "--Add API Url--" }) }),
9073
+ /* @__PURE__ */ jsxs(Group, { noWrap: true, spacing: 0, children: [
8985
9074
  /* @__PURE__ */ jsxs(
8986
9075
  Popover,
8987
9076
  {
@@ -8996,8 +9085,6 @@ function ApiInput({
8996
9085
  {
8997
9086
  size: "xs",
8998
9087
  onClick: () => setEditing((e) => !e),
8999
- variant: "filled",
9000
- color: editing ? "green" : "gray",
9001
9088
  children: editing ? /* @__PURE__ */ jsx(IconCircleCheck, {}) : /* @__PURE__ */ jsx(IconPencil, {})
9002
9089
  }
9003
9090
  ) }),
@@ -9020,6 +9107,14 @@ function ApiInput({
9020
9107
  ) })
9021
9108
  ]
9022
9109
  }
9110
+ ),
9111
+ /* @__PURE__ */ jsx(
9112
+ ActionIcon,
9113
+ {
9114
+ size: "xs",
9115
+ onClick: () => onDelete(),
9116
+ children: /* @__PURE__ */ jsx(IconTrash, { color: "red" })
9117
+ }
9023
9118
  )
9024
9119
  ] })
9025
9120
  ] });
@@ -9074,6 +9169,7 @@ var emptyStatus = {
9074
9169
  duration: null,
9075
9170
  error: null,
9076
9171
  log: [],
9172
+ responseSize: null,
9077
9173
  resp: null,
9078
9174
  allowed: true
9079
9175
  };
@@ -9094,27 +9190,9 @@ function BlockPreview(props) {
9094
9190
  const blockContent = types_default[block.type].renderPreviewOnEdit && blockStateContent ? blockStateContent : getBlockContent(block, locale);
9095
9191
  const [content, setContent] = useState(null);
9096
9192
  const [status, setStatus2] = useState(emptyStatus);
9097
- const [estimatedResponseSize, setEstimatedResponseSize] = useState(void 0);
9098
- const [estimatedOutputSize, setEstimatedOutputSize] = useState(void 0);
9099
- const [slugsInterceptor, setSlugsInterceptor] = useState(false);
9193
+ const estimatedOutputSize = block.type === BLOCK_TYPES.GENERATOR && content && content.outputVariables ? JSON.stringify(content.outputVariables).length / 1024 : void 0;
9194
+ const slugsInterceptor = block.type === BLOCK_TYPES.GENERATOR && status.api && status.api.indexOf("bespoke_slugs=") > -1 ? true : false;
9100
9195
  const readMemberFn = useReadMemberFn();
9101
- useEffect(() => {
9102
- if (block.type === BLOCK_TYPES.GENERATOR) {
9103
- if (status.resp) {
9104
- setEstimatedResponseSize(JSON.stringify(status.resp).length / 1024);
9105
- } else {
9106
- setEstimatedResponseSize(void 0);
9107
- }
9108
- if (content && content.outputVariables) {
9109
- setEstimatedOutputSize(JSON.stringify(content.outputVariables).length / 1024);
9110
- } else {
9111
- setEstimatedOutputSize(void 0);
9112
- }
9113
- if (status.api) {
9114
- setSlugsInterceptor(status.api.indexOf("bespoke_slugs=") > -1);
9115
- }
9116
- }
9117
- }, [status.resp, status.api, content, block.type]);
9118
9196
  useEffect(() => {
9119
9197
  const fetch = async () => {
9120
9198
  if (block.type === BLOCK_TYPES.VIZ) {
@@ -9223,16 +9301,16 @@ function BlockPreview(props) {
9223
9301
  ]
9224
9302
  }
9225
9303
  ),
9226
- estimatedResponseSize && /* @__PURE__ */ jsxs(
9304
+ status.responseSize && /* @__PURE__ */ jsxs(
9227
9305
  Badge,
9228
9306
  {
9229
9307
  leftSection: /* @__PURE__ */ jsx(IconBox, { size: rem(12) }),
9230
9308
  size: "md",
9231
9309
  pl: 5,
9232
- color: getSizeColor(estimatedResponseSize),
9310
+ color: getSizeColor(status.responseSize),
9233
9311
  children: [
9234
9312
  "Api Response Size: \u2248",
9235
- estimatedResponseSize.toFixed(2),
9313
+ status.responseSize.toFixed(2),
9236
9314
  " Kb"
9237
9315
  ]
9238
9316
  }
@@ -9733,9 +9811,9 @@ function VariableList({ id, setInlineId }) {
9733
9811
  size: "xs",
9734
9812
  compact: true,
9735
9813
  onClick: () => setInlineId(vid),
9736
- variant: "light",
9737
- color: "green",
9738
- leftIcon: /* @__PURE__ */ jsx(IconSettings, { size: 15 }),
9814
+ variant: "subtle",
9815
+ color: "gray",
9816
+ leftIcon: /* @__PURE__ */ jsx(IconPencil, { size: 15 }),
9739
9817
  children: "Edit this generator"
9740
9818
  }
9741
9819
  ),
@@ -9747,7 +9825,7 @@ function VariableList({ id, setInlineId }) {
9747
9825
  onClick: () => removeAsInputClick(vid),
9748
9826
  variant: "light",
9749
9827
  color: "red",
9750
- leftIcon: /* @__PURE__ */ jsx(IconTrash, { size: 15 }),
9828
+ leftIcon: /* @__PURE__ */ jsx(IconLinkOff, { size: 15 }),
9751
9829
  children: "Remove as input"
9752
9830
  }
9753
9831
  )
@@ -9760,6 +9838,7 @@ function VariableList({ id, setInlineId }) {
9760
9838
  var VariableList_default = VariableList;
9761
9839
 
9762
9840
  // components/blocks/BlockEditor.tsx
9841
+ init_consts();
9763
9842
  init_hooks();
9764
9843
 
9765
9844
  // components/sections/SectionVariablesView.tsx
@@ -9796,11 +9875,29 @@ function BlockEditor({
9796
9875
  }) {
9797
9876
  const [tab, setTab] = useState("Content");
9798
9877
  const status = useBlockStatus(id);
9799
- const resp = status?.resp;
9878
+ const resp = useMemo(() => {
9879
+ let apisResponse = null;
9880
+ if (status && status.resp) {
9881
+ console.log("RESP CHANGED");
9882
+ apisResponse = status.resp;
9883
+ }
9884
+ return apisResponse;
9885
+ }, [status]);
9800
9886
  const variables = useInputVariablesFlat(id);
9801
9887
  const { ref, toggle, fullscreen } = useFullscreen();
9888
+ const apiList = blockContent && blockContent.api ? blockContent.api.split(apiSeparator) : [];
9802
9889
  const onChangeCode = (logic, locale2) => setBlockContent({ logic }, locale2);
9803
- const onChangeAPI = (api) => setBlockContent({ api });
9890
+ const onChangeAPI = (changedApi, ix) => {
9891
+ const newApiList = [...apiList];
9892
+ newApiList[ix] = changedApi;
9893
+ setBlockContent({ api: newApiList.join(apiSeparator) });
9894
+ };
9895
+ const onAddAPI = () => {
9896
+ setBlockContent({ api: [...apiList, ""].join(apiSeparator) });
9897
+ };
9898
+ const onDeleteAPI = (deletedIx) => {
9899
+ setBlockContent({ api: apiList.filter((ai, ix) => ix !== deletedIx).join(apiSeparator) });
9900
+ };
9804
9901
  const codeEditor = /* @__PURE__ */ jsx(
9805
9902
  MonacoWrapper_default,
9806
9903
  {
@@ -9843,26 +9940,45 @@ function BlockEditor({
9843
9940
  /* @__PURE__ */ jsx(InputMenu_default, { id }),
9844
9941
  /* @__PURE__ */ jsx("div", { style: { width: "100%", maxHeight: "80vh", overflowY: "auto" }, children: /* @__PURE__ */ jsx(VariableList_default, { id, setInlineId }) }),
9845
9942
  blockType === BLOCK_TYPES.GENERATOR && /* @__PURE__ */ jsxs("div", { children: [
9846
- /* @__PURE__ */ jsx(Divider, { label: "API Data (resp)", labelPosition: "center" }),
9847
- /* @__PURE__ */ jsx(
9943
+ /* @__PURE__ */ jsx(Divider, { label: `${apiList.length} API's`, labelPosition: "center", mt: "md" }),
9944
+ apiList.map((apiUrl, ix) => /* @__PURE__ */ jsx(
9848
9945
  ApiInput_default,
9849
9946
  {
9850
- defaultValue: blockContent?.api,
9947
+ defaultValue: apiUrl,
9851
9948
  id,
9852
- onChange: onChangeAPI,
9853
- onEnterPress: () => onSave(true)
9854
- }
9855
- ),
9856
- resp && /* @__PURE__ */ jsx(
9857
- "div",
9949
+ onChange: (changedApi) => onChangeAPI(changedApi, ix),
9950
+ onEnterPress: () => onSave(true),
9951
+ onDelete: () => onDeleteAPI(ix)
9952
+ },
9953
+ `${id}-${ix}`
9954
+ )),
9955
+ /* @__PURE__ */ jsx(
9956
+ Button,
9858
9957
  {
9859
- style: {
9860
- maxHeight: 200,
9861
- overflowY: "scroll"
9862
- },
9863
- children: /* @__PURE__ */ jsx(InputMenuItem_default, { id, variables: resp })
9958
+ size: "xs",
9959
+ compact: true,
9960
+ onClick: onAddAPI,
9961
+ variant: "light",
9962
+ color: "blue",
9963
+ fullWidth: true,
9964
+ leftIcon: /* @__PURE__ */ jsx(IconPlus, { size: 15 }),
9965
+ my: 5,
9966
+ children: "Add API"
9864
9967
  }
9865
- )
9968
+ ),
9969
+ resp && /* @__PURE__ */ jsxs(Fragment, { children: [
9970
+ /* @__PURE__ */ jsx(Divider, { label: "Response data preview (resp)", labelPosition: "center", my: "sm" }),
9971
+ /* @__PURE__ */ jsx(
9972
+ "div",
9973
+ {
9974
+ style: {
9975
+ maxHeight: 200,
9976
+ overflowY: "scroll"
9977
+ },
9978
+ children: /* @__PURE__ */ jsx(ConsoleVariable_default, { value: resp, root: true, label: "resp" })
9979
+ }
9980
+ )
9981
+ ] })
9866
9982
  ] })
9867
9983
  ]
9868
9984
  }
@@ -11478,7 +11594,28 @@ var buildOptions = (options) => {
11478
11594
  };
11479
11595
  function FilterSidebar2({ onChange, loading }) {
11480
11596
  const dispatch = useAppDispatch();
11481
- const reportsNested = useAppSelector((state) => state.records.tree);
11597
+ const reportsNested = useAppSelector((state) => {
11598
+ return Object.values(state.records.entities.report).map((report) => {
11599
+ return {
11600
+ id: report.id,
11601
+ name: report.name,
11602
+ dimensions: report.dimensions.map((dId) => {
11603
+ const dimension = state.records.entities.dimension[dId];
11604
+ return {
11605
+ id: dimension.id,
11606
+ name: dimension.name,
11607
+ variants: dimension.variants.map((vId) => {
11608
+ const variant = state.records.entities.variant[vId];
11609
+ return {
11610
+ id: variant.id,
11611
+ name: variant.name
11612
+ };
11613
+ })
11614
+ };
11615
+ })
11616
+ };
11617
+ });
11618
+ });
11482
11619
  const localeDefault9 = useAppSelector((state) => state.status.localeDefault);
11483
11620
  const [query, setQuery] = useState("");
11484
11621
  const [loadingRegenerate, setLoadingRegenerate] = useState(false);
@@ -11910,7 +12047,7 @@ function MemberForm({ memberId, onEditEnd }) {
11910
12047
  {
11911
12048
  locale: "all",
11912
12049
  content_ids: [memberId],
11913
- all: false,
12050
+ all: true,
11914
12051
  mode: "content_ids",
11915
12052
  output: "full"
11916
12053
  }
package/dist/server.js CHANGED
@@ -4638,6 +4638,9 @@ function getRootBlocksForSection(sid, blocks) {
4638
4638
  );
4639
4639
  }
4640
4640
  var getRootBlocksForSection_default = getRootBlocksForSection;
4641
+
4642
+ // libs/consts.ts
4643
+ var apiSeparator = "|||";
4641
4644
  var verbose7 = getLogging_default();
4642
4645
  var debug = yn3(process.env.NEXT_PUBLIC_REPORTS_DEBUG);
4643
4646
  var ORIGIN = process.env.REPORTS_ORIGIN || "";
@@ -4736,8 +4739,8 @@ async function runSingleBlock(block, formatterFunctions, blockContext, readMembe
4736
4739
  const allowed = isBlockAllowed(block, blockContext, variables, formatterFunctions);
4737
4740
  let resp = null;
4738
4741
  const unswappedAPI = block.contentByLocale?.logic?.content?.api;
4739
- let apiResponse;
4740
4742
  let duration = null;
4743
+ let responseSize = null;
4741
4744
  let api = null;
4742
4745
  if (!allowed) {
4743
4746
  return {
@@ -4748,21 +4751,35 @@ async function runSingleBlock(block, formatterFunctions, blockContext, readMembe
4748
4751
  }
4749
4752
  if (unswappedAPI) {
4750
4753
  api = swapAPI(unswappedAPI, formatterFunctions, blockContext);
4751
- const isAbsolute = new RegExp("^(?:[a-z+]+:)?//", "i");
4752
- if (!isAbsolute.test(api)) {
4753
- api = `${process.env.NEXT_PUBLIC_REPORTS_BASE_URL}/${api}`.replace(/([^:]\/)\/+/g, "$1");
4754
+ const apis = api.split(apiSeparator).filter((apiItem) => apiItem !== "");
4755
+ const apiPromisesList = [];
4756
+ for (let index = 0; index < apis.length; index++) {
4757
+ let apiUrl = apis[index];
4758
+ const isAbsolute = new RegExp("^(?:[a-z+]+:)?//", "i");
4759
+ if (!isAbsolute.test(api)) {
4760
+ apiUrl = `${process.env.NEXT_PUBLIC_REPORTS_BASE_URL}/${apiUrl}`.replace(/([^:]\/)\/+/g, "$1");
4761
+ }
4762
+ apiPromisesList.push(
4763
+ apiFetch(
4764
+ apiUrl,
4765
+ block.settings,
4766
+ readMemberFn,
4767
+ locale
4768
+ ).then((result) => {
4769
+ if (verbose7)
4770
+ console.log("Variable loaded:", apiUrl, "response time: ", result.requestDuration);
4771
+ return result;
4772
+ }, (e) => {
4773
+ if (verbose7)
4774
+ console.error(`Error fetching ${apiUrl} block ${block.id}: ${e.message}`);
4775
+ return { data: {}, requestDuration: 0 };
4776
+ })
4777
+ );
4754
4778
  }
4755
- apiResponse = await apiFetch(api, block.settings, readMemberFn, locale).then((result) => {
4756
- if (verbose7)
4757
- console.log("Variable loaded:", api, "response time: ", result.requestDuration);
4758
- return result;
4759
- }, (e) => {
4760
- if (verbose7)
4761
- console.error(`Error fetching ${api} block ${block.id}: ${e.message}`);
4762
- return { data: {}, requestDuration: 0 };
4763
- });
4764
- resp = apiResponse.data;
4765
- duration = apiResponse.requestDuration;
4779
+ const apiResponses = await Promise.all(apiPromisesList);
4780
+ resp = apiResponses.length === 1 ? apiResponses[0].data : apiResponses.map((r) => r.data);
4781
+ responseSize = apiResponses.reduce((acc, r) => acc + JSON.stringify(r.data).length / 1024, 0);
4782
+ duration = Math.max(...apiResponses.map((ar) => ar.requestDuration));
4766
4783
  }
4767
4784
  if (block.type === BLOCK_TYPES.SELECTOR) {
4768
4785
  const { config, log: log2, error: error2 } = runSelector_default(
@@ -4804,6 +4821,7 @@ async function runSingleBlock(block, formatterFunctions, blockContext, readMembe
4804
4821
  error: "Configuration did not return an object",
4805
4822
  log,
4806
4823
  duration,
4824
+ responseSize,
4807
4825
  resp,
4808
4826
  api,
4809
4827
  allowed
@@ -4818,6 +4836,7 @@ async function runSingleBlock(block, formatterFunctions, blockContext, readMembe
4818
4836
  log,
4819
4837
  error,
4820
4838
  duration,
4839
+ responseSize,
4821
4840
  resp,
4822
4841
  api,
4823
4842
  allowed
@@ -4933,10 +4952,17 @@ var runConsumersV2 = async (blocks, sections, bid, formatterFunctions, blockCont
4933
4952
  ).then(({ outputVariables, status }) => {
4934
4953
  if (
4935
4954
  // store output variables for block that:
4936
- block.consumers.length > 0 && status.allowed && Object.keys(outputVariables).length > 0
4955
+ (block.consumers.length > 0 || block.type === BLOCK_TYPES.GENERATOR) && status.allowed && Object.keys(outputVariables).length > 0
4937
4956
  )
4938
4957
  variablesById[bid2] = outputVariables;
4939
- statusById[bid2] = mode === "report" ? { allowed: status.allowed } : status;
4958
+ if (mode === "report") {
4959
+ statusById[bid2] = { allowed: status.allowed };
4960
+ if (block.type === BLOCK_TYPES.GENERATOR) {
4961
+ statusById[bid2]["api"] = status.api;
4962
+ }
4963
+ } else {
4964
+ statusById[bid2] = status;
4965
+ }
4940
4966
  });
4941
4967
  const endBlock = /* @__PURE__ */ new Date();
4942
4968
  const blockTime = (endBlock.getTime() - startBlock.getTime()) / 1e3;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@datawheel/bespoke",
3
- "version": "0.1.34",
3
+ "version": "0.1.36",
4
4
  "description": "Content management system for creating automated data reports",
5
5
  "exports": {
6
6
  ".": {