Skip to content

SF-3437 Remove the blank: true properties from the texts data model #3339

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Open
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import merge from 'lodash/merge';
import { RecursivePartial } from '../../common/utils/type-utils';
import { CheckingAnswerExport } from './checking-config';
import { SFProject, SFProjectProfile } from './sf-project';
import { EditingRequires, SFProject, SFProjectProfile } from './sf-project';

function testProjectProfile(ordinal: number): SFProjectProfile {
return {
Expand Down Expand Up @@ -44,7 +44,8 @@ function testProjectProfile(ordinal: number): SFProjectProfile {
biblicalTermsEnabled: false,
hasRenderings: false
},
editable: true,
editable: false,
editingRequires: EditingRequires.ParatextEditingEnabled | EditingRequires.ViewModelBlankSupport,
defaultFontSize: 12,
defaultFont: 'Charis SIL',
maxGeneratedUsersPerShareKey: 250
Expand Down
24 changes: 24 additions & 0 deletions src/RealtimeServer/scriptureforge/models/sf-project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export interface SFProjectProfile extends Project {
noteTags?: NoteTag[];
sync: Sync;
editable: boolean;
editingRequires: EditingRequires;
defaultFontSize?: number;
defaultFont?: string;
maxGeneratedUsersPerShareKey?: number;
Expand All @@ -66,3 +67,26 @@ export function isResource(project: SFProjectProfile): boolean {
const resourceIdLength: number = DBL_RESOURCE_ID_LENGTH;
return project.paratextId.length === resourceIdLength;
}

/**
* A bitwise-flag enumeration to represent what frontend features are required to edit this project's text documents.
*
* To add more required features, add as follows:
*
* FutureFeatureA = 1 << 2, // 4
* FutureFeatureB = 1 << 3, // 8
*
* NOTE: Adding a new required feature and migrating editingRequires to include it will block older editors.
* The new required featured should be added to the MaxSupportedEditingRequiresValue below.
*/
export enum EditingRequires {
ParatextEditingEnabled = 1 << 0, // 1
ViewModelBlankSupport = 1 << 1 // 2
}

/**
* This value is by the frontend to determine if a feature has been added
* which should disable editing on the frontend until it is updated.
*/
export const MaxSupportedEditingRequiresValue: EditingRequires =
EditingRequires.ParatextEditingEnabled | EditingRequires.ViewModelBlankSupport;
Original file line number Diff line number Diff line change
Expand Up @@ -627,6 +627,69 @@ describe('SFProjectMigrations', () => {
expect(projectDoc.data.translateConfig.draftConfig.additionalTrainingData).toBeUndefined();
});
});

describe('version 25', () => {
it('migrates editable to editingRequires when true', async () => {
const env = new TestEnvironment(24);
const conn = env.server.connect();
await createDoc(conn, SF_PROJECTS_COLLECTION, 'project01', {
editable: true
});
let projectDoc = await fetchDoc(conn, SF_PROJECTS_COLLECTION, 'project01');
expect(projectDoc.data.editable).toBe(true);

await env.server.migrateIfNecessary();

projectDoc = await fetchDoc(conn, SF_PROJECTS_COLLECTION, 'project01');
expect(projectDoc.data.editable).toBe(false);
expect(projectDoc.data.editingRequires).toBe(3);
});

it('migrates editable to editingRequires when false', async () => {
const env = new TestEnvironment(24);
const conn = env.server.connect();
await createDoc(conn, SF_PROJECTS_COLLECTION, 'project01', {
editable: false
});
let projectDoc = await fetchDoc(conn, SF_PROJECTS_COLLECTION, 'project01');
expect(projectDoc.data.editable).toBe(false);

await env.server.migrateIfNecessary();

projectDoc = await fetchDoc(conn, SF_PROJECTS_COLLECTION, 'project01');
expect(projectDoc.data.editable).toBe(false);
expect(projectDoc.data.editingRequires).toBe(2);
});

it('migrates editable to editingRequires when null', async () => {
const env = new TestEnvironment(24);
const conn = env.server.connect();
await createDoc(conn, SF_PROJECTS_COLLECTION, 'project01', {});
let projectDoc = await fetchDoc(conn, SF_PROJECTS_COLLECTION, 'project01');
expect(projectDoc.data.editable).toBeUndefined();

await env.server.migrateIfNecessary();

projectDoc = await fetchDoc(conn, SF_PROJECTS_COLLECTION, 'project01');
expect(projectDoc.data.editable).toBe(false);
expect(projectDoc.data.editingRequires).toBe(3);
});

it('does not remigrate editingRequires', async () => {
const env = new TestEnvironment(24);
const conn = env.server.connect();
await createDoc(conn, SF_PROJECTS_COLLECTION, 'project01', { editingRequires: 6 });
let projectDoc = await fetchDoc(conn, SF_PROJECTS_COLLECTION, 'project01');
expect(projectDoc.data.editable).toBeUndefined();
expect(projectDoc.data.editingRequires).toBe(6);

await env.server.migrateIfNecessary();

projectDoc = await fetchDoc(conn, SF_PROJECTS_COLLECTION, 'project01');
expect(projectDoc.data.editable).toBeUndefined();
expect(projectDoc.data.editingRequires).toBe(6);
});
});
});

class TestEnvironment {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { DocMigration, MigrationConstructor, monotonicallyIncreasingMigrationLis
import { Operation } from '../../common/models/project-rights';
import { submitMigrationOp } from '../../common/realtime-server';
import { NoteTag } from '../models/note-tag';
import { EditingRequires } from '../models/sf-project';
import { SF_PROJECT_RIGHTS, SFProjectDomain } from '../models/sf-project-rights';
import { SFProjectRole } from '../models/sf-project-role';
import { TextInfoPermission } from '../models/text-info-permission';
Expand Down Expand Up @@ -453,6 +454,39 @@ class SFProjectMigration24 extends DocMigration {
}
}

class SFProjectMigration25 extends DocMigration {
static readonly VERSION = 25;

async migrateDoc(doc: Doc): Promise<void> {
const ops: Op[] = [];

if (doc.data.editingRequires == null) {
const editable: boolean = doc.data.editable !== false;
if (doc.data.editable == null) {
ops.push({
p: ['editable'],
oi: false
});
} else if (doc.data.editable == true) {
ops.push({
p: ['editable'],
od: true,
oi: false
});
}

ops.push({
p: ['editingRequires'],
oi: (editable ? EditingRequires.ParatextEditingEnabled : 0) | EditingRequires.ViewModelBlankSupport
});
}

if (ops.length > 0) {
await submitMigrationOp(SFProjectMigration25.VERSION, doc, ops);
}
}
}

export const SF_PROJECT_MIGRATIONS: MigrationConstructor[] = monotonicallyIncreasingMigrationList([
SFProjectMigration1,
SFProjectMigration2,
Expand All @@ -477,5 +511,6 @@ export const SF_PROJECT_MIGRATIONS: MigrationConstructor[] = monotonicallyIncrea
SFProjectMigration21,
SFProjectMigration22,
SFProjectMigration23,
SFProjectMigration24
SFProjectMigration24,
SFProjectMigration25
]);
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const SF_PROJECT_PROFILE_FIELDS: ShareDB.ProjectionFields = {
isRightToLeft: true,
biblicalTermsConfig: true,
editable: true,
editingRequires: true,
defaultFontSize: true,
defaultFont: true,
translateConfig: true,
Expand Down Expand Up @@ -513,6 +514,9 @@ export class SFProjectService extends ProjectService<SFProject> {
editable: {
bsonType: 'bool'
},
editingRequires: {
bsonType: 'int'
},
defaultFontSize: {
bsonType: 'int'
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,7 @@ describe('CheckingComponent', () => {
env.setBookChapter('JHN', 2);
env.fixture.detectChanges();
expect(env.component.questionsList!.activeQuestionDoc!.data!.dataId).toBe('q15Id');
tick();
flush();
discardPeriodicTasks();
}));
Expand Down Expand Up @@ -3847,19 +3848,15 @@ class TestEnvironment {
private createTextDataForChapter(chapter: number): TextData {
const delta = new Delta();
delta.insert({ chapter: { number: chapter.toString(), style: 'c' } });
delta.insert({ blank: true }, { segment: 'p_1' });
delta.insert({ verse: { number: '1', style: 'v' } });
delta.insert(`target: chapter ${chapter}, verse 1.`, { segment: `verse_${chapter}_1` });
delta.insert({ verse: { number: '2', style: 'v' } });
delta.insert({ blank: true }, { segment: `verse_${chapter}_2` });
delta.insert('\n', { para: { style: 'p' } });
delta.insert({ blank: true }, { segment: `verse_${chapter}_2/p_1` });
delta.insert({ verse: { number: '3', style: 'v' } });
delta.insert(`target: chapter ${chapter}, verse 3.`, { segment: `verse_${chapter}_3` });
delta.insert({ verse: { number: '4', style: 'v' } });
delta.insert(`target: chapter ${chapter}, verse 4.`, { segment: `verse_${chapter}_4` });
delta.insert('\n', { para: { style: 'p' } });
delta.insert({ blank: true }, { segment: `verse_${chapter}_4/p_1` });
delta.insert({ verse: { number: '5', style: 'v' } });
delta.insert(`target: chapter ${chapter}, `, { segment: `verse_${chapter}_5` });
delta.insert('\n', { para: { style: 'p' } });
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { fakeAsync, TestBed, tick } from '@angular/core/testing';
import { Delta } from 'quill';
import { EditingRequires } from 'realtime-server/lib/esm/scriptureforge/models/sf-project';
import { SFProjectRole } from 'realtime-server/lib/esm/scriptureforge/models/sf-project-role';
import { createTestProjectProfile } from 'realtime-server/lib/esm/scriptureforge/models/sf-project-test-data';
import { TextData } from 'realtime-server/lib/esm/scriptureforge/models/text-data';
Expand Down Expand Up @@ -79,7 +80,7 @@ describe('TextDocService', () => {
it('should return true if the project and user are correctly configured', () => {
const env = new TestEnvironment();
const project = createTestProjectProfile({
editable: true,
editingRequires: EditingRequires.ParatextEditingEnabled | EditingRequires.ViewModelBlankSupport,
sync: { dataInSync: true },
texts: [
{ bookNum: 1, chapters: [{ number: 1, isValid: true, permissions: { user01: TextInfoPermission.Write } }] }
Expand All @@ -105,7 +106,7 @@ describe('TextDocService', () => {
it('should return false if user does not have general edit right', () => {
const env = new TestEnvironment();
const project = createTestProjectProfile({
editable: true,
editingRequires: EditingRequires.ParatextEditingEnabled | EditingRequires.ViewModelBlankSupport,
sync: { dataInSync: true },
texts: [
{ bookNum: 1, chapters: [{ number: 1, isValid: true, permissions: { user01: TextInfoPermission.Write } }] }
Expand All @@ -121,7 +122,7 @@ describe('TextDocService', () => {
it('should return false if user does not have chapter edit permission', () => {
const env = new TestEnvironment();
const project = createTestProjectProfile({
editable: true,
editingRequires: EditingRequires.ParatextEditingEnabled | EditingRequires.ViewModelBlankSupport,
sync: { dataInSync: true },
texts: [
{ bookNum: 1, chapters: [{ number: 1, isValid: true, permissions: { user01: TextInfoPermission.Read } }] }
Expand All @@ -137,7 +138,7 @@ describe('TextDocService', () => {
it('should return false if data is not in sync', () => {
const env = new TestEnvironment();
const project = createTestProjectProfile({
editable: true,
editingRequires: EditingRequires.ParatextEditingEnabled | EditingRequires.ViewModelBlankSupport,
sync: { dataInSync: false },
texts: [
{ bookNum: 1, chapters: [{ number: 1, isValid: true, permissions: { user01: TextInfoPermission.Write } }] }
Expand All @@ -153,7 +154,7 @@ describe('TextDocService', () => {
it('should return false if editing is disabled', () => {
const env = new TestEnvironment();
const project = createTestProjectProfile({
editable: false,
editingRequires: EditingRequires.ViewModelBlankSupport,
sync: { dataInSync: true },
texts: [
{ bookNum: 1, chapters: [{ number: 1, isValid: true, permissions: { user01: TextInfoPermission.Write } }] }
Expand All @@ -169,7 +170,7 @@ describe('TextDocService', () => {
it('should return true if all conditions are met', () => {
const env = new TestEnvironment();
const project = createTestProjectProfile({
editable: true,
editingRequires: EditingRequires.ParatextEditingEnabled | EditingRequires.ViewModelBlankSupport,
sync: { dataInSync: true },
texts: [
{ bookNum: 1, chapters: [{ number: 1, isValid: true, permissions: { user01: TextInfoPermission.Write } }] }
Expand Down Expand Up @@ -241,7 +242,9 @@ describe('TextDocService', () => {

it('should return false if the project is editable', () => {
const env = new TestEnvironment();
const project = createTestProjectProfile({ editable: true });
const project = createTestProjectProfile({
editingRequires: EditingRequires.ParatextEditingEnabled | EditingRequires.ViewModelBlankSupport
});

// SUT
const actual: boolean = env.textDocService.isEditingDisabled(project);
Expand All @@ -250,7 +253,29 @@ describe('TextDocService', () => {

it('should return true if the project is not editable', () => {
const env = new TestEnvironment();
const project = createTestProjectProfile({ editable: false });
const project = createTestProjectProfile({ editingRequires: EditingRequires.ViewModelBlankSupport });

// SUT
const actual: boolean = env.textDocService.isEditingDisabled(project);
expect(actual).toBe(true);
});

it('should return true if the project is has been upgraded to a version beyond the supported version', () => {
const env = new TestEnvironment();
const project = createTestProjectProfile({
editingRequires: Number.MAX_SAFE_INTEGER
});

// SUT
const actual: boolean = env.textDocService.isEditingDisabled(project);
expect(actual).toBe(true);
});

it('should return true if the project is has not been upgraded to view model support', () => {
const env = new TestEnvironment();
const project = createTestProjectProfile({
editingRequires: EditingRequires.ParatextEditingEnabled
});

// SUT
const actual: boolean = env.textDocService.isEditingDisabled(project);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { Injectable } from '@angular/core';
import { Delta } from 'quill';
import { Operation } from 'realtime-server/lib/esm/common/models/project-rights';
import { SFProjectProfile } from 'realtime-server/lib/esm/scriptureforge/models/sf-project';
import {
EditingRequires,
MaxSupportedEditingRequiresValue,
SFProjectProfile
} from 'realtime-server/lib/esm/scriptureforge/models/sf-project';
import { SF_PROJECT_RIGHTS, SFProjectDomain } from 'realtime-server/lib/esm/scriptureforge/models/sf-project-rights';
import { TextData } from 'realtime-server/lib/esm/scriptureforge/models/text-data';
import { Chapter, TextInfo } from 'realtime-server/lib/esm/scriptureforge/models/text-info';
Expand Down Expand Up @@ -103,14 +107,29 @@ export class TextDocService {
return project?.sync?.dataInSync !== false;
}

/**
* Determines if an update is required to allow editing.
*
* @param {SFProjectProfile | undefined} project The project.
* @returns {boolean} A value indicating whether the app must be updated.
*/
isUpdateRequired(project: SFProjectProfile | undefined): boolean {
return (project?.editingRequires ?? 0) > MaxSupportedEditingRequiresValue;
}

/**
* Determines if editing is disabled for a project.
*
* @param {SFProjectProfile | undefined} project The project.
* @returns {boolean} A value indicating whether editing is disabled for the project.
*/
isEditingDisabled(project: SFProjectProfile | undefined): boolean {
return project?.editable === false;
return (
project != null &&
(this.isUpdateRequired(project) ||
(project.editingRequires & EditingRequires.ViewModelBlankSupport) !== EditingRequires.ViewModelBlankSupport ||
(project.editingRequires & EditingRequires.ParatextEditingEnabled) !== EditingRequires.ParatextEditingEnabled)
);
}

/**
Expand Down
Loading
Loading