-
Notifications
You must be signed in to change notification settings - Fork 1
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
Associate projects to data sets #469
Associate projects to data sets #469
Conversation
…ion into feat/skeleton-app
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've tried to review only new things, in the future I should be more careful in the order of review so we don't have PRs with common code, which makes review harder.
@@ -28,6 +28,8 @@ | |||
"d2-manifest": "1.0.0", | |||
"eslint-plugin-no-relative-import-paths": "^1.5.3", | |||
"font-awesome": "4.7.0", | |||
"lodash.keyby": "4.6.0", | |||
"lodash.mapvalues": "4.6.0", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
est-lodash cannot do that?
@@ -0,0 +1,6 @@ | |||
import { MetadataItem } from "$/domain/entities/MetadataItem"; | |||
import { FutureData } from "$/data/api-futures"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Move FutureData to the domain, we cannot reference the data layer from here.
src/domain/entities/MetadataItem.ts
Outdated
|
||
export type NamedCodeRef = NamedRef & { code: string }; | ||
|
||
export type MetadataItem = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That names is too generic, from the name we cannot imagine what it contains
src/domain/entities/MetadataItem.ts
Outdated
export type NamedCodeRef = NamedRef & { code: string }; | ||
|
||
export type MetadataItem = { | ||
attributes: { project: NamedCodeRef; createdByApp: NamedCodeRef }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd say "attributes" is a implementation detail, so it should appear referenced in the domain layer.
this.d2DataSetApi = new DataSetD2Api(this.api, metadata); | ||
} | ||
|
||
getAll(): FutureData<Project[]> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As a general rule, be careful with getAll methods that return an array, it's unbounded. There are solutions with Generators, but it's complex. Not saying it has to be changed here, but so we are aware of this.
|
||
export class GetProjectsUseCase { | ||
constructor(private projectRepository: ProjectRepository) {} | ||
execute(options: GetDataSetOptions): FutureData<Paginated<Project>> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
blank line
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very close, just some details:
src/data/repositories/D2ApiConfig.ts
Outdated
); | ||
|
||
return { | ||
attributes: { ...this.buildAttributes(attributes) }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to pack/unpack
src/data/repositories/D2ApiConfig.ts
Outdated
(d2Attribute): D2NamedCodeRef => { | ||
return { id: d2Attribute.id, name: d2Attribute.name, code: d2Attribute.code }; | ||
} | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The 3 blocks are not changing anything, right?
src/data/repositories/D2ApiConfig.ts
Outdated
}; | ||
} | ||
|
||
private getOrThrow(modelData: D2NamedCodeRef[], code: string): D2NamedCodeRef { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[subjective] Generic functions that do not use anything from the instance can go to the root level.
}); | ||
|
||
return $requests.map(d2Response => { | ||
const allDataSets = d2Response.flatMap(d2Response => d2Response.objects); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same
}, | ||
} | ||
: {}), | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Conditional fields will bring problems. If you check the response type, organisationUnits won't be nice to work with. It can be done with generics, but typically is better to keep it simple.
As a rule of thumb: If we need a method that does not get orgunits, then we have a separate method and a separate dataSet type (without orgUnits). Setting empty fields is a code smell.
): DataSet { | ||
const projectAttributeId = d2DataSet.attributeValues.find( | ||
attribute => attribute.attribute.id === this.metadata.attributes.project.id | ||
attribute => attribute.attribute.id === attributes.project.id | ||
)?.value; | ||
const projectDetails = projects.find(project => project.id === projectAttributeId); | ||
const dataElementGroups = this.buildDataElementsGroupsCodes(d2DataSet); | ||
return DataSet.create({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd add a blank before the return to let the code breath
generateFullPermission(permissions: Permissions): OctalNotationPermission { | ||
return `${this.convertPermissionToOctal( | ||
permissions.metadata | ||
)}${this.convertPermissionToOctal(permissions.data)}----`; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this will be more clear: [a,b,c].join("")
src/webapp/routes.ts
Outdated
[history] | ||
); | ||
|
||
return { navigateTo }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not important, but we plan to add more methods here? if we are sure we are not, then return the plain object.
history.push(path); | ||
}, | ||
[history] | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice, very close. There is only an edge case, when you try to use it like this: navigateTo("editDataSets")
, right? TS won't warn about the missing params. In this scenario, the solution is using ...args
somehow. I think that covers all cases (confirm):
const navigateTo = React.useCallback(
<Key extends RouteKey>(route: Key, ...args: Parameters<(typeof routes)[Key]>) => {
const path = (routes[route] as any)(...args);
history.push(path);
},
[history]
);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tested against gorstraining and it works
thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In-line:
"attributeValues.attribute.id": { in: attributesToFilter }, | ||
"attributeValues.value": options.filters.projectsIds | ||
? { in: [...this.getOrThrow(options.filters.projectsIds)] } | ||
: { eq: "true" }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks confusing to me. We have a filter id/value (that's a problem with DHIS2, as we already saw), and the filter is used to both filter by projectIds and something boolean (I guess, createdByApp). Not sure the best approach, but I think this needs some refactoring, first to be more declarative (join the key/value that you want to filter, in the options), and second how to manage the DHIS2 part.
identifiable: { token: options.filters.search }, | ||
id: { in: options.filters.ids }, | ||
}, | ||
fields: { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
a lot of repeated code with the previous method, we can abstract it (if it's conceptually the same)
paging: { page, pageSize }, | ||
filters: {}, | ||
sorting: { field: "name", order: "asc" }, | ||
includeOrgUnits: true, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
type D2DataSetOptions = GetDataSetOptions & { includeOrgUnits?: boolean };
is includeOrgUnits
used, after the refactor?
📌 References
📝 Implementation
📹 Screenshots/Screen capture
Project-Configuration-app_tabs_datasets_projects.webm
🔥 Notes to the tester
Run script:
#86960mp8j