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

OCLOMRS-967: Pick concepts from a dictionary (in addition to sources) #712

Merged
merged 25 commits into from
Jul 23, 2021

Conversation

jwnasambu
Copy link
Contributor

JIRA TICKET NAME:

Pick concepts from a dictionary (in addition to sources)

Summary:

I worked on a feature that allows a user to create a dictionary that contains concepts from another dictionary.

@coveralls
Copy link

coveralls commented Jun 30, 2021

Coverage Status

Coverage increased (+0.2%) to 46.72% when pulling 65f8860 on jwnasambu:OCLOMRS-967 into 02c0d02 on openmrs:master.

@jwnasambu jwnasambu marked this pull request as draft July 1, 2021 13:59
Copy link
Member

@ibacher ibacher 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 added quite a few comments, including one that should fix the build error.

Comment on lines 46 to 58
underline: {
"&&&:before": {
borderBottom: "none"
},
"&&:after": {
borderBottom: "none"
}
},
Copy link
Member

Choose a reason for hiding this comment

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

I assume this should be restored as well?

Comment on lines 53 to 62
dictionaryIcon:{
marginRight: "0.2rem",
fill: "#8080809c"
Copy link
Member

Choose a reason for hiding this comment

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

We don't really need a separate dictionaryIcon class... This can be shared with sourceIcon (at least until we develop a need to style the icons differently)

@@ -36,24 +38,21 @@ const useStyles = makeStyles((theme: Theme) =>
},
textField: {
padding: "0.2rem 1rem",
cursor: "none"
cursor: "none",
width: "100%"
Copy link
Member

Choose a reason for hiding this comment

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

Why this change?

Comment on lines 397 to 398
sourceUrl,
false
Copy link
Member

Choose a reason for hiding this comment

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

This is in the wrong order. sourceUrl should be the last argument.

Copy link
Member

Choose a reason for hiding this comment

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

Also, we should do away with this copy of recursivelyFetchToConcepts() since we're using that below, right?

src/apps/dictionaries/redux/actions.ts Show resolved Hide resolved
);

const groupedConcepts = groupBy(concepts.sourceUrl),
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
const groupedConcepts = groupBy(concepts.sourceUrl),
const groupedConcepts = groupBy(concepts.sourceUrl);

);

const groupedConcepts = groupBy(concepts.sourceUrl),
const referencesToAdd = Object.entries(groupedConcepts).map(([source, concepts]) => recursivelyFetchToConcepts(
concepts.sourceUrl,
Copy link
Member

Choose a reason for hiding this comment

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

The sourceUrl should be the source argument. Remember, however, that the sourceUrl now needs to be the last argument to this, if we're re-arranging the argument order for recursivelyFetchToConcepts()

@@ -78,7 +78,7 @@ describe("action", () => {
expect(indexedAction).toHaveBeenCalledWith("sources/retrieveSources", 1);
});

it("for public sources should use action index as 2 along with the corresponding actionType and api", () => {
it("for All Public Concepts should use action index as 2 along with the corresponding actionType and api", () => {
Copy link
Member

Choose a reason for hiding this comment

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

Don't replace this text... We only need things to read "All Public Concepts" in the UI or where we're testing something about the UI.

@@ -1,9 +1,9 @@
import { getSourceTypeFromPreviousPath } from "../utils";

describe("getSourceTypeFromPreviousPath", () => {
it("should return public sources for /sources/", () => {
it("should returnAll Public Concepts for /sources/", () => {
Copy link
Member

Choose a reason for hiding this comment

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

Ditto on not needing "All Public Concepts" here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@ibacher thanks for the review. Let me fix the proposed changes.

@hadijahkyampeire
Copy link
Collaborator

@jwnasambu why do we have two PRS for this work, can you please close one and maintain one?

@jwnasambu
Copy link
Contributor Author

@hadijahkyampeire sorry for the confusion caused! After shifting to a new pr I forgot to close the later. Hopefully its fixed now.

@hadijahkyampeire
Copy link
Collaborator

@jwnasambu we can still see two PRs for this work, please close one that you are not working on and maintain one which we can review.

Copy link
Member

@ibacher ibacher left a comment

Choose a reason for hiding this comment

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

@jwnasambu I've added some comments on a few points here. I'm not entirely sure this is all the changes necessary, but please try them out an let me know.

{!showAddConcepts ? null : (
{showAddConcepts ? null : (
Copy link
Member

Choose a reason for hiding this comment

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

I see what you're doing here, but this should actually still be:

{!showAddConcepts ? null : (

Because it's still possible to be viewing concepts without a dictionary to add them to

Comment on lines 393 to 412
// const referencesToAdd = await recursivelyFetchToConcepts(
// concepts.map(concept => concept.id),
// updateProgress,
// false,
// sourceUrl
// );

const referencesToAdd = await recursivelyFetchToConcepts(
'/sources/CIEL/',
concepts.map(concept => concept.id),
updateProgress
);

const groupedConcepts = groupBy(concepts, "source");
console.log(groupedConcepts)
// const referencesToAdd = await Object.entries(groupedConcepts).map(([source, concepts]) => recursivelyFetchToConcepts(
// source,
// concepts.map((concept: { id: any; }) => concept.id),
// updateProgress,1,
// ));
Copy link
Member

Choose a reason for hiding this comment

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

Alright so this is very confusing, but here's what I think the implementation should look like:

    const groupedConcepts = groupBy(concepts, "source_url");
    const referencesToAdd = flatten(
      await Promise.all(
        Object.entries(groupedConcepts).map(([source, concepts]) =>
          recursivelyFetchToConcepts(
            source,
            concepts.map((concept) => concept.id),
            updateProgress
          )
        )
      )
    );

The trick here is that by calling map() we're building an array of... whatever the return value is. In this case, it's the return value of the call to recursivelyFetchToConcepts() which actually returns a Promise<string[]> (a Promise to return an array of strings at a later point).

In the currently working version, the await before the recursivelyFetchToConcepts() waits until the call to recursivelyFetchToConcepts() returns. Since we're returning an array of promises, (one for each call to recursivelyFetchToConcepts(), i.e., one for each source), we need to wait until all those promises are finished. Promise.all() allows us to do this, turning an array of Promises into a promise that resolves with an array of results. We can safely await that.

Finally, I used lodash's flatten function to transform the return time from string[][] (an array of arrays of strings) to a single array, that should look much like the one that existed before all these changes.

dictionaryUrl: string,
rawConcepts: (APIConcept | string)[],
bulk: boolean = false
bulk: boolean = false,
sourceUrl?: string
) => {
return async (dispatch: Function, getState: Function) => {
const concepts = rawConcepts.map(concept =>
Copy link
Member

Choose a reason for hiding this comment

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

To address my previous concern, this should look like this:

    if (!!!sourceUrl && !!rawConcepts.find((c) => typeof c === "string")) {
      throw {
        message: "Cannot load string-only concepts without a source url",
      };
    }

    const concepts = rawConcepts.map((concept) =>
      typeof concept === "string"
        ? {
            id: concept,
            url: `${sourceUrl}concepts/${concept}/`,
            source_url: sourceUrl,
          }
        : concept
    );

Note that this throws an error if rawConcepts contains any strings an no sourceUrl was provided. It also adds the source_url property to our concepts so we can use this in the groupBy below.

src/apps/dictionaries/redux/actions.ts Show resolved Hide resolved
Comment on lines 486 to 492
function getState(): import("../../../redux").AppState {
throw new Error("Function not implemented.");
}

function dispatch(arg0: { type: string; actionIndex: number; meta: any[]; }) {
throw new Error("Function not implemented.");
}
Copy link
Member

Choose a reason for hiding this comment

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

Remove these... see my note above.

@@ -70,7 +70,7 @@ export const EnhancedTableToolbar = (props: EnhancedTableToolbarProps) => {
)}
{numSelected > 0 ? (
<>
{showAddConcepts ? null : (
{!showAddConcepts ? null : (
Copy link
Member

Choose a reason for hiding this comment

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

This line should read:

{showAddConcepts ? null : (

We need to change the conditions under which showAddConcepts is true, not just negate it (if we keep this in, this will show the add concepts part where we don't want to do that, i.e., where the user didn't start from their own dictionary).

}
: concept
);
return async (dispatch: Function, getState: function) => {
Copy link
Member

Choose a reason for hiding this comment

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

This is the small typo it's complaining about:

Suggested change
return async (dispatch: Function, getState: function) => {
return async (dispatch: Function, getState: Function) => {

function is a keyword Function is a type, if that makes any sense an here (after the :) this is serving as a type annotation.

@jwnasambu jwnasambu marked this pull request as ready for review July 21, 2021 20:07
Copy link
Member

@ibacher ibacher left a comment

Choose a reason for hiding this comment

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

LGTM! Thanks @jwnasambu!

@ibacher ibacher changed the title OCLOMRS-967:Pick concepts from a dictionary (in addition to sources) OCLOMRS-967: Pick concepts from a dictionary (in addition to sources) Jul 23, 2021
@ibacher ibacher merged commit 6cd0198 into openmrs:master Jul 23, 2021
@ibacher ibacher deleted the OCLOMRS-967 branch July 23, 2021 13:15
@jwnasambu
Copy link
Contributor Author

@ibacher thanks for your guidance you gave me a glimpse on how to work on a high level ticket and think out of the box.

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.

6 participants