@datawheel/bespoke 0.1.35 → 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 +234 -121
  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
  {
@@ -7144,7 +7242,7 @@ function Section({ section }) {
7144
7242
  columnSettings,
7145
7243
  sx: { flexBasis: `${100 / colsQty}%` },
7146
7244
  children: columnBlocks.map((item) => {
7147
- if (!item.id || !status[item.id].allowed && item.type !== BLOCK_TYPES.NAV)
7245
+ if (!item.id || !status[item.id]?.allowed && item.type !== BLOCK_TYPES.NAV)
7148
7246
  return null;
7149
7247
  const { settings: settings2, type } = blockRecords[item.id];
7150
7248
  const blockWidth = settings2.width && !settings2.width.stretch && settings2.width.unit ? formatters[settings2.width.unit](settings2.width.value) : settings2.display === "inline" ? "auto" : "100%";
@@ -8949,9 +9047,9 @@ function ApiInput({
8949
9047
  defaultValue,
8950
9048
  id,
8951
9049
  onChange,
8952
- onEnterPress
9050
+ onEnterPress,
9051
+ onDelete
8953
9052
  }) {
8954
- const status = useBlockStatus(id);
8955
9053
  const formatterFunctions = useFormatterFunctionsForLocale();
8956
9054
  const blockContext = useBlockContext(id);
8957
9055
  const [preview, setPreview] = useState(() => varSwap_default(defaultValue, formatterFunctions, blockContext));
@@ -8970,21 +9068,9 @@ function ApiInput({
8970
9068
  setPreview(varSwap_default(value, formatterFunctions, blockContext));
8971
9069
  onChange(value);
8972
9070
  };
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
- ] }),
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: [
8988
9074
  /* @__PURE__ */ jsxs(
8989
9075
  Popover,
8990
9076
  {
@@ -8999,8 +9085,6 @@ function ApiInput({
8999
9085
  {
9000
9086
  size: "xs",
9001
9087
  onClick: () => setEditing((e) => !e),
9002
- variant: "filled",
9003
- color: editing ? "green" : "gray",
9004
9088
  children: editing ? /* @__PURE__ */ jsx(IconCircleCheck, {}) : /* @__PURE__ */ jsx(IconPencil, {})
9005
9089
  }
9006
9090
  ) }),
@@ -9023,6 +9107,14 @@ function ApiInput({
9023
9107
  ) })
9024
9108
  ]
9025
9109
  }
9110
+ ),
9111
+ /* @__PURE__ */ jsx(
9112
+ ActionIcon,
9113
+ {
9114
+ size: "xs",
9115
+ onClick: () => onDelete(),
9116
+ children: /* @__PURE__ */ jsx(IconTrash, { color: "red" })
9117
+ }
9026
9118
  )
9027
9119
  ] })
9028
9120
  ] });
@@ -9077,6 +9169,7 @@ var emptyStatus = {
9077
9169
  duration: null,
9078
9170
  error: null,
9079
9171
  log: [],
9172
+ responseSize: null,
9080
9173
  resp: null,
9081
9174
  allowed: true
9082
9175
  };
@@ -9097,27 +9190,9 @@ function BlockPreview(props) {
9097
9190
  const blockContent = types_default[block.type].renderPreviewOnEdit && blockStateContent ? blockStateContent : getBlockContent(block, locale);
9098
9191
  const [content, setContent] = useState(null);
9099
9192
  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);
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;
9103
9195
  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
9196
  useEffect(() => {
9122
9197
  const fetch = async () => {
9123
9198
  if (block.type === BLOCK_TYPES.VIZ) {
@@ -9226,16 +9301,16 @@ function BlockPreview(props) {
9226
9301
  ]
9227
9302
  }
9228
9303
  ),
9229
- estimatedResponseSize && /* @__PURE__ */ jsxs(
9304
+ status.responseSize && /* @__PURE__ */ jsxs(
9230
9305
  Badge,
9231
9306
  {
9232
9307
  leftSection: /* @__PURE__ */ jsx(IconBox, { size: rem(12) }),
9233
9308
  size: "md",
9234
9309
  pl: 5,
9235
- color: getSizeColor(estimatedResponseSize),
9310
+ color: getSizeColor(status.responseSize),
9236
9311
  children: [
9237
9312
  "Api Response Size: \u2248",
9238
- estimatedResponseSize.toFixed(2),
9313
+ status.responseSize.toFixed(2),
9239
9314
  " Kb"
9240
9315
  ]
9241
9316
  }
@@ -9736,9 +9811,9 @@ function VariableList({ id, setInlineId }) {
9736
9811
  size: "xs",
9737
9812
  compact: true,
9738
9813
  onClick: () => setInlineId(vid),
9739
- variant: "light",
9740
- color: "green",
9741
- leftIcon: /* @__PURE__ */ jsx(IconSettings, { size: 15 }),
9814
+ variant: "subtle",
9815
+ color: "gray",
9816
+ leftIcon: /* @__PURE__ */ jsx(IconPencil, { size: 15 }),
9742
9817
  children: "Edit this generator"
9743
9818
  }
9744
9819
  ),
@@ -9750,7 +9825,7 @@ function VariableList({ id, setInlineId }) {
9750
9825
  onClick: () => removeAsInputClick(vid),
9751
9826
  variant: "light",
9752
9827
  color: "red",
9753
- leftIcon: /* @__PURE__ */ jsx(IconTrash, { size: 15 }),
9828
+ leftIcon: /* @__PURE__ */ jsx(IconLinkOff, { size: 15 }),
9754
9829
  children: "Remove as input"
9755
9830
  }
9756
9831
  )
@@ -9763,6 +9838,7 @@ function VariableList({ id, setInlineId }) {
9763
9838
  var VariableList_default = VariableList;
9764
9839
 
9765
9840
  // components/blocks/BlockEditor.tsx
9841
+ init_consts();
9766
9842
  init_hooks();
9767
9843
 
9768
9844
  // components/sections/SectionVariablesView.tsx
@@ -9799,11 +9875,29 @@ function BlockEditor({
9799
9875
  }) {
9800
9876
  const [tab, setTab] = useState("Content");
9801
9877
  const status = useBlockStatus(id);
9802
- 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]);
9803
9886
  const variables = useInputVariablesFlat(id);
9804
9887
  const { ref, toggle, fullscreen } = useFullscreen();
9888
+ const apiList = blockContent && blockContent.api ? blockContent.api.split(apiSeparator) : [];
9805
9889
  const onChangeCode = (logic, locale2) => setBlockContent({ logic }, locale2);
9806
- 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
+ };
9807
9901
  const codeEditor = /* @__PURE__ */ jsx(
9808
9902
  MonacoWrapper_default,
9809
9903
  {
@@ -9846,26 +9940,45 @@ function BlockEditor({
9846
9940
  /* @__PURE__ */ jsx(InputMenu_default, { id }),
9847
9941
  /* @__PURE__ */ jsx("div", { style: { width: "100%", maxHeight: "80vh", overflowY: "auto" }, children: /* @__PURE__ */ jsx(VariableList_default, { id, setInlineId }) }),
9848
9942
  blockType === BLOCK_TYPES.GENERATOR && /* @__PURE__ */ jsxs("div", { children: [
9849
- /* @__PURE__ */ jsx(Divider, { label: "API Data (resp)", labelPosition: "center" }),
9850
- /* @__PURE__ */ jsx(
9943
+ /* @__PURE__ */ jsx(Divider, { label: `${apiList.length} API's`, labelPosition: "center", mt: "md" }),
9944
+ apiList.map((apiUrl, ix) => /* @__PURE__ */ jsx(
9851
9945
  ApiInput_default,
9852
9946
  {
9853
- defaultValue: blockContent?.api,
9947
+ defaultValue: apiUrl,
9854
9948
  id,
9855
- onChange: onChangeAPI,
9856
- onEnterPress: () => onSave(true)
9857
- }
9858
- ),
9859
- resp && /* @__PURE__ */ jsx(
9860
- "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,
9861
9957
  {
9862
- style: {
9863
- maxHeight: 200,
9864
- overflowY: "scroll"
9865
- },
9866
- 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"
9867
9967
  }
9868
- )
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
+ ] })
9869
9982
  ] })
9870
9983
  ]
9871
9984
  }
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.35",
3
+ "version": "0.1.36",
4
4
  "description": "Content management system for creating automated data reports",
5
5
  "exports": {
6
6
  ".": {