@datawheel/bespoke 0.1.9 → 0.1.11

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 +3115 -5095
  2. package/dist/server.js +100 -64
  3. package/package.json +7 -6
package/dist/server.js CHANGED
@@ -12,7 +12,7 @@ import { strip, titleCase } from 'd3plus-text';
12
12
  import axios from 'axios';
13
13
  import slugifyFn from 'slugify';
14
14
  import lunr from 'lunr';
15
- import lunrStemmer from 'lunr-languages/lunr.stemmer.support.js';
15
+ import lunrStemmer from 'lunr-languages/lunr.stemmer.support';
16
16
  import Base58 from 'base58';
17
17
  import FlickrSDK from 'flickr-sdk';
18
18
  import sharp from 'sharp';
@@ -1748,19 +1748,26 @@ function ingestMembersFactory(db) {
1748
1748
  attributes: [],
1749
1749
  required: false,
1750
1750
  right: true,
1751
- where: { dimension_id, slug }
1751
+ where: { dimension_id, slug, id: { [Op.not]: id } }
1752
1752
  },
1753
1753
  group: "search.slug"
1754
1754
  }).then((arr) => Object.fromEntries(arr.map((item) => {
1755
1755
  const count = item.getDataValue("slugCount");
1756
1756
  return [item.slug, Number.parseInt(`${count}`, 10)];
1757
1757
  })));
1758
+ const toIngestSlugs = [];
1758
1759
  const searchMembers = members.map((member) => {
1759
- const slug2 = slugify_default(member[labelKey]);
1760
1760
  const memberId = member[idKey];
1761
+ const slug2 = slugify_default(member[labelKey]);
1762
+ const hasDuplicateSlug = allSlugsFromOtherMembers[slug2] || toIngestSlugs.includes(slug2);
1763
+ if (hasDuplicateSlug) {
1764
+ console.log("Duplicate slug!", slug2, "replaced by", `${slug2}-${memberId}`);
1765
+ }
1766
+ const validateSlug = hasDuplicateSlug ? `${slug2}-${memberId}` : slug2;
1767
+ toIngestSlugs.push(slug2);
1761
1768
  return {
1762
1769
  id: memberId,
1763
- slug: allSlugsFromOtherMembers[slug2] > 1 ? `${slug2}-${memberId}` : slug2,
1770
+ slug: validateSlug,
1764
1771
  variant_id: id,
1765
1772
  dimension_id,
1766
1773
  report_id,
@@ -1780,30 +1787,39 @@ function readMemberFactory(db) {
1780
1787
  const { search: Search } = db;
1781
1788
  return readMember;
1782
1789
  async function readMember(params) {
1783
- const {
1784
- mode,
1785
- ids,
1786
- slugs,
1787
- all,
1788
- locale
1789
- } = params;
1790
+ const { all, locale, mode } = params;
1790
1791
  const whereClause = {
1791
1792
  content_id: []
1792
1793
  };
1793
- if (mode === "ids") {
1794
- whereClause.content_id = ids;
1794
+ if (mode === "content_ids") {
1795
+ whereClause.content_id = params.content_ids;
1796
+ } else if (mode === "ids") {
1797
+ if (all) {
1798
+ throw new BackendError(404, "Search by ids doesn't support locale all. Pick one locale.");
1799
+ }
1800
+ const entities = await Search.findAll({
1801
+ attributes: ["content_id"],
1802
+ where: {
1803
+ id: params.ids
1804
+ },
1805
+ include: [{
1806
+ association: "contentByLocale",
1807
+ where: { locale }
1808
+ }]
1809
+ });
1810
+ whereClause.content_id = entities.map((item) => item.content_id);
1795
1811
  } else if (mode === "slugs") {
1796
1812
  const entities = await Search.findAll({
1797
1813
  attributes: ["content_id"],
1798
1814
  where: {
1799
- slug: slugs.map((item) => item.memberSlug)
1815
+ slug: params.slugs.map((item) => item.memberSlug)
1800
1816
  },
1801
1817
  include: {
1802
1818
  association: "variant",
1803
1819
  attributes: ["id", "name", "slug"],
1804
1820
  required: false,
1805
1821
  where: {
1806
- slug: slugs.map((item) => item.variantSlug)
1822
+ slug: params.slugs.map((item) => item.variantSlug)
1807
1823
  }
1808
1824
  }
1809
1825
  });
@@ -1835,48 +1851,57 @@ function readMemberFactory(db) {
1835
1851
  where: contentWhereClause
1836
1852
  }]
1837
1853
  });
1838
- if (memberSearchResults.length !== (mode === "ids" ? ids.length : slugs.length)) {
1839
- throw new BackendError(404, "One of the members was not found.");
1854
+ if (memberSearchResults.length !== params[mode].length) {
1855
+ throw new BackendError(404, "One or more members were not found.");
1840
1856
  }
1841
1857
  memberSearchResults.sort(
1842
1858
  (a, b) => whereClause.content_id.indexOf(a.content_id) - whereClause.content_id.indexOf(b.content_id)
1843
1859
  );
1844
1860
  const normalizedResults = memberSearchResults.map((item) => {
1845
- const newItem = {
1846
- ...item.toJSON(),
1861
+ const generalResult = {
1862
+ id: item.id,
1863
+ slug: item.slug,
1864
+ visible: item.visible,
1865
+ zvalue: item.zvalue,
1866
+ content_id: item.content_id,
1867
+ report_id: item.report_id,
1847
1868
  report_name: item.report.name,
1869
+ dimension_id: item.dimension_id,
1848
1870
  dimension_name: item.dimension.name,
1871
+ variant_id: item.variant_id,
1849
1872
  variant_name: item.variant.name,
1850
1873
  variant_slug: item.variant.slug
1851
1874
  };
1852
1875
  if (!all) {
1853
1876
  const localizedItem = item.getContent(locale);
1854
- newItem.locale = localizedItem.locale;
1855
- newItem.name = localizedItem.name;
1856
- newItem.attributes = localizedItem.attributes;
1857
- newItem.keywords = localizedItem.keywords;
1858
- delete newItem.contentByLocale;
1859
- if (item.image) {
1860
- const localizedImage = item.image.getContent(locale);
1861
- newItem.image = {
1862
- ...item.image,
1863
- meta: localizedImage.meta
1864
- };
1865
- delete newItem.image.contentByLocale;
1866
- }
1877
+ const localizedImage = item.image?.getContent(locale);
1878
+ const image = localizedImage && {
1879
+ id: item.image.id,
1880
+ author: item.image.author,
1881
+ url: item.image.url,
1882
+ license: item.image.license,
1883
+ meta: localizedImage.meta
1884
+ };
1885
+ const localizedResult = {
1886
+ ...generalResult,
1887
+ locale: localizedItem.locale,
1888
+ name: localizedItem.name,
1889
+ attributes: localizedItem.attributes,
1890
+ keywords: localizedItem.keywords,
1891
+ image: image || null,
1892
+ image_id: item.image_id
1893
+ };
1894
+ return localizedResult;
1895
+ } else {
1896
+ const contentByLocale = item.contentByLocale;
1897
+ generalResult.contentByLocale = contentByLocale;
1898
+ generalResult.image = item.image;
1899
+ generalResult.image_id = item.image_id;
1867
1900
  }
1868
- delete newItem.report;
1869
- delete newItem.dimension;
1870
- delete newItem.variant;
1871
- return newItem;
1901
+ return generalResult;
1872
1902
  });
1873
1903
  return {
1874
- meta: {
1875
- locale: stripHTML(locale),
1876
- mode,
1877
- ids,
1878
- slugs
1879
- },
1904
+ meta: params,
1880
1905
  results: normalizedResults
1881
1906
  };
1882
1907
  }
@@ -1956,16 +1981,21 @@ var verbose3 = getLogging_default();
1956
1981
  var { locales } = getLocales_default();
1957
1982
  var enabledLocales = locales.filter((d) => whitelist.includes(d));
1958
1983
  var search;
1984
+ var initializing = false;
1959
1985
  var getSearchIndexByLocale = async (db, forceRegenerate = false) => {
1960
1986
  if (forceRegenerate) {
1961
1987
  if (verbose3)
1962
1988
  console.log("REGENERATING SEARCH INDEX");
1963
1989
  search = void 0;
1990
+ initializing = false;
1964
1991
  }
1965
1992
  if (search)
1966
1993
  return search;
1967
1994
  try {
1968
- search = await newSearchIndex(db);
1995
+ if (!initializing) {
1996
+ initializing = true;
1997
+ search = await newSearchIndex(db);
1998
+ }
1969
1999
  } catch (e) {
1970
2000
  throw new Error(`
1971
2001
  Could not initialize Lunr search engine.
@@ -1983,7 +2013,7 @@ async function newSearchIndex(db) {
1983
2013
  });
1984
2014
  const setups = enabledLocales.map(async (locale) => {
1985
2015
  if (!lunr[locale] && whitelist.includes(locale) && locale !== "en") {
1986
- const lunrLang = await import(`lunr-languages/lunr.${locale}`);
2016
+ const lunrLang = (await import(`lunr-languages/lunr.${locale}`)).default;
1987
2017
  lunrLang(lunr);
1988
2018
  }
1989
2019
  return [locale, lunr(function() {
@@ -2443,14 +2473,20 @@ function dbReportFactory(db) {
2443
2473
  sections,
2444
2474
  ...reportData
2445
2475
  } = data;
2446
- const entity = await Report.findByPk(id, {
2447
- include: reportQuery.include,
2448
- rejectOnEmpty: true
2449
- });
2450
- entity.set(reportData);
2451
- await entity.save();
2452
- await entity.reload();
2453
- return entity.toJSON();
2476
+ try {
2477
+ console.log("Update Report ID", id, reportData);
2478
+ const entity = await Report.findByPk(id, {
2479
+ include: reportQuery.include,
2480
+ rejectOnEmpty: true
2481
+ });
2482
+ entity.set(reportData);
2483
+ await entity.save();
2484
+ await entity.reload();
2485
+ return entity.toJSON();
2486
+ } catch (error) {
2487
+ console.log("Report id:", id, error);
2488
+ throw new BackendError(500, "Update Report error");
2489
+ }
2454
2490
  }
2455
2491
  async function deleteReport(id) {
2456
2492
  const entity = await Report.findByPk(id, { rejectOnEmpty: true });
@@ -2816,15 +2852,13 @@ function endpointMemberFactory(operations) {
2816
2852
  } = operations;
2817
2853
  return [
2818
2854
  endpoint("GET", "read/members", async (req) => {
2819
- const params = parseReadMemberParams(req.query);
2820
- if (params.ids.length > 1 && params.slugs.length > 1) {
2821
- throw new BackendError(400, "Both 'ids' and 'slugs' are filled. Pick one.");
2855
+ const params = parseReadMembersParams(req.query);
2856
+ const list = params[params.mode] || [];
2857
+ if (list.length === 0) {
2858
+ throw new BackendError(400, `The request is missing the '${params.mode}' parameter.`);
2822
2859
  }
2823
- if (params.ids.length === 0 && params.slugs.length === 0) {
2824
- throw new BackendError(400, "Params 'ids' and 'slugs' are missing. Fill one.");
2825
- }
2826
- if ((params.mode === "ids" ? params.ids.length : params.slugs.length) > 5) {
2827
- throw new BackendError(400, "Too many 'ids' and 'slugs' in the request. Try with less than 5.");
2860
+ if (list.length > 20) {
2861
+ throw new BackendError(400, `You can look for up to 20 '${params.mode}' in a single request.`);
2828
2862
  }
2829
2863
  return readMember(params);
2830
2864
  }),
@@ -2845,15 +2879,17 @@ function endpointMemberFactory(operations) {
2845
2879
  })
2846
2880
  ];
2847
2881
  }
2848
- function parseReadMemberParams(query) {
2849
- const ids = normalizeList(query.ids).map(parseFiniteNumber);
2882
+ function parseReadMembersParams(query) {
2883
+ const ids = normalizeList(query.ids);
2884
+ const content_ids = normalizeList(query.content_ids).map(parseFiniteNumber);
2850
2885
  const locale = normalizeList(query.locale)[0] || localeDefault3 || "en";
2851
2886
  const localeIsAll = locale === "all";
2852
2887
  return {
2853
2888
  all: localeIsAll,
2889
+ locale: localeIsAll ? localeDefault3 : stripHTML(locale),
2890
+ mode: ids.length > 0 ? "ids" : content_ids.length > 0 ? "content_ids" : "slugs",
2854
2891
  ids,
2855
- locale: localeIsAll ? localeDefault3 : locale,
2856
- mode: ids.length > 0 ? "ids" : "slugs",
2892
+ content_ids,
2857
2893
  slugs: normalizeList(query.slugs).map((token) => {
2858
2894
  const [variantSlug, memberSlug] = token.split("/");
2859
2895
  return { variantSlug, memberSlug };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@datawheel/bespoke",
3
- "version": "0.1.9",
3
+ "version": "0.1.11",
4
4
  "description": "Content management system for creating automated data reports",
5
5
  "exports": {
6
6
  ".": {
@@ -10,8 +10,11 @@
10
10
  "import": "./dist/server.js"
11
11
  }
12
12
  },
13
- "files": ["dist", "README.md"],
14
- "style": "./dist/style.css",
13
+ "files": [
14
+ "dist",
15
+ "README.md"
16
+ ],
17
+ "style": "./dist/index.css",
15
18
  "scripts": {
16
19
  "prepublishOnly": "tsup",
17
20
  "build": "tsup",
@@ -81,14 +84,12 @@
81
84
  "normalizr": "^3.6.2",
82
85
  "pg": "^8.7.3",
83
86
  "pretty-format": "^28.1.1",
84
- "react": "^17.0.2",
87
+ "react": "^17.0.0||^18.0.0",
85
88
  "react-beautiful-dnd": "^13.1.0",
86
89
  "react-clipboard.js": "^2.0.16",
87
90
  "react-icons": "^4.3.1",
88
91
  "react-redux": "^7.2.6",
89
- "react-router-dom": "^6.3.0",
90
92
  "react-share": "^4.4.0",
91
- "react-table-6": "^6.11.0",
92
93
  "sequelize": "^6.17.0",
93
94
  "sharp": "^0.31.0",
94
95
  "shelljs": "^0.8.5",