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

feat: refined ux update a taxonomy by downloading and uploading [FC-0036] #732

Conversation

rpenido
Copy link
Contributor

@rpenido rpenido commented Dec 6, 2023

Description

This PR improves the import tags functionality for existing taxonomies implemented at #675.

Supporting Information

Testing instructions

Re-import tags to an existing taxonomy

  1. Have an import taxonomy template ready to be imported. You could get one from below:
  2. Go to the taxonomy list page (http://localhost:2001/taxonomies)
  3. Click on the Re-import menu from an existing taxonomy card
  4. You will be prompted with the first page, where you can export the current taxonomy in CVS or JSON format. Check if both buttons export the taxonomy in the desired format:
    image
  5. Click on Next
  6. Will you have to select a CSV or JSON format file to import. You could do this clicking on the component or using drag and drop. Only .csv and .json files should be accepted.
    image
  7. Click on Import to go to the next step.
  8. This page will show the preview actions for this import. Check it and click on Continue. If the file shows no differences, the Continue button will be disabled.
    image
  9. A new confirmation windows will be show. Clicking on Yes, import file should import the tags, replacing the old ones.
    image
  10. The Tag List should be automatically updated.

Private-ref:

@openedx-webhooks
Copy link

openedx-webhooks commented Dec 6, 2023

Thanks for the pull request, @rpenido! Please note that it may take us up to several weeks or months to complete a review and merge your PR.

Feel free to add as much of the following information to the ticket as you can:

  • supporting documentation
  • Open edX discussion forum threads
  • timeline information ("this must be merged by XX date", and why that is)
  • partner information ("this is a course on edx.org")
  • any other information that can help Product understand the context for the PR

All technical communication about the code itself will be done via the GitHub pull request interface. As a reminder, our process documentation is here.

Please let us know once your PR is ready for our review and all tests are green.

@openedx-webhooks openedx-webhooks added the open-source-contribution PR author is not from Axim or 2U label Dec 6, 2023
@rpenido rpenido force-pushed the rpenido/fal-3539-refined-ux-update-a-taxonomy-by-downloading-and-uploading branch 4 times, most recently from 2a56eab to 1f52c40 Compare December 7, 2023 00:06
@rpenido rpenido force-pushed the rpenido/fal-3539-refined-ux-update-a-taxonomy-by-downloading-and-uploading branch from 1f52c40 to 4c22a0a Compare December 7, 2023 11:35
Copy link
Contributor

@xitij2000 xitij2000 left a comment

Choose a reason for hiding this comment

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

Looks good! I need to test this, but I have a few nits till I reset my devstack and test this.

src/utils.js Outdated
Comment on lines 259 to 277

export const getFileSizeToClosestByte = (fileSize, numberOfDivides = 0) => {
if (fileSize > 1000) {
const updatedSize = fileSize / 1000;
const incrementNumberOfDivides = numberOfDivides + 1;
return getFileSizeToClosestByte(updatedSize, incrementNumberOfDivides);
}
const fileSizeFixedDecimal = Number.parseFloat(fileSize).toFixed(2);
switch (numberOfDivides) {
case 1:
return `${fileSizeFixedDecimal} KB`;
case 2:
return `${fileSizeFixedDecimal} MB`;
case 3:
return `${fileSizeFixedDecimal} GB`;
default:
return `${fileSizeFixedDecimal} B`;
}
};
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this function is overly complicated for what it's doing.

Suggested change
export const getFileSizeToClosestByte = (fileSize, numberOfDivides = 0) => {
if (fileSize > 1000) {
const updatedSize = fileSize / 1000;
const incrementNumberOfDivides = numberOfDivides + 1;
return getFileSizeToClosestByte(updatedSize, incrementNumberOfDivides);
}
const fileSizeFixedDecimal = Number.parseFloat(fileSize).toFixed(2);
switch (numberOfDivides) {
case 1:
return `${fileSizeFixedDecimal} KB`;
case 2:
return `${fileSizeFixedDecimal} MB`;
case 3:
return `${fileSizeFixedDecimal} GB`;
default:
return `${fileSizeFixedDecimal} B`;
}
};
export const getFileSizeToClosestByte = (fileSize) => {
let divides = 0;
let size = fileSize;
while (size > 1000) {
size /= 1000;
divides += 1;
}
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
const fileSizeFixedDecimal = Number.parseFloat(fileSize).toFixed(2);
return `${fileSizeFixedDecimal} ${units[divides]}`;
};

Copy link
Contributor

Choose a reason for hiding this comment

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

Or using logs:

Suggested change
export const getFileSizeToClosestByte = (fileSize, numberOfDivides = 0) => {
if (fileSize > 1000) {
const updatedSize = fileSize / 1000;
const incrementNumberOfDivides = numberOfDivides + 1;
return getFileSizeToClosestByte(updatedSize, incrementNumberOfDivides);
}
const fileSizeFixedDecimal = Number.parseFloat(fileSize).toFixed(2);
switch (numberOfDivides) {
case 1:
return `${fileSizeFixedDecimal} KB`;
case 2:
return `${fileSizeFixedDecimal} MB`;
case 3:
return `${fileSizeFixedDecimal} GB`;
default:
return `${fileSizeFixedDecimal} B`;
}
};
export const getFileSizeToClosestByte = (fileSize) => {
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
const unitPow = Math.floor(Math.log10(fileSize) / 3);
const fileSizeFixedDecimal = Number.parseFloat(fileSize / (1000 ** unitPow)).toFixed(2);
return `${fileSizeFixedDecimal} ${units[unitPow]}`;
};

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I just moved this code around without putting much thought into it. Refactored here: a86f597

I used the loop solution to add a max value to divide and handle >= 1000 TB without errors.

PS: Very clever log solution! I have never thought of it.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah I noticed that after typing this up, figured I might as well post it!

Copy link
Contributor

Choose a reason for hiding this comment

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

There is already a component like this in Paragon called StatefulButton.

I think the use case here could be covered by that component.

Copy link
Contributor Author

@rpenido rpenido Jan 12, 2024

Choose a reason for hiding this comment

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

It is a bit different. The StatefulButton shows the state, but the state resides outside the component. The LoadingButton also handles the state inside according to the onClick call, abstracting this from the developer.

I changed the LoadingButton to use the StatefulButton inside here bbb0b19. If you don't agree to add another component let me know, and I will change it to a standard button to avoid more boiler plate in this already big component.

Comment on lines +106 to +109
/*
className is working on Dropzone: https://github.com/openedx/paragon/pull/2950
className="h-200px"
*/
Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks for making this fix and including this context!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Unfortunately, upstream is openedx/paragon and we are still using edx/paragon here.

Comment on lines 86 to 88
await waitFor(() => {
expect(getByTestId('export-step')).toBeInTheDocument();
});
Copy link
Contributor

Choose a reason for hiding this comment

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

An alternative way to do the same.

Suggested change
await waitFor(() => {
expect(getByTestId('export-step')).toBeInTheDocument();
});
expect(await findByTestId('export-step')).toBeInTheDocument();

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done: 71b025e

Comment on lines 35 to 36
// @ts-ignore ToDo: fix object spread type error
{...alertProps}
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this todo intentional? Is it intended to be fixed in a future PR?

Copy link
Contributor Author

@rpenido rpenido Jan 12, 2024

Choose a reason for hiding this comment

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

No. I was having trouble fixing this type check before. It was happening because I was missing the state type.
Fixed e82a49c

} = render(<RootWrapper />);

const button = getByTestId('taxonomy-show-alert');
button.click();
Copy link
Contributor

Choose a reason for hiding this comment

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

Instead of button.click etc, you should ideally use fireEvent.click(button) or userEvent.click(button)

Suggested change
button.click();
fireEvent.click(button);

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done: 71b025e

@rpenido rpenido force-pushed the rpenido/fal-3539-refined-ux-update-a-taxonomy-by-downloading-and-uploading branch from 14be854 to a86f597 Compare January 12, 2024 12:33
@rpenido rpenido force-pushed the rpenido/fal-3539-refined-ux-update-a-taxonomy-by-downloading-and-uploading branch from bcbd490 to 62716ab Compare January 12, 2024 20:17
@rpenido rpenido force-pushed the rpenido/fal-3539-refined-ux-update-a-taxonomy-by-downloading-and-uploading branch from 62716ab to e82a49c Compare January 12, 2024 20:18
Comment on lines 49 to 66
it('renders the spinner correctly even with error', async () => {
const longFunction = () => new Promise((_resolve, reject) => {
setTimeout(reject, 1000);
});
const { container, getByRole, getByText } = render(RootWrapper(longFunction));
const buttonElement = getByRole('button');
fireEvent.click(buttonElement);
expect(container.getElementsByClassName('icon-spin').length).toBe(1);
expect(getByText(buttonTitle)).toBeInTheDocument();
// StatefulButton only sets aria-disabled (not disabled) when the state is pending
// expect(buttonElement).toBeDisabled();
expect(buttonElement).toHaveAttribute('aria-disabled', 'true');
await waitFor(() => {
// StatefulButton only sets aria-disabled (not disabled) when the state is pending
// expect(buttonElement).toBeEnabled();
expect(buttonElement).not.toHaveAttribute('aria-disabled', 'true');
expect(container.getElementsByClassName('icon-spin').length).toBe(0);
});
Copy link
Contributor

Choose a reason for hiding this comment

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

This test seems to be throwing an error.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed here: f0af395

Comment on lines 29 to 47
it('renders the spinner correctly', async () => {
const longFunction = () => new Promise((resolve) => {
setTimeout(resolve, 1000);
});
const { container, getByRole, getByText } = render(RootWrapper(longFunction));
const buttonElement = getByRole('button');
fireEvent.click(buttonElement);
expect(container.getElementsByClassName('icon-spin').length).toBe(1);
expect(getByText(buttonTitle)).toBeInTheDocument();
// StatefulButton only sets aria-disabled (not disabled) when the state is pending
// expect(buttonElement).toBeDisabled();
expect(buttonElement).toHaveAttribute('aria-disabled', 'true');
await waitFor(() => {
// StatefulButton only sets aria-disabled (not disabled) when the state is pending
// expect(buttonElement).toBeEnabled();
expect(buttonElement).not.toHaveAttribute('aria-disabled', 'true');
expect(container.getElementsByClassName('icon-spin').length).toBe(0);
});
});
Copy link
Contributor

Choose a reason for hiding this comment

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

Using setTimeout like this is forcing this function to take 1 second at least, which is about a 100 times slower than it needs to be.

Instead, you can take control of resolving the promise manually to speed things up.

First replace longFunction with a promise you resolve manually.

let resolve;
    let reject;
    const longFunction = () => new Promise((resolver, rejecter) => {
      resolve = resolver;
      reject = rejecter;
    });

Now you have the resolve function outside the scope of the promise. So you can add the following:

await act(async () => resolve());

This needs act since it will cause the component to update. Finally you can use the existing final two expect statements, but without the waitFor.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thank you @xitij2000! Very clean and elegant.
Solved here: 2c05a81

@rpenido rpenido force-pushed the rpenido/fal-3539-refined-ux-update-a-taxonomy-by-downloading-and-uploading branch 6 times, most recently from 0675714 to 6082f9a Compare January 15, 2024 19:12
@rpenido rpenido force-pushed the rpenido/fal-3539-refined-ux-update-a-taxonomy-by-downloading-and-uploading branch from 6082f9a to 2c05a81 Compare January 15, 2024 19:22
@rpenido rpenido force-pushed the rpenido/fal-3539-refined-ux-update-a-taxonomy-by-downloading-and-uploading branch from 2834a54 to f0af395 Compare January 15, 2024 20:38
@rpenido rpenido force-pushed the rpenido/fal-3539-refined-ux-update-a-taxonomy-by-downloading-and-uploading branch from b1163a2 to dd3e972 Compare January 15, 2024 22:33
@xitij2000 xitij2000 merged commit b59ecaf into openedx:master Jan 16, 2024
6 checks passed
@openedx-webhooks
Copy link

@rpenido 🎉 Your pull request was merged! Please take a moment to answer a two question survey so we can improve your experience in the future.

@xitij2000 xitij2000 deleted the rpenido/fal-3539-refined-ux-update-a-taxonomy-by-downloading-and-uploading branch January 16, 2024 06:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
open-source-contribution PR author is not from Axim or 2U
Projects
Archived in project
Development

Successfully merging this pull request may close these issues.

5 participants