Document emoji picker (#4338)
Co-authored-by: Tom Moor <tom.moor@gmail.com>
This commit is contained in:
@@ -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("🚵🏼♂️");
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user