Skip to content

Commit

Permalink
Merge pull request #27 from EyeSeeTea/feat/contact-emails
Browse files Browse the repository at this point in the history
DQ: Get Contact Emails for Follow-Up issue
  • Loading branch information
Ramon-Jimenez authored Mar 18, 2024
2 parents 8e7e53a + a32e156 commit 1496c53
Show file tree
Hide file tree
Showing 22 changed files with 360 additions and 80 deletions.
9 changes: 7 additions & 2 deletions i18n/en.pot
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ msgstr ""
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
"POT-Creation-Date: 2024-03-15T21:06:40.611Z\n"
"PO-Revision-Date: 2024-03-15T21:06:40.611Z\n"
"POT-Creation-Date: 2024-03-16T16:23:42.597Z\n"
"PO-Revision-Date: 2024-03-16T16:23:42.597Z\n"

msgid "Cannot be blank: {{fieldName}}"
msgstr ""
Expand Down Expand Up @@ -59,6 +59,11 @@ msgstr ""
msgid "Save Config Analysis"
msgstr ""

msgid ""
"No user with Capture rights and Organisation Unit associated to the issue "
"was found"
msgstr ""

msgid "Updating Issue..."
msgstr ""

Expand Down
7 changes: 6 additions & 1 deletion i18n/es.po
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
msgid ""
msgstr ""
"Project-Id-Version: i18next-conv\n"
"POT-Creation-Date: 2024-03-15T21:06:40.611Z\n"
"POT-Creation-Date: 2024-03-16T16:23:42.597Z\n"
"PO-Revision-Date: 2018-10-25T09:02:35.143Z\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
Expand Down Expand Up @@ -59,6 +59,11 @@ msgstr ""
msgid "Save Config Analysis"
msgstr ""

msgid ""
"No user with Capture rights and Organisation Unit associated to the issue "
"was found"
msgstr ""

msgid "Updating Issue..."
msgstr ""

Expand Down
8 changes: 7 additions & 1 deletion src/CompositionRoot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,13 @@ function getCompositionRoot(repositories: Repositories, metadata: MetadataItem)
repositories.settingsRepository
),
},
issues: { save: new SaveIssueUseCase(repositories.qualityAnalysisRepository, metadata) },
issues: {
save: new SaveIssueUseCase(
repositories.qualityAnalysisRepository,
repositories.usersRepository,
metadata
),
},
settings: { get: new GetSettingsUseCase(repositories.settingsRepository) },
nursingMidwifery: {
getDisaggregations: new GetMidwiferyPersonnelDisaggregationsUseCase(
Expand Down
21 changes: 14 additions & 7 deletions src/data/common/D2CategoryOption.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { FutureData, apiToFuture } from "../api-futures";
import { Id } from "../../domain/entities/Ref";
import _ from "../../domain/entities/generic/Collection";
import { CategoryOption } from "../../domain/entities/CategoryOption";
import { Maybe } from "$/utils/ts-utils";

export class D2CategoryOption {
constructor(private api: D2Api) {}
Expand All @@ -21,15 +22,21 @@ export class D2CategoryOption {
})
.map(d2Response => {
return d2Response.data.objects.map(d2CategoryOption => {
return {
id: d2CategoryOption.id,
name:
d2CategoryOption.displayShortName ||
d2CategoryOption.displayFormName ||
d2CategoryOption.displayName,
};
return { id: d2CategoryOption.id, name: this.getName(d2CategoryOption) };
});
})
);
}

private getName(d2Name: D2TranslatioName): string {
const name = d2Name.displayShortName || d2Name.displayFormName || d2Name.displayName;
if (name === "default") return "Total";
return name || "";
}
}

type D2TranslatioName = {
displayShortName: Maybe<string>;
displayFormName: Maybe<string>;
displayName: Maybe<string>;
};
1 change: 1 addition & 0 deletions src/data/common/D2Country.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export class D2OrgUnit {
d2OrgUnit.displayFormName ||
d2OrgUnit.displayName,
path: d2OrgUnit.path,
writeAccess: false,
};
});
});
Expand Down
62 changes: 56 additions & 6 deletions src/data/common/D2User.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,41 @@
import { User } from "../../domain/entities/User";
import { D2Api, MetadataPick } from "../../types/d2-api";
import { UserGroup } from "$/domain/entities/UserGroup";
import { Future } from "$/domain/entities/generic/Future";
import { User } from "$/domain/entities/User";
import { D2Api, MetadataPick } from "$/types/d2-api";
import { apiToFuture, FutureData } from "../api-futures";
import _ from "$/domain/entities/generic/Collection";

export class D2User {
constructor(private api: D2Api) {}

getByIds(ids: string[]): FutureData<User[]> {
const $requests = Future.sequential(
_(ids)
.chunk(50)
.map(usersIds => {
return apiToFuture(
this.api.models.users.get({
fields: userFields,
filter: { id: { in: usersIds } },
paging: false,
})
).map(d2Response => {
return d2Response.objects.map((d2User): User => {
return this.buildUser(d2User);
});
});
})
.value()
);

return Future.sequential([$requests]).flatMap(users => {
const first = _(users).first();
if (!first) return Future.success([]);
const allUsers = _(first).flatten().value();
return Future.success(allUsers);
});
}

getCurrent(): FutureData<User> {
return apiToFuture(this.api.currentUser.get({ fields: userFields })).map(d2User => {
return this.buildUser(d2User);
Expand All @@ -24,11 +55,24 @@ export class D2User {
}

private buildUser(d2User: D2UserEntity) {
const readAccessCountries = d2User.teiSearchOrganisationUnits.map(d2OrgUnit => {
return { ...d2OrgUnit, writeAccess: false };
});
const writeAccessCountries = d2User.organisationUnits.map(d2OrgUnit => {
return { ...d2OrgUnit, writeAccess: true };
});
return new User({
id: d2User.id,
name: d2User.displayName,
userGroups: d2User.userGroups,
countries: d2User.teiSearchOrganisationUnits,
userGroups: d2User.userGroups.map(d2UserGroup => {
return UserGroup.build({
id: d2UserGroup.id,
name: d2UserGroup.name,
usersIds: d2UserGroup.users.map(d2User => d2User.id),
}).get();
}),
countries: [...readAccessCountries, ...writeAccessCountries],
email: d2User.email,
...d2User.userCredentials,
});
}
Expand All @@ -37,9 +81,15 @@ export class D2User {
const userFields = {
id: true,
displayName: true,
userGroups: { id: true, name: true },
userCredentials: { username: true, userRoles: { id: true, name: true, authorities: true } },
email: true,
userGroups: { id: true, name: true, users: true },
userCredentials: {
lastLogin: true,
username: true,
userRoles: { id: true, name: true, authorities: true },
},
teiSearchOrganisationUnits: { id: true, name: true, path: true },
organisationUnits: { id: true, name: true, path: true },
} as const;

type D2UserEntity = MetadataPick<{ users: { fields: typeof userFields } }>["users"][number];
8 changes: 8 additions & 0 deletions src/data/repositories/MetadataD2Repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ const metadataCodes = {
correlative: "NHWA_DQI_Issue_Correlative_Number",
},
dataSets: { module1: "NHWA-M1-2023", module2: "NHWA-M2-2023" },
userGroups: {
dataCaptureModule1: "NHWA _DATA Capture Module 1",
dataCaptureModule2And4: "NHWA _DATA Capture Module 2-4",
},
};

const metadataFields = {
Expand Down Expand Up @@ -75,6 +79,10 @@ const metadataFields = {
},
filter: { code: { in: rec(metadataCodes.programs).values() } },
},
userGroups: {
fields: { id: true, name: true, code: true, users: true },
filter: { name: { in: rec(metadataCodes.userGroups).values() } },
},
};

export class MetadataD2Repository implements MetadataRepository {
Expand Down
14 changes: 9 additions & 5 deletions src/data/repositories/UserD2Repository.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import { User } from "../../domain/entities/User";
import { UserRepository } from "../../domain/repositories/UserRepository";
import { D2Api } from "../../types/d2-api";
import { FutureData } from "../api-futures";
import { D2User } from "../common/D2User";
import { User } from "$/domain/entities/User";
import { UserRepository } from "$/domain/repositories/UserRepository";
import { D2Api } from "$/types/d2-api";
import { FutureData } from "$/data/api-futures";
import { D2User } from "$/data/common/D2User";

export class UserD2Repository implements UserRepository {
private d2User: D2User;
constructor(private api: D2Api) {
this.d2User = new D2User(this.api);
}

getByIds(ids: string[]): FutureData<User[]> {
return this.d2User.getByIds(ids);
}

public getCurrent(): FutureData<User> {
return this.d2User.getCurrent();
}
Expand Down
13 changes: 8 additions & 5 deletions src/data/repositories/UserTestRepository.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { User } from "../../domain/entities/User";
import { createAdminUser } from "../../domain/entities/__tests__/userFixtures";
import { Future } from "../../domain/entities/generic/Future";
import { UserRepository } from "../../domain/repositories/UserRepository";
import { FutureData } from "../api-futures";
import { User } from "$/domain/entities/User";
import { createAdminUser } from "$/domain/entities/__tests__/userFixtures";
import { Future } from "$/domain/entities/generic/Future";
import { UserRepository } from "$/domain/repositories/UserRepository";
import { FutureData } from "$/data/api-futures";

export class UserTestRepository implements UserRepository {
getByIds(): FutureData<User[]> {
throw new Error("Method not implemented.");
}
getByUsernames(_: string[]): FutureData<User[]> {
throw new Error("Method not implemented.");
}
Expand Down
2 changes: 1 addition & 1 deletion src/domain/entities/Country.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import { NamedRef } from "./Ref";

export type Country = NamedRef & { path: string };
export type Country = NamedRef & { path: string; writeAccess: boolean };
6 changes: 5 additions & 1 deletion src/domain/entities/MetadataItem.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Id, NamedCodeRef, NamedRef } from "./Ref";
import { Id, NamedCodeRef, NamedRef, Ref } from "./Ref";

export interface OptionSet extends NamedCodeRef {
options: Array<{ id: Id; name: string; code: string }>;
Expand Down Expand Up @@ -40,4 +40,8 @@ export interface MetadataItem {
correlative: NamedCodeRef;
};
programs: { qualityIssues: NamedRef & { programStages: ProgramStage[] } };
userGroups: {
dataCaptureModule1: NamedCodeRef & { users: Ref[] };
dataCaptureModule2And4: NamedCodeRef & { users: Ref[] };
};
}
8 changes: 6 additions & 2 deletions src/domain/entities/User.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import { Maybe } from "$/utils/ts-utils";
import { Country } from "./Country";
import { Struct } from "./generic/Struct";
import { NamedRef } from "./Ref";
import { DateISOString, NamedRef } from "./Ref";
import { UserGroup } from "./UserGroup";

export interface UserAttrs {
id: string;
email: string;
name: string;
username: string;
lastLogin: Maybe<DateISOString>;
userRoles: UserRole[];
userGroups: NamedRef[];
userGroups: UserGroup[];
countries: Country[];
}

Expand Down
36 changes: 36 additions & 0 deletions src/domain/entities/UserGroup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Id } from "./Ref";
import { Either } from "./generic/Either";
import { ValidationError } from "./generic/Errors";
import { Struct } from "./generic/Struct";
import { validateRequired } from "./generic/validations";

type UserGroupAttrs = {
id: Id;
name: string;
usersIds: Id[];
};

export class UserGroup extends Struct<UserGroupAttrs>() {
static build(attrs: UserGroupAttrs): Either<ValidationError<UserGroup>[], UserGroup> {
const userGroup = new UserGroup(attrs);

const errors: ValidationError<UserGroup>[] = [
{
property: "name" as const,
errors: validateRequired(userGroup.name),
value: userGroup.name,
},
{
property: "id" as const,
errors: validateRequired(userGroup.id),
value: userGroup.id,
},
].filter(validation => validation.errors.length > 0);

if (errors.length === 0) {
return Either.success(userGroup);
} else {
return Either.error(errors);
}
}
}
9 changes: 7 additions & 2 deletions src/domain/entities/__tests__/User.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { describe, expect, it } from "vitest";
import { createAdminUser, createNonAdminUser, createUserWithGroups } from "./userFixtures";
import { UserGroup } from "../UserGroup";

describe("User", () => {
it("should be admin if has a role with authority ALL", () => {
Expand All @@ -15,15 +16,19 @@ describe("User", () => {
it("should return belong to user group equal to false when the id exist", () => {
const userGroupId = "BwyMfDBLih9";

const user = createUserWithGroups([{ id: userGroupId, name: "Group 1" }]);
const user = createUserWithGroups([
UserGroup.build({ id: userGroupId, name: "Group 1", usersIds: [] }).get(),
]);

expect(user.belongToUserGroup(userGroupId)).toBe(true);
});
it("should return belong to user group equal to false when the id does not exist", () => {
const existedUserGroupId = "BwyMfDBLih9";
const nonExistedUserGroupId = "f31IM13BgwJ";

const user = createUserWithGroups([{ id: existedUserGroupId, name: "Group 1" }]);
const user = createUserWithGroups([
UserGroup.build({ id: existedUserGroupId, name: "Group 1", usersIds: [] }).get(),
]);

expect(user.belongToUserGroup(nonExistedUserGroupId)).toBe(false);
});
Expand Down
Loading

0 comments on commit 1496c53

Please sign in to comment.