@datawheel/bespoke 0.1.35 → 0.1.37

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 +239 -122
  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 && status.api) {
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;
@@ -4119,15 +4152,20 @@ function useInitialState(pathSegmentsKey) {
4119
4152
  });
4120
4153
  }
4121
4154
  setPrivateBlocks({ resolved: true, blocks: [] });
4122
- }).catch(() => setPrivateBlocks({ resolved: true, blocks: [] }));
4155
+ setLoading(false);
4156
+ }).catch(() => {
4157
+ setPrivateBlocks({ resolved: true, blocks: [] });
4158
+ setLoading(false);
4159
+ });
4123
4160
  }
4124
4161
  if (!isLoading && !user) {
4125
4162
  setPrivateBlocks({ resolved: true, blocks: [] });
4163
+ setLoading(false);
4126
4164
  }
4127
4165
  }, [user, isLoading]);
4128
4166
  useEffect(() => {
4129
4167
  const hasParams = Object.keys(query).some((key) => key !== pathSegmentsKey);
4130
- if (hasParams && privateBlocks.resolved) {
4168
+ if ((hasParams || privateBlocks.blocks.length > 0) && privateBlocks.resolved) {
4131
4169
  const selectorIds = Object.keys(query).map((d) => Number(d.match(/\d+/)));
4132
4170
  const revalidateBlocks = selectorIds.concat(privateBlocks.blocks);
4133
4171
  Promise.all(
@@ -4151,17 +4189,13 @@ function useInitialState(pathSegmentsKey) {
4151
4189
  { variables, status },
4152
4190
  readMemberFn,
4153
4191
  "report"
4154
- ).then((data) => {
4155
- return dispatch(variablesActions.setVariableChange({ ...data, attributes }));
4156
- });
4192
+ );
4157
4193
  })
4158
- ).then(() => {
4159
- console.log("finished revalidate");
4194
+ ).then((d) => {
4160
4195
  setLoading(false);
4196
+ d.map((data) => dispatch(variablesActions.setVariableChange({ ...data, attributes })));
4161
4197
  });
4162
4198
  }
4163
- if (!hasParams && privateBlocks.resolved)
4164
- setLoading(false);
4165
4199
  }, [privateBlocks.resolved]);
4166
4200
  return loading || !privateBlocks.resolved;
4167
4201
  }
@@ -5076,33 +5110,86 @@ var evalType = (value) => {
5076
5110
  return t;
5077
5111
  };
5078
5112
  var sortOrder = ["object", "array", "string", "number"];
5113
+ var maxLevel = 5;
5079
5114
  function ConsoleVariable({
5080
5115
  dimmed = false,
5081
5116
  label,
5082
5117
  root = true,
5083
- value
5118
+ value,
5119
+ parentIsArray = false,
5120
+ level = 0
5084
5121
  }) {
5122
+ const theme = useMantineTheme();
5123
+ if (level === maxLevel)
5124
+ return null;
5125
+ const maxLevelElement = /* @__PURE__ */ jsx(Text, { lineClamp: 1, size: "xs", variant: "dimmed", p: 5, children: "[Max depth hitted for preview]" });
5085
5126
  const t = evalType(value);
5086
5127
  let v = value;
5087
5128
  const textValue = JSON.stringify(value);
5088
5129
  const isLongText = textValue && textValue.length > 50 ? true : false;
5089
- const theme = useMantineTheme();
5130
+ const limit = 10;
5131
+ const accordionStyles = {
5132
+ control: {
5133
+ padding: "5px 0px 5px 5px"
5134
+ },
5135
+ label: {
5136
+ padding: "5px 0px"
5137
+ },
5138
+ content: {
5139
+ paddingTop: 0,
5140
+ paddingBottom: 0,
5141
+ paddingRight: 0
5142
+ }
5143
+ };
5090
5144
  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) => {
5145
+ return /* @__PURE__ */ jsx(Accordion, { className: "cr-variable-accordion", variant: "contained", styles: accordionStyles, children: /* @__PURE__ */ jsxs(Accordion.Item, { value: "label", children: [
5146
+ /* @__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 }) }),
5147
+ /* @__PURE__ */ jsx(Accordion.Panel, { children: level + 1 < maxLevel ? Object.keys(value).sort((a, b) => {
5094
5148
  const aType = evalType(value[a]);
5095
5149
  const bType = evalType(value[b]);
5096
5150
  if (aType !== bType)
5097
5151
  return sortOrder.indexOf(aType) - sortOrder.indexOf(bType);
5098
5152
  return a.localeCompare(b);
5099
- }).map((k) => /* @__PURE__ */ jsx(ConsoleVariable, { root: false, label: k, value: value[k] }, k)) })
5153
+ }).map(
5154
+ (k) => /* @__PURE__ */ jsx(
5155
+ ConsoleVariable,
5156
+ {
5157
+ root: false,
5158
+ label: k,
5159
+ value: value[k],
5160
+ level: level + 1
5161
+ },
5162
+ k
5163
+ )
5164
+ ) : maxLevelElement })
5100
5165
  ] }) });
5101
5166
  }
5102
5167
  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)) })
5168
+ const isLargeArray = value.length > limit;
5169
+ const renderArray = isLargeArray ? value.slice(0, limit) : value;
5170
+ const otherElements = isLargeArray ? value.length - limit : 0;
5171
+ return /* @__PURE__ */ jsx(Accordion, { className: "cr-variable-accordion", variant: "contained", styles: accordionStyles, children: /* @__PURE__ */ jsxs(Accordion.Item, { value: "label", children: [
5172
+ /* @__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 }) }),
5173
+ /* @__PURE__ */ jsxs(Accordion.Panel, { children: [
5174
+ level + 1 < maxLevel ? renderArray.map(
5175
+ (k, i) => /* @__PURE__ */ jsx(
5176
+ ConsoleVariable,
5177
+ {
5178
+ root: false,
5179
+ label: i,
5180
+ value: k,
5181
+ level: level + 1,
5182
+ parentIsArray: true
5183
+ },
5184
+ i
5185
+ )
5186
+ ) : maxLevelElement,
5187
+ isLargeArray && /* @__PURE__ */ jsxs(Text, { lineClamp: 1, size: "xs", variant: "dimmed", p: 5, children: [
5188
+ "[+ ",
5189
+ otherElements,
5190
+ " other elements]"
5191
+ ] })
5192
+ ] })
5106
5193
  ] }) });
5107
5194
  }
5108
5195
  if (t === "string")
@@ -5120,13 +5207,14 @@ function ConsoleVariable({
5120
5207
  color: dimmed ? "dimmed" : theme.primaryColor,
5121
5208
  lineClamp: 1,
5122
5209
  size: "xs",
5210
+ pl: 5,
5123
5211
  style: {
5124
5212
  overflow: "visible",
5125
5213
  wordBreak: "keep-all",
5126
5214
  whiteSpace: "nowrap"
5127
5215
  },
5128
5216
  weight: 700,
5129
- children: root ? `{{${label}}}` : label
5217
+ children: root ? `{{${label}}}` : parentIsArray ? `[${label}]` : label
5130
5218
  }
5131
5219
  ),
5132
5220
  /* @__PURE__ */ jsx(
@@ -5136,10 +5224,21 @@ function ConsoleVariable({
5136
5224
  language: "jsx",
5137
5225
  noCopy: true,
5138
5226
  scrollAreaComponent: "div",
5139
- style: {
5140
- flex: "1 1 100%",
5141
- textAlign: "right",
5142
- overflow: "hidden"
5227
+ styles: {
5228
+ "root": {
5229
+ flex: "1 1 100%",
5230
+ textAlign: "right",
5231
+ overflow: "hidden"
5232
+ },
5233
+ "code": {
5234
+ padding: "5px 0 5px 5px"
5235
+ },
5236
+ "scrollArea": {
5237
+ marginLeft: "5px"
5238
+ },
5239
+ "line": {
5240
+ paddingRight: "5px"
5241
+ }
5143
5242
  },
5144
5243
  children: v
5145
5244
  }
@@ -5171,10 +5270,7 @@ function InputMenuItem({
5171
5270
  value: variables[d]
5172
5271
  },
5173
5272
  d
5174
- )) : /* @__PURE__ */ jsx(Text, { color: "dimmed", size: "xs", children: /* @__PURE__ */ jsxs("i", { children: [
5175
- "empty",
5176
- label
5177
- ] }) }) });
5273
+ )) : /* @__PURE__ */ jsx(Text, { color: "dimmed", size: "xs", children: /* @__PURE__ */ jsx("i", { children: `empty ${label}` }) }) });
5178
5274
  }
5179
5275
  var InputMenuItem_default = InputMenuItem;
5180
5276
  function Generator2({ outputVariables, status, hasAPI = false, debug: debug2 }) {
@@ -5755,6 +5851,7 @@ init_getBlockContent();
5755
5851
  init_hooks();
5756
5852
  init_varSwapRecursive();
5757
5853
  init_runConsumers();
5854
+ init_consts();
5758
5855
  var formatOptions = {
5759
5856
  csv: "CSV",
5760
5857
  json: "JSON",
@@ -5770,20 +5867,25 @@ function DataTab(props) {
5770
5867
  const router = useRouter();
5771
5868
  const sourcesOptions = useAppSelector((state) => {
5772
5869
  const dataInformation = [];
5773
- console.log("calculate api blocks");
5774
5870
  blocksIds.map((blockId) => state.records.entities.block[blockId]).filter((block) => [BLOCK_TYPES.GENERATOR, BLOCK_TYPES.VIZ].includes(block.type)).map((block) => {
5871
+ console.log(state.variables.status[block.id]);
5775
5872
  if (block.type === BLOCK_TYPES.GENERATOR) {
5776
5873
  const api = state.variables.variables[block.id]._api || state.variables.status[block.id].api;
5777
5874
  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
5875
+ const apis = api.split(apiSeparator);
5876
+ apis.forEach((apiUrl) => {
5877
+ if (apiUrl && apiUrl !== "") {
5878
+ const ix = dataInformation.length;
5879
+ dataInformation.push(
5880
+ {
5881
+ ix,
5882
+ id: block.id,
5883
+ title: `Data Source ${ix + 1}`,
5884
+ path: apiUrl
5885
+ }
5886
+ );
5785
5887
  }
5786
- );
5888
+ });
5787
5889
  }
5788
5890
  } else if (block.type === BLOCK_TYPES.VIZ) {
5789
5891
  const content = getBlockContent(block);
@@ -5800,7 +5902,7 @@ function DataTab(props) {
5800
5902
  }
5801
5903
  };
5802
5904
  const d3Props = d3plusPropify_default(transpiledLogic, formatterFunctions, variables, locale, block.id, {}, globals);
5803
- if (d3Props.config._api) {
5905
+ if (d3Props.config._api && d3Props.config._api !== "") {
5804
5906
  const ix = dataInformation.length;
5805
5907
  dataInformation.push(
5806
5908
  {
@@ -5810,7 +5912,7 @@ function DataTab(props) {
5810
5912
  path: d3Props.config._api
5811
5913
  }
5812
5914
  );
5813
- } else if (typeof d3Props.config.data === "string") {
5915
+ } else if (typeof d3Props.config.data === "string" && d3Props.config.data !== "") {
5814
5916
  const ix = dataInformation.length;
5815
5917
  dataInformation.push(
5816
5918
  {
@@ -5822,7 +5924,7 @@ function DataTab(props) {
5822
5924
  );
5823
5925
  } else if (Array.isArray(d3Props.config.data)) {
5824
5926
  d3Props.config.data.forEach((dataItem) => {
5825
- if (typeof dataItem === "string") {
5927
+ if (typeof dataItem === "string" && dataItem !== "") {
5826
5928
  const ix = dataInformation.length;
5827
5929
  dataInformation.push(
5828
5930
  {
@@ -5944,7 +6046,7 @@ function DataTab(props) {
5944
6046
  )) }) }),
5945
6047
  selectedSource && /* @__PURE__ */ jsx(CopyInput, { title: `Original ${selectedSource.title}:`, url: selectedSource.path }),
5946
6048
  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." }),
6049
+ 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
6050
  selectedSource && !loadingPreview && currentPreview.length === 0 && /* @__PURE__ */ jsx("a", { href: selectedSource.path, target: "_blank", rel: "noreferrer", children: /* @__PURE__ */ jsx(
5949
6051
  Button,
5950
6052
  {
@@ -7144,7 +7246,7 @@ function Section({ section }) {
7144
7246
  columnSettings,
7145
7247
  sx: { flexBasis: `${100 / colsQty}%` },
7146
7248
  children: columnBlocks.map((item) => {
7147
- if (!item.id || !status[item.id].allowed && item.type !== BLOCK_TYPES.NAV)
7249
+ if (!item.id || !status[item.id]?.allowed && item.type !== BLOCK_TYPES.NAV)
7148
7250
  return null;
7149
7251
  const { settings: settings2, type } = blockRecords[item.id];
7150
7252
  const blockWidth = settings2.width && !settings2.width.stretch && settings2.width.unit ? formatters[settings2.width.unit](settings2.width.value) : settings2.display === "inline" ? "auto" : "100%";
@@ -8949,9 +9051,9 @@ function ApiInput({
8949
9051
  defaultValue,
8950
9052
  id,
8951
9053
  onChange,
8952
- onEnterPress
9054
+ onEnterPress,
9055
+ onDelete
8953
9056
  }) {
8954
- const status = useBlockStatus(id);
8955
9057
  const formatterFunctions = useFormatterFunctionsForLocale();
8956
9058
  const blockContext = useBlockContext(id);
8957
9059
  const [preview, setPreview] = useState(() => varSwap_default(defaultValue, formatterFunctions, blockContext));
@@ -8970,21 +9072,9 @@ function ApiInput({
8970
9072
  setPreview(varSwap_default(value, formatterFunctions, blockContext));
8971
9073
  onChange(value);
8972
9074
  };
8973
- const getColor = (duration2) => {
8974
- if (duration2 < 150)
8975
- return "green";
8976
- if (duration2 < 1e3)
8977
- return "yellow";
8978
- return "red";
8979
- };
8980
- const duration = status?.duration;
8981
- return /* @__PURE__ */ jsxs(Group, { noWrap: true, position: "apart", spacing: "xs", children: [
8982
- /* @__PURE__ */ jsx(Text, { lineClamp: 1, size: "xs", children: /* @__PURE__ */ jsx("a", { href: preview, target: "_blank", rel: "noreferrer", children: preview }) }),
8983
- /* @__PURE__ */ jsxs(Group, { noWrap: true, spacing: "xs", children: [
8984
- duration && /* @__PURE__ */ jsxs(Badge, { color: getColor(duration), children: [
8985
- duration,
8986
- "ms"
8987
- ] }),
9075
+ return /* @__PURE__ */ jsxs(Group, { noWrap: true, position: "apart", spacing: "xs", py: 5, children: [
9076
+ /* @__PURE__ */ jsx(Text, { lineClamp: 1, size: "xs", children: /* @__PURE__ */ jsx("a", { href: preview, target: "_blank", rel: "noreferrer", children: preview || "--Add API Url--" }) }),
9077
+ /* @__PURE__ */ jsxs(Group, { noWrap: true, spacing: 0, children: [
8988
9078
  /* @__PURE__ */ jsxs(
8989
9079
  Popover,
8990
9080
  {
@@ -8999,8 +9089,6 @@ function ApiInput({
8999
9089
  {
9000
9090
  size: "xs",
9001
9091
  onClick: () => setEditing((e) => !e),
9002
- variant: "filled",
9003
- color: editing ? "green" : "gray",
9004
9092
  children: editing ? /* @__PURE__ */ jsx(IconCircleCheck, {}) : /* @__PURE__ */ jsx(IconPencil, {})
9005
9093
  }
9006
9094
  ) }),
@@ -9023,6 +9111,14 @@ function ApiInput({
9023
9111
  ) })
9024
9112
  ]
9025
9113
  }
9114
+ ),
9115
+ /* @__PURE__ */ jsx(
9116
+ ActionIcon,
9117
+ {
9118
+ size: "xs",
9119
+ onClick: () => onDelete(),
9120
+ children: /* @__PURE__ */ jsx(IconTrash, { color: "red" })
9121
+ }
9026
9122
  )
9027
9123
  ] })
9028
9124
  ] });
@@ -9077,6 +9173,7 @@ var emptyStatus = {
9077
9173
  duration: null,
9078
9174
  error: null,
9079
9175
  log: [],
9176
+ responseSize: null,
9080
9177
  resp: null,
9081
9178
  allowed: true
9082
9179
  };
@@ -9097,27 +9194,9 @@ function BlockPreview(props) {
9097
9194
  const blockContent = types_default[block.type].renderPreviewOnEdit && blockStateContent ? blockStateContent : getBlockContent(block, locale);
9098
9195
  const [content, setContent] = useState(null);
9099
9196
  const [status, setStatus2] = useState(emptyStatus);
9100
- const [estimatedResponseSize, setEstimatedResponseSize] = useState(void 0);
9101
- const [estimatedOutputSize, setEstimatedOutputSize] = useState(void 0);
9102
- const [slugsInterceptor, setSlugsInterceptor] = useState(false);
9197
+ const estimatedOutputSize = block.type === BLOCK_TYPES.GENERATOR && content && content.outputVariables ? JSON.stringify(content.outputVariables).length / 1024 : void 0;
9198
+ const slugsInterceptor = block.type === BLOCK_TYPES.GENERATOR && status.api && status.api.indexOf("bespoke_slugs=") > -1 ? true : false;
9103
9199
  const readMemberFn = useReadMemberFn();
9104
- useEffect(() => {
9105
- if (block.type === BLOCK_TYPES.GENERATOR) {
9106
- if (status.resp) {
9107
- setEstimatedResponseSize(JSON.stringify(status.resp).length / 1024);
9108
- } else {
9109
- setEstimatedResponseSize(void 0);
9110
- }
9111
- if (content && content.outputVariables) {
9112
- setEstimatedOutputSize(JSON.stringify(content.outputVariables).length / 1024);
9113
- } else {
9114
- setEstimatedOutputSize(void 0);
9115
- }
9116
- if (status.api) {
9117
- setSlugsInterceptor(status.api.indexOf("bespoke_slugs=") > -1);
9118
- }
9119
- }
9120
- }, [status.resp, status.api, content, block.type]);
9121
9200
  useEffect(() => {
9122
9201
  const fetch = async () => {
9123
9202
  if (block.type === BLOCK_TYPES.VIZ) {
@@ -9226,16 +9305,16 @@ function BlockPreview(props) {
9226
9305
  ]
9227
9306
  }
9228
9307
  ),
9229
- estimatedResponseSize && /* @__PURE__ */ jsxs(
9308
+ status.responseSize && /* @__PURE__ */ jsxs(
9230
9309
  Badge,
9231
9310
  {
9232
9311
  leftSection: /* @__PURE__ */ jsx(IconBox, { size: rem(12) }),
9233
9312
  size: "md",
9234
9313
  pl: 5,
9235
- color: getSizeColor(estimatedResponseSize),
9314
+ color: getSizeColor(status.responseSize),
9236
9315
  children: [
9237
9316
  "Api Response Size: \u2248",
9238
- estimatedResponseSize.toFixed(2),
9317
+ status.responseSize.toFixed(2),
9239
9318
  " Kb"
9240
9319
  ]
9241
9320
  }
@@ -9736,9 +9815,9 @@ function VariableList({ id, setInlineId }) {
9736
9815
  size: "xs",
9737
9816
  compact: true,
9738
9817
  onClick: () => setInlineId(vid),
9739
- variant: "light",
9740
- color: "green",
9741
- leftIcon: /* @__PURE__ */ jsx(IconSettings, { size: 15 }),
9818
+ variant: "subtle",
9819
+ color: "gray",
9820
+ leftIcon: /* @__PURE__ */ jsx(IconPencil, { size: 15 }),
9742
9821
  children: "Edit this generator"
9743
9822
  }
9744
9823
  ),
@@ -9750,7 +9829,7 @@ function VariableList({ id, setInlineId }) {
9750
9829
  onClick: () => removeAsInputClick(vid),
9751
9830
  variant: "light",
9752
9831
  color: "red",
9753
- leftIcon: /* @__PURE__ */ jsx(IconTrash, { size: 15 }),
9832
+ leftIcon: /* @__PURE__ */ jsx(IconLinkOff, { size: 15 }),
9754
9833
  children: "Remove as input"
9755
9834
  }
9756
9835
  )
@@ -9763,6 +9842,7 @@ function VariableList({ id, setInlineId }) {
9763
9842
  var VariableList_default = VariableList;
9764
9843
 
9765
9844
  // components/blocks/BlockEditor.tsx
9845
+ init_consts();
9766
9846
  init_hooks();
9767
9847
 
9768
9848
  // components/sections/SectionVariablesView.tsx
@@ -9799,11 +9879,29 @@ function BlockEditor({
9799
9879
  }) {
9800
9880
  const [tab, setTab] = useState("Content");
9801
9881
  const status = useBlockStatus(id);
9802
- const resp = status?.resp;
9882
+ const resp = useMemo(() => {
9883
+ let apisResponse = null;
9884
+ if (status && status.resp) {
9885
+ console.log("RESP CHANGED");
9886
+ apisResponse = status.resp;
9887
+ }
9888
+ return apisResponse;
9889
+ }, [status]);
9803
9890
  const variables = useInputVariablesFlat(id);
9804
9891
  const { ref, toggle, fullscreen } = useFullscreen();
9892
+ const apiList = blockContent && blockContent.api ? blockContent.api.split(apiSeparator) : [];
9805
9893
  const onChangeCode = (logic, locale2) => setBlockContent({ logic }, locale2);
9806
- const onChangeAPI = (api) => setBlockContent({ api });
9894
+ const onChangeAPI = (changedApi, ix) => {
9895
+ const newApiList = [...apiList];
9896
+ newApiList[ix] = changedApi;
9897
+ setBlockContent({ api: newApiList.join(apiSeparator) });
9898
+ };
9899
+ const onAddAPI = () => {
9900
+ setBlockContent({ api: [...apiList, ""].join(apiSeparator) });
9901
+ };
9902
+ const onDeleteAPI = (deletedIx) => {
9903
+ setBlockContent({ api: apiList.filter((ai, ix) => ix !== deletedIx).join(apiSeparator) });
9904
+ };
9807
9905
  const codeEditor = /* @__PURE__ */ jsx(
9808
9906
  MonacoWrapper_default,
9809
9907
  {
@@ -9846,26 +9944,45 @@ function BlockEditor({
9846
9944
  /* @__PURE__ */ jsx(InputMenu_default, { id }),
9847
9945
  /* @__PURE__ */ jsx("div", { style: { width: "100%", maxHeight: "80vh", overflowY: "auto" }, children: /* @__PURE__ */ jsx(VariableList_default, { id, setInlineId }) }),
9848
9946
  blockType === BLOCK_TYPES.GENERATOR && /* @__PURE__ */ jsxs("div", { children: [
9849
- /* @__PURE__ */ jsx(Divider, { label: "API Data (resp)", labelPosition: "center" }),
9850
- /* @__PURE__ */ jsx(
9947
+ /* @__PURE__ */ jsx(Divider, { label: `${apiList.length} API's`, labelPosition: "center", mt: "md" }),
9948
+ apiList.map((apiUrl, ix) => /* @__PURE__ */ jsx(
9851
9949
  ApiInput_default,
9852
9950
  {
9853
- defaultValue: blockContent?.api,
9951
+ defaultValue: apiUrl,
9854
9952
  id,
9855
- onChange: onChangeAPI,
9856
- onEnterPress: () => onSave(true)
9857
- }
9858
- ),
9859
- resp && /* @__PURE__ */ jsx(
9860
- "div",
9953
+ onChange: (changedApi) => onChangeAPI(changedApi, ix),
9954
+ onEnterPress: () => onSave(true),
9955
+ onDelete: () => onDeleteAPI(ix)
9956
+ },
9957
+ `${id}-${ix}`
9958
+ )),
9959
+ /* @__PURE__ */ jsx(
9960
+ Button,
9861
9961
  {
9862
- style: {
9863
- maxHeight: 200,
9864
- overflowY: "scroll"
9865
- },
9866
- children: /* @__PURE__ */ jsx(InputMenuItem_default, { id, variables: resp })
9962
+ size: "xs",
9963
+ compact: true,
9964
+ onClick: onAddAPI,
9965
+ variant: "light",
9966
+ color: "blue",
9967
+ fullWidth: true,
9968
+ leftIcon: /* @__PURE__ */ jsx(IconPlus, { size: 15 }),
9969
+ my: 5,
9970
+ children: "Add API"
9867
9971
  }
9868
- )
9972
+ ),
9973
+ resp && /* @__PURE__ */ jsxs(Fragment, { children: [
9974
+ /* @__PURE__ */ jsx(Divider, { label: "Response data preview (resp)", labelPosition: "center", my: "sm" }),
9975
+ /* @__PURE__ */ jsx(
9976
+ "div",
9977
+ {
9978
+ style: {
9979
+ maxHeight: 200,
9980
+ overflowY: "scroll"
9981
+ },
9982
+ children: /* @__PURE__ */ jsx(ConsoleVariable_default, { value: resp, root: true, label: "resp" })
9983
+ }
9984
+ )
9985
+ ] })
9869
9986
  ] })
9870
9987
  ]
9871
9988
  }
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 && status.api) {
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.35",
3
+ "version": "0.1.37",
4
4
  "description": "Content management system for creating automated data reports",
5
5
  "exports": {
6
6
  ".": {