@datawheel/bespoke 0.1.26 → 0.1.28

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 +627 -137
  2. package/dist/server.js +134 -46
  3. package/package.json +3 -1
package/dist/server.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { initAuth0 } from '@auth0/nextjs-auth0';
2
2
  import * as pg from 'pg';
3
- import { Sequelize, Error as Error$1, fn, col, Op, DataTypes, literal, Model } from 'sequelize';
3
+ import { Sequelize, Error as Error$1, fn, col, Op, DataTypes, QueryTypes, Model } from 'sequelize';
4
4
  import yn3 from 'yn';
5
5
  import * as d3Array from 'd3-array';
6
6
  import * as d3Collection from 'd3-collection';
@@ -1687,7 +1687,7 @@ var getDB = () => {
1687
1687
  ${e.message}
1688
1688
  `);
1689
1689
  }
1690
- Object.values(modelFactoryMap).map((modelFactory) => modelFactory(sequelize)).map((modelAssociator) => modelAssociator(sequelize.models));
1690
+ Object.values(modelFactoryMap).map((modelFactory) => modelFactory(sequelize)).map((modelAssociator) => modelAssociator({ ...sequelize.models, sequelize }));
1691
1691
  global.sequelize = sequelize.sync({ force: shouldWipe }).catch((err) => {
1692
1692
  if (err instanceof Error$1) {
1693
1693
  console.error(
@@ -1697,7 +1697,14 @@ var getDB = () => {
1697
1697
  );
1698
1698
  }
1699
1699
  global.sequelize = void 0;
1700
- }).then(() => sequelize.models);
1700
+ }).then(
1701
+ () => ({
1702
+ ...sequelize.models,
1703
+ // All the models
1704
+ sequelize
1705
+ // Store the whole sequelize object in global to use for raw queries
1706
+ })
1707
+ );
1701
1708
  } else {
1702
1709
  if (verbose2)
1703
1710
  console.log("\u267B\uFE0F RE-UTILIZING DB CONNECTION");
@@ -1830,7 +1837,7 @@ function ingestMembersFactory(db) {
1830
1837
  const contentHash = await Search.findAll({ where: { variant_id: id } }).then((arr) => Object.fromEntries(
1831
1838
  arr.map((content) => [content.id, content.content_id])
1832
1839
  ));
1833
- const contentMembers = members2.map((d) => ({
1840
+ const contentMembers = members2.filter((member) => member[idKey2] && member[idKey2] !== "" && member[labelKey2] && member[labelKey2] !== "").map((d) => ({
1834
1841
  id: contentHash[d[idKey2]],
1835
1842
  locale,
1836
1843
  name: d[labelKey2],
@@ -1854,7 +1861,7 @@ function ingestMembersFactory(db) {
1854
1861
  async function ingestDefaultMembers(variant, locale, members) {
1855
1862
  const { id, dimension_id, slug } = variant;
1856
1863
  const { report_id } = variant.dimension;
1857
- const { idKey, labelKey } = variant.config[locale];
1864
+ const { idKey, labelKey, zValueKey } = variant.config[locale];
1858
1865
  const allSlugsFromOtherMembers = await Search.findAll({
1859
1866
  attributes: ["slug", [fn("COUNT", col("variant.id")), "slugCount"]],
1860
1867
  include: {
@@ -1870,6 +1877,21 @@ function ingestMembersFactory(db) {
1870
1877
  return [item.slug, Number.parseInt(`${count}`, 10)];
1871
1878
  })));
1872
1879
  const toIngestSlugs = [];
1880
+ let minValue = null;
1881
+ let maxValue = null;
1882
+ if (zValueKey && zValueKey !== "") {
1883
+ for (const member of members) {
1884
+ if (typeof member[zValueKey] === "number" && !isNaN(member[zValueKey])) {
1885
+ const numericValue = member[zValueKey];
1886
+ if (minValue === null || numericValue < minValue) {
1887
+ minValue = numericValue;
1888
+ }
1889
+ if (maxValue === null || numericValue > maxValue) {
1890
+ maxValue = numericValue;
1891
+ }
1892
+ }
1893
+ }
1894
+ }
1873
1895
  const searchMembers = members.map((member) => {
1874
1896
  const memberId = member[idKey];
1875
1897
  const slug2 = slugify_default(member[labelKey]);
@@ -1879,13 +1901,24 @@ function ingestMembersFactory(db) {
1879
1901
  }
1880
1902
  const validateSlug = hasDuplicateSlug ? `${slug2}-${memberId}` : slug2;
1881
1903
  toIngestSlugs.push(slug2);
1904
+ let zvalue = Math.random();
1905
+ if (zValueKey && zValueKey !== "" && minValue && maxValue && member[zValueKey]) {
1906
+ let value;
1907
+ try {
1908
+ value = parseInt(member[zValueKey], 10);
1909
+ } catch (error) {
1910
+ value = 0;
1911
+ }
1912
+ const indexedValue = maxValue !== null && minValue !== null && maxValue !== minValue ? (value - minValue) / (maxValue - minValue) : 0;
1913
+ zvalue = maxValue === minValue ? 1 : parseFloat(indexedValue.toPrecision(5));
1914
+ }
1882
1915
  return {
1883
1916
  id: memberId,
1884
1917
  slug: validateSlug,
1885
1918
  variant_id: id,
1886
1919
  dimension_id,
1887
1920
  report_id,
1888
- zvalue: Math.random()
1921
+ zvalue
1889
1922
  };
1890
1923
  });
1891
1924
  try {
@@ -2151,7 +2184,7 @@ var search_default2 = getSearchIndexByLocale;
2151
2184
  // api/db/member/searchMember.ts
2152
2185
  var { localeDefault: localeDefault2 } = getLocales_default();
2153
2186
  function dbSearchMemberFactory(db) {
2154
- const { search: Search } = db;
2187
+ const { search: Search, sequelize } = db;
2155
2188
  return searchMember2;
2156
2189
  async function searchMember2(params) {
2157
2190
  const searchIndexByLocale = await search_default2(db);
@@ -2186,21 +2219,19 @@ function dbSearchMemberFactory(db) {
2186
2219
  const contentWhereClause = {
2187
2220
  locale: all ? { [Op.ne]: null } : locale
2188
2221
  };
2222
+ const calculatedLimit = limit && !isNaN(limit) ? limit : 20;
2189
2223
  if (query === "") {
2190
- const rank = await Search.findAll({
2191
- attributes: [
2192
- "id",
2193
- "content_id",
2194
- "report_id",
2195
- [literal("rank() over(partition by report_id order by zvalue desc)"), "rank"]
2196
- ],
2197
- where: whereClause,
2198
- include: [{
2199
- association: "contentByLocale",
2200
- where: contentWhereClause
2201
- }]
2202
- }).then((arr) => arr.map((d) => d.toJSON()));
2203
- resultsIds = rank.filter((item) => parseInt(item["rank"], 10) <= limit).map((d) => d.content_id);
2224
+ const rank = await sequelize.query(`
2225
+ SELECT t.content_id
2226
+ FROM (
2227
+ SELECT id, content_id, variant_id, zvalue,
2228
+ RANK() OVER (PARTITION BY variant_id ORDER BY zvalue DESC) AS rank
2229
+ FROM bespoke_reports_search
2230
+ ) t
2231
+ WHERE t.rank <= ${calculatedLimit}
2232
+ ORDER BY t.variant_id, t.zvalue DESC;
2233
+ `, { type: QueryTypes.SELECT });
2234
+ resultsIds = rank.map((d) => d.content_id);
2204
2235
  }
2205
2236
  whereClause.content_id = resultsIds;
2206
2237
  const includeClause = [{
@@ -3416,6 +3447,7 @@ var addProfilesData = (items, metadata) => {
3416
3447
  profileResults = profileResults.concat(members.map((member) => ({
3417
3448
  path: `/${variant.slug}/${member.slug}`,
3418
3449
  name: member.name,
3450
+ confidence: member.confidence,
3419
3451
  report: {
3420
3452
  id: report.id,
3421
3453
  name: report.name,
@@ -3429,7 +3461,7 @@ var addProfilesData = (items, metadata) => {
3429
3461
  } else if (report.mode === REPORT_MODES.MULTILATERAL) ;
3430
3462
  }
3431
3463
  });
3432
- return profileResults;
3464
+ return profileResults.sort((a, b) => a.confidence < b.confidence ? 1 : -1);
3433
3465
  };
3434
3466
  var formatNested = (items, metadata) => {
3435
3467
  if (!items)
@@ -3471,23 +3503,18 @@ function dbSearchReportFactory(db) {
3471
3503
  locale: stripHTML(locale),
3472
3504
  visible: true
3473
3505
  });
3474
- let reportSearchResults = memberSearchResponse.results ? memberSearchResponse.results : [];
3475
- let tempResults;
3506
+ const reportSearchResults = memberSearchResponse.results ? memberSearchResponse.results : [];
3476
3507
  const metadataResponse = await dbReadMetadata();
3477
3508
  const metadata = metadataResponse.data;
3478
- switch (format) {
3479
- case "profiles":
3480
- tempResults = formatNested(reportSearchResults, metadata);
3481
- reportSearchResults = addProfilesData(tempResults, metadata);
3482
- break;
3483
- }
3509
+ const tempResults = formatNested(reportSearchResults, metadata);
3484
3510
  return {
3485
3511
  meta: {
3486
3512
  ...memberSearchResponse.meta,
3487
3513
  format
3488
3514
  // Override format
3489
3515
  },
3490
- results: reportSearchResults
3516
+ results: addProfilesData(tempResults, metadata)
3517
+ // Group entities as profiles
3491
3518
  };
3492
3519
  }
3493
3520
  }
@@ -4303,7 +4330,6 @@ function httpImageSearchFactory(axios6, provider) {
4303
4330
 
4304
4331
  // api/http/index.ts
4305
4332
  var transformDeletePayload = (id) => {
4306
- console.log("transformDeletePayload", id);
4307
4333
  return { id };
4308
4334
  };
4309
4335
  var transformReadMembers = (params) => {
@@ -4521,6 +4547,7 @@ var runSelector_default = (logic, formatterFunctions, blockContext) => {
4521
4547
  const evalResult = mortarEval_default("variables", variables, transpiledLogic, formatterFunctions, void 0, blockContext);
4522
4548
  const { vars, log, error } = evalResult;
4523
4549
  const type = vars.type || SELECTOR_TYPES.SINGLE;
4550
+ const component = vars.component || "Selector";
4524
4551
  const name = vars.name || "Unlabeled Selector";
4525
4552
  const options = scaffoldDynamic_default(vars.options || []);
4526
4553
  const maybeFixForMulti = (defaultValue2) => type === SELECTOR_TYPES.MULTI && !Array.isArray(defaultValue2) ? [defaultValue2] : defaultValue2;
@@ -4539,7 +4566,8 @@ var runSelector_default = (logic, formatterFunctions, blockContext) => {
4539
4566
  name,
4540
4567
  type,
4541
4568
  options,
4542
- defaultValue
4569
+ defaultValue,
4570
+ component
4543
4571
  };
4544
4572
  return { config, log, error };
4545
4573
  };
@@ -4683,11 +4711,26 @@ async function apiFetch(url, settings, readMemberFn, locale = "en") {
4683
4711
  }
4684
4712
  async function runSingleBlock(block, formatterFunctions, blockContext, readMemberFn) {
4685
4713
  const { locale, variables, query } = parseBlockContext(blockContext);
4714
+ const allowed = isBlockAllowed(block, blockContext, variables, formatterFunctions);
4686
4715
  let resp = null;
4687
4716
  const unswappedAPI = block.contentByLocale?.logic?.content?.api;
4688
4717
  let apiResponse;
4689
4718
  let duration = null;
4690
4719
  let api = null;
4720
+ if (!allowed) {
4721
+ return {
4722
+ outputVariables: {},
4723
+ renderVariables: {},
4724
+ status: {
4725
+ api: null,
4726
+ duration: null,
4727
+ resp: null,
4728
+ log: [],
4729
+ error: null,
4730
+ allowed: false
4731
+ }
4732
+ };
4733
+ }
4691
4734
  if (unswappedAPI) {
4692
4735
  api = swapAPI(unswappedAPI, formatterFunctions, blockContext);
4693
4736
  const isAbsolute = new RegExp("^(?:[a-z+]+:)?//", "i");
@@ -4700,7 +4743,7 @@ async function runSingleBlock(block, formatterFunctions, blockContext, readMembe
4700
4743
  return result;
4701
4744
  }, (e) => {
4702
4745
  if (verbose7)
4703
- console.error(`Error fetching ${api}: ${e.message}`);
4746
+ console.error(`Error fetching ${api} block ${block.id}: ${e.message}`);
4704
4747
  return { data: {}, requestDuration: 0 };
4705
4748
  });
4706
4749
  resp = apiResponse.data;
@@ -4720,7 +4763,8 @@ async function runSingleBlock(block, formatterFunctions, blockContext, readMembe
4720
4763
  duration: null,
4721
4764
  resp: null,
4722
4765
  log: log2,
4723
- error: error2
4766
+ error: error2,
4767
+ allowed
4724
4768
  }
4725
4769
  };
4726
4770
  }
@@ -4734,7 +4778,8 @@ async function runSingleBlock(block, formatterFunctions, blockContext, readMembe
4734
4778
  duration: null,
4735
4779
  resp: null,
4736
4780
  log: [],
4737
- error: null
4781
+ error: null,
4782
+ allowed
4738
4783
  }
4739
4784
  };
4740
4785
  }
@@ -4751,7 +4796,8 @@ async function runSingleBlock(block, formatterFunctions, blockContext, readMembe
4751
4796
  log,
4752
4797
  duration,
4753
4798
  resp,
4754
- api
4799
+ api,
4800
+ allowed
4755
4801
  }
4756
4802
  };
4757
4803
  }
@@ -4764,31 +4810,56 @@ async function runSingleBlock(block, formatterFunctions, blockContext, readMembe
4764
4810
  error,
4765
4811
  duration,
4766
4812
  resp,
4767
- api
4813
+ api,
4814
+ allowed
4768
4815
  }
4769
4816
  };
4770
4817
  }
4771
- var getDependencies = (bid, blocks, acc = [], crawlUp = true, crawlDown2 = true) => {
4818
+ var getDependencies = (bid, blocks, acc = [], crawlUp = true, crawlDown2 = true, withinSection = true, visited = []) => {
4819
+ if (visited.includes(bid))
4820
+ return [];
4821
+ visited.push(bid);
4772
4822
  const rootBlock = blocks[bid];
4773
4823
  if (rootBlock.inputs.length && crawlUp) {
4774
4824
  rootBlock.inputs.forEach((iid) => {
4775
4825
  const rel = `${iid}-${bid}`;
4776
4826
  if (!acc.includes(rel))
4777
4827
  acc.push(rel);
4778
- rootBlock.inputs.forEach((iid2) => getDependencies(iid2, blocks, acc, crawlUp, false));
4828
+ rootBlock.inputs.forEach((iid2) => getDependencies(iid2, blocks, acc, crawlUp, false, withinSection, visited));
4779
4829
  });
4780
4830
  }
4781
4831
  if (rootBlock.consumers.length && crawlDown2) {
4782
4832
  rootBlock.consumers.forEach((cid) => {
4783
4833
  const rel = `${bid}-${cid}`;
4784
- if (!acc.includes(rel) && blocks[cid].section_id === blocks[bid].section_id)
4834
+ if (!acc.includes(rel) && (blocks[cid].section_id === blocks[bid].section_id || !withinSection))
4785
4835
  acc.push(rel);
4786
- rootBlock.consumers.filter((cid2) => blocks[cid2].section_id === blocks[bid].section_id).forEach((cid2) => getDependencies(cid2, blocks, acc, crawlUp, crawlDown2));
4836
+ rootBlock.consumers.filter((cid2) => blocks[cid2].section_id === blocks[bid].section_id || !withinSection).forEach((cid2) => getDependencies(cid2, blocks, acc, crawlUp, crawlDown2, withinSection, visited));
4787
4837
  });
4788
4838
  }
4789
4839
  return acc;
4790
4840
  };
4791
- var runConsumersV2 = async (blocks, sections, bid, formatterFunctions, blockContext, initialVariables = {}, readMemberFn) => {
4841
+ var isBlockAllowed = (block, blockContext, variables, formatterFunctions) => {
4842
+ let allowed = true;
4843
+ if (block && "allowed" in block.settings && block.settings.allowed !== "always") {
4844
+ if (block.settings.allowed === "never") {
4845
+ allowed = false;
4846
+ } else {
4847
+ const parsedBlockContext = parseBlockContext(blockContext);
4848
+ const { error, output } = mortarEval_default(
4849
+ "variables",
4850
+ variables,
4851
+ block.settings.allowedLogic || "return false;",
4852
+ formatterFunctions,
4853
+ void 0,
4854
+ parsedBlockContext
4855
+ );
4856
+ if (!error)
4857
+ allowed = Boolean(output);
4858
+ }
4859
+ }
4860
+ return allowed;
4861
+ };
4862
+ var runConsumersV2 = async (blocks, sections, bid, formatterFunctions, blockContext, initialVariables = {}, readMemberFn, mode = "section") => {
4792
4863
  if (!bid && !sections)
4793
4864
  return { variables: { ...initialVariables } };
4794
4865
  const variablesById = { ...initialVariables };
@@ -4796,8 +4867,9 @@ var runConsumersV2 = async (blocks, sections, bid, formatterFunctions, blockCont
4796
4867
  const parsedBlockContext = parseBlockContext(blockContext);
4797
4868
  const attributes = parsedBlockContext.variables;
4798
4869
  const rootBlocks = bid ? { [bid]: blocks[bid] } : sections.reduce((rootBlocks2, { id }) => ({ ...rootBlocks2, ...getRootBlocksForSection_default(id, blocks) }), {});
4870
+ const withinSection = mode === "section";
4799
4871
  const blockDeps = Object.keys(rootBlocks).reduce((deps, id) => {
4800
- const dependencies = getDependencies(Number(id), blocks, [], !bid).map((rel) => rel.split("-"));
4872
+ const dependencies = getDependencies(Number(id), blocks, [], !bid, true, withinSection).map((rel) => rel.split("-"));
4801
4873
  return [...deps, ...dependencies];
4802
4874
  }, []);
4803
4875
  const orderedDAG = Object.keys(rootBlocks).reduce(
@@ -4813,6 +4885,19 @@ var runConsumersV2 = async (blocks, sections, bid, formatterFunctions, blockCont
4813
4885
  );
4814
4886
  const runningTask = dependenciesDone.then(() => {
4815
4887
  const variables = block.inputs.reduce((acc, d) => ({ ...acc, ...variablesById[d] }), attributes);
4888
+ const inputNotAllowed = block.inputs.find((iid) => !statusById[iid].allowed || statusById[iid].hiddenByCascade);
4889
+ if (inputNotAllowed) {
4890
+ statusById[bid2] = {
4891
+ api: null,
4892
+ duration: null,
4893
+ resp: null,
4894
+ log: [],
4895
+ error: null,
4896
+ allowed: false,
4897
+ hiddenByCascade: statusById[inputNotAllowed].hiddenByCascade || inputNotAllowed
4898
+ };
4899
+ return;
4900
+ }
4816
4901
  return runSingleBlock(
4817
4902
  block,
4818
4903
  formatterFunctions,
@@ -5718,6 +5803,7 @@ var storeFactory = (_context) => configureStore({
5718
5803
  return getDefaultMiddleware({
5719
5804
  thunk: {
5720
5805
  extraArgument: apiFactory2("/api/cms/")
5806
+ // serializableCheck: false,
5721
5807
  }
5722
5808
  });
5723
5809
  }
@@ -5767,7 +5853,8 @@ function parseReportId(value) {
5767
5853
  var { locales: locales8 } = getLocales_default();
5768
5854
  var initialContext = {
5769
5855
  pathSegment: "path",
5770
- formatterFunctions: locales8.reduce((acc, d) => ({ ...acc, [d]: {} }), {})
5856
+ formatterFunctions: locales8.reduce((acc, d) => ({ ...acc, [d]: {} }), {}),
5857
+ profilePrefix: "path"
5771
5858
  };
5772
5859
  createContext(initialContext);
5773
5860
 
@@ -5898,7 +5985,8 @@ function BespokeRendererStaticProps(options) {
5898
5985
  variables: attributes
5899
5986
  },
5900
5987
  void 0,
5901
- readMemberFn
5988
+ readMemberFn,
5989
+ "report"
5902
5990
  ).then((data) => {
5903
5991
  dispatch(variablesActions.setVariableChange({ ...data, attributes }));
5904
5992
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@datawheel/bespoke",
3
- "version": "0.1.26",
3
+ "version": "0.1.28",
4
4
  "description": "Content management system for creating automated data reports",
5
5
  "exports": {
6
6
  ".": {
@@ -59,8 +59,10 @@
59
59
  "base58": "^2.0.1",
60
60
  "buble": "^0.20.0",
61
61
  "comment-parser": "^1.3.1",
62
+ "d3": "^7.8.4",
62
63
  "d3-array": "^3.1.6",
63
64
  "d3-collection": "^1.0.7",
65
+ "d3-dag": "^0.11.5",
64
66
  "d3-dsv": "^1.2.0",
65
67
  "d3-format": "^3.1.0",
66
68
  "d3-selection": "^3.0.0",