@datawheel/bespoke 0.6.0-rc.8 → 0.7.0-rc.0

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 +745 -595
  2. package/dist/server.js +266 -312
  3. package/package.json +2 -3
package/dist/server.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { initAuth0 } from '@auth0/nextjs-auth0';
2
2
  import auth0 from 'auth0';
3
3
  import * as pg from 'pg';
4
- import { Sequelize, Error as Error$1, Op, fn, col, DataTypes, QueryTypes, Model } from 'sequelize';
4
+ import { Sequelize, Error as Error$1, Op, fn, col, literal, DataTypes, Model } from 'sequelize';
5
5
  import yn2 from 'yn';
6
6
  import * as d3Array from 'd3-array';
7
7
  import * as d3Collection from 'd3-collection';
@@ -26,8 +26,7 @@ import * as allIcons from '@tabler/icons-react';
26
26
  import NextCors from 'nextjs-cors';
27
27
  import imageType from 'image-type';
28
28
  import formidable from 'formidable';
29
- import { BrowserPool, PlaywrightPlugin } from '@crawlee/browser-pool';
30
- import { chromium } from 'playwright-chromium';
29
+ import puppeteer from 'puppeteer';
31
30
  import DomParser from 'dom-parser';
32
31
  import path from 'path';
33
32
  import axiosRetry from 'axios-retry';
@@ -1454,7 +1453,8 @@ var getVariantSettingsDefaults = (settings) => ({
1454
1453
  regenerateSlugs: settings && typeof settings.regenerateSlugs !== "undefined" ? settings.regenerateSlugs : false,
1455
1454
  keepOldMembers: settings && typeof settings.keepOldMembers !== "undefined" ? settings.keepOldMembers : true,
1456
1455
  doIngest: settings && typeof settings.doIngest !== "undefined" ? settings.doIngest : false,
1457
- overrideAttributes: settings && typeof settings.overrideAttributes !== "undefined" ? settings.overrideAttributes : false
1456
+ overrideAttributes: settings && typeof settings.overrideAttributes !== "undefined" ? settings.overrideAttributes : false,
1457
+ order: settings && typeof settings.order !== "undefined" ? settings.order : 0
1458
1458
  });
1459
1459
 
1460
1460
  // api/db/member/ingestMembers.ts
@@ -1478,12 +1478,14 @@ function ingestMembersFactory(db) {
1478
1478
  const members = keyDiver(memberFetch.data, accessor);
1479
1479
  let toUpdateContent = [];
1480
1480
  try {
1481
+ console.log(`Ingesting default items for variant ${variant.name} for ${defaultLocale}`);
1481
1482
  toUpdateContent = await ingestDefaultMembers(variant, defaultLocale, members);
1482
1483
  } catch (error) {
1483
1484
  const errorMsg = `[ERROR] Ingesting default member items for ${defaultLocale}`;
1484
1485
  console.error(errorMsg, error);
1485
1486
  throw new BackendError(500, errorMsg);
1486
1487
  }
1488
+ console.log("toUpdateContent", toUpdateContent);
1487
1489
  const contentHash = await Search.findAll({ where: { variant_id: id } }).then((arr) => Object.fromEntries(
1488
1490
  arr.map(
1489
1491
  (content) => [
@@ -1530,6 +1532,7 @@ function ingestMembersFactory(db) {
1530
1532
  contentMember.attributes = calculatedAttributes;
1531
1533
  }
1532
1534
  } else {
1535
+ console.log("name", d2[labelKey2], "-");
1533
1536
  contentMember = {
1534
1537
  id: searchElement.content_id,
1535
1538
  locale,
@@ -1584,6 +1587,8 @@ function ingestMembersFactory(db) {
1584
1587
  allSlugsFromOtherMembersQuery,
1585
1588
  oldIngestedMembersQuery
1586
1589
  ]);
1590
+ console.log("allSlugsFromOtherMembers", allSlugsFromOtherMembers);
1591
+ console.log("oldIngestedMembers", oldIngestedMembers);
1587
1592
  const oldIngestedSlugs = Object.values(oldIngestedMembers).map((m) => m.slug);
1588
1593
  const toIngestSlugs = [];
1589
1594
  let minValue = null;
@@ -1605,7 +1610,7 @@ function ingestMembersFactory(db) {
1605
1610
  const toUpdate = [];
1606
1611
  const toUpdateContent = [];
1607
1612
  let toUpdateFlag = false;
1608
- members.forEach((member) => {
1613
+ members.filter((member) => member[idKey] && member[idKey] !== "" && member[labelKey] && member[labelKey] !== "").forEach((member) => {
1609
1614
  const memberId = member[idKey];
1610
1615
  const slug2 = slugify_default(member[labelKey]);
1611
1616
  toUpdateFlag = false;
@@ -2025,7 +2030,8 @@ function dbSearchMemberFactory(db) {
2025
2030
  noImage,
2026
2031
  includes,
2027
2032
  sort,
2028
- direction = "ASC"
2033
+ direction = "ASC",
2034
+ offset = 0
2029
2035
  } = params;
2030
2036
  if (query && query !== "" && searchIndexByLocale[locale]) {
2031
2037
  const terms = sanitizeQuery_default(query).replace(/([A-z]{2,})/g, (txt) => `+${txt}`).replace(/(.)$/g, (txt) => `${txt}*`);
@@ -2062,18 +2068,22 @@ function dbSearchMemberFactory(db) {
2062
2068
  locale: all ? { [Op.ne]: null } : locale
2063
2069
  };
2064
2070
  const calculatedLimit = limit && !isNaN(limit) ? limit : 20;
2071
+ const orderClause = [["zvalue", "DESC NULLS LAST"]];
2072
+ if (sort && sort !== "name") {
2073
+ orderClause.unshift([sort, direction.toUpperCase()]);
2074
+ }
2065
2075
  if (query === "") {
2066
- const rank = await sequelize.query(`
2067
- SELECT t.content_id
2068
- FROM (
2069
- SELECT id, content_id, variant_id, zvalue,
2070
- RANK() OVER (PARTITION BY variant_id ORDER BY zvalue DESC) AS rank
2071
- FROM bespoke_reports_search
2072
- ) t
2073
- WHERE t.rank <= ${calculatedLimit}
2074
- ORDER BY t.variant_id, t.zvalue DESC;
2075
- `, { type: QueryTypes.SELECT });
2076
- resultsIds = rank.map((d2) => d2.content_id);
2076
+ const noQueryIds = await Search.findAll({
2077
+ attributes: ["content_id"],
2078
+ limit: calculatedLimit,
2079
+ offset,
2080
+ where: whereClause,
2081
+ order: orderClause
2082
+ });
2083
+ resultsIds = noQueryIds.map((r2) => {
2084
+ const result = r2.toJSON();
2085
+ return result.content_id;
2086
+ });
2077
2087
  }
2078
2088
  whereClause.content_id = resultsIds;
2079
2089
  const includeClause = [{
@@ -2092,16 +2102,29 @@ function dbSearchMemberFactory(db) {
2092
2102
  attributes: ["id", "slug", "name"]
2093
2103
  });
2094
2104
  }
2095
- const orderClause = [["zvalue", "DESC NULLS LAST"]];
2096
- if (sort && sort !== "name") {
2097
- orderClause.unshift([sort, direction.toUpperCase()]);
2098
- }
2099
- const results = await Search.findAll({
2100
- limit: query ? limit : void 0,
2101
- where: whereClause,
2102
- include: includeClause,
2103
- order: orderClause
2104
- });
2105
+ const countWhereClause = {
2106
+ ...whereClause.visible ? { visible: whereClause.visible } : {},
2107
+ ...whereClause.noImage ? { image_id: whereClause.noImage } : {},
2108
+ ...whereClause.content_id ? { content_id: whereClause.content_id } : {}
2109
+ };
2110
+ const [results, variantCount] = await Promise.all([
2111
+ Search.findAll({
2112
+ limit: query ? calculatedLimit : void 0,
2113
+ offset: query ? offset : 0,
2114
+ where: whereClause,
2115
+ include: includeClause,
2116
+ order: orderClause
2117
+ }),
2118
+ Search.findAll({
2119
+ attributes: ["variant_id", [
2120
+ fn("COUNT", col("variant_id")),
2121
+ "members_count"
2122
+ ]],
2123
+ where: countWhereClause,
2124
+ group: ["variant_id"]
2125
+ })
2126
+ ]);
2127
+ const typedVariantCount = variantCount.map((vc) => vc.toJSON());
2105
2128
  if (sort === "name") {
2106
2129
  const sortDirection = direction.toUpperCase() === "ASC" ? 1 : -1;
2107
2130
  results.sort((a2, b2) => {
@@ -2142,7 +2165,11 @@ function dbSearchMemberFactory(db) {
2142
2165
  origin: "lunr",
2143
2166
  ...Object.fromEntries(
2144
2167
  Object.entries(params).map((entry) => [entry[0], stripHTML(entry[1])])
2145
- )
2168
+ ),
2169
+ variantCount: typedVariantCount.reduce((acc, vc) => {
2170
+ acc[vc.variant_id] = parseInt(vc.members_count, 10);
2171
+ return acc;
2172
+ }, {})
2146
2173
  },
2147
2174
  results: formattedResults
2148
2175
  };
@@ -2235,8 +2262,6 @@ function dbMemberFactory(db) {
2235
2262
  readMemberImage: readMemberImageFactory(db)
2236
2263
  };
2237
2264
  }
2238
-
2239
- // api/db/include.ts
2240
2265
  var blockQuery = {
2241
2266
  include: [
2242
2267
  { association: "contentByLocale", separate: true },
@@ -2247,19 +2272,33 @@ var blockQuery = {
2247
2272
  }
2248
2273
  ]
2249
2274
  };
2250
- var dimensionQuery = {
2251
- include: [{ association: "variants", separate: true }],
2275
+ var dimensionQuery = (searchModel) => ({
2276
+ include: [{
2277
+ association: "variants",
2278
+ separate: true,
2279
+ attributes: {
2280
+ include: [
2281
+ [fn("COUNT", col("members.id")), "members_count"]
2282
+ ]
2283
+ },
2284
+ include: [{
2285
+ model: searchModel,
2286
+ as: "members",
2287
+ attributes: []
2288
+ }],
2289
+ group: ["variant.id"]
2290
+ }],
2252
2291
  order: [["ordering", "ASC"]]
2253
- };
2292
+ });
2254
2293
  var sectionQuery = {
2255
2294
  include: [{ association: "blocks", separate: true, ...blockQuery }]
2256
2295
  };
2257
- var reportQuery = {
2296
+ var reportQuery = (model) => ({
2258
2297
  include: [
2259
- { association: "dimensions", separate: true, ...dimensionQuery },
2298
+ { association: "dimensions", separate: true, ...dimensionQuery(model) },
2260
2299
  { association: "sections", separate: true, ...sectionQuery }
2261
2300
  ]
2262
- };
2301
+ });
2263
2302
  var imageQueryThumbOnly = [
2264
2303
  {
2265
2304
  association: "image",
@@ -2415,7 +2454,7 @@ function dbBlockFactory(db) {
2415
2454
 
2416
2455
  // api/db/entityCRUD/dimension.ts
2417
2456
  function dbDimensionFactory(db) {
2418
- const { dimension: Dimension } = db;
2457
+ const { dimension: Dimension, search: SearchModel3 } = db;
2419
2458
  return {
2420
2459
  createDimension,
2421
2460
  readDimension,
@@ -2428,7 +2467,7 @@ function dbDimensionFactory(db) {
2428
2467
  report_id: data.report_id,
2429
2468
  variants: []
2430
2469
  }, {
2431
- include: dimensionQuery.include
2470
+ include: dimensionQuery(SearchModel3).include
2432
2471
  });
2433
2472
  return entity.toJSON();
2434
2473
  }
@@ -2436,7 +2475,7 @@ function dbDimensionFactory(db) {
2436
2475
  const idList = id == null ? [] : [].concat(id);
2437
2476
  const entities = await Dimension.findAll({
2438
2477
  where: idList.length > 0 ? { id: idList } : void 0,
2439
- include: include ? dimensionQuery.include : void 0
2478
+ include: include ? dimensionQuery(SearchModel3).include : void 0
2440
2479
  });
2441
2480
  if (idList.length === 0 || entities.length === idList.length) {
2442
2481
  return entities.map((entity) => entity.toJSON());
@@ -2448,15 +2487,18 @@ function dbDimensionFactory(db) {
2448
2487
  async function updateDimension(data) {
2449
2488
  const { id, variants, ...dimensionData } = data;
2450
2489
  const entity = await Dimension.findByPk(id, {
2451
- include: dimensionQuery.include,
2452
- rejectOnEmpty: true
2490
+ include: dimensionQuery(SearchModel3).include,
2491
+ rejectOnEmpty: false
2453
2492
  });
2493
+ if (!entity) {
2494
+ throw new BackendError(404, `Dimension(id=${id}) does not exist.`);
2495
+ }
2454
2496
  entity.set(dimensionData);
2455
2497
  await entity.save();
2456
2498
  return entity.toJSON();
2457
2499
  }
2458
2500
  async function deleteDimension(id) {
2459
- const entity = await Dimension.findByPk(id, { rejectOnEmpty: true });
2501
+ const entity = await Dimension.findByPk(id, { rejectOnEmpty: false });
2460
2502
  await entity.destroy();
2461
2503
  return { id, parentType: "report", parentId: entity.report_id };
2462
2504
  }
@@ -2503,7 +2545,7 @@ function dbFormatterFactory(db) {
2503
2545
  // api/db/entityCRUD/report.ts
2504
2546
  var { locales: reportLocales } = getLocales_default();
2505
2547
  function dbReportFactory(db) {
2506
- const { report: Report } = db;
2548
+ const { report: Report, search: SearchModel3 } = db;
2507
2549
  return {
2508
2550
  createReport,
2509
2551
  readReport,
@@ -2518,7 +2560,7 @@ function dbReportFactory(db) {
2518
2560
  dimensions: []
2519
2561
  },
2520
2562
  {
2521
- include: reportQuery.include
2563
+ include: reportQuery(SearchModel3).include
2522
2564
  }
2523
2565
  );
2524
2566
  return entity.toJSON();
@@ -2540,7 +2582,7 @@ function dbReportFactory(db) {
2540
2582
  );
2541
2583
  const entities = await Report.findAll({
2542
2584
  where: idList.length > 0 ? { id: idList } : void 0,
2543
- include: include ? reportQuery.include?.filter(
2585
+ include: include ? reportQuery(SearchModel3).include?.filter(
2544
2586
  (i2) => typeof include === "string" ? i2["association"] === include : include
2545
2587
  ) : void 0
2546
2588
  });
@@ -2572,16 +2614,19 @@ function dbReportFactory(db) {
2572
2614
  async function updateReport(data) {
2573
2615
  const { id, dimensions, sections, ...reportData } = data;
2574
2616
  const entity = await Report.findByPk(id, {
2575
- include: reportQuery.include,
2576
- rejectOnEmpty: true
2617
+ include: reportQuery(SearchModel3).include,
2618
+ rejectOnEmpty: false
2577
2619
  });
2620
+ if (!entity) {
2621
+ throw new BackendError(404, `Report(id=${id}) does not exist.`);
2622
+ }
2578
2623
  entity.set(reportData);
2579
2624
  await entity.save();
2580
2625
  await entity.reload();
2581
2626
  return entity.toJSON();
2582
2627
  }
2583
2628
  async function deleteReport(id) {
2584
- const entity = await Report.findByPk(id, { rejectOnEmpty: true });
2629
+ const entity = await Report.findByPk(id, { rejectOnEmpty: false });
2585
2630
  await entity.destroy();
2586
2631
  return { id };
2587
2632
  }
@@ -2637,7 +2682,7 @@ function dbSectionFactory(db) {
2637
2682
  }
2638
2683
  }
2639
2684
  function dbVariantFactory(db, crud) {
2640
- const { variant: Variant } = db;
2685
+ const { variant: Variant, search: Search } = db;
2641
2686
  return {
2642
2687
  createVariant,
2643
2688
  readVariant,
@@ -2658,7 +2703,19 @@ function dbVariantFactory(db, crud) {
2658
2703
  async function readVariant({ id } = {}) {
2659
2704
  const idList = id == null ? [] : [].concat(id);
2660
2705
  const entities = await Variant.findAll({
2661
- where: idList.length > 0 ? { id: idList } : void 0
2706
+ where: idList.length > 0 ? { id: idList } : void 0,
2707
+ attributes: {
2708
+ include: [[
2709
+ fn("COUNT", col("members.id")),
2710
+ "members_count"
2711
+ ]]
2712
+ },
2713
+ include: [{
2714
+ model: Search,
2715
+ attributes: [],
2716
+ as: "members"
2717
+ }],
2718
+ group: ["variant.id"]
2662
2719
  });
2663
2720
  if (idList.length === 0 || entities.length === idList.length) {
2664
2721
  return entities.map((entity) => entity.toJSON());
@@ -2677,6 +2734,7 @@ function dbVariantFactory(db, crud) {
2677
2734
  const doIngest = variantData.settings.doIngest;
2678
2735
  variantData.settings.doIngest = false;
2679
2736
  await entity.update(variantData);
2737
+ console.log(id, "doIngest", doIngest);
2680
2738
  if (doIngest) {
2681
2739
  await crud.ingestMembers(entity);
2682
2740
  await search_default2(db, true);
@@ -3113,7 +3171,8 @@ function parseSearchMemberParams(query) {
3113
3171
  report,
3114
3172
  all: localeIsAll,
3115
3173
  sort: normalizeList(query.sort)[0],
3116
- direction: normalizeList(query.direction)[0]
3174
+ direction: normalizeList(query.direction)[0],
3175
+ offset: Number(query.offset || 0)
3117
3176
  };
3118
3177
  }
3119
3178
 
@@ -3490,30 +3549,66 @@ function dbImageSearchFactory(db, provider) {
3490
3549
  }
3491
3550
  return resultWrapper((params) => searchProvider(db, params));
3492
3551
  }
3493
-
3494
- // api/db/readMetadata.ts
3552
+ var convertMemberCountInVariants = (report) => {
3553
+ if (report.dimensions) {
3554
+ report.dimensions.forEach((dimension) => {
3555
+ if (dimension.variants) {
3556
+ dimension.variants.forEach((variant) => {
3557
+ if (variant.members_count) {
3558
+ variant.members_count = parseInt(variant.members_count, 10);
3559
+ }
3560
+ });
3561
+ }
3562
+ });
3563
+ }
3564
+ return report;
3565
+ };
3495
3566
  function dbReadMetadataFactory(db) {
3496
- const { report: Report } = db;
3567
+ const { report: Report, search: Search } = db;
3497
3568
  return dbReadMetadata;
3498
3569
  async function dbReadMetadata(_params) {
3499
3570
  const results = await Report.findAll({
3500
- attributes: ["id", "name"],
3571
+ attributes: ["id", "name", [literal("COALESCE(NULLIF(settings ->> 'order', '')::INTEGER, 0)"), "ordering"]],
3572
+ order: [
3573
+ ["ordering", "ASC"]
3574
+ ],
3501
3575
  include: [{
3502
- attributes: ["id", "name"],
3576
+ attributes: ["id", "name", "ordering"],
3503
3577
  association: "dimensions",
3504
3578
  separate: true,
3579
+ order: [
3580
+ ["ordering", "ASC"]
3581
+ ],
3505
3582
  include: [
3506
3583
  {
3507
- attributes: ["id", "name", "slug"],
3584
+ attributes: [
3585
+ "id",
3586
+ "name",
3587
+ "slug",
3588
+ [fn("COUNT", col("members.id")), "members_count"],
3589
+ [literal("COALESCE(NULLIF(settings ->> 'order', '')::INTEGER, 0)"), "ordering"]
3590
+ ],
3591
+ include: [{
3592
+ model: Search,
3593
+ attributes: [],
3594
+ as: "members"
3595
+ }],
3508
3596
  association: "variants",
3509
- separate: true
3597
+ separate: true,
3598
+ // TS complains about this but it works (?)
3599
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
3600
+ // @ts-ignore
3601
+ group: ["variant.id"],
3602
+ order: [
3603
+ ["ordering", "ASC"]
3604
+ ]
3510
3605
  }
3511
3606
  ]
3512
3607
  }]
3513
3608
  });
3514
3609
  return {
3515
3610
  data: results.map((report) => ({
3516
- ...report.toJSON(),
3611
+ ...convertMemberCountInVariants(report.toJSON()),
3517
3612
  mode: report.dimensions.length === 1 ? REPORT_MODES.UNILATERAL : REPORT_MODES.MULTILATERAL
3518
3613
  }))
3519
3614
  };
@@ -4221,135 +4316,111 @@ function endpointUpdateMyDataFactory(operations) {
4221
4316
  }
4222
4317
  }, []);
4223
4318
  }
4224
- var minimal_args = [
4225
- "--autoplay-policy=user-gesture-required",
4226
- "--disable-background-networking",
4227
- "--disable-background-timer-throttling",
4228
- "--disable-backgrounding-occluded-windows",
4229
- "--disable-breakpad",
4230
- "--disable-client-side-phishing-detection",
4231
- "--disable-component-update",
4232
- "--disable-default-apps",
4233
- "--disable-dev-shm-usage",
4234
- "--disable-domain-reliability",
4235
- "--disable-extensions",
4236
- "--disable-features=AudioServiceOutOfProcess",
4237
- "--disable-hang-monitor",
4238
- "--disable-ipc-flooding-protection",
4239
- "--disable-notifications",
4240
- "--disable-offer-store-unmasked-wallet-cards",
4241
- "--disable-popup-blocking",
4242
- "--disable-print-preview",
4243
- "--disable-prompt-on-repost",
4244
- "--disable-renderer-backgrounding",
4245
- "--disable-setuid-sandbox",
4246
- "--disable-speech-api",
4247
- "--disable-sync",
4248
- "--hide-scrollbars",
4249
- "--ignore-gpu-blacklist",
4250
- "--metrics-recording-only",
4251
- "--mute-audio",
4252
- "--no-default-browser-check",
4253
- "--no-first-run",
4254
- "--no-pings",
4255
- "--no-zygote",
4256
- "--password-store=basic",
4257
- "--use-gl=swiftshader",
4258
- "--use-mock-keychain",
4259
- "--start-fullscreen",
4260
- "--disable-dev-shm-usage",
4261
- "--no-sandbox"
4262
- ];
4263
- var TIME = "playwright";
4264
- var width = 1366;
4265
- var height = 768;
4266
- var browserPool = new BrowserPool({
4267
- browserPlugins: [new PlaywrightPlugin(chromium, {
4268
- launchOptions: {
4269
- headless: true,
4270
- excecutablePath: process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH,
4319
+ async function generatePDF(path2) {
4320
+ const width = 1920;
4321
+ const height = 1080;
4322
+ const maxTimeoutPerPage = 0;
4323
+ try {
4324
+ const browser = await puppeteer.launch({
4271
4325
  args: [
4272
4326
  `--window-size=${width},${height}`,
4273
- ...minimal_args
4327
+ "--start-fullscreen"
4274
4328
  ],
4275
- viewport: { width, height },
4276
- deviceScaleFactor: 2
4277
- }
4278
- })]
4279
- });
4280
- async function generateImage(path2, section, format = "png", transparent = false) {
4281
- console.time(TIME);
4282
- try {
4283
- console.timeLog(TIME, "Browser started");
4329
+ defaultViewport: { width, height },
4330
+ headless: "new",
4331
+ ignoreHTTPSErrors: true
4332
+ });
4284
4333
  const url = new URL(path2);
4285
4334
  const searchParams = new URLSearchParams(url.search);
4335
+ searchParams.set("print", "true");
4286
4336
  url.search = searchParams.toString();
4287
- const page = await browserPool.newPage();
4288
- console.timeLog(TIME, "Creating page");
4289
- console.timeLog(TIME, "Setting request interceptor");
4290
- await page.goto(url.toString());
4291
- console.timeLog(TIME, "Navigated to page");
4292
- await page.waitForLoadState("networkidle");
4293
- if (transparent) {
4294
- await page.$eval(`.bespoke-Section-${section}`, (el) => el["style"].background = "transparent");
4295
- await page.evaluate(() => document.body.style.background = "transparent");
4296
- console.timeLog(TIME, "Added transparent background");
4297
- }
4298
- if (format === "svg") {
4299
- const element = await page.$(".d3plus-viz");
4300
- const svg = await element?.evaluate((el) => el.outerHTML);
4301
- console.timeLog(TIME, "Finishing SVG");
4302
- console.timeEnd(TIME);
4303
- await page.close();
4304
- return svg?.replace(/ {4}|[\t\n\r]/gm, "");
4305
- } else {
4306
- const element = await page.$(".bespoke-Section-container");
4307
- const screenshot = await element?.screenshot({
4308
- type: "png",
4309
- omitBackground: true
4337
+ const page = await browser.newPage();
4338
+ await page.goto(url.toString(), { waitUntil: "networkidle0", timeout: maxTimeoutPerPage });
4339
+ await page.waitForSelector("#bespoke-report", { timeout: maxTimeoutPerPage });
4340
+ await page.evaluate(async () => {
4341
+ await new Promise((resolve) => {
4342
+ const scrollInterval = setInterval(() => {
4343
+ window.scrollBy(0, window.innerHeight);
4344
+ }, 100);
4345
+ const checkNetworkActivity = () => {
4346
+ const activeRequests = performance.getEntriesByType("resource").filter((entry) => entry.duration === 0);
4347
+ return activeRequests.length === 0;
4348
+ };
4349
+ const checkInterval = setInterval(() => {
4350
+ if (checkNetworkActivity()) {
4351
+ clearInterval(scrollInterval);
4352
+ clearInterval(checkInterval);
4353
+ resolve();
4354
+ }
4355
+ }, 1e3);
4310
4356
  });
4311
- console.timeLog(TIME, "Finishing PNG");
4312
- console.timeEnd(TIME);
4313
- await page.close();
4314
- return screenshot;
4315
- }
4357
+ });
4358
+ const pdf = await page.pdf({
4359
+ displayHeaderFooter: true,
4360
+ footerTemplate: `
4361
+ <div
4362
+ style="
4363
+ color: lightgray; border-top: solid lightgray 1px; font-size: 10px;
4364
+ padding-top: 5px; text-align: center; width: 100%;
4365
+ "
4366
+ >
4367
+ <p>Page <span class="pageNumber" /> of <span class="totalPages" /></p>
4368
+ </div>`,
4369
+ format: "A4",
4370
+ // Letter,Legal,Tabloid,Ledger,A0,A1,A2,A3,A4,A5,A6
4371
+ headerTemplate: `
4372
+ <div
4373
+ style="
4374
+ color: lightgray; border-bottom: solid lightgray 1px; font-size: 10px;
4375
+ padding-bottom: 5px; text-align: center; width: 100%;
4376
+ "
4377
+ >
4378
+ <p class="url" />
4379
+ </div>`,
4380
+ // height: `${height}px`,
4381
+ landscape: false,
4382
+ margin: {
4383
+ bottom: 70,
4384
+ // minimum required for footer msg to display
4385
+ left: 20,
4386
+ right: 20,
4387
+ top: 70
4388
+ // minimum required for header msg to display
4389
+ },
4390
+ omitBackground: true,
4391
+ pageRanges: "",
4392
+ // All
4393
+ // path: "./test.pdf" // Path to save the file
4394
+ preferCSSPageSize: false,
4395
+ // Give any CSS @page size declared in the page priority over what is declared in the width or height or format option.
4396
+ printBackground: true,
4397
+ scale: 1,
4398
+ // From 0.1 to 2
4399
+ tagged: false,
4400
+ // Generate tagged (accessible) PDF.
4401
+ timeout: 0
4402
+ // width: `${width}px`,
4403
+ });
4404
+ await browser.close();
4405
+ return pdf.toString("base64");
4316
4406
  } catch (error) {
4317
- console.error("Error generating Image:", error);
4318
- console.timeLog(TIME, "Finishing with error");
4319
- console.timeEnd(TIME);
4407
+ console.error("Error generating PDF:", error);
4320
4408
  throw error;
4321
4409
  }
4322
4410
  }
4323
- function endpointExportImage() {
4324
- return endpoint("POST", "img", async (req, res) => {
4325
- const { slugs, path: path2, section, viz, query: selectors, locale, prefixLocale, transparent = false, svg = false } = req.body;
4326
- if (!slugs || !path2 || !section) {
4327
- throw new BackendError(400, "Missing parameter in request.");
4411
+ function endpointDownloadReport() {
4412
+ return endpoint("POST", "pdf", async (req, res) => {
4413
+ const { slugs, path: path2 } = req.body;
4414
+ if (!slugs || !path2) {
4415
+ throw new BackendError(400, "Missing path parameter in request.");
4328
4416
  } else {
4329
- try {
4330
- let path3 = prefixLocale ? `/${locale}/` : "/";
4331
- path3 += `embed/${JSON.parse(slugs).join("/")}`;
4332
- const url = new URL(path3, req.headers["origin"]);
4333
- const query = new URLSearchParams(Object.entries(selectors || {}));
4334
- query.set("section", section);
4335
- if (viz) {
4336
- query.set("viz", viz);
4337
- }
4338
- url.search = query.toString();
4339
- const format = yn2(svg) ? "svg" : "png";
4340
- const img = await generateImage(url, section, format, yn2(transparent));
4341
- return {
4342
- ok: true,
4343
- status: 200,
4344
- data: img
4345
- };
4346
- } catch (e) {
4347
- console.log(e);
4348
- return {
4349
- ok: false,
4350
- status: 500
4351
- };
4352
- }
4417
+ const url = new URL(path2, req.headers["origin"]);
4418
+ const pdf = await generatePDF(url);
4419
+ return {
4420
+ ok: true,
4421
+ status: 200,
4422
+ data: pdf
4423
+ };
4353
4424
  }
4354
4425
  });
4355
4426
  }
@@ -4407,6 +4478,8 @@ function endpointReportVariables() {
4407
4478
  }
4408
4479
  });
4409
4480
  }
4481
+
4482
+ // api/endpoints/index.ts
4410
4483
  var verbose3 = getLogging_default();
4411
4484
  function endpointKey(method, path2) {
4412
4485
  return `${method.toUpperCase()} ${path2.toLowerCase()}`;
@@ -4442,8 +4515,7 @@ function getEndpointMap(db) {
4442
4515
  endpointRevalidateReportFactory(api),
4443
4516
  endpointRevalidateUrlFactory(),
4444
4517
  endpointReadPrivateBlocksFactory(api),
4445
- // endpointDownloadReport(),
4446
- endpointExportImage(),
4518
+ endpointDownloadReport(),
4447
4519
  endpointListIconsFactory(api, "tabler"),
4448
4520
  endpointReadIconFactory(api, "tabler"),
4449
4521
  endpointReportVariables()
@@ -4494,7 +4566,7 @@ async function endpointNextJsHandlerFactory(req, res) {
4494
4566
  "Content-Length": contentLength.toString()
4495
4567
  }
4496
4568
  ).send(result.data);
4497
- } else if (req.url && req.url.indexOf("/icon.svg?name=") > 0 || req.url && req.url.startsWith("/api/cms/img") && yn2(req.body.svg)) {
4569
+ } else if (req.url && req.url.indexOf("/icon.svg?name=") > 0) {
4498
4570
  res.setHeader("Content-Type", "image/svg+xml");
4499
4571
  res.status(result.status).send(result.data);
4500
4572
  } else {
@@ -4532,7 +4604,6 @@ __export(actions_exports, {
4532
4604
  createEntity: () => createEntity,
4533
4605
  deleteEntity: () => deleteEntity,
4534
4606
  deleteQueryParam: () => deleteQueryParam,
4535
- normalizeSectionLayout: () => normalizeSectionLayout,
4536
4607
  readEntity: () => readEntity,
4537
4608
  readMember: () => readMember,
4538
4609
  readMetadata: () => readMetadata,
@@ -4745,11 +4816,11 @@ function mortarEval(varInnerName, varOuterValue, logic, formatterFunctions, attr
4745
4816
  });
4746
4817
  let output;
4747
4818
  if (attributes) {
4748
- const fn4 = new Function(varInnerName, "console", "libs", "formatters", "locale", "variables", logic);
4749
- output = fn4(varOuterValue, pConsole, libraries, formatterFunctions, locale, attributes);
4819
+ const fn7 = new Function(varInnerName, "console", "libs", "formatters", "locale", "variables", logic);
4820
+ output = fn7(varOuterValue, pConsole, libraries, formatterFunctions, locale, attributes);
4750
4821
  } else {
4751
- const fn4 = new Function(varInnerName, "console", "libs", "formatters", "locale", logic);
4752
- output = fn4(varOuterValue, pConsole, libraries, formatterFunctions, locale);
4822
+ const fn7 = new Function(varInnerName, "console", "libs", "formatters", "locale", logic);
4823
+ output = fn7(varOuterValue, pConsole, libraries, formatterFunctions, locale);
4753
4824
  }
4754
4825
  if (typeof output === "object" && Object.keys(output).length > 0) {
4755
4826
  Object.assign(vars, output);
@@ -5944,26 +6015,6 @@ var recordsSlice = createSlice({
5944
6015
  }
5945
6016
  }
5946
6017
  };
5947
- },
5948
- normalizeSection: (state, action) => {
5949
- const sectionId = action.payload.section;
5950
- const section = state.entities.section[sectionId];
5951
- return {
5952
- ...state,
5953
- entities: {
5954
- ...state.entities,
5955
- section: {
5956
- // ...state.entities.section,
5957
- [sectionId]: {
5958
- ...section,
5959
- settings: {
5960
- ...section.settings,
5961
- width: "full"
5962
- }
5963
- }
5964
- }
5965
- }
5966
- };
5967
6018
  }
5968
6019
  },
5969
6020
  extraReducers: {
@@ -6386,14 +6437,14 @@ var un = function() {
6386
6437
  }, e;
6387
6438
  }();
6388
6439
  var an = new un();
6389
- var fn3 = an.produce;
6440
+ var fn6 = an.produce;
6390
6441
  an.produceWithPatches.bind(an);
6391
6442
  an.setAutoFreeze.bind(an);
6392
6443
  an.setUseProxies.bind(an);
6393
6444
  an.applyPatches.bind(an);
6394
6445
  an.createDraft.bind(an);
6395
6446
  an.finishDraft.bind(an);
6396
- var immer_esm_default = fn3;
6447
+ var immer_esm_default = fn6;
6397
6448
 
6398
6449
  // store/recordsActions.ts
6399
6450
  var recordsActions = recordsSlice.actions;
@@ -7027,12 +7078,6 @@ function removeBlocksFromState(privateBlockIds) {
7027
7078
  await dispatch(removeBlocks(privateBlockIds));
7028
7079
  };
7029
7080
  }
7030
- function normalizeSectionLayout(sectionId) {
7031
- const { normalizeSection } = recordsSlice.actions;
7032
- return async (dispatch) => {
7033
- await dispatch(normalizeSection(sectionId));
7034
- };
7035
- }
7036
7081
  function addBlockToState(newBlocks) {
7037
7082
  const { addBlocks } = recordsSlice.actions;
7038
7083
  return async (dispatch) => {
@@ -7244,8 +7289,14 @@ function BespokeRendererStaticProps(options) {
7244
7289
  } = context;
7245
7290
  const slugs = Array.from(getSlugSegments(params[pathSegmentsKey]));
7246
7291
  if (slugs.length === 0) {
7292
+ await Promise.all([
7293
+ dispatch(readEntity("formatter", {})),
7294
+ dispatch(readEntity("report", { include: true }))
7295
+ ]);
7247
7296
  return {
7248
- notFound: true
7297
+ props: {
7298
+ buildTime
7299
+ }
7249
7300
  };
7250
7301
  }
7251
7302
  const members = await dispatch(
@@ -7295,7 +7346,7 @@ function BespokeRendererStaticProps(options) {
7295
7346
  },
7296
7347
  void 0,
7297
7348
  readMemberFn,
7298
- "section"
7349
+ "report"
7299
7350
  ).then((data) => {
7300
7351
  dispatch(variablesActions.setVariableChange({ ...data, attributes }));
7301
7352
  });
@@ -7324,101 +7375,4 @@ function getSlugSegments(slugs = []) {
7324
7375
  };
7325
7376
  }
7326
7377
 
7327
- // views/EmbedRendererSSR.tsx
7328
- function BespokeEmbedServerSideProps(options) {
7329
- const {
7330
- pathSegmentsKey = "bespoke"
7331
- } = options || {};
7332
- try {
7333
- return storeWrapper.getServerSideProps((store) => {
7334
- const { dispatch } = store;
7335
- return async (context) => {
7336
- const { section, block } = context.query;
7337
- const selectors = Object.fromEntries(
7338
- Object.entries(context.query).filter(([key]) => key.match(/selector\d+id/))
7339
- );
7340
- if (!section && !block) {
7341
- return {
7342
- notFound: true
7343
- };
7344
- }
7345
- const sectionId = Number(section);
7346
- await dispatch(useDatabaseApi);
7347
- const buildTime = (/* @__PURE__ */ new Date()).toISOString();
7348
- const {
7349
- locale = localeDefault9,
7350
- // TODO: detect or use app default
7351
- params = {}
7352
- } = context;
7353
- const slugs = Array.from(getSlugSegments(params[pathSegmentsKey]));
7354
- if (slugs.length === 0) {
7355
- return {
7356
- notFound: true
7357
- };
7358
- }
7359
- const members = await dispatch(
7360
- readMember({ mode: "slugs", locale, slugs, output: "full" })
7361
- );
7362
- dispatch(actions_exports.setPreviews(members.results));
7363
- await Promise.all([
7364
- dispatch(readEntity("formatter", {})),
7365
- dispatch(readEntity("report", {
7366
- id: members.results.map((member) => member.report_id),
7367
- include: true,
7368
- locales: [locale]
7369
- }))
7370
- ]);
7371
- const state = store.getState();
7372
- const formatterList = selectFormatterList(state);
7373
- const formatters = funcifyFormattersByLocale(formatterList, locale);
7374
- const sectionList = selectSectionList(state).filter((s2) => s2.id === sectionId);
7375
- const blockRecords = selectBlockRecords(state);
7376
- const publicBlockRecords = { ...blockRecords };
7377
- let privateBlockIds = Object.values(blockRecords).reduce((acc, block2) => {
7378
- if ("access" in block2.settings && !["public", "guest"].some((grant) => !block2.settings.access || block2.settings.access.includes(grant))) {
7379
- acc.push(block2.id);
7380
- }
7381
- return acc;
7382
- }, []);
7383
- privateBlockIds = privateBlockIds.concat(crawlDown(privateBlockIds, Object.values(publicBlockRecords)));
7384
- privateBlockIds.forEach((privateBlockId) => {
7385
- delete publicBlockRecords[privateBlockId];
7386
- });
7387
- await dispatch(removeBlocksFromState(privateBlockIds));
7388
- const attributes = {
7389
- ...previewsToAttributes_default(members.results),
7390
- locale,
7391
- ...publicEnvVarsToAttributes_default()
7392
- };
7393
- const readMemberFn = async (innerParams) => await dispatch(readMember(innerParams));
7394
- await runConsumersV2(
7395
- publicBlockRecords,
7396
- sectionList,
7397
- void 0,
7398
- formatters,
7399
- {
7400
- locale,
7401
- query: { ...params, ...selectors },
7402
- variables: attributes
7403
- },
7404
- void 0,
7405
- readMemberFn,
7406
- "section"
7407
- ).then((data) => {
7408
- dispatch(variablesActions.setVariableChange({ ...data, attributes }));
7409
- });
7410
- const notSectionBlocks = Object.keys(publicBlockRecords).filter((bid) => publicBlockRecords[bid].section_id !== sectionId);
7411
- await dispatch(removeBlocksFromState(notSectionBlocks));
7412
- return {
7413
- props: {
7414
- buildTime
7415
- }
7416
- };
7417
- };
7418
- });
7419
- } catch (error) {
7420
- console.log("ERROR SSR", error);
7421
- }
7422
- }
7423
-
7424
- export { BespokeEmbedServerSideProps, getSession_default as BespokeGetSession, handleAuth_default as BespokeHandleAuth, BespokeManagerServerSideProps, BespokeRendererStaticPaths, BespokeRendererStaticProps, searchUsersByMetadata_default as BespokeSearchUsersByMetadata, updateCurrentUserAppMetadata_default as BespokeUpdateCurrentUserAppMetadata, updateCurrentUserMetadata_default as BespokeUpdateCurrentUserMetadata, withApiRoleAuthRequired_default as BespokeWithApiRoleAuthRequired, crosswalk_default as ReportCrosswalkHandler, apiFactory as dbApiFactory, endpointKey, endpointNextJsHandlerFactory, getDB, useDatabaseApi };
7378
+ export { getSession_default as BespokeGetSession, handleAuth_default as BespokeHandleAuth, BespokeManagerServerSideProps, BespokeRendererStaticPaths, BespokeRendererStaticProps, searchUsersByMetadata_default as BespokeSearchUsersByMetadata, updateCurrentUserAppMetadata_default as BespokeUpdateCurrentUserAppMetadata, updateCurrentUserMetadata_default as BespokeUpdateCurrentUserMetadata, withApiRoleAuthRequired_default as BespokeWithApiRoleAuthRequired, crosswalk_default as ReportCrosswalkHandler, apiFactory as dbApiFactory, endpointKey, endpointNextJsHandlerFactory, getDB, useDatabaseApi };