diff --git a/src/meta/assessment/activityLog.ts b/src/meta/assessment/activityLog.ts index 1686a5b8ae..e861a29fd6 100644 --- a/src/meta/assessment/activityLog.ts +++ b/src/meta/assessment/activityLog.ts @@ -6,6 +6,7 @@ export enum ActivityLogMessage { assessmentCreate = 'assessmentCreate', assessmentCycleCreate = 'assessmentCycleCreate', assessmentCycleDelete = 'assessmentCycleDelete', + assessmentCycleRename = 'assessmentCycleRename', assessmentStatusUpdate = 'assessmentStatusUpdate', assessmentUpdate = 'assessmentUpdate', contactCreate = 'contactCreate', diff --git a/src/meta/assessment/cols.ts b/src/meta/assessment/cols.ts index 71e6a8b193..ccee32b0cb 100644 --- a/src/meta/assessment/cols.ts +++ b/src/meta/assessment/cols.ts @@ -26,6 +26,8 @@ const cloneProps = (props: { cycleSource: Cycle; cycleTarget: Cycle; col: Col }) _props.labels[cycleTargetUuid] = Objects.cloneDeep(_props.labels[cycleSourceUuid]) if (!Objects.isNil(_props.linkedNodes?.[cycleSourceUuid])) _props.linkedNodes[cycleTargetUuid] = Objects.cloneDeep(_props.linkedNodes[cycleSourceUuid]) + if (!Objects.isNil(_props.select?.[cycleSourceUuid])) + _props.select[cycleTargetUuid] = Objects.cloneDeep(_props.select[cycleSourceUuid]) if (!Objects.isNil(_props.style?.[cycleSourceUuid])) _props.style[cycleTargetUuid] = Objects.cloneDeep(_props.style[cycleSourceUuid]) if (!Objects.isNil(_props.validateFns?.[cycleSourceUuid])) diff --git a/src/server/controller/assessment/index.ts b/src/server/controller/assessment/index.ts index 3ba427dc4a..50289b0c33 100644 --- a/src/server/controller/assessment/index.ts +++ b/src/server/controller/assessment/index.ts @@ -9,6 +9,7 @@ import { generateMetadataCache } from './generateMetadataCache' import { getOneWithCycle } from './getOne' import { remove } from './remove' import { removeCycle } from './removeCycle' +import { renameCycle } from './renameCycle' import { updateDefaultCycle } from './update' export const AssessmentController = { @@ -24,6 +25,7 @@ export const AssessmentController = { cloneCycle, createCycle, removeCycle, + renameCycle, // data cache generateDataCache, diff --git a/src/server/controller/assessment/renameCycle/index.ts b/src/server/controller/assessment/renameCycle/index.ts new file mode 100644 index 0000000000..d690c29481 --- /dev/null +++ b/src/server/controller/assessment/renameCycle/index.ts @@ -0,0 +1 @@ +export { renameCycle } from './renameCycle' diff --git a/src/server/controller/assessment/renameCycle/renameCycle.ts b/src/server/controller/assessment/renameCycle/renameCycle.ts new file mode 100644 index 0000000000..abeb77c53d --- /dev/null +++ b/src/server/controller/assessment/renameCycle/renameCycle.ts @@ -0,0 +1,43 @@ +import { ActivityLogMessage, Assessment, Cycle } from 'meta/assessment' +import { User } from 'meta/user' + +import { generateMetaCache } from 'server/controller/assessment/generateMetaCache' +import { getOneWithCycle } from 'server/controller/assessment/getOne' +import { renameDataCache } from 'server/controller/assessment/renameCycle/renameDataCache' +import { renameMetadataCache } from 'server/controller/assessment/renameCycle/renameMetadataCache' +import { BaseProtocol, DB } from 'server/db' +import { CycleRepository } from 'server/repository/assessmentCycle/cycle' +import { ActivityLogRepository } from 'server/repository/public/activityLog' + +type Props = { + assessment: Assessment + cycle: Cycle + name: string + user: User +} + +type Returned = { + assessment: Assessment + cycle: Cycle +} + +export const renameCycle = async (props: Props, client: BaseProtocol = DB): Promise => { + const { assessment, cycle: cycleSource, name, user } = props + const { name: assessmentName } = assessment.props + const { uuid: cycleUuid } = cycleSource + + return client.tx(async (t) => { + const cycleTarget = await CycleRepository.rename({ assessment, cycle: cycleSource, name }, t) + + // update cache + await generateMetaCache(t) + await renameMetadataCache({ assessment, cycleSource, cycleTarget }, t) + await renameDataCache({ assessment, cycleSource, cycleTarget }, t) + + const message = ActivityLogMessage.assessmentCycleRename + const activityLog = { target: cycleTarget, section: 'assessment', message, user } + await ActivityLogRepository.insertActivityLog({ activityLog, assessment }, t) + + return getOneWithCycle({ assessmentName, cycleUuid }, t) + }) +} diff --git a/src/server/controller/assessment/renameCycle/renameDataCache.ts b/src/server/controller/assessment/renameCycle/renameDataCache.ts new file mode 100644 index 0000000000..bf3b6c73d4 --- /dev/null +++ b/src/server/controller/assessment/renameCycle/renameDataCache.ts @@ -0,0 +1,24 @@ +import { Assessment, Cycle } from 'meta/assessment' + +import { BaseProtocol, DB } from 'server/db' +import { CountryRepository } from 'server/repository/assessmentCycle/country' +import { DataRedisRepository } from 'server/repository/redis/data' +import { Logger } from 'server/utils/logger' + +type Props = { + assessment: Assessment + cycleSource: Cycle + cycleTarget: Cycle +} + +export const renameDataCache = async (props: Props, client: BaseProtocol = DB): Promise => { + const { assessment, cycleSource, cycleTarget } = props + const { name: assessmentName } = assessment.props + const { name: cycleName } = cycleTarget + + const countries = await CountryRepository.getMany({ assessment, cycle: cycleTarget }, client) + const countryISOs = countries.map((c) => c.countryIso) + + await DataRedisRepository.renameCountriesData({ assessment, countryISOs, cycleSource, cycleTarget }) + Logger.info(`${assessmentName}-${cycleName}: Data renamed from redis`) +} diff --git a/src/server/controller/assessment/renameCycle/renameMetadataCache.ts b/src/server/controller/assessment/renameCycle/renameMetadataCache.ts new file mode 100644 index 0000000000..d68e7f36f6 --- /dev/null +++ b/src/server/controller/assessment/renameCycle/renameMetadataCache.ts @@ -0,0 +1,24 @@ +import { Assessment, Cycle } from 'meta/assessment' + +import { BaseProtocol, DB } from 'server/db' +import { RowRedisRepository } from 'server/repository/redis/row' +import { SectionRedisRepository } from 'server/repository/redis/section' +import { Logger } from 'server/utils/logger' + +type Props = { + assessment: Assessment + cycleSource: Cycle + cycleTarget: Cycle +} + +export const renameMetadataCache = async (props: Props, client: BaseProtocol = DB) => { + const { assessment, cycleSource, cycleTarget } = props + const { name: assessmentName } = assessment.props + const { name: cycleName } = cycleTarget + + const rows = await RowRedisRepository.getRows({ assessment, force: true }, client) + Logger.debug(`${assessmentName}: "${Object.keys(rows).length} rows" generated`) + + await SectionRedisRepository.renameCycleEntries({ assessment, cycleSource, cycleTarget }) + Logger.debug(`${assessmentName}-${cycleName}: Metadata renamed from redis`) +} diff --git a/src/server/repository/assessmentCycle/cycle/index.ts b/src/server/repository/assessmentCycle/cycle/index.ts index 2348144939..0d44344f69 100644 --- a/src/server/repository/assessmentCycle/cycle/index.ts +++ b/src/server/repository/assessmentCycle/cycle/index.ts @@ -1,11 +1,13 @@ import { create } from './create' import { remove } from './remove' import { removeSchema } from './removeSchema' +import { rename } from './rename' import { update } from './update' export const CycleRepository = { create, remove, removeSchema, + rename, update, } diff --git a/src/server/repository/assessmentCycle/cycle/rename.ts b/src/server/repository/assessmentCycle/cycle/rename.ts new file mode 100644 index 0000000000..b04db5e359 --- /dev/null +++ b/src/server/repository/assessmentCycle/cycle/rename.ts @@ -0,0 +1,30 @@ +import { Objects } from 'utils/objects' + +import { Assessment, Cycle } from 'meta/assessment' + +import { BaseProtocol, DB, Schemas } from 'server/db' + +type Props = { + assessment: Assessment + cycle: Cycle + name: string +} + +export const rename = async (props: Props, client: BaseProtocol = DB): Promise => { + const { assessment, cycle, name } = props + + const schemaCycleSource = Schemas.getNameCycle(assessment, cycle) + const schemaCycleTarget = Schemas.getNameCycle(assessment, { ...cycle, name }) + + await DB.query(`alter schema ${schemaCycleSource} rename to ${schemaCycleTarget};`) + + return client.one( + ` + update public.assessment_cycle + set name = $2 + where uuid = $1 + returning *`, + [cycle.uuid, name], + Objects.camelize + ) +} diff --git a/src/server/repository/redis/data/index.ts b/src/server/repository/redis/data/index.ts index f0e9e631bc..b5b0fe3380 100644 --- a/src/server/repository/redis/data/index.ts +++ b/src/server/repository/redis/data/index.ts @@ -3,6 +3,7 @@ import { getCountriesData } from './getCountriesData' import { getODPYears } from './getODPYears' import { removeCountriesData } from './removeCountriesData' import { removeNodes } from './removeNodes' +import { renameCountriesData } from './renameCountriesData' import { updateNode } from './updateNode' import { updateNodes } from './updateNodes' @@ -12,6 +13,7 @@ export const DataRedisRepository = { getODPYears, removeCountriesData, removeNodes, + renameCountriesData, updateNode, updateNodes, } diff --git a/src/server/repository/redis/data/renameCountriesData.ts b/src/server/repository/redis/data/renameCountriesData.ts new file mode 100644 index 0000000000..8e8373ce86 --- /dev/null +++ b/src/server/repository/redis/data/renameCountriesData.ts @@ -0,0 +1,26 @@ +import { CountryIso } from 'meta/area' +import { Assessment, Cycle } from 'meta/assessment' + +import { getKeyCountry, Keys } from 'server/repository/redis/keys' +import { RedisData } from 'server/repository/redis/redisData' + +type PropsCache = { + assessment: Assessment + countryISOs: Array + cycleSource: Cycle + cycleTarget: Cycle +} + +export const renameCountriesData = async (props: PropsCache): Promise => { + const { assessment, cycleSource, cycleTarget, countryISOs } = props + + const redis = RedisData.getInstance() + + await Promise.all( + countryISOs.map(async (countryIso) => { + const key = getKeyCountry({ assessment, cycle: cycleSource, countryIso, key: Keys.Data.data }) + const keyNew = getKeyCountry({ assessment, cycle: cycleTarget, countryIso, key: Keys.Data.data }) + await redis.rename(key, keyNew) + }) + ) +} diff --git a/src/server/repository/redis/section/index.ts b/src/server/repository/redis/section/index.ts index cc0f1ba54a..5f36d6c667 100644 --- a/src/server/repository/redis/section/index.ts +++ b/src/server/repository/redis/section/index.ts @@ -2,10 +2,12 @@ import { getMany } from './getMany' import { getManyMetadata } from './getManyMetadata' import { getSubSection } from './getSubSection' import { removeCycleEntries } from './removeCycleEntries' +import { renameCycleEntries } from './renameCycleEntries' export const SectionRedisRepository = { getMany, getManyMetadata, getSubSection, removeCycleEntries, + renameCycleEntries, } diff --git a/src/server/repository/redis/section/renameCycleEntries.ts b/src/server/repository/redis/section/renameCycleEntries.ts new file mode 100644 index 0000000000..7d15d5215f --- /dev/null +++ b/src/server/repository/redis/section/renameCycleEntries.ts @@ -0,0 +1,29 @@ +import { Assessment, Cycle } from 'meta/assessment' + +import { getKeyCycle, Keys } from 'server/repository/redis/keys' +import { RedisData } from 'server/repository/redis/redisData' + +type Props = { + assessment: Assessment + cycleSource: Cycle + cycleTarget: Cycle +} + +export const renameCycleEntries = async (props: Props): Promise => { + const { assessment, cycleSource, cycleTarget } = props + + const redis = RedisData.getInstance() + const sectionsKey = getKeyCycle({ assessment, cycle: cycleSource, key: Keys.Section.sections }) + const sectionsKeyNew = getKeyCycle({ assessment, cycle: cycleTarget, key: Keys.Section.sections }) + const sectionIndexKey = getKeyCycle({ assessment, cycle: cycleSource, key: Keys.Section.sectionsIndex }) + const sectionIndexKeyNew = getKeyCycle({ assessment, cycle: cycleTarget, key: Keys.Section.sectionsIndex }) + const subSectionIndexKey = getKeyCycle({ assessment, cycle: cycleSource, key: Keys.Section.subSectionsIndex }) + const subSectionIndexKeyNew = getKeyCycle({ assessment, cycle: cycleTarget, key: Keys.Section.subSectionsIndex }) + const sectionsMetadataKey = getKeyCycle({ assessment, cycle: cycleSource, key: Keys.Section.sectionsMetadata }) + const sectionsMetadataKeyNew = getKeyCycle({ assessment, cycle: cycleTarget, key: Keys.Section.sectionsMetadata }) + + await redis.rename(sectionsKey, sectionsKeyNew) + await redis.rename(sectionIndexKey, sectionIndexKeyNew) + await redis.rename(subSectionIndexKey, subSectionIndexKeyNew) + await redis.rename(sectionsMetadataKey, sectionsMetadataKeyNew) +}