Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 33 additions & 2 deletions src/compositionRoot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,20 @@ import { SaveMonitoringTwoFactorUseCase } from "./domain/reports/twofactor-monit
import { MonitoringTwoFactorD2Repository } from "./data/reports/twofactor-monitoring/MonitoringTwoFactorD2Repository";
import { GetOrgUnitsWithChildrenUseCase } from "./domain/reports/glass-data-submission/usecases/GetOrgUnitsWithChildrenUseCase";
import { GetAllOrgUnitsByLevelUseCase } from "./domain/common/usecases/GetAllOrgUnitsByLevelUseCase";
import { GetSubscriptionReportUseCase } from "./domain/reports/mal-data-subscription/usecases/GetSubscriptionReportUseCase";
import { DashboardSubscriptionD2Repository } from "./data/reports/mal-data-subscription/DashboardSubscriptionD2Repository";
import { DataElementSubscriptionD2Repository } from "./data/reports/mal-data-subscription/DataElementSubscriptionD2Repository";
import { SubscriptionStatusDatastoreRepository } from "./data/reports/mal-data-subscription/SubscriptionStatusDatastoreRepository";
import { VisualizationD2SubscriptionRepository } from "./data/reports/mal-data-subscription/VisualizationSubscriptionD2Repository";
import { GetSubscriptionFilterOptionsUseCase } from "./domain/reports/mal-data-subscription/usecases/GetSubscriptionFilterOptionsUseCase";
import { GetTableSettingsUseCase } from "./domain/common/usecases/GetTableSettingsUseCase";
import { SaveTableSettingsUseCase } from "./domain/common/usecases/SaveTableSettingsUseCase";
import { TableSettingsDataStoreRepository } from "./data/common/TableSettingsDataStoreRepository";
import { UpdateMonitoringUseCase } from "./domain/reports/mal-data-subscription/usecases/UpdateMonitoringUseCase";
import { MonitoringDataStoreRepository } from "./data/reports/mal-data-subscription/MonitoringDataStoreRepository";

export function getCompositionRoot(api: D2Api) {
const tableSettingsRepository = new TableSettingsDataStoreRepository(api);
const configRepository = new Dhis2ConfigRepository(api, getReportType());
const csyAuditEmergencyRepository = new CSYAuditEmergencyD2Repository(api);
const csyAuditTraumaRepository = new CSYAuditTraumaD2Repository(api);
Expand All @@ -130,7 +142,17 @@ export function getCompositionRoot(api: D2Api) {
const authoritiesMonitoringRepository = new AuthoritiesMonitoringDefaultRepository(api);
const monitoringTwoFactorD2Repository = new MonitoringTwoFactorD2Repository(api);

const dashboardSubscriptionRepository = new DashboardSubscriptionD2Repository(api);
const dataElementSubscriptionRepository = new DataElementSubscriptionD2Repository(api);
const subscriptionStatusRepository = new SubscriptionStatusDatastoreRepository(api);
const visualizationSubscriptionRepository = new VisualizationD2SubscriptionRepository(api);
const monitoringRepository = new MonitoringDataStoreRepository(api);

return {
tableSettings: getExecute({
get: new GetTableSettingsUseCase(tableSettingsRepository),
save: new SaveTableSettingsUseCase(tableSettingsRepository),
}),
admin: getExecute({
get: new GetWIDPAdminDefaultUseCase(widpAdminDefaultRepository),
save: new SaveWIDPAdminDefaultCsvUseCase(widpAdminDefaultRepository),
Expand Down Expand Up @@ -168,12 +190,21 @@ export function getCompositionRoot(api: D2Api) {
get: new GetMalDataElementsSubscriptionUseCase(dataSubscriptionRepository),
getDashboardDataElements: new GetMalDashboardsSubscriptionUseCase(dataSubscriptionRepository),
getMonitoringDetails: new GetMonitoringDetailsUseCase(dataSubscriptionRepository),
getMonitoring: new GetSubscriptionMonitoringUseCase(dataSubscriptionRepository),
saveMonitoring: new SaveSubscriptionMonitoringUseCase(dataSubscriptionRepository),
getColumns: new GetMalDataSubscriptionColumnsUseCase(dataSubscriptionRepository),
saveColumns: new SaveMalDataSubscriptionColumnsUseCase(dataSubscriptionRepository),
getSubscription: new GetSubscriptionUseCase(dataSubscriptionRepository),
saveSubscription: new SaveSubscriptionUseCase(dataSubscriptionRepository),
getMonitoring: new GetSubscriptionMonitoringUseCase(dataSubscriptionRepository),
saveMonitoring: new SaveSubscriptionMonitoringUseCase(dataSubscriptionRepository),

getSubscriptionReport: new GetSubscriptionReportUseCase({
dashboardSubscriptionRepository: dashboardSubscriptionRepository,
dataElementSubscriptionRepository: dataElementSubscriptionRepository,
subscriptionStatusRepository: subscriptionStatusRepository,
visualizationSubscriptionRepository: visualizationSubscriptionRepository,
}),
getSubscriptionFilterOptions: new GetSubscriptionFilterOptionsUseCase(dataElementSubscriptionRepository),
updateMonitoring: new UpdateMonitoringUseCase(dataElementSubscriptionRepository, monitoringRepository),
}),
auditEmergency: getExecute({
get: new GetAuditEmergencyUseCase(csyAuditEmergencyRepository),
Expand Down
30 changes: 30 additions & 0 deletions src/data/common/TableSettingsDataStoreRepository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { D2Api } from "../../types/d2-api";
import { TableSettingsRepository } from "../../domain/common/repositories/TableSettingsRepository";
import { StorageClient } from "./clients/storage/StorageClient";
import { Instance } from "./entities/Instance";
import { DataStoreStorageClient } from "./clients/storage/DataStoreStorageClient";
import { TableSettings } from "../../domain/common/entities/TableSettings";

export class TableSettingsDataStoreRepository implements TableSettingsRepository {
private storageClient: StorageClient;

constructor(private api: D2Api) {
const instance = Instance.fromInstance(api);
this.storageClient = new DataStoreStorageClient("user", instance);
}

async get<T>(name: string): Promise<TableSettings<T>> {
const visibleColumns = (await this.storageClient.getObject<Array<keyof T>>(name)) ?? [];

return {
visibleColumns: visibleColumns,
name: name,
};
}

async save<T>(settings: TableSettings<T>): Promise<void> {
const { name, visibleColumns } = settings;

return this.storageClient.saveObject<Array<keyof T>>(name, visibleColumns);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { D2Api, MetadataPick } from "../../../types/d2-api";
import { DashboardSubscription } from "../../../domain/reports/mal-data-subscription/entities/DashboardSubscription";
import {
DashboardSubscriptionOptions,
DashboardSubscriptionRepository,
} from "../../../domain/reports/mal-data-subscription/repositories/DashboardSubscriptionRepository";
import { D2DataElement, dataElementFields } from "./DataElementSubscriptionD2Repository";
import { getDataElementsInDashboard } from "./utils/DataSubscriptionMapper";
import { visualizationFields } from "./VisualizationSubscriptionD2Repository";

export class DashboardSubscriptionD2Repository implements DashboardSubscriptionRepository {
constructor(private api: D2Api) {}

async get(options: DashboardSubscriptionOptions): Promise<DashboardSubscription[]> {
const dashboards = await this.getD2Dashboards(options);
const dataElements = await this.getD2DataElements(options);

return dashboards.map(dashboard =>
getDataElementsInDashboard({ type: "dashboards", entity: dashboard, dataElements: dataElements })
);
}

private async getD2Dashboards(options: DashboardSubscriptionOptions): Promise<D2Dashboard[]> {
const { paging } = options;
const { objects: d2Dashboards } = await this.api.models.dashboards
.get({
fields: dashboardFields,
page: paging.page,
pageSize: paging.pageSize,
})
.getData();

return d2Dashboards;
}

private async getD2DataElements(options: DashboardSubscriptionOptions): Promise<D2DataElement[]> {
const { paging } = options;

const { objects: dataElements } = await this.api.models.dataElements
.get({
fields: dataElementFields,
page: paging.page,
pageSize: paging.pageSize,
})
.getData();

return dataElements;
}
}

const dashboardFields = {
id: true,
name: true,
dashboardItems: {
visualization: visualizationFields,
},
} as const;

export type D2Dashboard = MetadataPick<{
dashboards: { fields: typeof dashboardFields };
}>["dashboards"][number];

export type D2DataDimensionItem = {
indicator: {
numerator: string;
denominator: string;
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { D2Api, MetadataPick } from "../../../types/d2-api";
import { DataElementSubscription } from "../../../domain/reports/mal-data-subscription/entities/DataElementSubscription";
import {
DataElementSubscriptionOptions,
DataElementSubscriptionRepository,
} from "../../../domain/reports/mal-data-subscription/repositories/DataElementSubscriptionRepository";
import _ from "lodash";

export class DataElementSubscriptionD2Repository implements DataElementSubscriptionRepository {
constructor(private api: D2Api) {}

async get(options: DataElementSubscriptionOptions): Promise<DataElementSubscription[]> {
const filter = this.getDataElementFilter(options);
const d2DataElements = await this.getD2DataElements(options, filter);

return this.mapD2DataElementsToSubscription(d2DataElements);
}

private getDataElementFilter(options: DataElementSubscriptionOptions): Filter {
const { dataElementGroups, sections } = options.filterOptions;

return {
name: {
ilike: "apvd",
},
"dataElementGroups.id": {
in: dataElementGroups,
},
"dataSetElements.dataSet.sections.id": {
in: sections,
},
};
}

async getAll(): Promise<DataElementSubscription[]> {
const { objects: d2DataElements } = await this.api.models.dataElements
.get({
fields: dataElementFields,
paging: false,
})
.getData();

return this.mapD2DataElementsToSubscription(d2DataElements);
}

private mapD2DataElementsToSubscription(dataElements: D2DataElement[]): DataElementSubscription[] {
return dataElements.map(dataElement => {
const section = dataElement.dataSetElements
.flatMap(dataSetElement => dataSetElement.dataSet.sections)
.find(section => _.some(section.dataElements, { id: dataElement.id }));

return {
dataElementId: dataElement.id,
dataElementName: dataElement.name,
dataElementCode: dataElement.code,
dataSetName: dataElement.dataSetElements[0]?.dataSet.name ?? "",
dataElementGroups: dataElement.dataElementGroups,
section: section,
};
});
}

private async getD2DataElements(
options: DataElementSubscriptionOptions,
filter?: Filter
): Promise<D2DataElement[]> {
const { paging } = options;

const { objects: dataElements } = await this.api.models.dataElements
.get({
filter: filter,
fields: dataElementFields,
page: paging.page,
pageSize: paging.pageSize,
})
.getData();

return dataElements;
}
}

export const dataElementFields = {
id: true,
name: true,
code: true,
dataElementGroups: {
id: true,
name: true,
},
dataSetElements: {
dataSet: {
id: true,
name: true,
sections: {
id: true,
name: true,
dataElements: {
id: true,
},
},
},
},
} as const;

export type D2DataElement = MetadataPick<{
dataElements: { fields: typeof dataElementFields };
}>["dataElements"][number];

type Filter = Record<string, object>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { D2Api } from "../../../types/d2-api";
import { StorageClient } from "../../common/clients/storage/StorageClient";
import { Instance } from "../../common/entities/Instance";
import { DataStoreStorageClient } from "../../common/clients/storage/DataStoreStorageClient";
import { Namespaces } from "../../common/clients/storage/Namespaces";
import { MonitoringRepository } from "../../../domain/reports/mal-data-subscription/repositories/MonitoringRepository";
import { Monitoring } from "../../../domain/reports/mal-data-subscription/entities/Monitoring";

export class MonitoringDataStoreRepository implements MonitoringRepository {
private globalStorageClient: StorageClient;
private namespace: string = Namespaces.MONITORING;

constructor(private api: D2Api) {
const instance = new Instance({ url: this.api.baseUrl });
this.globalStorageClient = new DataStoreStorageClient("global", instance);
}

async get(): Promise<Monitoring> {
const monitoring = await this.globalStorageClient.getObject<Monitoring>(this.namespace);

return monitoring ?? { dataElements: [] };
}

async save(monitoring: Monitoring): Promise<void> {
return await this.globalStorageClient.saveObject<Monitoring>(this.namespace, monitoring);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { D2Api } from "../../../types/d2-api";
import { SubscriptionStatusRepository } from "../../../domain/reports/mal-data-subscription/repositories/SubscriptionStatusRepository";
import { StorageClient } from "../../common/clients/storage/StorageClient";
import { Instance } from "../../common/entities/Instance";
import { DataStoreStorageClient } from "../../common/clients/storage/DataStoreStorageClient";
import { SubscriptionStatus } from "../../../domain/reports/mal-data-subscription/entities/SubscriptionStatus";
import { Namespaces } from "../../common/clients/storage/Namespaces";

export class SubscriptionStatusDatastoreRepository implements SubscriptionStatusRepository {
private globalStorageClient: StorageClient;
private namespace: string = Namespaces.MAL_SUBSCRIPTION_STATUS;

constructor(private api: D2Api) {
const instance = new Instance({ url: this.api.baseUrl });
this.globalStorageClient = new DataStoreStorageClient("global", instance);
}

async get(): Promise<SubscriptionStatus[]> {
const subscription = await this.globalStorageClient.getObject<SubscriptionStatus[]>(this.namespace);

return subscription ?? [];
}

async save(subscription: SubscriptionStatus[]): Promise<void> {
return await this.globalStorageClient.saveObject<SubscriptionStatus[]>(this.namespace, subscription);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { D2Api } from "../../../types/d2-api";
import { D2Dashboard } from "./DashboardSubscriptionD2Repository";
import {
VisualizationSubscriptionOptions,
VisualizationSubscriptionRepository,
} from "../../../domain/reports/mal-data-subscription/repositories/VisualizationSubscriptionRepository";
import { VisualizationSubscription } from "../../../domain/reports/mal-data-subscription/entities/DashboardSubscription";
import { D2DataElement, dataElementFields } from "./DataElementSubscriptionD2Repository";
import { getDataElementsInVisualization } from "./utils/DataSubscriptionMapper";

export class VisualizationD2SubscriptionRepository implements VisualizationSubscriptionRepository {
constructor(private api: D2Api) {}

async get(options: VisualizationSubscriptionOptions): Promise<VisualizationSubscription[]> {
const visualizations = await this.getD2Visualizations(options);
const dataElements = await this.getD2DataElements(options);

return visualizations.map(visualization =>
getDataElementsInVisualization({
type: "visualizations",
entity: visualization,
dataElements: dataElements,
})
);
}

private async getD2Visualizations(options: VisualizationSubscriptionOptions): Promise<D2Visualization[]> {
const { paging } = options;
const { objects: d2Visualizations } = await this.api.models.visualizations
.get({
fields: visualizationFields,
page: paging.page,
pageSize: paging.pageSize,
})
.getData();

return d2Visualizations;
}

private async getD2DataElements(options: VisualizationSubscriptionOptions): Promise<D2DataElement[]> {
const { paging } = options;

const { objects: dataElements } = await this.api.models.dataElements
.get({
fields: dataElementFields,
page: paging.page,
pageSize: paging.pageSize,
})
.getData();

return dataElements;
}
}

export const visualizationFields = {
id: true,
name: true,
dataDimensionItems: {
indicator: {
numerator: true,
denominator: true,
},
},
} as const;

export type D2Visualization = D2Dashboard["dashboardItems"][number]["visualization"];
Loading