From 0c2c499035f4b357572682e822af680b02834ab3 Mon Sep 17 00:00:00 2001 From: Yaroslav Serhieiev Date: Mon, 11 Nov 2024 08:54:00 +0200 Subject: [PATCH] feat(engine): new beta flow (#15) --- packages/engine/src/cli/synsets-cmd/argv.ts | 5 +- packages/engine/src/cli/synsets-cmd/pull.ts | 4 +- packages/engine/src/cli/synsets-cmd/push.ts | 5 +- packages/engine/src/cli/synsets.ts | 11 ++-- packages/engine/src/sync/words/GSheetsOp.ts | 11 ++-- packages/engine/src/sync/words/Git2Gsheets.ts | 51 +++++++++++-------- packages/engine/src/sync/words/utils.ts | 3 -- packages/google/src/sheets/BatchExecutor.ts | 11 ++-- 8 files changed, 60 insertions(+), 41 deletions(-) delete mode 100644 packages/engine/src/sync/words/utils.ts diff --git a/packages/engine/src/cli/synsets-cmd/argv.ts b/packages/engine/src/cli/synsets-cmd/argv.ts index 68b4e2c..4dd2f95 100644 --- a/packages/engine/src/cli/synsets-cmd/argv.ts +++ b/packages/engine/src/cli/synsets-cmd/argv.ts @@ -3,14 +3,15 @@ export type SynsetsArgvAny = PullArgv & PushArgv & RebuildArgv; export type PullArgv = { subcommand: 'pull'; - beta: boolean; + partial: boolean; only: boolean; _: string[]; }; export type PushArgv = { subcommand: 'push'; - beta: boolean; + partial: boolean; + note: string; only: boolean; _: string[]; }; diff --git a/packages/engine/src/cli/synsets-cmd/pull.ts b/packages/engine/src/cli/synsets-cmd/pull.ts index 4a1d0e4..f6730e8 100644 --- a/packages/engine/src/cli/synsets-cmd/pull.ts +++ b/packages/engine/src/cli/synsets-cmd/pull.ts @@ -1,6 +1,6 @@ import { GSheets2Git } from '../../sync'; -import type { PullArgv } from './common'; +import type { PullArgv } from './argv'; import { getGoogleGitSyncPrerequisites, parseSelectedSynsets } from './common'; export async function pull(argv: PullArgv) { @@ -16,11 +16,11 @@ export async function pull(argv: PullArgv) { } const sync = new GSheets2Git({ - beta: argv.beta, multisynsets, wordsAddLang, words, selectedIds, + partialSync: argv.partial, }); console.log('Fetching synsets...'); diff --git a/packages/engine/src/cli/synsets-cmd/push.ts b/packages/engine/src/cli/synsets-cmd/push.ts index 9e16567..c765d06 100644 --- a/packages/engine/src/cli/synsets-cmd/push.ts +++ b/packages/engine/src/cli/synsets-cmd/push.ts @@ -1,6 +1,6 @@ import { Git2Gsheets } from '../../sync'; -import type { PushArgv } from './common'; +import type { PushArgv } from './argv'; import { getGoogleGitSyncPrerequisites, parseSelectedSynsets } from './common'; export async function push(argv: PushArgv) { @@ -16,11 +16,12 @@ export async function push(argv: PushArgv) { } const sync = new Git2Gsheets({ - beta: argv.beta, words, wordsAddLang, multisynsets, selectedIds, + partialSync: argv.partial, + changeNote: argv.note, }); console.log('Pushing synsets...'); diff --git a/packages/engine/src/cli/synsets.ts b/packages/engine/src/cli/synsets.ts index 4d033ac..de12bfe 100644 --- a/packages/engine/src/cli/synsets.ts +++ b/packages/engine/src/cli/synsets.ts @@ -29,10 +29,15 @@ export const builder: CommandBuilder = { description: 'Subcommand to execute', demandOption: true, }, - beta: { + note: { + type: 'string', + description: 'Note for the change', + default: process.env.ISV_NOTE, + }, + partial: { type: 'boolean', - description: 'Use beta features', - default: process.env.ISV_BETA === 'true', + description: 'Partial sync (disable deletion)', + default: false, }, only: { type: 'boolean', diff --git a/packages/engine/src/sync/words/GSheetsOp.ts b/packages/engine/src/sync/words/GSheetsOp.ts index 06b6b5e..b17b0b4 100644 --- a/packages/engine/src/sync/words/GSheetsOp.ts +++ b/packages/engine/src/sync/words/GSheetsOp.ts @@ -11,14 +11,13 @@ import type { } from '../../google'; import { log } from '../../utils'; -import { isBeta } from './utils'; - export type GSheetsOpOptions = { - readonly beta: boolean; readonly words: WordsSheet; readonly wordsAddLang: WordsAddLangSheet; readonly selectedIds?: number[]; readonly multisynsets: MultilingualSynsetRepository; + /** Disables deletion of entities */ + readonly partialSync: boolean; }; type TableDTO = WordsRecord | WordsAddLangRecord; @@ -28,20 +27,21 @@ export abstract class GSheetsOp extends IdSyncOperation { private _wordsAdd?: Promise>; protected readonly wordsSheet: WordsSheet; protected readonly wordsAddLangSheet: WordsAddLangSheet; - protected readonly beta: boolean; protected readonly multisynsets: MultilingualSynsetRepository; protected readonly selectedIds?: Set; protected constructor(options: Readonly) { super(); - this.beta = options.beta; this.wordsSheet = options.words; this.wordsAddLangSheet = options.wordsAddLang; this.multisynsets = options.multisynsets; if (options.selectedIds) { this.selectedIds = new Set(options.selectedIds); } + if (options.partialSync) { + this.delete = async () => {}; + } } protected async wordIds(): Promise { @@ -74,7 +74,6 @@ export abstract class GSheetsOp extends IdSyncOperation { const dtos = (await sheet.getValues()) as unknown as DTO[]; const grecords = new Map( dtos - .filter((dto) => this.beta || !isBeta(dto)) .map((dto) => [Math.abs(+dto.id), dto]), ); diff --git a/packages/engine/src/sync/words/Git2Gsheets.ts b/packages/engine/src/sync/words/Git2Gsheets.ts index c0649a2..868dd2b 100644 --- a/packages/engine/src/sync/words/Git2Gsheets.ts +++ b/packages/engine/src/sync/words/Git2Gsheets.ts @@ -10,19 +10,23 @@ import type { } from '../../google'; import { log } from '../../utils'; -import type { GSheetsOpOptions } from './GSheetsOp'; -import { GSheetsOp } from './GSheetsOp'; -import { isBeta } from './utils'; +import { GSheetsOp, type GSheetsOpOptions } from './GSheetsOp'; -export type Git2GsheetsOptions = GSheetsOpOptions; +export type Git2GsheetsOptions = GSheetsOpOptions & { + readonly changeNote: string; +}; export class Git2Gsheets extends GSheetsOp { + private readonly changeNote: string; + constructor(options: Git2GsheetsOptions) { super(options); - if (!options.beta) { - throw new Error('Git2Gsheets is only for beta words'); + if (!options.changeNote) { + throw new TypeError('Change note is required — you cannot just change the dictionary without explaining why'); } + + this.changeNote = options.changeNote; } protected async getAfterIds(): Promise { @@ -46,8 +50,11 @@ export class Git2Gsheets extends GSheetsOp { const dto = this._synset2dto(synset!); const dtoAddLang = this._synset2dtoAddLang(synset!); + const notes = [[...this._annotate(null, dto)] as string[]]; + this.wordsSheet.batch.appendRows({ values: [[...dto]], + notes, }); this.wordsAddLangSheet.batch.appendRows({ @@ -92,7 +99,7 @@ export class Git2Gsheets extends GSheetsOp { const steen = isv.lemmas[0]!.steen!; const dto: WordsDTO = new Mapper({ - id: isBeta(ms) ? -ms.id : ms.id, + id: ms.id, isv: isv.toString(), addition: steen.addition ?? '', partOfSpeech: steen.partOfSpeech ?? '', @@ -118,7 +125,7 @@ export class Git2Gsheets extends GSheetsOp { frequency: steen.frequency ?? '', intelligibility: '', using_example: steen.using_example ?? '', - }); + }, undefined, []); for (const key of ms.steen?.debated ?? []) { dto[key] = `#${dto[key]}`; @@ -134,7 +141,7 @@ export class Git2Gsheets extends GSheetsOp { // TODO: this is a violation of synset vs lemma separation const steen = isv.lemmas[0]!.steen!; const dto: WordsAddLangDTO = new Mapper({ - id: isBeta(ms) ? -ms.id : ms.id, + id: ms.id, isv: isv.toString(), addition: steen.addition ?? '', partOfSpeech: steen.partOfSpeech ?? '', @@ -214,9 +221,7 @@ export class Git2Gsheets extends GSheetsOp { return false; } - const notes = isBeta(dtoOld) - ? undefined - : [[...this._annotate(dtoOld, dtoNew)] as string[]]; + const notes = [[...this._annotate(dtoOld, dtoNew)] as string[]]; this.wordsSheet.batch.updateRows({ startRowIndex, @@ -248,8 +253,10 @@ export class Git2Gsheets extends GSheetsOp { } } - private _annotate(dtoOld: WordsDTO, dtoNew: WordsDTO) { - const keys = [ + private _annotate(dtoOld: WordsDTO | null, dtoNew: WordsDTO) { + let addedNote = false; + + const allKeys = [ 'isv', 'addition', 'partOfSpeech', @@ -257,17 +264,21 @@ export class Git2Gsheets extends GSheetsOp { 'en', 'sameInLanguages', ] as const; + const keys = dtoOld ? allKeys : ['isv'] as const; - const notes = dtoOld.getNotes()!.getCopy(); + const notes = (dtoOld ?? dtoNew).getNotes()!.getCopy(); for (const key of keys) { - const sOld = asString(dtoOld[key]); + const sOld = asString(dtoOld?.[key]); const sNew = asString(dtoNew[key]); if (sOld && sOld !== sNew) { - notes[key] ??= sOld; - if (notes[key] === sNew) { - notes[key] = undefined; - } else if (!sNew.startsWith('#')) { + if (!addedNote) { + notes[key] ??= sOld + ' → ' + sNew; + notes[key] += `\n\n${this.changeNote}`; + addedNote = true; + } + + if (!sNew.startsWith('#') && !sNew.startsWith('!#')) { dtoNew[key] = `#${sNew}`; } } diff --git a/packages/engine/src/sync/words/utils.ts b/packages/engine/src/sync/words/utils.ts deleted file mode 100644 index 5c42d5f..0000000 --- a/packages/engine/src/sync/words/utils.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function isBeta(dto: { readonly id: string | number }): boolean { - return Math.abs(+dto.id) >= 37_000; -} diff --git a/packages/google/src/sheets/BatchExecutor.ts b/packages/google/src/sheets/BatchExecutor.ts index 3d5ec45..01ed0ba 100644 --- a/packages/google/src/sheets/BatchExecutor.ts +++ b/packages/google/src/sheets/BatchExecutor.ts @@ -9,6 +9,7 @@ export type BatchExecutorConfig = { export type BatchExecutor$AppendRowsRequest = { sheetId?: number; values: unknown[][]; + notes?: string[][]; }; export type BatchExecutor$UpdateRowsRequest = { @@ -133,11 +134,15 @@ export class BatchExecutor { } public appendRows(request: BatchExecutor$AppendRowsRequest): this { + const hasNotes = request.notes != null; + this.appendCells({ - fields: 'userEnteredValue', + fields: 'userEnteredValue' + (hasNotes ? ',note' : ''), sheetId: request.sheetId ?? this.sheetId, - rows: request.values.map((row) => ({ - values: row.map((value) => this._toCellData(value)), + rows: request.values.map((row, rowIndex) => ({ + values: row.map((value, colIndex) => + this._toCellData(value, request.notes?.[rowIndex]?.[colIndex]) + ), })), });