Skip to content

Commit

Permalink
refactor: sidecar data into separate function
Browse files Browse the repository at this point in the history
  • Loading branch information
Gum-Joe committed Jul 31, 2024
1 parent 4c8b4e7 commit 4c55b18
Show file tree
Hide file tree
Showing 6 changed files with 201 additions and 49 deletions.
32 changes: 11 additions & 21 deletions packages/email/docsoc-mail-merge/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import { TemplatePreviews } from "./engines/types";
import { getFileNameSchemeInteractively } from "./interactivity/getFileNameSchemeInteractively";
import getRunNameInteractively from "./interactivity/getRunNameInteractively";
import mapCSVFieldsInteractive from "./interactivity/mapCSVFieldsInteractive";
import { getRecordPreviewPrefixForIndividual, getRecordPreviewPrefixForMetadata } from "./sideCardData";
import { SidecardData } from "./sideCardData/types";
import { getRecordPreviewPrefixForIndividual, getRecordPreviewPrefixForMetadata, writeMetadata } from "./sideCarData";
import { SidecardData } from "./sideCarData/types";
import { stopIfCriticalFsError } from "./util/files";
import createLogger from "./util/logger";
import { CliOptions, CSVRecord } from "./util/types";
Expand Down Expand Up @@ -106,28 +106,18 @@ async function main(opts: CliOptions) {
const operations = previews.map(async (preview) => {
const fileName = getRecordPreviewPrefixForIndividual(record, fileNamer, opts.templateEngine, preview);
logger.debug(`Writing ${fileName}__${opts.templateEngine}__${preview.name}`);
await fs.writeFile(
join(previewsRoot, `${fileName}__${opts.templateEngine}__${preview.name}`),
preview.content,
await stopIfCriticalFsError(
fs.writeFile(
join(previewsRoot, `${fileName}__${opts.templateEngine}__${preview.name}`),
preview.content,
),
);
});

const sidecar: SidecardData = {
record: record,
engine: opts.templateEngine,
engineOptions: opts.templateOptions,
files: previews.map((preview) => ({
filename: getRecordPreviewPrefixForIndividual(record, fileNamer, opts.templateEngine, preview),
engineData: {
...preview,
content: undefined,
},
})),
};

const metadataFile = getRecordPreviewPrefixForMetadata(record, fileNamer);
logger.debug(`Writing metadata for ${fileNamer(record)} to ${metadataFile}`);
operations.push(fs.writeFile(join(previewsRoot, metadataFile), JSON.stringify(sidecar, null, 4)));
// Add metadata write operation
operations.push(
writeMetadata(record, opts.templateEngine, opts.templateOptions, previews, fileNamer, previewsRoot),
);

return operations;
}),
Expand Down
4 changes: 2 additions & 2 deletions packages/email/docsoc-mail-merge/src/engines/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
import nunjucksEngine from "./nunjucks-md";
import { TemplateEngineConstructor } from "./types";

export type TEMPLATE_ENGINE = "nunjucks";
export type TEMPLATE_ENGINES = "nunjucks";

/** Map of engine names (provided on the CLI) to constructors for those engines */
export const ENGINES_MAP: Record<TEMPLATE_ENGINE, TemplateEngineConstructor> = {
export const ENGINES_MAP: Record<TEMPLATE_ENGINES, TemplateEngineConstructor> = {
nunjucks: nunjucksEngine,
};
94 changes: 94 additions & 0 deletions packages/email/docsoc-mail-merge/src/sideCarData/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/**
* Handle "sidecar" data - these are JSON files that sit next to rendered template previews,
* containing metadata about the preview, so that we can re-render them later.
*
* @packageDocumentation
*/
import fs from "fs/promises";
import { join } from "path";

import { TEMPLATE_ENGINES } from "../engines";
import { TemplatePreview, TemplatePreviews } from "../engines/types";
import { stopIfCriticalFsError } from "../util/files";
import createLogger from "../util/logger";
import { CliOptions, CSVRecord } from "../util/types";
import { SidecardData } from "./types";

const PARTS_SEPARATOR = "__";
const logger = createLogger("docsoc.sidecar");

/**
* Generate the first part of a filename for a record's previews - this part
* identifies the record itself via the fileNamer function.
* @param record The record to generate a prefix for
* @param fileNamer A function that generates a filename for a record
* @returns
*/
export const getRecordPreviewPrefix = (record: CSVRecord, fileNamer: (record: CSVRecord) => string) =>
`${fileNamer(record)}`;

/**
* Generate predicable prefixes for preview names (including record specific part
*
* @example
* const record = { id: "1", name: "Test Record" };
* const fileNamer = (record: CSVRecord) => `file_${record["id"]}`;
* getRecordPreviewPrefixForIndividual(record, fileNamer, "nunjucks", { name: "preview1.txt", content: "content1", metadata: { key: "value" } })
* // => "file_1__nunjucks__preview1.txt"
*/
export const getRecordPreviewPrefixForIndividual = (
record: CSVRecord,
fileNamer: (record: CSVRecord) => string,
templateEngine: string,
preview: TemplatePreview,
) => [getRecordPreviewPrefix(record, fileNamer), templateEngine, preview.name].join(PARTS_SEPARATOR);

/**
* Generate the filename for the metadata file for a record
* @param record The record to generate a metadata filename for
* @param fileNamer A function that generates a filename for a record
* @example
* const record = { id: "1", name: "Test Record" };
* const fileNamer = (record: CSVRecord) => `file_${record["id"]}`;
* getRecordPreviewPrefixForMetadata(record, fileNamer)
* // => "file_1-metadata.json"
*/
export const getRecordPreviewPrefixForMetadata = (record: CSVRecord, fileNamer: (record: CSVRecord) => string) =>
`${getRecordPreviewPrefix(record, fileNamer)}-metadata.json`;

/**
* Write the metadata for a record & its associated previews to a JSON file.
* @param record The record to write metadata for
* @param templateEngine The engine used to render the previews
* @param templateOptions The options given to the engine
* @param previews The previews rendered for the record
* @param fileNamer A function that generates a filename for a record
* @param previewsRoot The root directory to write the metadata to
* @returns
*/
export async function writeMetadata(
record: CSVRecord,
templateEngine: TEMPLATE_ENGINES,
templateOptions: CliOptions["templateOptions"],
previews: TemplatePreviews,
fileNamer: (record: CSVRecord) => string,
previewsRoot: string,
): Promise<void> {
const sidecar: SidecardData = {
record: record,
engine: templateEngine,
engineOptions: templateOptions,
files: previews.map((preview) => ({
filename: getRecordPreviewPrefixForIndividual(record, fileNamer, templateEngine, preview),
engineData: {
...preview,
content: undefined,
},
})),
};

const metadataFile = getRecordPreviewPrefixForMetadata(record, fileNamer);
logger.debug(`Writing metadata for ${fileNamer(record)} to ${metadataFile}`);
await stopIfCriticalFsError(fs.writeFile(join(previewsRoot, metadataFile), JSON.stringify(sidecar, null, 4)));
return Promise.resolve();
}
26 changes: 0 additions & 26 deletions packages/email/docsoc-mail-merge/src/sideCardData/index.ts

This file was deleted.

94 changes: 94 additions & 0 deletions packages/email/docsoc-mail-merge/test/sideCarData/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// index.test.ts
import fs from "fs/promises";
import { join } from "path";

import { TEMPLATE_ENGINES } from "../../src/engines";
import { TemplatePreviews } from "../../src/engines/types";
import {
getRecordPreviewPrefix,
getRecordPreviewPrefixForIndividual,
getRecordPreviewPrefixForMetadata,
writeMetadata,
} from "../../src/sideCarData/index";
import { SidecardData } from "../../src/sideCarData/types";
import { stopIfCriticalFsError } from "../../src/util/files";
import { CliOptions, CSVRecord } from "../../src/util/types";

jest.mock("fs/promises");
jest.mock("../../src/util/logger", () => {
const logger = {
info: jest.fn(),
debug: jest.fn(),
warn: jest.fn(),
error: jest.fn(),
};
return () => logger;
});
jest.mock("../../src/util/files");

describe("Sidecar Data Functions", () => {
const mockRecord: CSVRecord = { id: "1", name: "Test Record" };
const mockFileNamer = (record: CSVRecord) => `file_${record["id"]}`;
const mockTemplateEngine = "nunjucks" as TEMPLATE_ENGINES;
const mockTemplateOptions: CliOptions["templateOptions"] = {};
const mockPreviews: TemplatePreviews = [{ name: "preview1", content: "content1", metadata: { key: "value" } }];
const mockPreviewsRoot = "/mock/previews/root";

beforeEach(() => {
jest.clearAllMocks();
});

test("getRecordPreviewPrefix should return correct prefix", () => {
const result = getRecordPreviewPrefix(mockRecord, mockFileNamer);
expect(result).toBe("file_1");
});

test("getRecordPreviewPrefixForIndividual should return correct prefix", () => {
const result = getRecordPreviewPrefixForIndividual(
mockRecord,
mockFileNamer,
mockTemplateEngine,
mockPreviews[0],
);
expect(result).toBe(`file_1__${mockTemplateEngine}__preview1`);
});

test("getRecordPreviewPrefixForMetadata should return correct metadata filename", () => {
const result = getRecordPreviewPrefixForMetadata(mockRecord, mockFileNamer);
expect(result).toBe("file_1-metadata.json");
});

test("writeMetadata should write metadata to a JSON file", async () => {
const mockSidecar: SidecardData = {
record: mockRecord,
engine: mockTemplateEngine,
engineOptions: mockTemplateOptions,
files: [
{
filename: `file_1__${mockTemplateEngine}__preview1`,
engineData: {
name: "preview1",
content: undefined,
metadata: { key: "value" },
},
},
],
};

(stopIfCriticalFsError as jest.Mock).mockImplementation((promise) => promise);

await writeMetadata(
mockRecord,
mockTemplateEngine,
mockTemplateOptions,
mockPreviews,
mockFileNamer,
mockPreviewsRoot,
);

expect(fs.writeFile).toHaveBeenCalledWith(
join(mockPreviewsRoot, "file_1-metadata.json"),
JSON.stringify(mockSidecar, null, 4),
);
});
});

0 comments on commit 4c55b18

Please sign in to comment.