-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
18 changed files
with
523 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
import { D2Api } from "../../types/d2-api"; | ||
import { DataSource, isDhisInstance } from "../../domain/instance/entities/DataSource"; | ||
import { getD2APiFromInstance } from "../../utils/d2-utils"; | ||
import { DataStore, DataStoreKey } from "../../domain/metadata/entities/MetadataEntities"; | ||
import { promiseMap } from "../../utils/common"; | ||
import { DataStoreMetadata } from "../../domain/data-store/DataStoreMetadata"; | ||
|
||
export class D2ApiDataStore { | ||
private api: D2Api; | ||
|
||
constructor(instance: DataSource) { | ||
if (!isDhisInstance(instance)) { | ||
throw new Error("Invalid instance type for MetadataD2ApiRepository"); | ||
} | ||
this.api = getD2APiFromInstance(instance); | ||
} | ||
|
||
async getDataStore(): Promise<DataStore[]> { | ||
const response = await this.api.request<string[]>({ method: "get", url: "/dataStore" }).getData(); | ||
const namespacesWithKeys = await this.getAllKeysFromNamespaces(response); | ||
return namespacesWithKeys; | ||
} | ||
|
||
private async getAllKeysFromNamespaces(namespaces: string[]): Promise<DataStore[]> { | ||
const result = await promiseMap<string, DataStore>(namespaces, async namespace => { | ||
const keys = await this.getKeysPaginated([], namespace); | ||
return { | ||
code: namespace, | ||
displayName: namespace, | ||
externalAccess: false, | ||
favorites: [], | ||
id: `${namespace}${DataStoreMetadata.NS_SEPARATOR}`, | ||
keys: keys, | ||
name: namespace, | ||
translations: [], | ||
}; | ||
}); | ||
return result; | ||
} | ||
|
||
private async getKeysPaginated(keysState: DataStoreKey[], namespace: string): Promise<DataStoreKey[]> { | ||
const keyResponse = await this.getKeysByNameSpace(namespace); | ||
const newKeys = [...keysState, ...keyResponse]; | ||
return newKeys; | ||
} | ||
|
||
private async getKeysByNameSpace(namespace: string): Promise<DataStoreKey[]> { | ||
const response = await this.api | ||
.request<string[]>({ | ||
method: "get", | ||
url: `/dataStore/${namespace}`, | ||
// Since v38 we can use the fields parameter to get keys and values in the same request | ||
// Empty fields returns a paginated response | ||
// https://docs.dhis2.org/en/full/develop/dhis-core-version-240/developer-manual.html#query-api | ||
// params: { fields: "", page: page, pageSize: 200 }, | ||
}) | ||
.getData(); | ||
|
||
return this.buildArrayDataStoreKey(response, namespace); | ||
} | ||
|
||
private buildArrayDataStoreKey(keys: string[], namespace: string): DataStoreKey[] { | ||
return keys.map(key => ({ id: `${namespace}${DataStoreMetadata.NS_SEPARATOR}${key}`, displayName: key })); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
import _ from "lodash"; | ||
import { DataStoreMetadata } from "../../domain/data-store/DataStoreMetadata"; | ||
import { DataStoreMetadataRepository, SaveOptions } from "../../domain/data-store/DataStoreMetadataRepository"; | ||
import { Instance } from "../../domain/instance/entities/Instance"; | ||
import { Stats } from "../../domain/reports/entities/Stats"; | ||
import { SynchronizationResult } from "../../domain/reports/entities/SynchronizationResult"; | ||
import { promiseMap } from "../../utils/common"; | ||
import { StorageDataStoreClient } from "../storage/StorageDataStoreClient"; | ||
|
||
export class DataStoreMetadataD2Repository implements DataStoreMetadataRepository { | ||
private instance: Instance; | ||
|
||
constructor(instance: Instance) { | ||
this.instance = instance; | ||
} | ||
|
||
async get(dataStores: DataStoreMetadata[]): Promise<DataStoreMetadata[]> { | ||
const result = await promiseMap(dataStores, async dataStore => { | ||
const dataStoreClient = new StorageDataStoreClient(this.instance, dataStore.namespace); | ||
const dataStoreWithValue = this.getValuesByDataStore(dataStoreClient, dataStore); | ||
return dataStoreWithValue; | ||
}); | ||
return result; | ||
} | ||
|
||
private async getValuesByDataStore( | ||
dataStoreClient: StorageDataStoreClient, | ||
dataStore: DataStoreMetadata | ||
): Promise<DataStoreMetadata> { | ||
const keys = await this.getAllKeys(dataStoreClient, dataStore); | ||
const keyWithValue = await promiseMap(keys, async key => { | ||
const keyValue = await dataStoreClient.getObject(key.id); | ||
return { id: key.id, value: keyValue }; | ||
}); | ||
const keyInNamespace = _(dataStore.keys).first()?.id; | ||
const sharing = keyInNamespace ? await dataStoreClient.getObjectSharing(keyInNamespace) : undefined; | ||
return new DataStoreMetadata({ | ||
namespace: dataStore.namespace, | ||
keys: keyWithValue, | ||
sharing, | ||
}); | ||
} | ||
|
||
private async getAllKeys( | ||
dataStoreClient: StorageDataStoreClient, | ||
dataStore: DataStoreMetadata | ||
): Promise<DataStoreMetadata["keys"]> { | ||
if (dataStore.keys.length > 0) return dataStore.keys; | ||
const keys = await dataStoreClient.listKeys(); | ||
return keys.map(key => ({ id: key, value: "" })); | ||
} | ||
|
||
async save( | ||
dataStores: DataStoreMetadata[], | ||
options: SaveOptions = { mergeMode: "MERGE" } | ||
): Promise<SynchronizationResult> { | ||
const keysIdsToDelete = await this.getKeysToDelete(dataStores, options); | ||
|
||
const resultStats = await promiseMap(dataStores, async dataStore => { | ||
const dataStoreClient = new StorageDataStoreClient(this.instance, dataStore.namespace); | ||
const stats = await promiseMap(dataStore.keys, async key => { | ||
const exist = await dataStoreClient.getObject(key.id); | ||
await dataStoreClient.saveObject(key.id, key.value); | ||
if (dataStore.sharing) { | ||
await dataStoreClient.saveObjectSharing(key.id, dataStore.sharing); | ||
} | ||
return exist ? Stats.createOrEmpty({ updated: 1 }) : Stats.createOrEmpty({ imported: 1 }); | ||
}); | ||
return stats; | ||
}); | ||
|
||
const deleteStats = await promiseMap(keysIdsToDelete, async keyId => { | ||
const [namespace, key] = keyId.split(DataStoreMetadata.NS_SEPARATOR); | ||
const dataStoreClient = new StorageDataStoreClient(this.instance, namespace); | ||
await dataStoreClient.removeObject(key); | ||
return Stats.createOrEmpty({ deleted: 1 }); | ||
}); | ||
|
||
const allStats = resultStats.flatMap(result => result).concat(deleteStats); | ||
const dataStoreStats = { ...Stats.combine(allStats.map(stat => Stats.create(stat))), type: "DataStore Keys" }; | ||
|
||
const result: SynchronizationResult = { | ||
date: new Date(), | ||
instance: this.instance, | ||
status: "SUCCESS", | ||
type: "metadata", | ||
stats: dataStoreStats, | ||
typeStats: [dataStoreStats], | ||
}; | ||
|
||
return result; | ||
} | ||
|
||
private async getKeysToDelete(dataStores: DataStoreMetadata[], options: SaveOptions) { | ||
if (options.mergeMode === "MERGE") return []; | ||
|
||
const existingRecords = await this.get(dataStores.map(x => ({ ...x, keys: [] }))); | ||
const existingKeysIds = existingRecords.flatMap(dataStore => { | ||
return dataStore.keys.map(key => `${dataStore.namespace}[NS]${key.id}`); | ||
}); | ||
|
||
const keysIdsToSave = dataStores.flatMap(dataStore => { | ||
return dataStore.keys.map(key => `${dataStore.namespace}[NS]${key.id}`); | ||
}); | ||
|
||
const keysIdsToDelete = existingKeysIds.filter(id => !keysIdsToSave.includes(id)); | ||
return keysIdsToDelete; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
/** | ||
* Base class for typical classes with attributes. Features: create, update. | ||
* | ||
* ``` | ||
* class Counter extends Struct<{ id: Id; value: number }>() { | ||
* add(value: number): Counter { | ||
* return this._update({ value: this.value + value }); | ||
* } | ||
* } | ||
* | ||
* const counter1 = Counter.create({ id: "some-counter", value: 1 }); | ||
* const counter2 = counter1._update({ value: 2 }); | ||
* ``` | ||
*/ | ||
|
||
export function Struct<Attrs>() { | ||
abstract class Base { | ||
constructor(_attributes: Attrs) { | ||
Object.assign(this, _attributes); | ||
} | ||
|
||
_getAttributes(): Attrs { | ||
const entries = Object.getOwnPropertyNames(this).map(key => [key, (this as any)[key]]); | ||
return Object.fromEntries(entries) as Attrs; | ||
} | ||
|
||
protected _update(partialAttrs: Partial<Attrs>): this { | ||
const ParentClass = this.constructor as new (values: Attrs) => typeof this; | ||
return new ParentClass({ ...this._getAttributes(), ...partialAttrs }); | ||
} | ||
|
||
static create<U extends Base>(this: new (attrs: Attrs) => U, attrs: Attrs): U { | ||
return new this(attrs); | ||
} | ||
} | ||
|
||
return Base as { | ||
new (values: Attrs): Attrs & Base; | ||
create: typeof Base["create"]; | ||
}; | ||
} | ||
|
||
const GenericStruct = Struct<unknown>(); | ||
|
||
export type GenericStructInstance = InstanceType<typeof GenericStruct>; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.