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

Associate projects to data sets #469

Merged

Conversation

eperedo
Copy link
Contributor

@eperedo eperedo commented Oct 17, 2024

📌 References

📝 Implementation

  • Add tabs to show list for dataSets and projects
  • Script to migrate project information from category options to custom attribute (metadata attached in issue)

📹 Screenshots/Screen capture

Project-Configuration-app_tabs_datasets_projects.webm

🔥 Notes to the tester

Run script:

yarn run migrate-ds-project -u 'http://localhost:8080' --username 'username' --password 'mypassword'

#86960mp8j

@tokland tokland mentioned this pull request Oct 22, 2024
2 tasks
Copy link
Collaborator

@tokland tokland left a 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",
Copy link
Collaborator

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";
Copy link
Collaborator

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.


export type NamedCodeRef = NamedRef & { code: string };

export type MetadataItem = {
Copy link
Collaborator

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

export type NamedCodeRef = NamedRef & { code: string };

export type MetadataItem = {
attributes: { project: NamedCodeRef; createdByApp: NamedCodeRef };
Copy link
Collaborator

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[]> {
Copy link
Collaborator

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>> {
Copy link
Collaborator

Choose a reason for hiding this comment

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

blank line

@eperedo eperedo requested a review from tokland November 4, 2024 02:11
Copy link
Collaborator

@tokland tokland left a 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/D2ApiCategoryOption.ts Show resolved Hide resolved
);

return {
attributes: { ...this.buildAttributes(attributes) },
Copy link
Collaborator

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

(d2Attribute): D2NamedCodeRef => {
return { id: d2Attribute.id, name: d2Attribute.name, code: d2Attribute.code };
}
);
Copy link
Collaborator

@tokland tokland Nov 4, 2024

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?

};
}

private getOrThrow(modelData: D2NamedCodeRef[], code: string): D2NamedCodeRef {
Copy link
Collaborator

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);
Copy link
Collaborator

Choose a reason for hiding this comment

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

same

},
}
: {}),
},
Copy link
Collaborator

@tokland tokland Nov 4, 2024

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({
Copy link
Collaborator

@tokland tokland Nov 4, 2024

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)}----`;
Copy link
Collaborator

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("")

[history]
);

return { navigateTo };
Copy link
Collaborator

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]
);
Copy link
Collaborator

@tokland tokland Nov 4, 2024

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]
);

Copy link
Contributor

@MiquelAdell MiquelAdell left a 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!

@eperedo eperedo requested a review from tokland November 13, 2024 00:13
@eperedo eperedo mentioned this pull request Nov 13, 2024
2 tasks
Copy link
Collaborator

@tokland tokland left a 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" },
Copy link
Collaborator

@tokland tokland Nov 13, 2024

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: {
Copy link
Collaborator

@tokland tokland Nov 13, 2024

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,
Copy link
Collaborator

@tokland tokland Nov 13, 2024

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?

@eperedo eperedo requested a review from tokland November 14, 2024 17:20
@MiquelAdell MiquelAdell merged commit dc05678 into development-project-configurator Nov 18, 2024
1 check passed
@MiquelAdell MiquelAdell deleted the feature/associate-projects-datasets branch November 18, 2024 07:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants