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(interview): Copy dataset [3] #1128

Merged
merged 18 commits into from
Jan 17, 2025
Merged

feat(interview): Copy dataset [3] #1128

merged 18 commits into from
Jan 17, 2025

Conversation

jochenklar
Copy link
Member

This PR adds the possibility to copy tabs (i.e. datasets) or better to create a new tab with all values from the current tab. It works a bit like deleting a set. If a set_value is present (e.g. project/dataset/id), the catalog is used to get all values for the current set and copy them, along with a new set_value. For # style sets without attributes this is done in the front end. There is some black magic involved when the set_prefix of values is updated with the set_index of the new set.

@jochenklar jochenklar self-assigned this Aug 22, 2024
@jochenklar jochenklar added this to the RDMO 2.3.0 milestone Aug 22, 2024
@jochenklar jochenklar force-pushed the interview-copy-dataset branch from cdb35a2 to 1082398 Compare August 22, 2024 07:34
@jochenklar jochenklar force-pushed the interview-apply-to-all branch from 5c65410 to 498ae19 Compare August 25, 2024 16:14
@jochenklar jochenklar force-pushed the interview-copy-dataset branch from 1082398 to 40a8e80 Compare August 25, 2024 17:26
Copy link
Member

@MyPyDavid MyPyDavid left a comment

Choose a reason for hiding this comment

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

think it looks good, thanks! 🙏

@jochenklar jochenklar changed the title Interview copy dataset feature(interview): Copy dataset [3] Nov 19, 2024
@jochenklar jochenklar changed the title feature(interview): Copy dataset [3] feat(interview): Copy dataset [3] Nov 19, 2024
@jochenklar jochenklar force-pushed the interview-apply-to-all branch from 498ae19 to 5ec0b86 Compare December 12, 2024 14:52
Base automatically changed from interview-apply-to-all to 2.3.0 January 10, 2025 12:49
@jochenklar jochenklar force-pushed the interview-copy-dataset branch from 01d9ef8 to b453485 Compare January 10, 2025 13:12
@jochenklar
Copy link
Member Author

Rebased and good to go.

@jochenklar jochenklar marked this pull request as ready for review January 10, 2025 14:34

if (value.set_prefix == set.set_prefix) {
value.set_index = set.set_index
} else {
Copy link
Member Author

Choose a reason for hiding this comment

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

@MyPyDavid This is complicated, can you check if this is understandable to you. I just fixed a bug here. This front-end part is for copying blocks/questionset (and tabs/pages without attribute).

Copy link
Member

Choose a reason for hiding this comment

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

ok, yes, I can not make any cheese out of this but the comments help ;).
I've asked a LLM to explain it to me..
You don't want to split it a bit more into functions with meaningful names?

function adjustSetPrefix(value, set, newSetPrefixDepth) {
  value.set_prefix = value.set_prefix.split('|').map((segment, idx) => {
    return idx === newSetPrefixDepth ? set.set_index : segment;
  }).join('|');
  return value;
}
export function copySet(currentSet, currentSetValue, attrs) {
  const pendingId = `copySet/${currentSet.set_prefix}/${currentSet.set_index}`;

  return (dispatch, getState) => {
    dispatch(addToPending(pendingId));
    dispatch(copySetInit());

    const set = createNewSet(attrs); // Encapsulates set creation logic
    const value = prepareSetValue(attrs); // Encapsulates value preparation logic

    if (isNil(value)) {
      handleSetWithoutAttribute(dispatch, getState, pendingId, currentSet, set, finalizeCopySet(dispatch, getState));
    } else {
      handleSetWithAttribute(dispatch, pendingId, currentSetValue, value, finalizeCopySet(dispatch, getState));
    }
  };
}

Copy link
Member Author

@jochenklar jochenklar Jan 16, 2025

Choose a reason for hiding this comment

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

I think I will keep it like it is. Moving dispatch into functions somehow feels wrong. To me it is actually harder to read with the functions.

set_value_serializer.is_valid(raise_exception=True)
set_value = set_value_serializer.save()
set_value_data = set_value_serializer.data

Copy link
Member Author

Choose a reason for hiding this comment

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

This is the corresponding part in the backend for tabs/pages with an attribute. The problem before was that set_value.set_prefix.split('|') for '' and '0' is both 1. I hope I fixed it now.

Copy link
Member

Choose a reason for hiding this comment

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

ok, wanna handle each value maybe in a separate function inside of the copy_set method?

    def adjust_set_prefix(value, set_value, set_prefix_length):
        if value.set_prefix == set_value.set_prefix:
            value.set_index = set_value.set_index
        else:
            value.set_prefix = '|'.join([
                str(set_value.set_index) if (index == set_prefix_length) else part
                for index, part in enumerate(value.set_prefix.split('|'))
            ])
        return value

Copy link
Member Author

@jochenklar jochenklar Jan 17, 2025

Choose a reason for hiding this comment

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

ok, after some thought, I added a compute_set_prefix_from_set_value function for the else part. Mainly because I want some parametrized tests for it.

@jochenklar jochenklar requested a review from MyPyDavid January 13, 2025 09:49
Copy link
Member

@MyPyDavid MyPyDavid left a comment

Choose a reason for hiding this comment

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

Overall I think that the current implementation behaves as expected.
I have made only few optional code refactoring remarks.

Somethings about the UX of the tabs of the datasets, they should behave similar to tabs in a browser right?

  • on deleting a dataset, it should activate the one to the right first when it's possible
  • for UI design, should the delete icon not be inside of the activated tab? (or on all tabs??)
  • in the delete modal, it should mention the name/label of the tab that will be deleted

During the interview, when applying to all or making the copy, there are multiple requests to /navigation and /progress. Could this maybe be optimized? I think it calls get requests for each dataset that is updated. Don't know if this could be handled somehow in a single request to each endpoint.


if (value.set_prefix == set.set_prefix) {
value.set_index = set.set_index
} else {
Copy link
Member

Choose a reason for hiding this comment

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

ok, yes, I can not make any cheese out of this but the comments help ;).
I've asked a LLM to explain it to me..
You don't want to split it a bit more into functions with meaningful names?

function adjustSetPrefix(value, set, newSetPrefixDepth) {
  value.set_prefix = value.set_prefix.split('|').map((segment, idx) => {
    return idx === newSetPrefixDepth ? set.set_index : segment;
  }).join('|');
  return value;
}
export function copySet(currentSet, currentSetValue, attrs) {
  const pendingId = `copySet/${currentSet.set_prefix}/${currentSet.set_index}`;

  return (dispatch, getState) => {
    dispatch(addToPending(pendingId));
    dispatch(copySetInit());

    const set = createNewSet(attrs); // Encapsulates set creation logic
    const value = prepareSetValue(attrs); // Encapsulates value preparation logic

    if (isNil(value)) {
      handleSetWithoutAttribute(dispatch, getState, pendingId, currentSet, set, finalizeCopySet(dispatch, getState));
    } else {
      handleSetWithAttribute(dispatch, pendingId, currentSetValue, value, finalizeCopySet(dispatch, getState));
    }
  };
}


# collect the attributes of all questions of all pages or questionsets
# of this catalog, which have the attribute of this value
attributes = set()
Copy link
Member

Choose a reason for hiding this comment

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

suggestion for this:

    attributes = {descendant.attribute for element in (catalog.pages + catalog.questions)
                  if element.attribute == set_value.attribute
                  for descendant in element.descendants}

Would be the same right? as a set comprehension

set_value_serializer.is_valid(raise_exception=True)
set_value = set_value_serializer.save()
set_value_data = set_value_serializer.data

Copy link
Member

Choose a reason for hiding this comment

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

ok, wanna handle each value maybe in a separate function inside of the copy_set method?

    def adjust_set_prefix(value, set_value, set_prefix_length):
        if value.set_prefix == set_value.set_prefix:
            value.set_index = set_value.set_index
        else:
            value.set_prefix = '|'.join([
                str(set_value.set_index) if (index == set_prefix_length) else part
                for index, part in enumerate(value.set_prefix.split('|'))
            ])
        return value

@jochenklar
Copy link
Member Author

I would like to keep the icons where they are, I think it would be to crowded to add all the options to all tabs. (Makes more sense for a browser where you open and close tabs all the time.)

@jochenklar
Copy link
Member Author

I added the name of the tab to the delete modal:

image

@MyPyDavid
Copy link
Member

I would like to keep the icons where they are, I think it would be to crowded to add all the options to all tabs. (Makes more sense for a browser where you open and close tabs all the time.)

this is also fine with me! 👍

@jochenklar
Copy link
Member Author

The remark with the /progress opened a can of worms. I now refactored copyValue quiet heavily. It now stores all values and then calls fetchNavigation, updateProgress and checks for a refresh. I think the original version could also been prone to race conditions, since a refresh could be triggered before all values are stored.

@MyPyDavid MyPyDavid self-requested a review January 17, 2025 07:46
@jochenklar
Copy link
Member Author

I also added 2 small fixes for the bugs we found in yesterdays meeting. Can you check again and approve?

@MyPyDavid
Copy link
Member

I also added 2 small fixes for the bugs we found in yesterdays meeting. Can you check again and approve?

ok great, thanks so much! I'll check it today!

@MyPyDavid
Copy link
Member

The remark with the /progress opened a can of worms. I now refactored copyValue quiet heavily. It now stores all values and then calls fetchNavigation, updateProgress and checks for a refresh. I think the original version could also been prone to race conditions, since a refresh could be triggered before all values are stored.

thanks, this looks smooth now!

@MyPyDavid
Copy link
Member

Can the range slider also do apply to all? I can't see the icon for it..

And for file uploads, I would guess not right?

Copy link
Member

@MyPyDavid MyPyDavid left a comment

Choose a reason for hiding this comment

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

datasets can be copied smoothly now!

@jochenklar
Copy link
Member Author

🙏

(the slider works, but it needs a value first)

@jochenklar
Copy link
Member Author

I broke something else, good that we have tests 😏

@jochenklar jochenklar merged commit 6fb05a5 into 2.3.0 Jan 17, 2025
19 checks passed
@jochenklar jochenklar deleted the interview-copy-dataset branch January 17, 2025 14:54
@MyPyDavid
Copy link
Member

I broke something else, good that we have tests 😏

yes, only for all this new interview stuff the front-end (playwright) tests are still missing! 🚨 😉

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.

2 participants