Document emoji picker (#4338)

Co-authored-by: Tom Moor <tom.moor@gmail.com>
This commit is contained in:
Apoorv Mishra
2023-09-03 18:41:14 +05:30
committed by GitHub
parent 0054b7152e
commit 1c7bb65c7a
57 changed files with 1367 additions and 510 deletions

View File

@@ -0,0 +1,120 @@
import { Document } from "@server/models";
import { buildDocument, buildDraftDocument } from "@server/test/factories";
import { setupTestDatabase } from "@server/test/support";
import script from "./20230815063834-migrate-emoji-in-document-title";
setupTestDatabase();
describe("#work", () => {
it("should correctly update title and emoji for a draft document", async () => {
const document = await buildDraftDocument({
title: "😵 Title draft",
});
expect(document.publishedAt).toBeNull();
expect(document.emoji).toBeNull();
await script();
const draft = await Document.unscoped().findByPk(document.id);
expect(draft).not.toBeNull();
expect(draft?.title).toEqual("Title draft");
expect(draft?.emoji).toEqual("😵");
});
it("should correctly update title and emoji for a published document", async () => {
const document = await buildDocument({
title: "👱🏽‍♀️ Title published",
});
expect(document.publishedAt).toBeTruthy();
expect(document.emoji).toBeNull();
await script();
const published = await Document.unscoped().findByPk(document.id);
expect(published).not.toBeNull();
expect(published?.title).toEqual("Title published");
expect(published?.emoji).toEqual("👱🏽‍♀️");
});
it("should correctly update title and emoji for an archived document", async () => {
const document = await buildDocument({
title: "🍇 Title archived",
});
await document.archive(document.createdById);
expect(document.archivedAt).toBeTruthy();
expect(document.emoji).toBeNull();
await script();
const archived = await Document.unscoped().findByPk(document.id);
expect(archived).not.toBeNull();
expect(archived?.title).toEqual("Title archived");
expect(archived?.emoji).toEqual("🍇");
});
it("should correctly update title and emoji for a template", async () => {
const document = await buildDocument({
title: "🐹 Title template",
template: true,
});
expect(document.template).toBe(true);
expect(document.emoji).toBeNull();
await script();
const template = await Document.unscoped().findByPk(document.id);
expect(template).not.toBeNull();
expect(template?.title).toEqual("Title template");
expect(template?.emoji).toEqual("🐹");
});
it("should correctly update title and emoji for a deleted document", async () => {
const document = await buildDocument({
title: "🚵🏼‍♂️ Title deleted",
});
await document.destroy();
expect(document.deletedAt).toBeTruthy();
expect(document.emoji).toBeNull();
await script();
const deleted = await Document.unscoped().findByPk(document.id, {
paranoid: false,
});
expect(deleted).not.toBeNull();
expect(deleted?.title).toEqual("Title deleted");
expect(deleted?.emoji).toEqual("🚵🏼‍♂️");
});
it("should correctly update title emoji when there are leading spaces", async () => {
const document = await buildDocument({
title: " 🤨 Title with spaces",
});
expect(document.emoji).toBeNull();
await script();
const doc = await Document.unscoped().findByPk(document.id);
expect(doc).not.toBeNull();
expect(doc?.title).toEqual("Title with spaces");
expect(doc?.emoji).toEqual("🤨");
});
it("should correctly paginate and update title emojis", async () => {
const buildManyDocuments = [];
for (let i = 1; i <= 10; i++) {
buildManyDocuments.push(buildDocument({ title: "🚵🏼‍♂️ Title" }));
}
const manyDocuments = await Promise.all(buildManyDocuments);
for (const document of manyDocuments) {
expect(document.title).toEqual("🚵🏼‍♂️ Title");
expect(document.emoji).toBeNull();
}
await script(false, 2);
const documents = await Document.unscoped().findAll();
for (const document of documents) {
expect(document.title).toEqual("Title");
expect(document.emoji).toEqual("🚵🏼‍♂️");
}
});
});

View File

@@ -0,0 +1,69 @@
import "./bootstrap";
import { Transaction, Op } from "sequelize";
import parseTitle from "@shared/utils/parseTitle";
import { Document } from "@server/models";
import { sequelize } from "@server/storage/database";
let page = parseInt(process.argv[2], 10);
page = Number.isNaN(page) ? 0 : page;
export default async function main(exit = false, limit = 1000) {
const work = async (page: number): Promise<void> => {
console.log(`Backfill document emoji from title… page ${page}`);
let documents: Document[] = [];
await sequelize.transaction(async (transaction) => {
documents = await Document.unscoped().findAll({
attributes: {
exclude: ["state"],
},
where: {
version: {
[Op.ne]: null,
},
},
limit,
offset: page * limit,
order: [["createdAt", "ASC"]],
paranoid: false,
lock: Transaction.LOCK.UPDATE,
transaction,
});
for (const document of documents) {
try {
const { emoji, strippedTitle } = parseTitle(document.title);
if (emoji) {
document.emoji = emoji;
document.title = strippedTitle;
if (document.changed()) {
console.log(`Migrating ${document.id}`);
await document.save({
silent: true,
transaction,
});
}
}
} catch (err) {
console.error(`Failed at ${document.id}:`, err);
continue;
}
}
});
return documents.length === limit ? work(page + 1) : undefined;
};
await work(page);
console.log("Backfill complete");
if (exit) {
process.exit(0);
}
}
// In the test suite we import the script rather than run via node CLI
if (process.env.NODE_ENV !== "test") {
void main(true);
}

View File

@@ -0,0 +1,64 @@
import "./bootstrap";
import { Transaction } from "sequelize";
import parseTitle from "@shared/utils/parseTitle";
import { Revision } from "@server/models";
import { sequelize } from "@server/storage/database";
let page = parseInt(process.argv[2], 10);
page = Number.isNaN(page) ? 0 : page;
export default async function main(exit = false, limit = 1000) {
const work = async (page: number): Promise<void> => {
console.log(`Backfill revision emoji from title… page ${page}`);
let revisions: Revision[] = [];
await sequelize.transaction(async (transaction) => {
revisions = await Revision.unscoped().findAll({
attributes: {
exclude: ["text"],
},
limit,
offset: page * limit,
order: [["createdAt", "ASC"]],
paranoid: false,
lock: Transaction.LOCK.UPDATE,
transaction,
});
for (const revision of revisions) {
try {
const { emoji, strippedTitle } = parseTitle(revision.title);
if (emoji) {
revision.emoji = emoji;
revision.title = strippedTitle;
if (revision.changed()) {
console.log(`Migrating ${revision.id}`);
await revision.save({
silent: true,
transaction,
});
}
}
} catch (err) {
console.error(`Failed at ${revision.id}:`, err);
continue;
}
}
});
return revisions.length === limit ? work(page + 1) : undefined;
};
await work(page);
console.log("Backfill complete");
if (exit) {
process.exit(0);
}
}
// In the test suite we import the script rather than run via node CLI
if (process.env.NODE_ENV !== "test") {
void main(true);
}