@ministryofjustice/hmpps-digital-prison-reporting-frontend 7.5.0 → 7.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"dprReportingClient.js","sources":["../../../../../../../../../src/dpr/data/dprReportingClient.ts"],"sourcesContent":["import ReportingClient from './reportingClient'\nimport DashboardClient from './dashboardClient'\nimport ReportDataStore, { RedisClient } from './reportDataStore'\nimport { ApiConfig, FeatureFlagConfig, MigrationServiceConfig } from './types'\nimport { FeatureFlagService } from '../services/featureFlagService'\nimport ProductCollectionClient from './productCollectionClient'\nimport MissingReportClient from './missingReportClient'\nimport { ReportIdMigrationService } from '../services/reportIdMigrationService'\n\nexport const initDprReportingClients = (\n reportingApiConfig: ApiConfig,\n redisClient: RedisClient,\n storePrefix?: string,\n featureFlagConfig?: FeatureFlagConfig,\n migrationServiceConfig?: MigrationServiceConfig,\n) => {\n return {\n reportingClient: new ReportingClient(reportingApiConfig),\n dashboardClient: new DashboardClient(reportingApiConfig),\n reportDataStore: new ReportDataStore(redisClient, storePrefix),\n missingReportClient: new MissingReportClient(reportingApiConfig),\n productCollectionClient: new ProductCollectionClient(reportingApiConfig),\n featureFlagService: new FeatureFlagService(featureFlagConfig),\n reportIdMigrationService: new ReportIdMigrationService(redisClient, migrationServiceConfig),\n }\n}\n\nexport default initDprReportingClients\n"],"names":["ReportingClient","DashboardClient","ReportDataStore","MissingReportClient","ProductCollectionClient","FeatureFlagService","ReportIdMigrationService"],"mappings":";;;;;;;;;;;;AASO,MAAM,uBAAuB,GAAG,CACrC,kBAA6B,EAC7B,WAAwB,EACxB,WAAoB,EACpB,iBAAqC,EACrC,sBAA+C,KAC7C;IACF,OAAO;AACL,QAAA,eAAe,EAAE,IAAIA,+BAAe,CAAC,kBAAkB,CAAC;AACxD,QAAA,eAAe,EAAE,IAAIC,+BAAe,CAAC,kBAAkB,CAAC;AACxD,QAAA,eAAe,EAAE,IAAIC,+BAAe,CAAC,WAAW,EAAE,WAAW,CAAC;AAC9D,QAAA,mBAAmB,EAAE,IAAIC,uCAAmB,CAAC,kBAAkB,CAAC;AAChE,QAAA,uBAAuB,EAAE,IAAIC,+CAAuB,CAAC,kBAAkB,CAAC;AACxE,QAAA,kBAAkB,EAAE,IAAIC,qCAAkB,CAAC,iBAAiB,CAAC;AAC7D,QAAA,wBAAwB,EAAE,IAAIC,iDAAwB,CAAC,WAAW,EAAE,sBAAsB,CAAC;KAC5F;AACH;;;;;"}
1
+ {"version":3,"file":"dprReportingClient.js","sources":["../../../../../../../../../src/dpr/data/dprReportingClient.ts"],"sourcesContent":["import type { RedisClientType } from 'redis'\nimport ReportingClient from './reportingClient'\nimport DashboardClient from './dashboardClient'\nimport ReportDataStore from './reportDataStore'\nimport { ApiConfig, FeatureFlagConfig, MigrationServiceConfig } from './types'\nimport { FeatureFlagService } from '../services/featureFlagService'\nimport ProductCollectionClient from './productCollectionClient'\nimport MissingReportClient from './missingReportClient'\nimport { ReportIdMigrationService } from '../services/reportIdMigrationService'\n\nexport const initDprReportingClients = (\n reportingApiConfig: ApiConfig,\n redisClient: RedisClientType,\n storePrefix?: string,\n featureFlagConfig?: FeatureFlagConfig,\n migrationServiceConfig?: MigrationServiceConfig,\n) => {\n return {\n reportingClient: new ReportingClient(reportingApiConfig),\n dashboardClient: new DashboardClient(reportingApiConfig),\n reportDataStore: new ReportDataStore(redisClient, storePrefix),\n missingReportClient: new MissingReportClient(reportingApiConfig),\n productCollectionClient: new ProductCollectionClient(reportingApiConfig),\n featureFlagService: new FeatureFlagService(featureFlagConfig),\n reportIdMigrationService: new ReportIdMigrationService(redisClient, migrationServiceConfig),\n }\n}\n\nexport default initDprReportingClients\n"],"names":["ReportingClient","DashboardClient","ReportDataStore","MissingReportClient","ProductCollectionClient","FeatureFlagService","ReportIdMigrationService"],"mappings":";;;;;;;;;;;;AAUO,MAAM,uBAAuB,GAAG,CACrC,kBAA6B,EAC7B,WAA4B,EAC5B,WAAoB,EACpB,iBAAqC,EACrC,sBAA+C,KAC7C;IACF,OAAO;AACL,QAAA,eAAe,EAAE,IAAIA,+BAAe,CAAC,kBAAkB,CAAC;AACxD,QAAA,eAAe,EAAE,IAAIC,+BAAe,CAAC,kBAAkB,CAAC;AACxD,QAAA,eAAe,EAAE,IAAIC,+BAAe,CAAC,WAAW,EAAE,WAAW,CAAC;AAC9D,QAAA,mBAAmB,EAAE,IAAIC,uCAAmB,CAAC,kBAAkB,CAAC;AAChE,QAAA,uBAAuB,EAAE,IAAIC,+CAAuB,CAAC,kBAAkB,CAAC;AACxE,QAAA,kBAAkB,EAAE,IAAIC,qCAAkB,CAAC,iBAAiB,CAAC;AAC7D,QAAA,wBAAwB,EAAE,IAAIC,iDAAwB,CAAC,WAAW,EAAE,sBAAsB,CAAC;KAC5F;AACH;;;;;"}
@@ -1 +1 @@
1
- {"version":3,"file":"reportDataStore.js","sources":["../../../../../../../../../src/dpr/data/reportDataStore.ts"],"sourcesContent":["// eslint-disable-next-line import/no-extraneous-dependencies\nimport { createClient } from 'redis'\nimport logger from '../utils/logger'\nimport { ReportStoreConfig } from '../types/ReportStore'\n\nexport type RedisClient = ReturnType<typeof createClient>\n\nclass ReportDataStore {\n prefix: string\n\n constructor(\n private readonly redisClient: RedisClient,\n prefix = 'dprReportStoreUser:',\n ) {\n redisClient.on('error', error => {\n logger.error(error, `Redis error`)\n })\n this.prefix = prefix\n }\n\n private async ensureConnected() {\n if (!this.redisClient.isOpen) {\n await this.redisClient.connect()\n }\n }\n\n public async setUserConfig(userId: string, config: ReportStoreConfig): Promise<void> {\n await this.ensureConnected()\n await this.redisClient.set(`${this.prefix}${userId}`, JSON.stringify(config))\n }\n\n public async getUserConfig(userId: string): Promise<ReportStoreConfig> {\n await this.ensureConnected()\n const userConfig = await this.redisClient.get(`${this.prefix}${userId}`)\n return userConfig ? JSON.parse(String(userConfig)) : this.setBaseplate(userId)\n }\n\n private async setBaseplate(userId: string) {\n logger.info(`Initialising new DPR user config in store. prefix: ${this.prefix}`)\n const userConfig = { ...baseplateStore }\n await this.setUserConfig(userId, userConfig)\n return { ...baseplateStore }\n }\n}\n\nconst baseplateStore: ReportStoreConfig = {\n requestedReports: [],\n recentlyViewedReports: [],\n bookmarks: [],\n subscriptions: [],\n downloadPermissions: [],\n defaultFilters: [],\n}\n\nexport { ReportDataStore }\nexport default ReportDataStore\n"],"names":["logger"],"mappings":";;;;;;AAOA,MAAM,eAAe,CAAA;AAIA,IAAA,WAAA;AAHnB,IAAA,MAAM;AAEN,IAAA,WAAA,CACmB,WAAwB,EACzC,MAAM,GAAG,qBAAqB,EAAA;QADb,IAAA,CAAA,WAAW,GAAX,WAAW;AAG5B,QAAA,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE,KAAK,IAAG;AAC9B,YAAAA,cAAM,CAAC,KAAK,CAAC,KAAK,EAAE,CAAA,WAAA,CAAa,CAAC;AACpC,QAAA,CAAC,CAAC;AACF,QAAA,IAAI,CAAC,MAAM,GAAG,MAAM;IACtB;AAEQ,IAAA,MAAM,eAAe,GAAA;AAC3B,QAAA,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE;AAC5B,YAAA,MAAM,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE;QAClC;IACF;AAEO,IAAA,MAAM,aAAa,CAAC,MAAc,EAAE,MAAyB,EAAA;AAClE,QAAA,MAAM,IAAI,CAAC,eAAe,EAAE;QAC5B,MAAM,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAA,EAAG,IAAI,CAAC,MAAM,GAAG,MAAM,CAAA,CAAE,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IAC/E;IAEO,MAAM,aAAa,CAAC,MAAc,EAAA;AACvC,QAAA,MAAM,IAAI,CAAC,eAAe,EAAE;AAC5B,QAAA,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAA,EAAG,IAAI,CAAC,MAAM,GAAG,MAAM,CAAA,CAAE,CAAC;QACxE,OAAO,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,GAAG,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC;IAChF;IAEQ,MAAM,YAAY,CAAC,MAAc,EAAA;QACvCA,cAAM,CAAC,IAAI,CAAC,CAAA,mDAAA,EAAsD,IAAI,CAAC,MAAM,CAAA,CAAE,CAAC;AAChF,QAAA,MAAM,UAAU,GAAG,EAAE,GAAG,cAAc,EAAE;QACxC,MAAM,IAAI,CAAC,aAAa,CAAC,MAAM,EAAE,UAAU,CAAC;AAC5C,QAAA,OAAO,EAAE,GAAG,cAAc,EAAE;IAC9B;AACD;AAED,MAAM,cAAc,GAAsB;AACxC,IAAA,gBAAgB,EAAE,EAAE;AACpB,IAAA,qBAAqB,EAAE,EAAE;AACzB,IAAA,SAAS,EAAE,EAAE;AACb,IAAA,aAAa,EAAE,EAAE;AACjB,IAAA,mBAAmB,EAAE,EAAE;AACvB,IAAA,cAAc,EAAE,EAAE;CACnB;;;;;"}
1
+ {"version":3,"file":"reportDataStore.js","sources":["../../../../../../../../../src/dpr/data/reportDataStore.ts"],"sourcesContent":["// eslint-disable-next-line import/no-extraneous-dependencies\nimport { createClient } from 'redis'\nimport type { RedisClientType } from 'redis'\nimport logger from '../utils/logger'\nimport { ReportStoreConfig } from '../types/ReportStore'\n\nexport type RedisClient = ReturnType<typeof createClient>\n\nclass ReportDataStore {\n prefix: string\n\n constructor(\n private readonly redisClient: RedisClientType,\n prefix = 'dprReportStoreUser:',\n ) {\n redisClient.on('error', error => {\n logger.error(error, `Redis error`)\n })\n this.prefix = prefix\n }\n\n private async ensureConnected() {\n if (!this.redisClient.isOpen) {\n await this.redisClient.connect()\n }\n }\n\n public async setUserConfig(userId: string, config: ReportStoreConfig): Promise<void> {\n await this.ensureConnected()\n await this.redisClient.set(`${this.prefix}${userId}`, JSON.stringify(config))\n }\n\n public async getUserConfig(userId: string): Promise<ReportStoreConfig> {\n await this.ensureConnected()\n const userConfig = await this.redisClient.get(`${this.prefix}${userId}`)\n return userConfig ? JSON.parse(String(userConfig)) : this.setBaseplate(userId)\n }\n\n private async setBaseplate(userId: string) {\n logger.info(`Initialising new DPR user config in store. prefix: ${this.prefix}`)\n const userConfig = { ...baseplateStore }\n await this.setUserConfig(userId, userConfig)\n return { ...baseplateStore }\n }\n}\n\nconst baseplateStore: ReportStoreConfig = {\n requestedReports: [],\n recentlyViewedReports: [],\n bookmarks: [],\n subscriptions: [],\n downloadPermissions: [],\n defaultFilters: [],\n}\n\nexport { ReportDataStore }\nexport default ReportDataStore\n"],"names":["logger"],"mappings":";;;;;;AAQA,MAAM,eAAe,CAAA;AAIA,IAAA,WAAA;AAHnB,IAAA,MAAM;AAEN,IAAA,WAAA,CACmB,WAA4B,EAC7C,MAAM,GAAG,qBAAqB,EAAA;QADb,IAAA,CAAA,WAAW,GAAX,WAAW;AAG5B,QAAA,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE,KAAK,IAAG;AAC9B,YAAAA,cAAM,CAAC,KAAK,CAAC,KAAK,EAAE,CAAA,WAAA,CAAa,CAAC;AACpC,QAAA,CAAC,CAAC;AACF,QAAA,IAAI,CAAC,MAAM,GAAG,MAAM;IACtB;AAEQ,IAAA,MAAM,eAAe,GAAA;AAC3B,QAAA,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE;AAC5B,YAAA,MAAM,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE;QAClC;IACF;AAEO,IAAA,MAAM,aAAa,CAAC,MAAc,EAAE,MAAyB,EAAA;AAClE,QAAA,MAAM,IAAI,CAAC,eAAe,EAAE;QAC5B,MAAM,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAA,EAAG,IAAI,CAAC,MAAM,GAAG,MAAM,CAAA,CAAE,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IAC/E;IAEO,MAAM,aAAa,CAAC,MAAc,EAAA;AACvC,QAAA,MAAM,IAAI,CAAC,eAAe,EAAE;AAC5B,QAAA,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAA,EAAG,IAAI,CAAC,MAAM,GAAG,MAAM,CAAA,CAAE,CAAC;QACxE,OAAO,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,GAAG,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC;IAChF;IAEQ,MAAM,YAAY,CAAC,MAAc,EAAA;QACvCA,cAAM,CAAC,IAAI,CAAC,CAAA,mDAAA,EAAsD,IAAI,CAAC,MAAM,CAAA,CAAE,CAAC;AAChF,QAAA,MAAM,UAAU,GAAG,EAAE,GAAG,cAAc,EAAE;QACxC,MAAM,IAAI,CAAC,aAAa,CAAC,MAAM,EAAE,UAAU,CAAC;AAC5C,QAAA,OAAO,EAAE,GAAG,cAAc,EAAE;IAC9B;AACD;AAED,MAAM,cAAc,GAAsB;AACxC,IAAA,gBAAgB,EAAE,EAAE;AACpB,IAAA,qBAAqB,EAAE,EAAE;AACzB,IAAA,SAAS,EAAE,EAAE;AACb,IAAA,aAAa,EAAE,EAAE;AACjB,IAAA,mBAAmB,EAAE,EAAE;AACvB,IAAA,cAAc,EAAE,EAAE;CACnB;;;;;"}
@@ -1 +1 @@
1
- {"version":3,"file":"reportIdMigrationService.js","sources":["../../../../../../../../../src/dpr/services/reportIdMigrationService.ts"],"sourcesContent":["import { RedisClient } from '../data/reportDataStore'\nimport { MigrationServiceConfig } from '../data/types'\nimport { ReportStoreConfig } from '../types/ReportStore'\nimport logger from '../utils/logger'\n\ninterface ConfigToMigrate {\n key: string\n config: ReportStoreConfig\n}\n\n/**\n * One-off migration that updates report IDs stored in Redis\n * from: abc123\n * to: dpr_abc123\n *\n * The migration is idempotent and will only be marked as complete once\n * all user configs have been successfully processed.\n */\nexport class ReportIdMigrationService {\n enabled: boolean\n\n private static readonly MIGRATION_KEY = 'migration:dpr-report-ids:v1'\n\n private static readonly USER_CONFIG_PREFIX = 'userConfig:'\n\n private static readonly MAX_RETRIES = 3\n\n constructor(\n private readonly redisClient: RedisClient,\n migrationServiceConfig?: MigrationServiceConfig | undefined,\n ) {\n this.enabled = migrationServiceConfig ? Boolean(migrationServiceConfig.enabled) : false\n if (!this.enabled) logger.info(`Migration service: disabled `)\n\n this.redisClient.on('error', error => {\n logger.error(error, 'Redis error')\n })\n }\n\n /**\n * Runs the migration.\n */\n public async migrate(): Promise<void> {\n if (!this.enabled) {\n logger.info('DPR report ID migration service is disabled')\n\n return\n }\n\n await this.ensureConnected()\n\n const migrationComplete = await this.redisClient.get(ReportIdMigrationService.MIGRATION_KEY)\n if (migrationComplete) {\n logger.info('DPR report ID migration already completed')\n return\n }\n\n logger.info('Starting DPR report ID migration')\n\n // 1. get all the user configs from redis\n const configs: ConfigToMigrate[] = await this.getAllConfigs()\n\n let updatedConfigs = 0\n\n const initialFailures = await Promise.all(\n // 2. Loop over user configs\n configs.map(async config => {\n try {\n // 3. Migrate IDs for all a users sub configs\n const updated = await this.migrateSingleConfig(config)\n\n if (updated) {\n updatedConfigs += 1\n }\n\n return null\n } catch (error) {\n logger.error(error, `Failed to migrate config for key ${config.key}`)\n // 4. return failed configs so can be retried\n return config\n }\n }),\n )\n\n // 6. Retry any failed configs - max retries = 3\n const remainingFailures = await this.retryFailedConfigs(\n initialFailures.filter((config): config is ConfigToMigrate => config !== null),\n )\n\n if (remainingFailures.length > 0) {\n // Dont mark as complete if there are any remaining failures\n logger.error(\n {\n failureCount: remainingFailures.length,\n failedKeys: remainingFailures.map(config => config.key),\n },\n 'DPR report ID migration failed. Migration flag will not be set.',\n )\n\n // Leaving the flag unset allows the migration to retry on the next startup.\n return\n }\n\n // 7. DONE Successfully - Set the completed flag globally\n await this.redisClient.set(ReportIdMigrationService.MIGRATION_KEY, 'true')\n\n logger.info(\n {\n totalConfigs: configs.length,\n updatedConfigs,\n },\n 'DPR report ID migration completed successfully',\n )\n }\n\n /**\n * Retries failed migrations up to the configured maximum number\n * of attempts.\n */\n private async retryFailedConfigs(failedConfigs: ConfigToMigrate[], attempt = 1): Promise<ConfigToMigrate[]> {\n if (failedConfigs.length === 0 || attempt > ReportIdMigrationService.MAX_RETRIES) {\n return failedConfigs\n }\n\n logger.warn(\n {\n attempt,\n remainingFailures: failedConfigs.length,\n },\n 'Retrying failed DPR report migrations',\n )\n\n const retryResults = await Promise.all(\n failedConfigs.map(async config => {\n try {\n await this.migrateSingleConfig(config)\n\n return null\n } catch (error) {\n logger.error(error, `Retry failed for key ${config.key}`)\n\n return config\n }\n }),\n )\n\n const remainingFailures = retryResults.filter((config): config is ConfigToMigrate => config !== null)\n\n return this.retryFailedConfigs(remainingFailures, attempt + 1)\n }\n\n /**\n * Migrates a single user config and persists it if any report IDs\n * were updated.\n */\n private async migrateSingleConfig(configToMigrate: ConfigToMigrate): Promise<boolean> {\n const { key, config } = configToMigrate\n\n const { updatedConfig, updated } = this.migrateConfig(config)\n\n if (!updated) {\n return false\n }\n\n await this.redisClient.set(key, JSON.stringify(updatedConfig))\n\n return true\n }\n\n /**\n * Retrieves every stored report config from Redis together with its\n * associated Redis key.\n */\n private async getAllConfigs(): Promise<ConfigToMigrate[]> {\n const keys = await this.getUserConfigKeys()\n\n if (keys.length === 0) {\n return []\n }\n\n // Fetch all user configs in a single Redis call.\n const values = await this.redisClient.mGet(keys)\n\n return keys.flatMap((key, index) => {\n const value = values[index]\n\n if (!value) {\n return []\n }\n\n try {\n return [\n {\n key,\n config: JSON.parse(value) as ReportStoreConfig,\n },\n ]\n } catch (error) {\n logger.error(error, `Failed to parse config for key ${key}`)\n\n return []\n }\n })\n }\n\n /**\n * Retrieves all Redis keys containing user report store data.\n */\n private async getUserConfigKeys(): Promise<string[]> {\n const scan = async (cursor = '0', keys: string[] = []): Promise<string[]> => {\n const result = await this.redisClient.scan(cursor, {\n MATCH: `${ReportIdMigrationService.USER_CONFIG_PREFIX}*`,\n })\n\n const nextKeys = [...keys, ...result.keys]\n\n return result.cursor === '0' ? nextKeys : scan(result.cursor, nextKeys)\n }\n\n return scan()\n }\n\n /**\n * Updates all report IDs in a user config and returns the updated\n * config along with whether any changes were made.\n */\n private migrateConfig(config: ReportStoreConfig): {\n updatedConfig: ReportStoreConfig\n updated: boolean\n } {\n const requestedReports = this.updateReportIds(config.requestedReports)\n\n const recentlyViewedReports = this.updateReportIds(config.recentlyViewedReports)\n\n const bookmarks = this.updateReportIds(config.bookmarks)\n\n const downloadPermissions = this.updateReportIds(config.downloadPermissions ?? [])\n\n const defaultFilters = this.updateReportIds(config.defaultFilters ?? [])\n\n const updated = [requestedReports, recentlyViewedReports, bookmarks, downloadPermissions, defaultFilters].some(\n result => result.updated,\n )\n\n return {\n updated,\n updatedConfig: {\n ...config,\n requestedReports: requestedReports.items,\n recentlyViewedReports: recentlyViewedReports.items,\n bookmarks: bookmarks.items,\n downloadPermissions: downloadPermissions.items,\n defaultFilters: defaultFilters.items,\n },\n }\n }\n\n /**\n * Updates report IDs in a collection and returns the updated\n * collection together with whether any items changed.\n */\n private updateReportIds<T extends { reportId: string }>(\n items: T[],\n ): {\n items: T[]\n updated: boolean\n } {\n let updated = false\n\n const updatedItems = items.map(item => {\n const newId = this.getMigratedReportId(item.reportId)\n\n if (newId === item.reportId) {\n return item\n }\n\n updated = true\n\n return {\n ...item,\n reportId: newId,\n }\n })\n\n return {\n items: updatedItems,\n updated,\n }\n }\n\n /**\n * Converts a legacy report ID into the new DPR-prefixed format.\n *\n * The method is intentionally idempotent so that rerunning the\n * migration will never double-prefix IDs.\n */\n private getMigratedReportId(reportId: string): string {\n // Already migrated IDs are returned unchanged.\n return reportId.startsWith('dpr_') ? reportId : `dpr_${reportId}`\n }\n\n private async ensureConnected(): Promise<void> {\n if (!this.redisClient.isOpen) {\n await this.redisClient.connect()\n }\n }\n}\n"],"names":["logger"],"mappings":";;;;AAUA;;;;;;;AAOG;MACU,wBAAwB,CAAA;AAUhB,IAAA,WAAA;AATnB,IAAA,OAAO;AAEC,IAAA,OAAgB,aAAa,GAAG,6BAA6B;AAE7D,IAAA,OAAgB,kBAAkB,GAAG,aAAa;AAElD,IAAA,OAAgB,WAAW,GAAG,CAAC;IAEvC,WAAA,CACmB,WAAwB,EACzC,sBAA2D,EAAA;QAD1C,IAAA,CAAA,WAAW,GAAX,WAAW;AAG5B,QAAA,IAAI,CAAC,OAAO,GAAG,sBAAsB,GAAG,OAAO,CAAC,sBAAsB,CAAC,OAAO,CAAC,GAAG,KAAK;QACvF,IAAI,CAAC,IAAI,CAAC,OAAO;AAAE,YAAAA,cAAM,CAAC,IAAI,CAAC,CAAA,4BAAA,CAA8B,CAAC;QAE9D,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE,KAAK,IAAG;AACnC,YAAAA,cAAM,CAAC,KAAK,CAAC,KAAK,EAAE,aAAa,CAAC;AACpC,QAAA,CAAC,CAAC;IACJ;AAEA;;AAEG;AACI,IAAA,MAAM,OAAO,GAAA;AAClB,QAAA,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE;AACjB,YAAAA,cAAM,CAAC,IAAI,CAAC,6CAA6C,CAAC;YAE1D;QACF;AAEA,QAAA,MAAM,IAAI,CAAC,eAAe,EAAE;AAE5B,QAAA,MAAM,iBAAiB,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,wBAAwB,CAAC,aAAa,CAAC;QAC5F,IAAI,iBAAiB,EAAE;AACrB,YAAAA,cAAM,CAAC,IAAI,CAAC,2CAA2C,CAAC;YACxD;QACF;AAEA,QAAAA,cAAM,CAAC,IAAI,CAAC,kCAAkC,CAAC;;AAG/C,QAAA,MAAM,OAAO,GAAsB,MAAM,IAAI,CAAC,aAAa,EAAE;QAE7D,IAAI,cAAc,GAAG,CAAC;AAEtB,QAAA,MAAM,eAAe,GAAG,MAAM,OAAO,CAAC,GAAG;;AAEvC,QAAA,OAAO,CAAC,GAAG,CAAC,OAAM,MAAM,KAAG;AACzB,YAAA,IAAI;;gBAEF,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,mBAAmB,CAAC,MAAM,CAAC;gBAEtD,IAAI,OAAO,EAAE;oBACX,cAAc,IAAI,CAAC;gBACrB;AAEA,gBAAA,OAAO,IAAI;YACb;YAAE,OAAO,KAAK,EAAE;gBACdA,cAAM,CAAC,KAAK,CAAC,KAAK,EAAE,CAAA,iCAAA,EAAoC,MAAM,CAAC,GAAG,CAAA,CAAE,CAAC;;AAErE,gBAAA,OAAO,MAAM;YACf;QACF,CAAC,CAAC,CACH;;QAGD,MAAM,iBAAiB,GAAG,MAAM,IAAI,CAAC,kBAAkB,CACrD,eAAe,CAAC,MAAM,CAAC,CAAC,MAAM,KAAgC,MAAM,KAAK,IAAI,CAAC,CAC/E;AAED,QAAA,IAAI,iBAAiB,CAAC,MAAM,GAAG,CAAC,EAAE;;YAEhCA,cAAM,CAAC,KAAK,CACV;gBACE,YAAY,EAAE,iBAAiB,CAAC,MAAM;AACtC,gBAAA,UAAU,EAAE,iBAAiB,CAAC,GAAG,CAAC,MAAM,IAAI,MAAM,CAAC,GAAG,CAAC;aACxD,EACD,iEAAiE,CAClE;;YAGD;QACF;;AAGA,QAAA,MAAM,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,wBAAwB,CAAC,aAAa,EAAE,MAAM,CAAC;QAE1EA,cAAM,CAAC,IAAI,CACT;YACE,YAAY,EAAE,OAAO,CAAC,MAAM;YAC5B,cAAc;SACf,EACD,gDAAgD,CACjD;IACH;AAEA;;;AAGG;AACK,IAAA,MAAM,kBAAkB,CAAC,aAAgC,EAAE,OAAO,GAAG,CAAC,EAAA;AAC5E,QAAA,IAAI,aAAa,CAAC,MAAM,KAAK,CAAC,IAAI,OAAO,GAAG,wBAAwB,CAAC,WAAW,EAAE;AAChF,YAAA,OAAO,aAAa;QACtB;QAEAA,cAAM,CAAC,IAAI,CACT;YACE,OAAO;YACP,iBAAiB,EAAE,aAAa,CAAC,MAAM;SACxC,EACD,uCAAuC,CACxC;AAED,QAAA,MAAM,YAAY,GAAG,MAAM,OAAO,CAAC,GAAG,CACpC,aAAa,CAAC,GAAG,CAAC,OAAM,MAAM,KAAG;AAC/B,YAAA,IAAI;AACF,gBAAA,MAAM,IAAI,CAAC,mBAAmB,CAAC,MAAM,CAAC;AAEtC,gBAAA,OAAO,IAAI;YACb;YAAE,OAAO,KAAK,EAAE;gBACdA,cAAM,CAAC,KAAK,CAAC,KAAK,EAAE,CAAA,qBAAA,EAAwB,MAAM,CAAC,GAAG,CAAA,CAAE,CAAC;AAEzD,gBAAA,OAAO,MAAM;YACf;QACF,CAAC,CAAC,CACH;AAED,QAAA,MAAM,iBAAiB,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC,MAAM,KAAgC,MAAM,KAAK,IAAI,CAAC;QAErG,OAAO,IAAI,CAAC,kBAAkB,CAAC,iBAAiB,EAAE,OAAO,GAAG,CAAC,CAAC;IAChE;AAEA;;;AAGG;IACK,MAAM,mBAAmB,CAAC,eAAgC,EAAA;AAChE,QAAA,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,eAAe;AAEvC,QAAA,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC;QAE7D,IAAI,CAAC,OAAO,EAAE;AACZ,YAAA,OAAO,KAAK;QACd;AAEA,QAAA,MAAM,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC;AAE9D,QAAA,OAAO,IAAI;IACb;AAEA;;;AAGG;AACK,IAAA,MAAM,aAAa,GAAA;AACzB,QAAA,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,iBAAiB,EAAE;AAE3C,QAAA,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE;AACrB,YAAA,OAAO,EAAE;QACX;;QAGA,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC;QAEhD,OAAO,IAAI,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,KAAK,KAAI;AACjC,YAAA,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC;YAE3B,IAAI,CAAC,KAAK,EAAE;AACV,gBAAA,OAAO,EAAE;YACX;AAEA,YAAA,IAAI;gBACF,OAAO;AACL,oBAAA;wBACE,GAAG;AACH,wBAAA,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAsB;AAC/C,qBAAA;iBACF;YACH;YAAE,OAAO,KAAK,EAAE;gBACdA,cAAM,CAAC,KAAK,CAAC,KAAK,EAAE,CAAA,+BAAA,EAAkC,GAAG,CAAA,CAAE,CAAC;AAE5D,gBAAA,OAAO,EAAE;YACX;AACF,QAAA,CAAC,CAAC;IACJ;AAEA;;AAEG;AACK,IAAA,MAAM,iBAAiB,GAAA;QAC7B,MAAM,IAAI,GAAG,OAAO,MAAM,GAAG,GAAG,EAAE,IAAA,GAAiB,EAAE,KAAuB;YAC1E,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE;AACjD,gBAAA,KAAK,EAAE,CAAA,EAAG,wBAAwB,CAAC,kBAAkB,CAAA,CAAA,CAAG;AACzD,aAAA,CAAC;YAEF,MAAM,QAAQ,GAAG,CAAC,GAAG,IAAI,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC;YAE1C,OAAO,MAAM,CAAC,MAAM,KAAK,GAAG,GAAG,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC;AACzE,QAAA,CAAC;QAED,OAAO,IAAI,EAAE;IACf;AAEA;;;AAGG;AACK,IAAA,aAAa,CAAC,MAAyB,EAAA;QAI7C,MAAM,gBAAgB,GAAG,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,gBAAgB,CAAC;QAEtE,MAAM,qBAAqB,GAAG,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,qBAAqB,CAAC;QAEhF,MAAM,SAAS,GAAG,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,SAAS,CAAC;AAExD,QAAA,MAAM,mBAAmB,GAAG,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,mBAAmB,IAAI,EAAE,CAAC;AAElF,QAAA,MAAM,cAAc,GAAG,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,cAAc,IAAI,EAAE,CAAC;QAExE,MAAM,OAAO,GAAG,CAAC,gBAAgB,EAAE,qBAAqB,EAAE,SAAS,EAAE,mBAAmB,EAAE,cAAc,CAAC,CAAC,IAAI,CAC5G,MAAM,IAAI,MAAM,CAAC,OAAO,CACzB;QAED,OAAO;YACL,OAAO;AACP,YAAA,aAAa,EAAE;AACb,gBAAA,GAAG,MAAM;gBACT,gBAAgB,EAAE,gBAAgB,CAAC,KAAK;gBACxC,qBAAqB,EAAE,qBAAqB,CAAC,KAAK;gBAClD,SAAS,EAAE,SAAS,CAAC,KAAK;gBAC1B,mBAAmB,EAAE,mBAAmB,CAAC,KAAK;gBAC9C,cAAc,EAAE,cAAc,CAAC,KAAK;AACrC,aAAA;SACF;IACH;AAEA;;;AAGG;AACK,IAAA,eAAe,CACrB,KAAU,EAAA;QAKV,IAAI,OAAO,GAAG,KAAK;QAEnB,MAAM,YAAY,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI,IAAG;YACpC,MAAM,KAAK,GAAG,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,QAAQ,CAAC;AAErD,YAAA,IAAI,KAAK,KAAK,IAAI,CAAC,QAAQ,EAAE;AAC3B,gBAAA,OAAO,IAAI;YACb;YAEA,OAAO,GAAG,IAAI;YAEd,OAAO;AACL,gBAAA,GAAG,IAAI;AACP,gBAAA,QAAQ,EAAE,KAAK;aAChB;AACH,QAAA,CAAC,CAAC;QAEF,OAAO;AACL,YAAA,KAAK,EAAE,YAAY;YACnB,OAAO;SACR;IACH;AAEA;;;;;AAKG;AACK,IAAA,mBAAmB,CAAC,QAAgB,EAAA;;AAE1C,QAAA,OAAO,QAAQ,CAAC,UAAU,CAAC,MAAM,CAAC,GAAG,QAAQ,GAAG,CAAA,IAAA,EAAO,QAAQ,EAAE;IACnE;AAEQ,IAAA,MAAM,eAAe,GAAA;AAC3B,QAAA,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE;AAC5B,YAAA,MAAM,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE;QAClC;IACF;;;;;"}
1
+ {"version":3,"file":"reportIdMigrationService.js","sources":["../../../../../../../../../src/dpr/services/reportIdMigrationService.ts"],"sourcesContent":["import type { RedisClientType } from 'redis'\nimport { MigrationServiceConfig } from '../data/types'\nimport { ReportStoreConfig } from '../types/ReportStore'\nimport logger from '../utils/logger'\n\ninterface ConfigToMigrate {\n key: string\n config: ReportStoreConfig\n}\n\n/**\n * One-off migration that updates report IDs stored in Redis\n * from: abc123\n * to: dpr_abc123\n *\n * The migration is idempotent and will only be marked as complete once\n * all user configs have been successfully processed.\n */\nexport class ReportIdMigrationService {\n enabled: boolean\n\n private static readonly MIGRATION_KEY = 'migration:dpr-report-ids:v1'\n\n private static readonly USER_CONFIG_PREFIX = 'userConfig:'\n\n private static readonly MAX_RETRIES = 3\n\n constructor(\n private readonly redisClient: RedisClientType,\n migrationServiceConfig?: MigrationServiceConfig | undefined,\n ) {\n this.enabled = migrationServiceConfig ? Boolean(migrationServiceConfig.enabled) : false\n if (!this.enabled) logger.info(`Migration service: disabled `)\n\n this.redisClient.on('error', error => {\n logger.error(error, 'Redis error')\n })\n }\n\n /**\n * Runs the migration.\n */\n public async migrate(): Promise<void> {\n if (!this.enabled) {\n logger.info('DPR report ID migration service is disabled')\n\n return\n }\n\n await this.ensureConnected()\n\n const migrationComplete = await this.redisClient.get(ReportIdMigrationService.MIGRATION_KEY)\n if (migrationComplete) {\n logger.info('DPR report ID migration already completed')\n return\n }\n\n logger.info('Starting DPR report ID migration')\n\n // 1. get all the user configs from redis\n const configs: ConfigToMigrate[] = await this.getAllConfigs()\n\n let updatedConfigs = 0\n\n const initialFailures = await Promise.all(\n // 2. Loop over user configs\n configs.map(async config => {\n try {\n // 3. Migrate IDs for all a users sub configs\n const updated = await this.migrateSingleConfig(config)\n\n if (updated) {\n updatedConfigs += 1\n }\n\n return null\n } catch (error) {\n logger.error(error, `Failed to migrate config for key ${config.key}`)\n // 4. return failed configs so can be retried\n return config\n }\n }),\n )\n\n // 6. Retry any failed configs - max retries = 3\n const remainingFailures = await this.retryFailedConfigs(\n initialFailures.filter((config): config is ConfigToMigrate => config !== null),\n )\n\n if (remainingFailures.length > 0) {\n // Dont mark as complete if there are any remaining failures\n logger.error(\n {\n failureCount: remainingFailures.length,\n failedKeys: remainingFailures.map(config => config.key),\n },\n 'DPR report ID migration failed. Migration flag will not be set.',\n )\n\n // Leaving the flag unset allows the migration to retry on the next startup.\n return\n }\n\n // 7. DONE Successfully - Set the completed flag globally\n await this.redisClient.set(ReportIdMigrationService.MIGRATION_KEY, 'true')\n\n logger.info(\n {\n totalConfigs: configs.length,\n updatedConfigs,\n },\n 'DPR report ID migration completed successfully',\n )\n }\n\n /**\n * Retries failed migrations up to the configured maximum number\n * of attempts.\n */\n private async retryFailedConfigs(failedConfigs: ConfigToMigrate[], attempt = 1): Promise<ConfigToMigrate[]> {\n if (failedConfigs.length === 0 || attempt > ReportIdMigrationService.MAX_RETRIES) {\n return failedConfigs\n }\n\n logger.warn(\n {\n attempt,\n remainingFailures: failedConfigs.length,\n },\n 'Retrying failed DPR report migrations',\n )\n\n const retryResults = await Promise.all(\n failedConfigs.map(async config => {\n try {\n await this.migrateSingleConfig(config)\n\n return null\n } catch (error) {\n logger.error(error, `Retry failed for key ${config.key}`)\n\n return config\n }\n }),\n )\n\n const remainingFailures = retryResults.filter((config): config is ConfigToMigrate => config !== null)\n\n return this.retryFailedConfigs(remainingFailures, attempt + 1)\n }\n\n /**\n * Migrates a single user config and persists it if any report IDs\n * were updated.\n */\n private async migrateSingleConfig(configToMigrate: ConfigToMigrate): Promise<boolean> {\n const { key, config } = configToMigrate\n\n const { updatedConfig, updated } = this.migrateConfig(config)\n\n if (!updated) {\n return false\n }\n\n await this.redisClient.set(key, JSON.stringify(updatedConfig))\n\n return true\n }\n\n /**\n * Retrieves every stored report config from Redis together with its\n * associated Redis key.\n */\n private async getAllConfigs(): Promise<ConfigToMigrate[]> {\n const keys = await this.getUserConfigKeys()\n\n if (keys.length === 0) {\n return []\n }\n\n // Fetch all user configs in a single Redis call.\n const values = await this.redisClient.mGet(keys)\n\n return keys.flatMap((key, index) => {\n const value = values[index]\n\n if (!value) {\n return []\n }\n\n try {\n return [\n {\n key,\n config: JSON.parse(value) as ReportStoreConfig,\n },\n ]\n } catch (error) {\n logger.error(error, `Failed to parse config for key ${key}`)\n\n return []\n }\n })\n }\n\n /**\n * Retrieves all Redis keys containing user report store data.\n */\n private async getUserConfigKeys(): Promise<string[]> {\n const scan = async (cursor = '0', keys: string[] = []): Promise<string[]> => {\n const result = await this.redisClient.scan(cursor, {\n MATCH: `${ReportIdMigrationService.USER_CONFIG_PREFIX}*`,\n })\n\n const nextKeys = [...keys, ...result.keys]\n\n return result.cursor === '0' ? nextKeys : scan(result.cursor, nextKeys)\n }\n\n return scan()\n }\n\n /**\n * Updates all report IDs in a user config and returns the updated\n * config along with whether any changes were made.\n */\n private migrateConfig(config: ReportStoreConfig): {\n updatedConfig: ReportStoreConfig\n updated: boolean\n } {\n const requestedReports = this.updateReportIds(config.requestedReports)\n\n const recentlyViewedReports = this.updateReportIds(config.recentlyViewedReports)\n\n const bookmarks = this.updateReportIds(config.bookmarks)\n\n const downloadPermissions = this.updateReportIds(config.downloadPermissions ?? [])\n\n const defaultFilters = this.updateReportIds(config.defaultFilters ?? [])\n\n const updated = [requestedReports, recentlyViewedReports, bookmarks, downloadPermissions, defaultFilters].some(\n result => result.updated,\n )\n\n return {\n updated,\n updatedConfig: {\n ...config,\n requestedReports: requestedReports.items,\n recentlyViewedReports: recentlyViewedReports.items,\n bookmarks: bookmarks.items,\n downloadPermissions: downloadPermissions.items,\n defaultFilters: defaultFilters.items,\n },\n }\n }\n\n /**\n * Updates report IDs in a collection and returns the updated\n * collection together with whether any items changed.\n */\n private updateReportIds<T extends { reportId: string }>(\n items: T[],\n ): {\n items: T[]\n updated: boolean\n } {\n let updated = false\n\n const updatedItems = items.map(item => {\n const newId = this.getMigratedReportId(item.reportId)\n\n if (newId === item.reportId) {\n return item\n }\n\n updated = true\n\n return {\n ...item,\n reportId: newId,\n }\n })\n\n return {\n items: updatedItems,\n updated,\n }\n }\n\n /**\n * Converts a legacy report ID into the new DPR-prefixed format.\n *\n * The method is intentionally idempotent so that rerunning the\n * migration will never double-prefix IDs.\n */\n private getMigratedReportId(reportId: string): string {\n // Already migrated IDs are returned unchanged.\n return reportId.startsWith('dpr_') ? reportId : `dpr_${reportId}`\n }\n\n private async ensureConnected(): Promise<void> {\n if (!this.redisClient.isOpen) {\n await this.redisClient.connect()\n }\n }\n}\n"],"names":["logger"],"mappings":"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"}
@@ -1,13 +1,14 @@
1
+ import { RedisClientType } from 'redis';
1
2
  import { ReportingClient } from './reportingClient.js';
2
3
  import { DashboardClient } from './dashboardClient.js';
3
- import { RedisClient, ReportDataStore } from './reportDataStore.js';
4
+ import { ReportDataStore } from './reportDataStore.js';
4
5
  import { ApiConfig, FeatureFlagConfig, MigrationServiceConfig } from './types.d.js';
5
6
  import { FeatureFlagService } from '../services/featureFlagService.js';
6
7
  import { ProductCollectionClient } from './productCollectionClient.js';
7
8
  import { MissingReportClient } from './missingReportClient.js';
8
9
  import { ReportIdMigrationService } from '../services/reportIdMigrationService.js';
9
10
 
10
- declare const initDprReportingClients: (reportingApiConfig: ApiConfig, redisClient: RedisClient, storePrefix?: string, featureFlagConfig?: FeatureFlagConfig, migrationServiceConfig?: MigrationServiceConfig) => {
11
+ declare const initDprReportingClients: (reportingApiConfig: ApiConfig, redisClient: RedisClientType, storePrefix?: string, featureFlagConfig?: FeatureFlagConfig, migrationServiceConfig?: MigrationServiceConfig) => {
11
12
  reportingClient: ReportingClient;
12
13
  dashboardClient: DashboardClient;
13
14
  reportDataStore: ReportDataStore;
@@ -1 +1 @@
1
- {"version":3,"file":"dprReportingClient.js","sources":["../../../../../../../../src/dpr/data/dprReportingClient.ts"],"sourcesContent":["import ReportingClient from './reportingClient'\nimport DashboardClient from './dashboardClient'\nimport ReportDataStore, { RedisClient } from './reportDataStore'\nimport { ApiConfig, FeatureFlagConfig, MigrationServiceConfig } from './types'\nimport { FeatureFlagService } from '../services/featureFlagService'\nimport ProductCollectionClient from './productCollectionClient'\nimport MissingReportClient from './missingReportClient'\nimport { ReportIdMigrationService } from '../services/reportIdMigrationService'\n\nexport const initDprReportingClients = (\n reportingApiConfig: ApiConfig,\n redisClient: RedisClient,\n storePrefix?: string,\n featureFlagConfig?: FeatureFlagConfig,\n migrationServiceConfig?: MigrationServiceConfig,\n) => {\n return {\n reportingClient: new ReportingClient(reportingApiConfig),\n dashboardClient: new DashboardClient(reportingApiConfig),\n reportDataStore: new ReportDataStore(redisClient, storePrefix),\n missingReportClient: new MissingReportClient(reportingApiConfig),\n productCollectionClient: new ProductCollectionClient(reportingApiConfig),\n featureFlagService: new FeatureFlagService(featureFlagConfig),\n reportIdMigrationService: new ReportIdMigrationService(redisClient, migrationServiceConfig),\n }\n}\n\nexport default initDprReportingClients\n"],"names":[],"mappings":";;;;;;;;AASO,MAAM,uBAAuB,GAAG,CACrC,kBAA6B,EAC7B,WAAwB,EACxB,WAAoB,EACpB,iBAAqC,EACrC,sBAA+C,KAC7C;IACF,OAAO;AACL,QAAA,eAAe,EAAE,IAAI,eAAe,CAAC,kBAAkB,CAAC;AACxD,QAAA,eAAe,EAAE,IAAI,eAAe,CAAC,kBAAkB,CAAC;AACxD,QAAA,eAAe,EAAE,IAAI,eAAe,CAAC,WAAW,EAAE,WAAW,CAAC;AAC9D,QAAA,mBAAmB,EAAE,IAAI,mBAAmB,CAAC,kBAAkB,CAAC;AAChE,QAAA,uBAAuB,EAAE,IAAI,uBAAuB,CAAC,kBAAkB,CAAC;AACxE,QAAA,kBAAkB,EAAE,IAAI,kBAAkB,CAAC,iBAAiB,CAAC;AAC7D,QAAA,wBAAwB,EAAE,IAAI,wBAAwB,CAAC,WAAW,EAAE,sBAAsB,CAAC;KAC5F;AACH;;;;"}
1
+ {"version":3,"file":"dprReportingClient.js","sources":["../../../../../../../../src/dpr/data/dprReportingClient.ts"],"sourcesContent":["import type { RedisClientType } from 'redis'\nimport ReportingClient from './reportingClient'\nimport DashboardClient from './dashboardClient'\nimport ReportDataStore from './reportDataStore'\nimport { ApiConfig, FeatureFlagConfig, MigrationServiceConfig } from './types'\nimport { FeatureFlagService } from '../services/featureFlagService'\nimport ProductCollectionClient from './productCollectionClient'\nimport MissingReportClient from './missingReportClient'\nimport { ReportIdMigrationService } from '../services/reportIdMigrationService'\n\nexport const initDprReportingClients = (\n reportingApiConfig: ApiConfig,\n redisClient: RedisClientType,\n storePrefix?: string,\n featureFlagConfig?: FeatureFlagConfig,\n migrationServiceConfig?: MigrationServiceConfig,\n) => {\n return {\n reportingClient: new ReportingClient(reportingApiConfig),\n dashboardClient: new DashboardClient(reportingApiConfig),\n reportDataStore: new ReportDataStore(redisClient, storePrefix),\n missingReportClient: new MissingReportClient(reportingApiConfig),\n productCollectionClient: new ProductCollectionClient(reportingApiConfig),\n featureFlagService: new FeatureFlagService(featureFlagConfig),\n reportIdMigrationService: new ReportIdMigrationService(redisClient, migrationServiceConfig),\n }\n}\n\nexport default initDprReportingClients\n"],"names":[],"mappings":";;;;;;;;AAUO,MAAM,uBAAuB,GAAG,CACrC,kBAA6B,EAC7B,WAA4B,EAC5B,WAAoB,EACpB,iBAAqC,EACrC,sBAA+C,KAC7C;IACF,OAAO;AACL,QAAA,eAAe,EAAE,IAAI,eAAe,CAAC,kBAAkB,CAAC;AACxD,QAAA,eAAe,EAAE,IAAI,eAAe,CAAC,kBAAkB,CAAC;AACxD,QAAA,eAAe,EAAE,IAAI,eAAe,CAAC,WAAW,EAAE,WAAW,CAAC;AAC9D,QAAA,mBAAmB,EAAE,IAAI,mBAAmB,CAAC,kBAAkB,CAAC;AAChE,QAAA,uBAAuB,EAAE,IAAI,uBAAuB,CAAC,kBAAkB,CAAC;AACxE,QAAA,kBAAkB,EAAE,IAAI,kBAAkB,CAAC,iBAAiB,CAAC;AAC7D,QAAA,wBAAwB,EAAE,IAAI,wBAAwB,CAAC,WAAW,EAAE,sBAAsB,CAAC;KAC5F;AACH;;;;"}
@@ -1,11 +1,10 @@
1
- import { createClient } from 'redis';
1
+ import { RedisClientType } from 'redis';
2
2
  import { ReportStoreConfig } from '../types/ReportStore.js';
3
3
 
4
- type RedisClient = ReturnType<typeof createClient>;
5
4
  declare class ReportDataStore {
6
5
  private readonly redisClient;
7
6
  prefix: string;
8
- constructor(redisClient: RedisClient, prefix?: string);
7
+ constructor(redisClient: RedisClientType, prefix?: string);
9
8
  private ensureConnected;
10
9
  setUserConfig(userId: string, config: ReportStoreConfig): Promise<void>;
11
10
  getUserConfig(userId: string): Promise<ReportStoreConfig>;
@@ -13,4 +12,3 @@ declare class ReportDataStore {
13
12
  }
14
13
 
15
14
  export { ReportDataStore, ReportDataStore as default };
16
- export type { RedisClient };
@@ -1 +1 @@
1
- {"version":3,"file":"reportDataStore.js","sources":["../../../../../../../../src/dpr/data/reportDataStore.ts"],"sourcesContent":["// eslint-disable-next-line import/no-extraneous-dependencies\nimport { createClient } from 'redis'\nimport logger from '../utils/logger'\nimport { ReportStoreConfig } from '../types/ReportStore'\n\nexport type RedisClient = ReturnType<typeof createClient>\n\nclass ReportDataStore {\n prefix: string\n\n constructor(\n private readonly redisClient: RedisClient,\n prefix = 'dprReportStoreUser:',\n ) {\n redisClient.on('error', error => {\n logger.error(error, `Redis error`)\n })\n this.prefix = prefix\n }\n\n private async ensureConnected() {\n if (!this.redisClient.isOpen) {\n await this.redisClient.connect()\n }\n }\n\n public async setUserConfig(userId: string, config: ReportStoreConfig): Promise<void> {\n await this.ensureConnected()\n await this.redisClient.set(`${this.prefix}${userId}`, JSON.stringify(config))\n }\n\n public async getUserConfig(userId: string): Promise<ReportStoreConfig> {\n await this.ensureConnected()\n const userConfig = await this.redisClient.get(`${this.prefix}${userId}`)\n return userConfig ? JSON.parse(String(userConfig)) : this.setBaseplate(userId)\n }\n\n private async setBaseplate(userId: string) {\n logger.info(`Initialising new DPR user config in store. prefix: ${this.prefix}`)\n const userConfig = { ...baseplateStore }\n await this.setUserConfig(userId, userConfig)\n return { ...baseplateStore }\n }\n}\n\nconst baseplateStore: ReportStoreConfig = {\n requestedReports: [],\n recentlyViewedReports: [],\n bookmarks: [],\n subscriptions: [],\n downloadPermissions: [],\n defaultFilters: [],\n}\n\nexport { ReportDataStore }\nexport default ReportDataStore\n"],"names":[],"mappings":";;AAOA,MAAM,eAAe,CAAA;AAIA,IAAA,WAAA;AAHnB,IAAA,MAAM;AAEN,IAAA,WAAA,CACmB,WAAwB,EACzC,MAAM,GAAG,qBAAqB,EAAA;QADb,IAAA,CAAA,WAAW,GAAX,WAAW;AAG5B,QAAA,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE,KAAK,IAAG;AAC9B,YAAA,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,CAAA,WAAA,CAAa,CAAC;AACpC,QAAA,CAAC,CAAC;AACF,QAAA,IAAI,CAAC,MAAM,GAAG,MAAM;IACtB;AAEQ,IAAA,MAAM,eAAe,GAAA;AAC3B,QAAA,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE;AAC5B,YAAA,MAAM,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE;QAClC;IACF;AAEO,IAAA,MAAM,aAAa,CAAC,MAAc,EAAE,MAAyB,EAAA;AAClE,QAAA,MAAM,IAAI,CAAC,eAAe,EAAE;QAC5B,MAAM,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAA,EAAG,IAAI,CAAC,MAAM,GAAG,MAAM,CAAA,CAAE,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IAC/E;IAEO,MAAM,aAAa,CAAC,MAAc,EAAA;AACvC,QAAA,MAAM,IAAI,CAAC,eAAe,EAAE;AAC5B,QAAA,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAA,EAAG,IAAI,CAAC,MAAM,GAAG,MAAM,CAAA,CAAE,CAAC;QACxE,OAAO,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,GAAG,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC;IAChF;IAEQ,MAAM,YAAY,CAAC,MAAc,EAAA;QACvC,MAAM,CAAC,IAAI,CAAC,CAAA,mDAAA,EAAsD,IAAI,CAAC,MAAM,CAAA,CAAE,CAAC;AAChF,QAAA,MAAM,UAAU,GAAG,EAAE,GAAG,cAAc,EAAE;QACxC,MAAM,IAAI,CAAC,aAAa,CAAC,MAAM,EAAE,UAAU,CAAC;AAC5C,QAAA,OAAO,EAAE,GAAG,cAAc,EAAE;IAC9B;AACD;AAED,MAAM,cAAc,GAAsB;AACxC,IAAA,gBAAgB,EAAE,EAAE;AACpB,IAAA,qBAAqB,EAAE,EAAE;AACzB,IAAA,SAAS,EAAE,EAAE;AACb,IAAA,aAAa,EAAE,EAAE;AACjB,IAAA,mBAAmB,EAAE,EAAE;AACvB,IAAA,cAAc,EAAE,EAAE;CACnB;;;;"}
1
+ {"version":3,"file":"reportDataStore.js","sources":["../../../../../../../../src/dpr/data/reportDataStore.ts"],"sourcesContent":["// eslint-disable-next-line import/no-extraneous-dependencies\nimport { createClient } from 'redis'\nimport type { RedisClientType } from 'redis'\nimport logger from '../utils/logger'\nimport { ReportStoreConfig } from '../types/ReportStore'\n\nexport type RedisClient = ReturnType<typeof createClient>\n\nclass ReportDataStore {\n prefix: string\n\n constructor(\n private readonly redisClient: RedisClientType,\n prefix = 'dprReportStoreUser:',\n ) {\n redisClient.on('error', error => {\n logger.error(error, `Redis error`)\n })\n this.prefix = prefix\n }\n\n private async ensureConnected() {\n if (!this.redisClient.isOpen) {\n await this.redisClient.connect()\n }\n }\n\n public async setUserConfig(userId: string, config: ReportStoreConfig): Promise<void> {\n await this.ensureConnected()\n await this.redisClient.set(`${this.prefix}${userId}`, JSON.stringify(config))\n }\n\n public async getUserConfig(userId: string): Promise<ReportStoreConfig> {\n await this.ensureConnected()\n const userConfig = await this.redisClient.get(`${this.prefix}${userId}`)\n return userConfig ? JSON.parse(String(userConfig)) : this.setBaseplate(userId)\n }\n\n private async setBaseplate(userId: string) {\n logger.info(`Initialising new DPR user config in store. prefix: ${this.prefix}`)\n const userConfig = { ...baseplateStore }\n await this.setUserConfig(userId, userConfig)\n return { ...baseplateStore }\n }\n}\n\nconst baseplateStore: ReportStoreConfig = {\n requestedReports: [],\n recentlyViewedReports: [],\n bookmarks: [],\n subscriptions: [],\n downloadPermissions: [],\n defaultFilters: [],\n}\n\nexport { ReportDataStore }\nexport default ReportDataStore\n"],"names":[],"mappings":";;AAQA,MAAM,eAAe,CAAA;AAIA,IAAA,WAAA;AAHnB,IAAA,MAAM;AAEN,IAAA,WAAA,CACmB,WAA4B,EAC7C,MAAM,GAAG,qBAAqB,EAAA;QADb,IAAA,CAAA,WAAW,GAAX,WAAW;AAG5B,QAAA,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE,KAAK,IAAG;AAC9B,YAAA,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,CAAA,WAAA,CAAa,CAAC;AACpC,QAAA,CAAC,CAAC;AACF,QAAA,IAAI,CAAC,MAAM,GAAG,MAAM;IACtB;AAEQ,IAAA,MAAM,eAAe,GAAA;AAC3B,QAAA,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE;AAC5B,YAAA,MAAM,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE;QAClC;IACF;AAEO,IAAA,MAAM,aAAa,CAAC,MAAc,EAAE,MAAyB,EAAA;AAClE,QAAA,MAAM,IAAI,CAAC,eAAe,EAAE;QAC5B,MAAM,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAA,EAAG,IAAI,CAAC,MAAM,GAAG,MAAM,CAAA,CAAE,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IAC/E;IAEO,MAAM,aAAa,CAAC,MAAc,EAAA;AACvC,QAAA,MAAM,IAAI,CAAC,eAAe,EAAE;AAC5B,QAAA,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAA,EAAG,IAAI,CAAC,MAAM,GAAG,MAAM,CAAA,CAAE,CAAC;QACxE,OAAO,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,GAAG,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC;IAChF;IAEQ,MAAM,YAAY,CAAC,MAAc,EAAA;QACvC,MAAM,CAAC,IAAI,CAAC,CAAA,mDAAA,EAAsD,IAAI,CAAC,MAAM,CAAA,CAAE,CAAC;AAChF,QAAA,MAAM,UAAU,GAAG,EAAE,GAAG,cAAc,EAAE;QACxC,MAAM,IAAI,CAAC,aAAa,CAAC,MAAM,EAAE,UAAU,CAAC;AAC5C,QAAA,OAAO,EAAE,GAAG,cAAc,EAAE;IAC9B;AACD;AAED,MAAM,cAAc,GAAsB;AACxC,IAAA,gBAAgB,EAAE,EAAE;AACpB,IAAA,qBAAqB,EAAE,EAAE;AACzB,IAAA,SAAS,EAAE,EAAE;AACb,IAAA,aAAa,EAAE,EAAE;AACjB,IAAA,mBAAmB,EAAE,EAAE;AACvB,IAAA,cAAc,EAAE,EAAE;CACnB;;;;"}
@@ -1,4 +1,4 @@
1
- import { RedisClient } from '../data/reportDataStore.js';
1
+ import { RedisClientType } from 'redis';
2
2
  import { MigrationServiceConfig } from '../data/types.d.js';
3
3
 
4
4
  /**
@@ -15,7 +15,7 @@ declare class ReportIdMigrationService {
15
15
  private static readonly MIGRATION_KEY;
16
16
  private static readonly USER_CONFIG_PREFIX;
17
17
  private static readonly MAX_RETRIES;
18
- constructor(redisClient: RedisClient, migrationServiceConfig?: MigrationServiceConfig | undefined);
18
+ constructor(redisClient: RedisClientType, migrationServiceConfig?: MigrationServiceConfig | undefined);
19
19
  /**
20
20
  * Runs the migration.
21
21
  */
@@ -1 +1 @@
1
- {"version":3,"file":"reportIdMigrationService.js","sources":["../../../../../../../../src/dpr/services/reportIdMigrationService.ts"],"sourcesContent":["import { RedisClient } from '../data/reportDataStore'\nimport { MigrationServiceConfig } from '../data/types'\nimport { ReportStoreConfig } from '../types/ReportStore'\nimport logger from '../utils/logger'\n\ninterface ConfigToMigrate {\n key: string\n config: ReportStoreConfig\n}\n\n/**\n * One-off migration that updates report IDs stored in Redis\n * from: abc123\n * to: dpr_abc123\n *\n * The migration is idempotent and will only be marked as complete once\n * all user configs have been successfully processed.\n */\nexport class ReportIdMigrationService {\n enabled: boolean\n\n private static readonly MIGRATION_KEY = 'migration:dpr-report-ids:v1'\n\n private static readonly USER_CONFIG_PREFIX = 'userConfig:'\n\n private static readonly MAX_RETRIES = 3\n\n constructor(\n private readonly redisClient: RedisClient,\n migrationServiceConfig?: MigrationServiceConfig | undefined,\n ) {\n this.enabled = migrationServiceConfig ? Boolean(migrationServiceConfig.enabled) : false\n if (!this.enabled) logger.info(`Migration service: disabled `)\n\n this.redisClient.on('error', error => {\n logger.error(error, 'Redis error')\n })\n }\n\n /**\n * Runs the migration.\n */\n public async migrate(): Promise<void> {\n if (!this.enabled) {\n logger.info('DPR report ID migration service is disabled')\n\n return\n }\n\n await this.ensureConnected()\n\n const migrationComplete = await this.redisClient.get(ReportIdMigrationService.MIGRATION_KEY)\n if (migrationComplete) {\n logger.info('DPR report ID migration already completed')\n return\n }\n\n logger.info('Starting DPR report ID migration')\n\n // 1. get all the user configs from redis\n const configs: ConfigToMigrate[] = await this.getAllConfigs()\n\n let updatedConfigs = 0\n\n const initialFailures = await Promise.all(\n // 2. Loop over user configs\n configs.map(async config => {\n try {\n // 3. Migrate IDs for all a users sub configs\n const updated = await this.migrateSingleConfig(config)\n\n if (updated) {\n updatedConfigs += 1\n }\n\n return null\n } catch (error) {\n logger.error(error, `Failed to migrate config for key ${config.key}`)\n // 4. return failed configs so can be retried\n return config\n }\n }),\n )\n\n // 6. Retry any failed configs - max retries = 3\n const remainingFailures = await this.retryFailedConfigs(\n initialFailures.filter((config): config is ConfigToMigrate => config !== null),\n )\n\n if (remainingFailures.length > 0) {\n // Dont mark as complete if there are any remaining failures\n logger.error(\n {\n failureCount: remainingFailures.length,\n failedKeys: remainingFailures.map(config => config.key),\n },\n 'DPR report ID migration failed. Migration flag will not be set.',\n )\n\n // Leaving the flag unset allows the migration to retry on the next startup.\n return\n }\n\n // 7. DONE Successfully - Set the completed flag globally\n await this.redisClient.set(ReportIdMigrationService.MIGRATION_KEY, 'true')\n\n logger.info(\n {\n totalConfigs: configs.length,\n updatedConfigs,\n },\n 'DPR report ID migration completed successfully',\n )\n }\n\n /**\n * Retries failed migrations up to the configured maximum number\n * of attempts.\n */\n private async retryFailedConfigs(failedConfigs: ConfigToMigrate[], attempt = 1): Promise<ConfigToMigrate[]> {\n if (failedConfigs.length === 0 || attempt > ReportIdMigrationService.MAX_RETRIES) {\n return failedConfigs\n }\n\n logger.warn(\n {\n attempt,\n remainingFailures: failedConfigs.length,\n },\n 'Retrying failed DPR report migrations',\n )\n\n const retryResults = await Promise.all(\n failedConfigs.map(async config => {\n try {\n await this.migrateSingleConfig(config)\n\n return null\n } catch (error) {\n logger.error(error, `Retry failed for key ${config.key}`)\n\n return config\n }\n }),\n )\n\n const remainingFailures = retryResults.filter((config): config is ConfigToMigrate => config !== null)\n\n return this.retryFailedConfigs(remainingFailures, attempt + 1)\n }\n\n /**\n * Migrates a single user config and persists it if any report IDs\n * were updated.\n */\n private async migrateSingleConfig(configToMigrate: ConfigToMigrate): Promise<boolean> {\n const { key, config } = configToMigrate\n\n const { updatedConfig, updated } = this.migrateConfig(config)\n\n if (!updated) {\n return false\n }\n\n await this.redisClient.set(key, JSON.stringify(updatedConfig))\n\n return true\n }\n\n /**\n * Retrieves every stored report config from Redis together with its\n * associated Redis key.\n */\n private async getAllConfigs(): Promise<ConfigToMigrate[]> {\n const keys = await this.getUserConfigKeys()\n\n if (keys.length === 0) {\n return []\n }\n\n // Fetch all user configs in a single Redis call.\n const values = await this.redisClient.mGet(keys)\n\n return keys.flatMap((key, index) => {\n const value = values[index]\n\n if (!value) {\n return []\n }\n\n try {\n return [\n {\n key,\n config: JSON.parse(value) as ReportStoreConfig,\n },\n ]\n } catch (error) {\n logger.error(error, `Failed to parse config for key ${key}`)\n\n return []\n }\n })\n }\n\n /**\n * Retrieves all Redis keys containing user report store data.\n */\n private async getUserConfigKeys(): Promise<string[]> {\n const scan = async (cursor = '0', keys: string[] = []): Promise<string[]> => {\n const result = await this.redisClient.scan(cursor, {\n MATCH: `${ReportIdMigrationService.USER_CONFIG_PREFIX}*`,\n })\n\n const nextKeys = [...keys, ...result.keys]\n\n return result.cursor === '0' ? nextKeys : scan(result.cursor, nextKeys)\n }\n\n return scan()\n }\n\n /**\n * Updates all report IDs in a user config and returns the updated\n * config along with whether any changes were made.\n */\n private migrateConfig(config: ReportStoreConfig): {\n updatedConfig: ReportStoreConfig\n updated: boolean\n } {\n const requestedReports = this.updateReportIds(config.requestedReports)\n\n const recentlyViewedReports = this.updateReportIds(config.recentlyViewedReports)\n\n const bookmarks = this.updateReportIds(config.bookmarks)\n\n const downloadPermissions = this.updateReportIds(config.downloadPermissions ?? [])\n\n const defaultFilters = this.updateReportIds(config.defaultFilters ?? [])\n\n const updated = [requestedReports, recentlyViewedReports, bookmarks, downloadPermissions, defaultFilters].some(\n result => result.updated,\n )\n\n return {\n updated,\n updatedConfig: {\n ...config,\n requestedReports: requestedReports.items,\n recentlyViewedReports: recentlyViewedReports.items,\n bookmarks: bookmarks.items,\n downloadPermissions: downloadPermissions.items,\n defaultFilters: defaultFilters.items,\n },\n }\n }\n\n /**\n * Updates report IDs in a collection and returns the updated\n * collection together with whether any items changed.\n */\n private updateReportIds<T extends { reportId: string }>(\n items: T[],\n ): {\n items: T[]\n updated: boolean\n } {\n let updated = false\n\n const updatedItems = items.map(item => {\n const newId = this.getMigratedReportId(item.reportId)\n\n if (newId === item.reportId) {\n return item\n }\n\n updated = true\n\n return {\n ...item,\n reportId: newId,\n }\n })\n\n return {\n items: updatedItems,\n updated,\n }\n }\n\n /**\n * Converts a legacy report ID into the new DPR-prefixed format.\n *\n * The method is intentionally idempotent so that rerunning the\n * migration will never double-prefix IDs.\n */\n private getMigratedReportId(reportId: string): string {\n // Already migrated IDs are returned unchanged.\n return reportId.startsWith('dpr_') ? reportId : `dpr_${reportId}`\n }\n\n private async ensureConnected(): Promise<void> {\n if (!this.redisClient.isOpen) {\n await this.redisClient.connect()\n }\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"reportIdMigrationService.js","sources":["../../../../../../../../src/dpr/services/reportIdMigrationService.ts"],"sourcesContent":["import type { RedisClientType } from 'redis'\nimport { MigrationServiceConfig } from '../data/types'\nimport { ReportStoreConfig } from '../types/ReportStore'\nimport logger from '../utils/logger'\n\ninterface ConfigToMigrate {\n key: string\n config: ReportStoreConfig\n}\n\n/**\n * One-off migration that updates report IDs stored in Redis\n * from: abc123\n * to: dpr_abc123\n *\n * The migration is idempotent and will only be marked as complete once\n * all user configs have been successfully processed.\n */\nexport class ReportIdMigrationService {\n enabled: boolean\n\n private static readonly MIGRATION_KEY = 'migration:dpr-report-ids:v1'\n\n private static readonly USER_CONFIG_PREFIX = 'userConfig:'\n\n private static readonly MAX_RETRIES = 3\n\n constructor(\n private readonly redisClient: RedisClientType,\n migrationServiceConfig?: MigrationServiceConfig | undefined,\n ) {\n this.enabled = migrationServiceConfig ? Boolean(migrationServiceConfig.enabled) : false\n if (!this.enabled) logger.info(`Migration service: disabled `)\n\n this.redisClient.on('error', error => {\n logger.error(error, 'Redis error')\n })\n }\n\n /**\n * Runs the migration.\n */\n public async migrate(): Promise<void> {\n if (!this.enabled) {\n logger.info('DPR report ID migration service is disabled')\n\n return\n }\n\n await this.ensureConnected()\n\n const migrationComplete = await this.redisClient.get(ReportIdMigrationService.MIGRATION_KEY)\n if (migrationComplete) {\n logger.info('DPR report ID migration already completed')\n return\n }\n\n logger.info('Starting DPR report ID migration')\n\n // 1. get all the user configs from redis\n const configs: ConfigToMigrate[] = await this.getAllConfigs()\n\n let updatedConfigs = 0\n\n const initialFailures = await Promise.all(\n // 2. Loop over user configs\n configs.map(async config => {\n try {\n // 3. Migrate IDs for all a users sub configs\n const updated = await this.migrateSingleConfig(config)\n\n if (updated) {\n updatedConfigs += 1\n }\n\n return null\n } catch (error) {\n logger.error(error, `Failed to migrate config for key ${config.key}`)\n // 4. return failed configs so can be retried\n return config\n }\n }),\n )\n\n // 6. Retry any failed configs - max retries = 3\n const remainingFailures = await this.retryFailedConfigs(\n initialFailures.filter((config): config is ConfigToMigrate => config !== null),\n )\n\n if (remainingFailures.length > 0) {\n // Dont mark as complete if there are any remaining failures\n logger.error(\n {\n failureCount: remainingFailures.length,\n failedKeys: remainingFailures.map(config => config.key),\n },\n 'DPR report ID migration failed. Migration flag will not be set.',\n )\n\n // Leaving the flag unset allows the migration to retry on the next startup.\n return\n }\n\n // 7. DONE Successfully - Set the completed flag globally\n await this.redisClient.set(ReportIdMigrationService.MIGRATION_KEY, 'true')\n\n logger.info(\n {\n totalConfigs: configs.length,\n updatedConfigs,\n },\n 'DPR report ID migration completed successfully',\n )\n }\n\n /**\n * Retries failed migrations up to the configured maximum number\n * of attempts.\n */\n private async retryFailedConfigs(failedConfigs: ConfigToMigrate[], attempt = 1): Promise<ConfigToMigrate[]> {\n if (failedConfigs.length === 0 || attempt > ReportIdMigrationService.MAX_RETRIES) {\n return failedConfigs\n }\n\n logger.warn(\n {\n attempt,\n remainingFailures: failedConfigs.length,\n },\n 'Retrying failed DPR report migrations',\n )\n\n const retryResults = await Promise.all(\n failedConfigs.map(async config => {\n try {\n await this.migrateSingleConfig(config)\n\n return null\n } catch (error) {\n logger.error(error, `Retry failed for key ${config.key}`)\n\n return config\n }\n }),\n )\n\n const remainingFailures = retryResults.filter((config): config is ConfigToMigrate => config !== null)\n\n return this.retryFailedConfigs(remainingFailures, attempt + 1)\n }\n\n /**\n * Migrates a single user config and persists it if any report IDs\n * were updated.\n */\n private async migrateSingleConfig(configToMigrate: ConfigToMigrate): Promise<boolean> {\n const { key, config } = configToMigrate\n\n const { updatedConfig, updated } = this.migrateConfig(config)\n\n if (!updated) {\n return false\n }\n\n await this.redisClient.set(key, JSON.stringify(updatedConfig))\n\n return true\n }\n\n /**\n * Retrieves every stored report config from Redis together with its\n * associated Redis key.\n */\n private async getAllConfigs(): Promise<ConfigToMigrate[]> {\n const keys = await this.getUserConfigKeys()\n\n if (keys.length === 0) {\n return []\n }\n\n // Fetch all user configs in a single Redis call.\n const values = await this.redisClient.mGet(keys)\n\n return keys.flatMap((key, index) => {\n const value = values[index]\n\n if (!value) {\n return []\n }\n\n try {\n return [\n {\n key,\n config: JSON.parse(value) as ReportStoreConfig,\n },\n ]\n } catch (error) {\n logger.error(error, `Failed to parse config for key ${key}`)\n\n return []\n }\n })\n }\n\n /**\n * Retrieves all Redis keys containing user report store data.\n */\n private async getUserConfigKeys(): Promise<string[]> {\n const scan = async (cursor = '0', keys: string[] = []): Promise<string[]> => {\n const result = await this.redisClient.scan(cursor, {\n MATCH: `${ReportIdMigrationService.USER_CONFIG_PREFIX}*`,\n })\n\n const nextKeys = [...keys, ...result.keys]\n\n return result.cursor === '0' ? nextKeys : scan(result.cursor, nextKeys)\n }\n\n return scan()\n }\n\n /**\n * Updates all report IDs in a user config and returns the updated\n * config along with whether any changes were made.\n */\n private migrateConfig(config: ReportStoreConfig): {\n updatedConfig: ReportStoreConfig\n updated: boolean\n } {\n const requestedReports = this.updateReportIds(config.requestedReports)\n\n const recentlyViewedReports = this.updateReportIds(config.recentlyViewedReports)\n\n const bookmarks = this.updateReportIds(config.bookmarks)\n\n const downloadPermissions = this.updateReportIds(config.downloadPermissions ?? [])\n\n const defaultFilters = this.updateReportIds(config.defaultFilters ?? [])\n\n const updated = [requestedReports, recentlyViewedReports, bookmarks, downloadPermissions, defaultFilters].some(\n result => result.updated,\n )\n\n return {\n updated,\n updatedConfig: {\n ...config,\n requestedReports: requestedReports.items,\n recentlyViewedReports: recentlyViewedReports.items,\n bookmarks: bookmarks.items,\n downloadPermissions: downloadPermissions.items,\n defaultFilters: defaultFilters.items,\n },\n }\n }\n\n /**\n * Updates report IDs in a collection and returns the updated\n * collection together with whether any items changed.\n */\n private updateReportIds<T extends { reportId: string }>(\n items: T[],\n ): {\n items: T[]\n updated: boolean\n } {\n let updated = false\n\n const updatedItems = items.map(item => {\n const newId = this.getMigratedReportId(item.reportId)\n\n if (newId === item.reportId) {\n return item\n }\n\n updated = true\n\n return {\n ...item,\n reportId: newId,\n }\n })\n\n return {\n items: updatedItems,\n updated,\n }\n }\n\n /**\n * Converts a legacy report ID into the new DPR-prefixed format.\n *\n * The method is intentionally idempotent so that rerunning the\n * migration will never double-prefix IDs.\n */\n private getMigratedReportId(reportId: string): string {\n // Already migrated IDs are returned unchanged.\n return reportId.startsWith('dpr_') ? reportId : `dpr_${reportId}`\n }\n\n private async ensureConnected(): Promise<void> {\n if (!this.redisClient.isOpen) {\n await this.redisClient.connect()\n }\n }\n}\n"],"names":[],"mappings":";;AAUA;;;;;;;AAOG;MACU,wBAAwB,CAAA;AAUhB,IAAA,WAAA;AATnB,IAAA,OAAO;AAEC,IAAA,OAAgB,aAAa,GAAG,6BAA6B;AAE7D,IAAA,OAAgB,kBAAkB,GAAG,aAAa;AAElD,IAAA,OAAgB,WAAW,GAAG,CAAC;IAEvC,WAAA,CACmB,WAA4B,EAC7C,sBAA2D,EAAA;QAD1C,IAAA,CAAA,WAAW,GAAX,WAAW;AAG5B,QAAA,IAAI,CAAC,OAAO,GAAG,sBAAsB,GAAG,OAAO,CAAC,sBAAsB,CAAC,OAAO,CAAC,GAAG,KAAK;QACvF,IAAI,CAAC,IAAI,CAAC,OAAO;AAAE,YAAA,MAAM,CAAC,IAAI,CAAC,CAAA,4BAAA,CAA8B,CAAC;QAE9D,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE,KAAK,IAAG;AACnC,YAAA,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,aAAa,CAAC;AACpC,QAAA,CAAC,CAAC;IACJ;AAEA;;AAEG;AACI,IAAA,MAAM,OAAO,GAAA;AAClB,QAAA,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE;AACjB,YAAA,MAAM,CAAC,IAAI,CAAC,6CAA6C,CAAC;YAE1D;QACF;AAEA,QAAA,MAAM,IAAI,CAAC,eAAe,EAAE;AAE5B,QAAA,MAAM,iBAAiB,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,wBAAwB,CAAC,aAAa,CAAC;QAC5F,IAAI,iBAAiB,EAAE;AACrB,YAAA,MAAM,CAAC,IAAI,CAAC,2CAA2C,CAAC;YACxD;QACF;AAEA,QAAA,MAAM,CAAC,IAAI,CAAC,kCAAkC,CAAC;;AAG/C,QAAA,MAAM,OAAO,GAAsB,MAAM,IAAI,CAAC,aAAa,EAAE;QAE7D,IAAI,cAAc,GAAG,CAAC;AAEtB,QAAA,MAAM,eAAe,GAAG,MAAM,OAAO,CAAC,GAAG;;AAEvC,QAAA,OAAO,CAAC,GAAG,CAAC,OAAM,MAAM,KAAG;AACzB,YAAA,IAAI;;gBAEF,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,mBAAmB,CAAC,MAAM,CAAC;gBAEtD,IAAI,OAAO,EAAE;oBACX,cAAc,IAAI,CAAC;gBACrB;AAEA,gBAAA,OAAO,IAAI;YACb;YAAE,OAAO,KAAK,EAAE;gBACd,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,CAAA,iCAAA,EAAoC,MAAM,CAAC,GAAG,CAAA,CAAE,CAAC;;AAErE,gBAAA,OAAO,MAAM;YACf;QACF,CAAC,CAAC,CACH;;QAGD,MAAM,iBAAiB,GAAG,MAAM,IAAI,CAAC,kBAAkB,CACrD,eAAe,CAAC,MAAM,CAAC,CAAC,MAAM,KAAgC,MAAM,KAAK,IAAI,CAAC,CAC/E;AAED,QAAA,IAAI,iBAAiB,CAAC,MAAM,GAAG,CAAC,EAAE;;YAEhC,MAAM,CAAC,KAAK,CACV;gBACE,YAAY,EAAE,iBAAiB,CAAC,MAAM;AACtC,gBAAA,UAAU,EAAE,iBAAiB,CAAC,GAAG,CAAC,MAAM,IAAI,MAAM,CAAC,GAAG,CAAC;aACxD,EACD,iEAAiE,CAClE;;YAGD;QACF;;AAGA,QAAA,MAAM,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,wBAAwB,CAAC,aAAa,EAAE,MAAM,CAAC;QAE1E,MAAM,CAAC,IAAI,CACT;YACE,YAAY,EAAE,OAAO,CAAC,MAAM;YAC5B,cAAc;SACf,EACD,gDAAgD,CACjD;IACH;AAEA;;;AAGG;AACK,IAAA,MAAM,kBAAkB,CAAC,aAAgC,EAAE,OAAO,GAAG,CAAC,EAAA;AAC5E,QAAA,IAAI,aAAa,CAAC,MAAM,KAAK,CAAC,IAAI,OAAO,GAAG,wBAAwB,CAAC,WAAW,EAAE;AAChF,YAAA,OAAO,aAAa;QACtB;QAEA,MAAM,CAAC,IAAI,CACT;YACE,OAAO;YACP,iBAAiB,EAAE,aAAa,CAAC,MAAM;SACxC,EACD,uCAAuC,CACxC;AAED,QAAA,MAAM,YAAY,GAAG,MAAM,OAAO,CAAC,GAAG,CACpC,aAAa,CAAC,GAAG,CAAC,OAAM,MAAM,KAAG;AAC/B,YAAA,IAAI;AACF,gBAAA,MAAM,IAAI,CAAC,mBAAmB,CAAC,MAAM,CAAC;AAEtC,gBAAA,OAAO,IAAI;YACb;YAAE,OAAO,KAAK,EAAE;gBACd,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,CAAA,qBAAA,EAAwB,MAAM,CAAC,GAAG,CAAA,CAAE,CAAC;AAEzD,gBAAA,OAAO,MAAM;YACf;QACF,CAAC,CAAC,CACH;AAED,QAAA,MAAM,iBAAiB,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC,MAAM,KAAgC,MAAM,KAAK,IAAI,CAAC;QAErG,OAAO,IAAI,CAAC,kBAAkB,CAAC,iBAAiB,EAAE,OAAO,GAAG,CAAC,CAAC;IAChE;AAEA;;;AAGG;IACK,MAAM,mBAAmB,CAAC,eAAgC,EAAA;AAChE,QAAA,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,eAAe;AAEvC,QAAA,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC;QAE7D,IAAI,CAAC,OAAO,EAAE;AACZ,YAAA,OAAO,KAAK;QACd;AAEA,QAAA,MAAM,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC;AAE9D,QAAA,OAAO,IAAI;IACb;AAEA;;;AAGG;AACK,IAAA,MAAM,aAAa,GAAA;AACzB,QAAA,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,iBAAiB,EAAE;AAE3C,QAAA,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE;AACrB,YAAA,OAAO,EAAE;QACX;;QAGA,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC;QAEhD,OAAO,IAAI,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,KAAK,KAAI;AACjC,YAAA,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC;YAE3B,IAAI,CAAC,KAAK,EAAE;AACV,gBAAA,OAAO,EAAE;YACX;AAEA,YAAA,IAAI;gBACF,OAAO;AACL,oBAAA;wBACE,GAAG;AACH,wBAAA,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAsB;AAC/C,qBAAA;iBACF;YACH;YAAE,OAAO,KAAK,EAAE;gBACd,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,CAAA,+BAAA,EAAkC,GAAG,CAAA,CAAE,CAAC;AAE5D,gBAAA,OAAO,EAAE;YACX;AACF,QAAA,CAAC,CAAC;IACJ;AAEA;;AAEG;AACK,IAAA,MAAM,iBAAiB,GAAA;QAC7B,MAAM,IAAI,GAAG,OAAO,MAAM,GAAG,GAAG,EAAE,IAAA,GAAiB,EAAE,KAAuB;YAC1E,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE;AACjD,gBAAA,KAAK,EAAE,CAAA,EAAG,wBAAwB,CAAC,kBAAkB,CAAA,CAAA,CAAG;AACzD,aAAA,CAAC;YAEF,MAAM,QAAQ,GAAG,CAAC,GAAG,IAAI,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC;YAE1C,OAAO,MAAM,CAAC,MAAM,KAAK,GAAG,GAAG,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC;AACzE,QAAA,CAAC;QAED,OAAO,IAAI,EAAE;IACf;AAEA;;;AAGG;AACK,IAAA,aAAa,CAAC,MAAyB,EAAA;QAI7C,MAAM,gBAAgB,GAAG,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,gBAAgB,CAAC;QAEtE,MAAM,qBAAqB,GAAG,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,qBAAqB,CAAC;QAEhF,MAAM,SAAS,GAAG,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,SAAS,CAAC;AAExD,QAAA,MAAM,mBAAmB,GAAG,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,mBAAmB,IAAI,EAAE,CAAC;AAElF,QAAA,MAAM,cAAc,GAAG,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,cAAc,IAAI,EAAE,CAAC;QAExE,MAAM,OAAO,GAAG,CAAC,gBAAgB,EAAE,qBAAqB,EAAE,SAAS,EAAE,mBAAmB,EAAE,cAAc,CAAC,CAAC,IAAI,CAC5G,MAAM,IAAI,MAAM,CAAC,OAAO,CACzB;QAED,OAAO;YACL,OAAO;AACP,YAAA,aAAa,EAAE;AACb,gBAAA,GAAG,MAAM;gBACT,gBAAgB,EAAE,gBAAgB,CAAC,KAAK;gBACxC,qBAAqB,EAAE,qBAAqB,CAAC,KAAK;gBAClD,SAAS,EAAE,SAAS,CAAC,KAAK;gBAC1B,mBAAmB,EAAE,mBAAmB,CAAC,KAAK;gBAC9C,cAAc,EAAE,cAAc,CAAC,KAAK;AACrC,aAAA;SACF;IACH;AAEA;;;AAGG;AACK,IAAA,eAAe,CACrB,KAAU,EAAA;QAKV,IAAI,OAAO,GAAG,KAAK;QAEnB,MAAM,YAAY,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI,IAAG;YACpC,MAAM,KAAK,GAAG,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,QAAQ,CAAC;AAErD,YAAA,IAAI,KAAK,KAAK,IAAI,CAAC,QAAQ,EAAE;AAC3B,gBAAA,OAAO,IAAI;YACb;YAEA,OAAO,GAAG,IAAI;YAEd,OAAO;AACL,gBAAA,GAAG,IAAI;AACP,gBAAA,QAAQ,EAAE,KAAK;aAChB;AACH,QAAA,CAAC,CAAC;QAEF,OAAO;AACL,YAAA,KAAK,EAAE,YAAY;YACnB,OAAO;SACR;IACH;AAEA;;;;;AAKG;AACK,IAAA,mBAAmB,CAAC,QAAgB,EAAA;;AAE1C,QAAA,OAAO,QAAQ,CAAC,UAAU,CAAC,MAAM,CAAC,GAAG,QAAQ,GAAG,CAAA,IAAA,EAAO,QAAQ,EAAE;IACnE;AAEQ,IAAA,MAAM,eAAe,GAAA;AAC3B,QAAA,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE;AAC5B,YAAA,MAAM,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE;QAClC;IACF;;;;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ministryofjustice/hmpps-digital-prison-reporting-frontend",
3
- "version": "7.5.0",
3
+ "version": "7.6.0",
4
4
  "description": "The Digital Prison Reporting Frontend contains templates and code to help display data effectively in UI applications.",
5
5
  "main": "./cjs/index.js",
6
6
  "module": "./index.js",
@@ -96,7 +96,7 @@
96
96
  "nunjucks": "^3.2.4",
97
97
  "nunjucks-date": "^1.5.0",
98
98
  "parseurl": "^1.3.3",
99
- "redis": "^5.12.1",
99
+ "redis": "^6.1.0",
100
100
  "sanitize-html": "2.17.5",
101
101
  "superagent": "^10.3.0",
102
102
  "truncate-html": "1.2.2",
@@ -5,5 +5,5 @@
5
5
  /**
6
6
  * See https://playwright.dev/docs/test-configuration.
7
7
  */
8
- declare const _default: import("@playwright/test").PlaywrightTestConfig<{}, {}>;
8
+ declare const _default: import("playwright/test").PlaywrightTestConfig<{}, {}>;
9
9
  export default _default;
@@ -1,12 +1,13 @@
1
+ import type { RedisClientType } from 'redis';
1
2
  import ReportingClient from './reportingClient';
2
3
  import DashboardClient from './dashboardClient';
3
- import ReportDataStore, { RedisClient } from './reportDataStore';
4
+ import ReportDataStore from './reportDataStore';
4
5
  import { ApiConfig, FeatureFlagConfig, MigrationServiceConfig } from './types';
5
6
  import { FeatureFlagService } from '../services/featureFlagService';
6
7
  import ProductCollectionClient from './productCollectionClient';
7
8
  import MissingReportClient from './missingReportClient';
8
9
  import { ReportIdMigrationService } from '../services/reportIdMigrationService';
9
- export declare const initDprReportingClients: (reportingApiConfig: ApiConfig, redisClient: RedisClient, storePrefix?: string, featureFlagConfig?: FeatureFlagConfig, migrationServiceConfig?: MigrationServiceConfig) => {
10
+ export declare const initDprReportingClients: (reportingApiConfig: ApiConfig, redisClient: RedisClientType, storePrefix?: string, featureFlagConfig?: FeatureFlagConfig, migrationServiceConfig?: MigrationServiceConfig) => {
10
11
  reportingClient: ReportingClient;
11
12
  dashboardClient: DashboardClient;
12
13
  reportDataStore: ReportDataStore;
@@ -1,10 +1,11 @@
1
1
  import { createClient } from 'redis';
2
+ import type { RedisClientType } from 'redis';
2
3
  import { ReportStoreConfig } from '../types/ReportStore';
3
4
  export type RedisClient = ReturnType<typeof createClient>;
4
5
  declare class ReportDataStore {
5
6
  private readonly redisClient;
6
7
  prefix: string;
7
- constructor(redisClient: RedisClient, prefix?: string);
8
+ constructor(redisClient: RedisClientType, prefix?: string);
8
9
  private ensureConnected;
9
10
  setUserConfig(userId: string, config: ReportStoreConfig): Promise<void>;
10
11
  getUserConfig(userId: string): Promise<ReportStoreConfig>;
@@ -1,4 +1,4 @@
1
- import { RedisClient } from '../data/reportDataStore';
1
+ import type { RedisClientType } from 'redis';
2
2
  import { MigrationServiceConfig } from '../data/types';
3
3
  /**
4
4
  * One-off migration that updates report IDs stored in Redis
@@ -14,7 +14,7 @@ export declare class ReportIdMigrationService {
14
14
  private static readonly MIGRATION_KEY;
15
15
  private static readonly USER_CONFIG_PREFIX;
16
16
  private static readonly MAX_RETRIES;
17
- constructor(redisClient: RedisClient, migrationServiceConfig?: MigrationServiceConfig | undefined);
17
+ constructor(redisClient: RedisClientType, migrationServiceConfig?: MigrationServiceConfig | undefined);
18
18
  /**
19
19
  * Runs the migration.
20
20
  */
@@ -1,6 +1,7 @@
1
1
  import { Response, Request, NextFunction, Router } from 'express';
2
+ import type { RedisClientType } from 'redis';
2
3
  import { Services } from './Services';
3
- import UserDataStore, { RedisClient } from '../data/reportDataStore';
4
+ import UserDataStore from '../data/reportDataStore';
4
5
  export interface EmbeddedReportUtilsParams {
5
6
  req?: Request;
6
7
  res: Response;
@@ -30,7 +31,7 @@ export interface EmbeddedReportFeatures {
30
31
  }
31
32
  export interface EmbeddedReportFeaturesConfig {
32
33
  userId?: string;
33
- redisClient?: RedisClient;
34
+ redisClient?: RedisClientType;
34
35
  userDataStore?: UserDataStore;
35
36
  }
36
37
  interface ReportingClientArgs {