From 568b692eb3e8ecc7383ac65aec978100c87b21e6 Mon Sep 17 00:00:00 2001 From: Guilhem CARRON <gcarron@grandlyon.com> Date: Wed, 28 Sep 2022 09:23:12 +0000 Subject: [PATCH] feat(init): Improve splash loading time --- src/components/Home/releaseNotesModal.tsx | 3 +- src/components/Splash/SplashRoot.tsx | 4 +- .../TotalConsumption/TotalConsumption.tsx | 11 +- src/migrations/migration.data.ts | 16 ++- src/migrations/migration.service.spec.ts | 136 ++++++++++++++---- src/migrations/migration.service.ts | 85 +++++++---- src/migrations/migration.ts | 7 + src/migrations/migration.type.ts | 1 + src/models/initialisationSteps.model.ts | 1 - src/services/initialization.service.spec.ts | 33 ----- src/services/initialization.service.ts | 92 ------------ src/services/queryRunner.service.ts | 5 +- src/utils/decoreText.tsx | 10 ++ 13 files changed, 205 insertions(+), 199 deletions(-) diff --git a/src/components/Home/releaseNotesModal.tsx b/src/components/Home/releaseNotesModal.tsx index e46be103f..dfd8fe168 100644 --- a/src/components/Home/releaseNotesModal.tsx +++ b/src/components/Home/releaseNotesModal.tsx @@ -5,6 +5,7 @@ import './releaseNotesModal.scss' import Dialog from '@material-ui/core/Dialog' import { AppStore } from 'store' import { useSelector } from 'react-redux' +import { decoreText } from 'utils/decoreText' interface ReleaseNotesModalProps { open: boolean @@ -52,7 +53,7 @@ const ReleaseNotesModal: React.FC<ReleaseNotesModalProps> = ({ {note.title} </div> <div className="release-note-description text-16-normal"> - {note.description} + {decoreText(note.description)} </div> </div> ))} diff --git a/src/components/Splash/SplashRoot.tsx b/src/components/Splash/SplashRoot.tsx index 3db7ec06d..b15bc3415 100644 --- a/src/components/Splash/SplashRoot.tsx +++ b/src/components/Splash/SplashRoot.tsx @@ -116,8 +116,6 @@ const SplashRoot = ({ fadeTimer = 1000, children }: SplashRootProps) => { const migrationsResult: ReleaseNotes = await ms.runMigrations( migrations ) - // Init index - await initializationService.initIndex() // Init last release notes when they exist dispatch( @@ -300,7 +298,7 @@ const SplashRoot = ({ fadeTimer = 1000, children }: SplashRootProps) => { splashStart: true, })) } - } catch (err) { + } catch (err: any) { if (err.message === 'Failed to fetch' && !initStepErrors) { setinitStepErrors(InitStepsErrors.UNKNOWN_ERROR) } diff --git a/src/components/TotalConsumption/TotalConsumption.tsx b/src/components/TotalConsumption/TotalConsumption.tsx index 53fcde843..5f9855ca0 100644 --- a/src/components/TotalConsumption/TotalConsumption.tsx +++ b/src/components/TotalConsumption/TotalConsumption.tsx @@ -30,10 +30,13 @@ const TotalConsumption: React.FC<TotalConsumptionProps> = ({ useEffect(() => { const calculateTotalValue = async () => { const consumptionService = new ConsumptionService(client) - const activateHalfHourLoad = await consumptionService.checkDoctypeEntries( - FluidType.ELECTRICITY, - TimeStep.HALF_AN_HOUR - ) + const activateHalfHourLoad = + fluidType === FluidType.ELECTRICITY + ? await consumptionService.checkDoctypeEntries( + FluidType.ELECTRICITY, + TimeStep.HALF_AN_HOUR + ) + : false const converterService = new ConverterService() let total = 0 diff --git a/src/migrations/migration.data.ts b/src/migrations/migration.data.ts index d8661b43c..39a18c6d6 100644 --- a/src/migrations/migration.data.ts +++ b/src/migrations/migration.data.ts @@ -249,7 +249,7 @@ export const migrations: Migration[] = [ releaseNotes: null, docTypes: PROFILETYPE_DOCTYPE, run: async (_client: Client, docs: any[]): Promise<any[]> => { - function checkDate(d1, d2) { + function checkDate(d1: string, d2: string) { const dtd1: DateTime = DateTime.fromISO(d1) const dtd2: DateTime = DateTime.fromISO(d2) return dtd1.year === dtd2.year && dtd1.month === dtd2.month @@ -531,4 +531,18 @@ export const migrations: Migration[] = [ }) }, }, + { + baseSchemaVersion: 20, + targetSchemaVersion: 21, + appVersion: '1.11.0', + description: 'Inform user of the new SGE konnector', + releaseNotes: { + title: 'Vos connecteurs evoluent !', + description: + "Pour continuer à accéder à vos données, merci de vous reconnecter via ce nouveau parcours. Aucune donnée ne sera perdue, et vos données seront à nouveau mises à jour quotidiennement. <p>Pourquoi ce changement ?</p> Pour faciliter l'accès aux données de consommation au plus grand nombre. Plus besoin de se créer un compte Enedis, l'accès aux données en est facilité. N'hésitez pas à en parler autour de vous ! :)", + }, + docTypes: '', + run: async (): Promise<any> => {}, + isEmpty: true, + }, ] diff --git a/src/migrations/migration.service.spec.ts b/src/migrations/migration.service.spec.ts index 88d32046e..850480717 100644 --- a/src/migrations/migration.service.spec.ts +++ b/src/migrations/migration.service.spec.ts @@ -8,12 +8,28 @@ import { MIGRATION_RESULT_COMPLETE, MIGRATION_RESULT_FAILED, } from './migration.data' +import { Client, QueryResult } from 'cozy-client' +import { Notes, ReleaseNotes } from 'models/releaseNotes.model' +import { Schema } from 'models/schema.models' const migrateSpy = jest.spyOn(Migrate, 'migrate') describe('Migration service', () => { - const ms = new MigrationService(mockClient) - - it('should run migrations', () => { + const ms = new MigrationService(mockClient, jest.fn()) + const releaseNotes: Notes = { + title: '', + description: '', + } + beforeEach(() => { + migrateSpy.mockClear() + }) + it('should run migrations', async () => { + const schema: Schema = { _id: '1', version: 0 } + const mockQueryResult: QueryResult<Schema[]> = { + data: [schema], + bookmark: '', + next: false, + skip: 0, + } const migrations: Migration[] = [ { baseSchemaVersion: 0, @@ -21,7 +37,8 @@ describe('Migration service', () => { appVersion: '1.2.4', description: 'Removing mailToken from profil', docTypes: PROFILE_DOCTYPE, - run: (docs: any[]): Profile[] => { + releaseNotes: releaseNotes, + run: async (client: Client, docs: any[]): Promise<Profile[]> => { return docs.map(doc => { if (doc.mailToken) { delete doc.mailToken @@ -31,11 +48,19 @@ describe('Migration service', () => { }, }, ] - ms.runMigrations(migrations) + mockClient.query.mockResolvedValue(mockQueryResult) + await ms.runMigrations(migrations) expect(migrateSpy).toBeCalledTimes(1) }) - it('should run migrations with one fail', () => { + it('should run migrations with one fail', async () => { + const schema: Schema = { _id: '1', version: 0 } + const mockQueryResult: QueryResult<Schema[]> = { + data: [schema], + bookmark: '', + next: false, + skip: 0, + } const migrations: Migration[] = [ { baseSchemaVersion: 0, @@ -43,7 +68,8 @@ describe('Migration service', () => { appVersion: '1.2.4', description: 'Removing mailToken from profil', docTypes: PROFILE_DOCTYPE, - run: (): Profile[] => { + releaseNotes: releaseNotes, + run: async (client: Client, docs: any[]): Promise<Profile[]> => { return [] }, }, @@ -53,12 +79,20 @@ describe('Migration service', () => { errors: [], } const migrateSpyOneFail = migrateSpy.mockResolvedValueOnce(result) - ms.runMigrations(migrations) + mockClient.query.mockResolvedValue(mockQueryResult) + + await ms.runMigrations(migrations) expect(migrateSpyOneFail).toBeCalledTimes(2) }) - it('should not run migrations with two fail', () => { - migrateSpy.mockClear() + it('should not run migrations with two fail', async () => { + const schema: Schema = { _id: '1', version: 0 } + const mockQueryResult: QueryResult<Schema[]> = { + data: [schema], + bookmark: '', + next: false, + skip: 0, + } const migrations: Migration[] = [ { baseSchemaVersion: 0, @@ -66,7 +100,8 @@ describe('Migration service', () => { appVersion: '1.2.4', description: 'Removing mailToken from profil', docTypes: PROFILE_DOCTYPE, - run: (): Profile[] => { + releaseNotes: releaseNotes, + run: async (client: Client, docs: any[]): Promise<Profile[]> => { return [] }, }, @@ -75,17 +110,28 @@ describe('Migration service', () => { type: MIGRATION_RESULT_FAILED, errors: [], } + mockClient.query.mockResolvedValue(mockQueryResult) const migrateSpyTwoFailsKo = jest .spyOn(Migrate, 'migrate') .mockResolvedValueOnce(result) .mockResolvedValueOnce(result) - ms.runMigrations(migrations) - expect(migrateSpyTwoFailsKo).toBeCalledTimes(1) - expect(migrateSpy).toBeCalledTimes(1) + try { + await ms.runMigrations(migrations) + expect(migrateSpyTwoFailsKo).toBeCalledTimes(1) + expect(migrateSpy).toBeCalledTimes(1) + } catch (error) { + expect(error).toEqual(new Error()) + } }) - it('should run migrations with two fail', () => { - migrateSpy.mockClear() + it('should skip migrations if schema number is up to date', async () => { + const schema: Schema = { _id: '1', version: 1 } + const mockQueryResult: QueryResult<Schema[]> = { + data: [schema], + bookmark: '', + next: false, + skip: 0, + } const migrations: Migration[] = [ { baseSchemaVersion: 0, @@ -93,25 +139,53 @@ describe('Migration service', () => { appVersion: '1.2.4', description: 'Removing mailToken from profil', docTypes: PROFILE_DOCTYPE, - run: (): Profile[] => { + releaseNotes: releaseNotes, + run: async (client: Client, docs: any[]): Promise<Profile[]> => { return [] }, }, ] - const result: MigrationResult = { - type: MIGRATION_RESULT_FAILED, - errors: [], - } - const resultOk: MigrationResult = { - type: MIGRATION_RESULT_COMPLETE, - errors: [], + mockClient.query.mockResolvedValue(mockQueryResult) + + await ms.runMigrations(migrations) + expect(migrateSpy).toBeCalledTimes(0) + }) + it('should run 2 migrations properly from a fresh instance and dont show releasenotes', async () => { + const schema: Schema = { _id: '1', version: 0 } + const mockQueryResult: QueryResult<Schema[]> = { + data: [schema], + bookmark: '', + next: false, + skip: 0, } - const migrateSpyTwoFails = jest - .spyOn(Migrate, 'migrate') - .mockResolvedValueOnce(result) - .mockResolvedValueOnce(resultOk) - ms.runMigrations(migrations) - expect(migrateSpyTwoFails).toBeCalledTimes(1) - expect(migrateSpy).toBeCalledTimes(1) + const migrations: Migration[] = [ + { + baseSchemaVersion: 0, + targetSchemaVersion: 1, + appVersion: '1.2.4', + description: 'Removing mailToken from profil', + docTypes: PROFILE_DOCTYPE, + releaseNotes: releaseNotes, + run: async (client: Client, docs: any[]): Promise<Profile[]> => { + return [] + }, + }, + { + baseSchemaVersion: 1, + targetSchemaVersion: 2, + appVersion: '1.2.4', + description: 'Removing mailToken from profil', + docTypes: PROFILE_DOCTYPE, + releaseNotes: releaseNotes, + run: async (client: Client, docs: any[]): Promise<Profile[]> => { + return [] + }, + }, + ] + mockClient.query.mockResolvedValue(mockQueryResult) + + const res = await ms.runMigrations(migrations) + expect(migrateSpy).toBeCalledTimes(2) + expect(res.show).toBeFalsy() }) }) diff --git a/src/migrations/migration.service.ts b/src/migrations/migration.service.ts index 5d65dddbc..0ede26eff 100644 --- a/src/migrations/migration.service.ts +++ b/src/migrations/migration.service.ts @@ -1,4 +1,4 @@ -import { Client } from 'cozy-client' +import { Client, QueryDefinition, QueryResult, Q } from 'cozy-client' import { Migration, MigrationResult } from './migration.type' import { migrationLog, migrate } from './migration' import { @@ -8,6 +8,8 @@ import { import log from 'utils/logger' import { ReleaseNotes } from 'models/releaseNotes.model' import { InitStepsErrors } from 'models/initialisationSteps.model' +import { Schema } from 'models/schema.models' +import { SCHEMAS_DOCTYPE } from 'doctypes/com-grandlyon-ecolyo-schemas' export class MigrationService { private readonly _client: Client @@ -23,8 +25,18 @@ export class MigrationService { this._client = _client this._setinitStepError = _setinitStepError } + /** + * Return schema version + * @param _client cozyClient + * @returns Promise<number> Version number of schema + */ + public async currentSchemaVersion(_client: Client): Promise<number> { + const query: QueryDefinition = Q(SCHEMAS_DOCTYPE) + const data: QueryResult<Schema[]> = await _client.query(query.limitBy(1)) + return data?.data[0]?.version || 0 + } - async runMigrations(migrations: Migration[]): Promise<ReleaseNotes> { + public async runMigrations(migrations: Migration[]): Promise<ReleaseNotes> { log.info('[Migration] Running migrations...') let releaseStatus = false const releaseNotes: ReleaseNotes = { @@ -36,37 +48,52 @@ export class MigrationService { }, ], } - for (const migration of migrations) { - // First attempt - const migrationResult: MigrationResult = await migrate( - migration, - this._client - ) - log.info(migrationLog(migration, migrationResult)) + const currentVersion = await this.currentSchemaVersion(this._client) + const targetVersion = migrations[migrations.length - 1].targetSchemaVersion + + // Prevent Migration service to run every migration if not needed + if (currentVersion != targetVersion) { + const startMigrationIndex = + migrations.length - (targetVersion - currentVersion) + const migrationsToRun = migrations.splice(startMigrationIndex) + + for (const migration of migrationsToRun) { + // First attempt + const migrationResult: MigrationResult = await migrate( + migration, + this._client + ) + log.info(migrationLog(migration, migrationResult)) - if (migrationResult.type === MIGRATION_RESULT_FAILED) { - // Retry in case of failure - const result = await migrate(migration, this._client) - if (result.type === MIGRATION_RESULT_FAILED) { - // Error in case of second failure - this._setinitStepError(InitStepsErrors.MIGRATION_ERROR) - log.error(migrationLog(migration, result)) - throw new Error() - } else { - log.info(migrationLog(migration, result)) + if (migrationResult.type === MIGRATION_RESULT_FAILED) { + // Retry in case of failure + const result = await migrate(migration, this._client) + if (result.type === MIGRATION_RESULT_FAILED) { + // Error in case of second failure + this._setinitStepError(InitStepsErrors.MIGRATION_ERROR) + log.error(migrationLog(migration, result)) + throw new Error() + } else { + log.info(migrationLog(migration, result)) + } } - } - if ( - migration.releaseNotes !== null && - migrationResult.type === MIGRATION_RESULT_COMPLETE - ) { - releaseNotes.notes.push(migration.releaseNotes) - releaseStatus = true + if ( + migration.releaseNotes !== null && + migrationResult.type === MIGRATION_RESULT_COMPLETE + ) { + releaseNotes.notes.push(migration.releaseNotes) + releaseStatus = true + } } + releaseNotes.show = releaseStatus + // In case of first instance, don't show release notes + if (startMigrationIndex === 0) releaseNotes.show = false + log.info('[Migration] Done') + return releaseNotes + } else { + log.info('[Migration] Skipped Migration Process, already up-to-date') + return releaseNotes } - releaseNotes.show = releaseStatus - log.info('[Migration] Done') - return releaseNotes } } diff --git a/src/migrations/migration.ts b/src/migrations/migration.ts index bf0ee97ec..402db8290 100644 --- a/src/migrations/migration.ts +++ b/src/migrations/migration.ts @@ -130,6 +130,13 @@ export async function migrate( migration: Migration, _client: Client ): Promise<MigrationResult> { + if (migration.isEmpty) { + updateSchemaVersion(_client, migration.targetSchemaVersion) + return { + errors: [], + type: 'MigrationComplete', + } + } if (!(await schemaExist(_client))) { await initSchemaDoctype(_client) } diff --git a/src/migrations/migration.type.ts b/src/migrations/migration.type.ts index c5d87f581..b65e8c2d6 100644 --- a/src/migrations/migration.type.ts +++ b/src/migrations/migration.type.ts @@ -30,6 +30,7 @@ export type Migration = { isCreate?: boolean isDeprecated?: boolean queryOptions?: MigrationQueryOptions + isEmpty?: boolean appVersion: string run: (_client: Client, docs: any[]) => Promise<any[]> } diff --git a/src/models/initialisationSteps.model.ts b/src/models/initialisationSteps.model.ts index e139357ef..f8584e812 100644 --- a/src/models/initialisationSteps.model.ts +++ b/src/models/initialisationSteps.model.ts @@ -15,7 +15,6 @@ export enum InitStepsErrors { ECOGESTURE_ERROR = 'ecogesture_error', CHALLENGES_ERROR = 'challenges_error', ANALYSIS_ERROR = 'analysis_error', - INDEX_ERROR = 'index_error', PRICES_ERROR = 'prices_error', CONSOS_ERROR = 'consos_error', PARTNERS_ERROR = 'partners_error', diff --git a/src/services/initialization.service.spec.ts b/src/services/initialization.service.spec.ts index e782eedb8..ba4cc3537 100644 --- a/src/services/initialization.service.spec.ts +++ b/src/services/initialization.service.spec.ts @@ -175,39 +175,6 @@ describe('Initialization service', () => { mockClient.create.mockClear() }) - describe('initIndex method', () => { - beforeEach(() => { - mockCreateIndexKonnector.mockClear() - mockCreateIndexAccount.mockClear() - }) - it('should return true when all indexes created', async () => { - const mockQueryResult: QueryResult<boolean> = { - data: true, - bookmark: '', - next: false, - skip: 0, - } - mockClient.query.mockResolvedValueOnce(mockQueryResult) - mockCreateIndexKonnector.mockResolvedValueOnce(mockQueryResult) - mockCreateIndexAccount.mockResolvedValueOnce(mockQueryResult) - await expect(initializationService.initIndex()).resolves.toBe(true) - }) - it('should throw error when an index is not created', async () => { - const mockQueryResult: QueryResult<boolean> = { - data: true, - bookmark: '', - next: false, - skip: 0, - } - mockClient.query.mockResolvedValueOnce(mockQueryResult) - mockCreateIndexKonnector.mockRejectedValueOnce(new Error()) - mockCreateIndexAccount.mockResolvedValueOnce(mockQueryResult) - await expect(initializationService.initIndex()).rejects.toThrow( - new Error() - ) - }) - }) - describe('initProfile method', () => { beforeEach(() => { mockGetProfile.mockClear() diff --git a/src/services/initialization.service.ts b/src/services/initialization.service.ts index 77553f502..e199dff50 100644 --- a/src/services/initialization.service.ts +++ b/src/services/initialization.service.ts @@ -9,22 +9,11 @@ import { CHALLENGE_DOCTYPE, DUEL_DOCTYPE, ECOGESTURE_DOCTYPE, - EGL_DAY_DOCTYPE, - EGL_MONTH_DOCTYPE, - EGL_YEAR_DOCTYPE, - ENEDIS_DAY_DOCTYPE, - ENEDIS_MINUTE_DOCTYPE, - ENEDIS_MONTH_DOCTYPE, - ENEDIS_YEAR_DOCTYPE, EXPLORATION_DOCTYPE, - GRDF_DAY_DOCTYPE, - GRDF_MONTH_DOCTYPE, - GRDF_YEAR_DOCTYPE, PROFILE_DOCTYPE, QUIZ_DOCTYPE, } from 'doctypes' import { FluidType } from 'enum/fluid.enum' -import { TimeStep } from 'enum/timeStep.enum' import { DateTime } from 'luxon' import { Dataload, @@ -38,13 +27,11 @@ import { import { InitSteps, InitStepsErrors } from 'models/initialisationSteps.model' import { ProfileEcogesture } from 'models/profileEcogesture.model' import React from 'react' -import AccountService from 'services/account.service' import ChallengeService from 'services/challenge.service' import DuelService from 'services/duel.service' import EcogestureService from 'services/ecogesture.service' import ExplorationService from 'services/exploration.service' import FluidService from 'services/fluid.service' -import KonnectorService from 'services/konnector.service' import KonnectorStatusService from 'services/konnectorStatus.service' import ProfileService from 'services/profile.service' import QuizService from 'services/quiz.service' @@ -76,85 +63,6 @@ export default class InitializationService { this._setinitStepError = _setinitStepError } - /* - * Call a query with where clause to create the index if not exist - */ - private async createIndex( - doctype: string, - timestep: TimeStep - ): Promise<object> { - const getMongoSelector = () => { - switch (timestep) { - case TimeStep.YEAR: - return { - year: { - $lte: 9999, - }, - } - case TimeStep.MONTH: - return { - year: { - $lte: 9999, - }, - month: { - $lte: 12, - }, - } - case TimeStep.DAY: - case TimeStep.HALF_AN_HOUR: - return { - year: { - $lte: 9999, - }, - month: { - $lte: 12, - }, - day: { - $lte: 31, - }, - } - default: - return {} - } - } - const query: QueryDefinition = Q(doctype) - .where(getMongoSelector()) - .limitBy(1) - return await this._client.query(query) - } - - /* - * create index for each Doctype - * sucess return: true - * failure throw error - */ - public async initIndex(): Promise<boolean> { - try { - const accountService = new AccountService(this._client) - const konnectorService = new KonnectorService(this._client) - await Promise.all([ - this.createIndex(EGL_YEAR_DOCTYPE, TimeStep.YEAR), - this.createIndex(EGL_MONTH_DOCTYPE, TimeStep.MONTH), - this.createIndex(EGL_DAY_DOCTYPE, TimeStep.DAY), - this.createIndex(ENEDIS_YEAR_DOCTYPE, TimeStep.YEAR), - this.createIndex(ENEDIS_MONTH_DOCTYPE, TimeStep.MONTH), - this.createIndex(ENEDIS_DAY_DOCTYPE, TimeStep.DAY), - this.createIndex(ENEDIS_MINUTE_DOCTYPE, TimeStep.HALF_AN_HOUR), - this.createIndex(GRDF_YEAR_DOCTYPE, TimeStep.YEAR), - this.createIndex(GRDF_MONTH_DOCTYPE, TimeStep.MONTH), - this.createIndex(GRDF_DAY_DOCTYPE, TimeStep.DAY), - konnectorService.createIndexKonnector(), - accountService.createIndexAccount(), - ]) - log.info('[Initialization] Indexes created') - return true - } catch (error) { - this._setinitStepError(InitStepsErrors.INDEX_ERROR) - log.error('Initialization error - initIndex: ', error) - throw error - } - } - /* * Check if profil exist * If not, the profil is created diff --git a/src/services/queryRunner.service.ts b/src/services/queryRunner.service.ts index 4df247207..e27c295fb 100644 --- a/src/services/queryRunner.service.ts +++ b/src/services/queryRunner.service.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ import { Client, QueryDefinition, Q } from 'cozy-client' import { DateTime, Interval } from 'luxon' import { @@ -45,7 +44,6 @@ export default class QueryRunner { limit: number ) { const doctype = this.getRelevantDoctype(fluidType, timeStep) - return Q(doctype) .where(this.getPredicate(timePeriod, timeStep)) .limitBy(limit) @@ -237,7 +235,6 @@ export default class QueryRunner { private getRelevantDoctype(fluidType: FluidType, timeStep: TimeStep) { let doctype = '' - switch (fluidType) { case FluidType.ELECTRICITY: { @@ -357,7 +354,7 @@ export default class QueryRunner { if (timeStep === TimeStep.HALF_AN_HOUR) { const lastDayOfPreviousMonth = { startDate: maxTimePeriod.startDate.plus({ day: -1 }), - endDate: maxTimePeriod.startDate.plus({ day: -1 }).endOf('days'), + endDate: maxTimePeriod.startDate.plus({ day: -1 }).endOf('day'), } const lastDayOfPreviousMonthQuery: QueryDefinition = this.buildMaxQuery( timeStep, diff --git a/src/utils/decoreText.tsx b/src/utils/decoreText.tsx index 595da2417..ebf8d6213 100644 --- a/src/utils/decoreText.tsx +++ b/src/utils/decoreText.tsx @@ -18,6 +18,16 @@ export const decoreText = (line: string, action?: () => void) => { {line.substring(indexEnd + 4, line.length)} </> ) + } else if (line.includes('<p>')) { + const indexStart = line.indexOf('<p>') + const indexEnd = line.indexOf('</p>') + return ( + <> + {line.substring(0, indexStart)} + <p>{line.substring(indexStart + 3, indexEnd)}</p> + {line.substring(indexEnd + 4, line.length)} + </> + ) } else if (line.includes('<span>')) { const indexStart = line.indexOf('<span>') const indexEnd = line.indexOf('</span>') -- GitLab