Skip to content

First run for Snowplow Vue event dictionary entries (shorter filenames)

What does this MR do and why?

Related to #333993 (closed)

This covers all Snowplow events emitted from frontend using the Vue mixin until date. Any missing event will be added on a follow-up pass.

This is the 2nd version of this MR, as the first one introduced an infrastructure issue and was reverted. The issue was long filenames, so this iteration both removes parts of the filename and also slices it at 95 chars, although there are no cases like this.

Generator used
/* eslint-disable import/no-commonjs */
/* eslint-disable no-use-before-define */
/* eslint-disable no-restricted-syntax */
const fs = require('fs');
const csv = require('csv-parser');

const SOURCE_FILE_PATH = './events.csv';
const FOLDER_PATH = './event_dictionary_generator_output';
const ARRAY_SEPARATOR = ';';

// extra_properties
const baseTemplate = `description:
category:
action:
label_description:
property_description:
value_description:
extra_properties:
identifiers:
product_section:
product_stage:
product_group:
product_category:
milestone:
introduced_by_url:
distributions:
tiers:
`;

createFolderIfNeeded();
iterateRows();

function createFolderIfNeeded() {
  if (!fs.existsSync(FOLDER_PATH)) {
    fs.mkdirSync(FOLDER_PATH);
  }
}

function iterateRows() {
  let fileIndex = 0;
  fs.createReadStream(SOURCE_FILE_PATH)
    .pipe(csv())
    .on('data', (chunk) => {
      createFileFromRow(chunk, fileIndex++)
    });
}

function createFileFromRow(row, fileIndex) {
  let template = String(baseTemplate);

  for (const [name, rawValue] of Object.entries(row)) {
    const label = `${name}:`;
    let value = rawValue;
    const isArray = value.includes(ARRAY_SEPARATOR) || ['distributions', 'tiers'].includes(name);

    if (name === 'category') {
      value = value || 'default';
    }

    if (
      [
        'description',
        'milestone',
        'label_description',
        'property_description',
        'value_description',
      ].includes(name)
    ) {
      value = `"${value}"`;
    }

    if (isArray) {
      const items = value.split(ARRAY_SEPARATOR).join('\n- ');
      template = template.replace(label, `${label}\n- ${items}`);
    } else {
      const variableValue = value.replace(/^`(.+?)`$/, '"`$1`"');
      template = template.replace(label, `${label} ${variableValue}`);
    }
  }

  // Timestamp
  const pad = (number, at = 2) => `0${number}`.slice(at * -1);
  const now = new Date(Date.now() + (fileIndex * 1000));
  const timestamp = [
    now.getFullYear(),
    pad(now.getMonth() + 1),
    pad(now.getDate()),
    pad(now.getHours()),
    pad(now.getMinutes()),
    pad(now.getSeconds()),
  ].join('');
  // Custom slug logic attempt
  let filename = `${timestamp}_${row.category || 'default'}_${row.action}`
    .replace(/[`\[\]]/g, '')
    .replace(/(?:\||:)/g, '_')
    .replace(/\s+/g, '_')
    .toLowerCase()
    .trim()
    .replace(/_{2,}/, '_');
  // Make sure filename length is lower than 100
  filename = filename.slice(0, 95);

  const path = `${FOLDER_PATH}/${filename}.yml`;
  // Create file
  fs.writeFileSync(path, template);
  console.info(path, 'created');
}

MR acceptance checklist

This checklist encourages us to confirm any changes have been analyzed to reduce risks in quality, performance, reliability, security, and maintainability.

Edited by Axel García

Merge request reports