Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add sharing step #472

Merged
merged 9 commits into from
Jan 13, 2025
73 changes: 73 additions & 0 deletions src/data/repositories/ConfigD2Repository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { apiToFuture } from "$/data/api-futures";
import { metadataCodes } from "$/data/repositories/D2ApiMetadata";
import { Config, UserGroup } from "$/domain/entities/Config";
import { Region } from "$/domain/entities/Region";
import { Future, FutureData } from "$/domain/entities/generic/Future";
import { ConfigRepository } from "$/domain/repositories/ConfigRepository";
import { D2Api } from "$/types/d2-api";

export class ConfigD2Repository implements ConfigRepository {
constructor(private api: D2Api) {}

get(): FutureData<Config> {
return this.getOrgUnitLevelGroup().flatMap(orgUnitLevel => {
return Future.joinObj({
regions: this.getRegions(orgUnitLevel),
userGroups: this.getUserGroups(),
});
});
}

private getOrgUnitLevelGroup(): FutureData<number> {
return apiToFuture(
this.api.models.organisationUnitLevels.get({
fields: { id: true, level: true },
filter: { name: { eq: metadataCodes.orgUnitLevels.country } },
})
).flatMap(d2Response => {
const orgUnitLevel = d2Response.objects[0];
return orgUnitLevel
? Future.success(orgUnitLevel.level)
: Future.error(new Error("Country level not found"));
});
}

private getRegions(level: number): FutureData<Region[]> {
return apiToFuture(
this.api.models.organisationUnits.get({
fields: { id: true, code: true, name: true },
filter: { level: { eq: String(level) }, children: { gt: "0" } },
paging: false,
})
).map(d2Response => {
return d2Response.objects.map(region => ({
id: region.id,
name: region.name,
code: this.extractRegionCode(region.code),
}));
});
}

private getUserGroups(): FutureData<UserGroup[]> {
return apiToFuture(
this.api.models.userGroups.get({
fields: { id: true, name: true },
paging: false,
})
).map(d2Response => {
return d2Response.objects.map(region => ({
Copy link
Collaborator

@tokland tokland Dec 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we want "userGroup" here instead of "region".

id: region.id,
name: region.name,
code: this.extractCode(region.name),
}));
});
}

private extractRegionCode(code: string): string {
return (code.slice(0, 2) || "").toUpperCase();
Copy link
Collaborator

@tokland tokland Dec 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add comment with an example, so the reader knows why are we processing it.

}

private extractCode(code: string): string {
return (code.split("_")[0] || "").toUpperCase();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same

}
}
15 changes: 12 additions & 3 deletions src/data/repositories/D2ApiCategoryOption.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,27 @@ export class D2ApiCategoryOption {
return apiToFuture(
this.api.models.categoryOptions.get({
filter: { id: { in: categoryOptionsIds } },
fields: { id: true, displayName: true, lastUpdated: true },
fields: { id: true, code: true, displayName: true, lastUpdated: true },
paging: false,
})
).map(response => response.objects);
});
}
}

export type D2CategoryOptionType = { id: string; displayName: string; lastUpdated: ISODateString };
export type D2CategoryOptionType = {
code: string;
id: string;
displayName: string;
lastUpdated: ISODateString;
};
export type D2CategoryOptionDates = {
startDate: Maybe<ISODateString>;
endDate: Maybe<ISODateString>;
};

export type D2CategoryOptionWithDates = D2CategoryOptionType & D2CategoryOptionDates;
export type D2CategoryOptionWithDates = D2CategoryOptionType &
D2CategoryOptionDates & {
code: string;
organisationUnits: { id: string; code: string; displayName: string; path: string }[];
};
1 change: 1 addition & 0 deletions src/data/repositories/D2ApiMetadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export const metadataCodes = {
localIndicator: "Local Indicators",
},
indicatorGroupSets: { theme: "Theme", status: "Status" },
orgUnitLevels: { country: "Country" },
};

const metadataFields = {
Expand Down
9 changes: 6 additions & 3 deletions src/data/repositories/DataSetD2Api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { Project } from "$/domain/entities/Project";
import { D2ApiCategoryOption } from "$/data/repositories/D2ApiCategoryOption";
import { D2ApiConfig, D2Config } from "$/data/repositories/D2ApiMetadata";
import { Pager } from "@eyeseetea/d2-api/api";
import { D2OrgUnit } from "$/data/repositories/OrgUnitD2Repository";

export class DataSetD2Api {
private d2ApiCategoryOption: D2ApiCategoryOption;
Expand Down Expand Up @@ -149,6 +150,8 @@ export class DataSetD2Api {
name: categoryOption.displayName,
lastUpdated: categoryOption.lastUpdated,
isOpen: false,
orgsUnits: [],
code: categoryOption.code,
});
});
});
Expand Down Expand Up @@ -207,6 +210,7 @@ export class DataSetD2Api {
orgUnits: d2DataSet.organisationUnits
? d2DataSet.organisationUnits.map((ou): OrgUnit => {
return {
code: ou.code,
id: ou.id,
name: ou.displayName,
path: ou.path.split("/").slice(1),
Expand Down Expand Up @@ -261,7 +265,7 @@ export class DataSetD2Api {
return ccCodeParts.join("_");
}

private buildPermission(permissions: string, permissionType: "data" | "metadata"): Permission {
buildPermission(permissions: string, permissionType: "data" | "metadata"): Permission {
if (permissionType === "metadata") {
const { canRead, canWrite } = this.buildPermissionByType(permissions, permissionType);
return Permission.create({ read: canRead, write: canWrite });
Expand Down Expand Up @@ -314,13 +318,12 @@ export const dataSetFields = {

export const dataSetFieldsWithOrgUnits = {
...dataSetFields,
organisationUnits: { id: true, displayName: true, path: true },
organisationUnits: { id: true, code: true, displayName: true, path: true },
};

type D2DataSetFields = MetadataPick<{
dataSets: { fields: typeof dataSetFields };
}>["dataSets"][number];

type D2DataSet = { organisationUnits?: D2OrgUnit[] } & D2DataSetFields;
type D2OrgUnit = { id: Id; path: string; displayName: string };
export type OctalNotationPermission = string;
27 changes: 23 additions & 4 deletions src/data/repositories/DataSetD2Repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { D2AttributeValue, MetadataPick } from "@eyeseetea/d2-api/2.36";
import { D2Api, MetadataResponse } from "$/types/d2-api";

import { apiToFuture } from "$/data/api-futures";
import { DataSet, DataSetList } from "$/domain/entities/DataSet";
import { AccessData, DataSet, DataSetList } from "$/domain/entities/DataSet";
import { Paginated } from "$/domain/entities/Paginated";
import {
DataSetName,
Expand All @@ -12,7 +12,11 @@ import {
import { Future, FutureData } from "$/domain/entities/generic/Future";
import { getUid } from "$/utils/uid";
import _ from "$/domain/entities/generic/Collection";
import { DataSetD2Api, dataSetFieldsWithOrgUnits } from "$/data/repositories/DataSetD2Api";
import {
DataSetD2Api,
OctalNotationPermission,
dataSetFieldsWithOrgUnits,
} from "$/data/repositories/DataSetD2Api";
import { Maybe } from "$/utils/ts-utils";
import { chunkRequest, runMetadata } from "$/data/utils";
import { D2Config } from "$/data/repositories/D2ApiMetadata";
Expand Down Expand Up @@ -309,8 +313,8 @@ export class DataSetD2Repository implements DataSetRepository {
};
}),
userGroupAccesses: _(dataSet.access)
.compactMap(access => {
if (access.type !== "groups") return undefined;
.filter(access => access.type === "groups")
.map(access => {
Copy link
Collaborator

@tokland tokland Dec 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

declarative (nit-picking): after the filter, it's not just an "access", it's a "groupAccess"

return {
access: this.d2DataSetApi.generateFullPermission(access.permissions),
id: access.id,
Expand All @@ -326,6 +330,20 @@ export class DataSetD2Repository implements DataSetRepository {
};
}

private convertSharingGroupsToAccessData(d2UserGroups: Maybe<SharingUserGroup>): AccessData[] {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this method used?

if (!d2UserGroups || Object.keys(d2UserGroups).length === 0) return [];

return Object.values(d2UserGroups).map(({ id, access }) => ({
id,
permissions: {
data: this.d2DataSetApi.buildPermission(access, "data"),
metadata: this.d2DataSetApi.buildPermission(access, "metadata"),
},
name: "",
type: "groups",
}));
}

private buildDataSetElements(dataSet: DataSetToSave) {
const relatedDataElements = dataSet.indicators
.filter(indicator => indicator.type === "outcomes")
Expand Down Expand Up @@ -398,6 +416,7 @@ type D2DataSetSection = {
indicators: Ref[];
};

type SharingUserGroup = Record<Id, { id: Id; access: OctalNotationPermission }>;
const indicatorTypeLabel: Record<IndicatorAttrs["type"], string> = {
outcomes: "Outcomes",
outputs: "Outputs",
Expand Down
4 changes: 2 additions & 2 deletions src/data/repositories/DataSetTestRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ export class DataSetTestRepository implements DataSetRepository {
throw new Error("Method not implemented.");
}
getByIds(): FutureData<DataSet[]> {
throw new Error("Method not implemented.");
return Future.success([]);
}
save(): FutureData<void> {
throw new Error("Method not implemented.");
return Future.void();
}
delete(): FutureData<void> {
throw new Error("Method not implemented.");
Expand Down
5 changes: 3 additions & 2 deletions src/data/repositories/OrgUnitD2Repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export class OrgUnitD2Repository implements OrgUnitRepository {
const d2OrgsUnits$ = chunkRequest<D2OrgUnit>(ids, idsToFetch => {
return apiToFuture(
this.api.models.organisationUnits.get({
fields: { id: true, displayName: true, path: true },
fields: { id: true, code: true, displayName: true, path: true },
filter: { id: { in: idsToFetch } },
paging: false,
})
Expand All @@ -23,6 +23,7 @@ export class OrgUnitD2Repository implements OrgUnitRepository {
return d2OrgsUnits$.map(d2OrgUnits => {
return d2OrgUnits.map(d2OrgUnit => {
return {
code: d2OrgUnit.code,
id: d2OrgUnit.id,
name: d2OrgUnit.displayName,
path: d2OrgUnit.path.split("/").slice(1),
Expand All @@ -32,4 +33,4 @@ export class OrgUnitD2Repository implements OrgUnitRepository {
}
}

type D2OrgUnit = { id: string; displayName: string; path: string };
export type D2OrgUnit = { id: string; code: string; displayName: string; path: string };
30 changes: 28 additions & 2 deletions src/data/repositories/ProjectD2Repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,13 @@ export class ProjectD2Repository implements ProjectRepository {
return apiToFuture(
this.api.models.categoryOptions.get({
fields: {
code: true,
id: true,
displayName: true,
startDate: true,
endDate: true,
lastUpdated: true,
organisationUnits: { id: true, code: true, displayName: true, path: true },
},
filter: { "categories.code": { eq: code } },
order: "displayName:asc",
Expand All @@ -58,10 +60,17 @@ export class ProjectD2Repository implements ProjectRepository {
return categoryOptions.map(d2CategoryOption => {
return Project.build({
dataSets: [],
code: d2CategoryOption.code,
id: d2CategoryOption.id,
name: d2CategoryOption.displayName,
lastUpdated: d2CategoryOption.lastUpdated,
isOpen: this.isProjectOpen(d2CategoryOption.startDate, d2CategoryOption.endDate),
orgsUnits: d2CategoryOption.organisationUnits.map(orgUnit => ({
id: orgUnit.id,
code: orgUnit.code,
name: orgUnit.displayName,
path: orgUnit.path.split("/").slice(1),
})),
});
});
}
Expand Down Expand Up @@ -109,7 +118,13 @@ export class ProjectD2Repository implements ProjectRepository {
},
page: options.paging.page,
pageSize: options.paging.pageSize,
fields: { id: true, displayName: true, lastUpdated: true },
fields: {
id: true,
code: true,
displayName: true,
lastUpdated: true,
organisationUnits: { id: true, code: true, path: true, displayName: true },
},
order: this.buildOrderParam(options),
})
).flatMap(d2Response => {
Expand Down Expand Up @@ -153,13 +168,22 @@ export class ProjectD2Repository implements ProjectRepository {
});
}

private buildProject(d2CategoryOption: D2CategoryOptionType): Project {
private buildProject(
d2CategoryOption: D2CategoryOptionType & { organisationUnits: D2OrgUnit[] }
): Project {
return Project.build({
code: d2CategoryOption.code,
id: d2CategoryOption.id,
name: d2CategoryOption.displayName,
lastUpdated: d2CategoryOption.lastUpdated,
dataSets: [],
isOpen: false,
orgsUnits: d2CategoryOption.organisationUnits.map(orgUnit => ({
code: orgUnit.code,
id: orgUnit.id,
name: orgUnit.displayName,
path: orgUnit.path.split("/").slice(1),
})),
});
}

Expand All @@ -168,3 +192,5 @@ export class ProjectD2Repository implements ProjectRepository {
return `${options.sorting.field}:${options.sorting.order}`;
}
}

type D2OrgUnit = { id: Id; code: string; displayName: string; path: string };
12 changes: 12 additions & 0 deletions src/data/repositories/RegionD2Repository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Future, FutureData } from "$/domain/entities/generic/Future";
import { Region } from "$/domain/entities/Region";
import { RegionRepository } from "$/domain/repositories/RegionRepository";
import { D2Api } from "$/types/d2-api";

export class RegionD2Repository implements RegionRepository {
constructor(private _api: D2Api) {}

get(): FutureData<Region[]> {
return Future.success([]);
}
}
6 changes: 6 additions & 0 deletions src/domain/entities/Config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { NamedCodeRef } from "$/domain/entities/Ref";
import { Region } from "$/domain/entities/Region";

export type UserGroup = NamedCodeRef;

export type Config = { regions: Region[]; userGroups: UserGroup[] };
Loading
Loading