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

Massive cleanup in component rendering/generation #976

Merged
merged 36 commits into from
Mar 9, 2023

Conversation

olemartinorg
Copy link
Contributor

@olemartinorg olemartinorg commented Mar 7, 2023

Description

This PR includes an overhaul to the way we render layout components. Until now, we've had the legacy code that generates component properties, resolves data model bindings/mapping/text resource bindings for repeating groups, and in the end injects these properties into GenericComponent to render it. After this PR, some things will change under the hood:

  • When rendering GenericComponent, a node object is passed into it. You can no longer render imaginary components that do not belong anywhere in the node hierarchy (and by extension, exist inside the redux store). This makes sure we develop new functionality with the node hierarchy and dynamic expressions in mind.
  • A few properties can still be overridden when rendering a component (and Summary), but we should avoid polluting the code with custom properties, as expressions are not aware of these.
  • The LayoutComponent class has now been split into 3: PresentationComponent, FormComponent and ActionComponent (for buttons, navigation). These 3 component types may get different functionality in the future, and make sure we implement the important parts for every component type (i.e. we have to implement a function to get the component data to display as a string in the repeating group table, and every form component also needs to be able to render itself as a Summary item).
  • Summary has been majorly refactored in this PR, and now relies on the node hierarchy throughout. As mentioned, every component now decide for themselves how it should be rendered in a Summary.
  • Lots of deprecated functionality and dead code has been removed.
  • Some unit tests that were hard to fix has been removed - we should prioritize Cypress tests instead, as those tend to survive these kinds of refactoring jobs that do not alter functionality.

Related Issue(s)

Verification/QA

  • Manual functionality testing
    • I have tested these changes manually
    • Creator of the original issue (or service owner) has been contacted for manual testing (or will be contacted when released in alpha)
    • No testing done/necessary
  • Automated tests
    • Unit test(s) have been added/updated
    • Cypress E2E test(s) have been added/updated
    • No automatic tests are needed here (no functional changes/additions)
    • I want someone to help me make some tests
  • UU/WCAG (follow these guidelines until we have our own)
    • I have tested with a screen reader/keyboard navigation/automated wcag validator
    • No testing done/necessary (no DOM/visual changes)
    • I want someone to help me perform accessibility testing
  • User documentation @ altinn-studio-docs
    • Has been added/updated
    • No functionality has been changed/added, so no documentation is needed
    • I will do that later/have created an issue
  • Changes/additions to component properties
    • Changes are reflected in both src/layout/layout.d.ts and layout.schema.v1.json, and these are all backwards-compatible
    • No changes made
  • Support in Altinn Studio
    • Issue(s) created for support in Studio
    • This change/feature does not require any changes to Altinn Studio
  • Sprint board
    • The original issue (or this PR itself) has been added to the Team Apps project and to the current sprint board
    • I don't have permissions to do that, please help me out
  • Labels
    • I have added a kind/* label to this PR for proper release notes grouping
    • I don't have permissions to add labels, please help me out

Ole Martin Handeland added 28 commits March 2, 2023 14:36
…know some tests broke now, probably a lot more.
…ead of relying on straight up component properties
# Conflicts:
#	src/components/summary/SummaryComponent.tsx
#	src/components/summary/SummaryComponentSwitch.tsx
… that was passed around everywhere in the Summary components
…ponent type. This way we can also check for specific component types using `obj instanceof FormComponent` and implement form-component specific functionality (like having to support a summary)
…summary/repeating group table data as react hooks. The latter removes the need for SummaryContext, so I'm removing that.
…d looks somewhat like the code in `main`. Still some inconsistencies, though.
…t component display data in repeating groups
…This is now just as broken as it used to be, but at least I didn't break anything even more.
…there are no rows in a repeating group), otherwise it was just streamlining DOM IDs and testids.
…w the table is not rendered at all when there are no rows to display, so we cannot assert there are 0-length rows, but rather have to assert that the entire table does not exist.
…ey were just needlessly difficult to fix, and provides little to no value (I've made a note of these to make sure we write Cypress tests for them instead).
@olemartinorg olemartinorg added the kind/other Pull requests containing chores/repo structure/other changes label Mar 7, 2023
@olemartinorg olemartinorg marked this pull request as ready for review March 7, 2023 17:54
@olemartinorg olemartinorg requested review from bjosttveit, Magnusrm, framitdavid and lassopicasso and removed request for bjosttveit March 8, 2023 08:21
@bjosttveit
Copy link
Member

🤯🤯🤯🤯

Copy link
Member

@bjosttveit bjosttveit left a comment

Choose a reason for hiding this comment

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

Christmas came incredibly early this year 🤩

Copy link
Contributor

@framitdavid framitdavid left a comment

Choose a reason for hiding this comment

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

First of all, you have done a lot of quality and great work! 🚀 This will help the team a lot to maintain and understand the code base. I'm happy to see your changes, thank you!! 👏

I have only a few considerations and thoughts which I commented on this PR.

src/layout/Button/index.tsx Show resolved Hide resolved
const value = node.item.dataModelBindings?.simpleBinding
? formData[node.item.dataModelBindings.simpleBinding] || ''
: '';
return useCommaSeparatedOptionsToText(node.item, value);
Copy link
Contributor

Choose a reason for hiding this comment

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

The project has a folder named hooks, hence I think hooks should be created within the hooks folder instead or I might misunderstand the architecture. The useCommaSeparatedOptionsToText method is imported from the utils folder. In my head when I'm thinking of utility methods they do not have an internal state like useCommaSeparatedOptionsToText has. Utility methods should only depend on the inputs and return the same result as long the inputs are the same.

What do you think about my thoughts? I'm new on the team so I have might misunderstand some standards within this project. But I'm still thinking of utilities as static functions that only depend on their input. :D

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 agree completely! Will move these hooks into the hooks folder.

Comment on lines 16 to 29

return uuids
.map((uuid) => {
const attachmentsForComponent = attachments[componentId];
if (attachmentsForComponent) {
const foundAttachment = attachmentsForComponent.find((a) => a.id === uuid);
if (foundAttachment && foundAttachment.name) {
return foundAttachment;
}
}

return null;
})
.filter((a) => a !== null) as IAttachment[];
Copy link
Contributor

Choose a reason for hiding this comment

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

What do you think about refactoring a little to avoid filtering null and casting the type? Would it be a little more readable to follow the suggestion? The suggestion has moved attachmentsForComponent outside the loop to avoid creating and assigning on every iteration. :)

Suggested change
return uuids
.map((uuid) => {
const attachmentsForComponent = attachments[componentId];
if (attachmentsForComponent) {
const foundAttachment = attachmentsForComponent.find((a) => a.id === uuid);
if (foundAttachment && foundAttachment.name) {
return foundAttachment;
}
}
return null;
})
.filter((a) => a !== null) as IAttachment[];
const attachmentsForComponent = attachments[componentId];
if (!attachmentsForComponent) {
return [];
}
let componentAttachments: IAttachment[] = [];
uuids.forEach((uuid) => {
const foundAttachment = attachmentsForComponent.find((a) => a.id === uuid);
if (foundAttachment?.name) {
componentAttachments = [...componentAttachments, foundAttachment];
}
});
return componentAttachments;

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Finally looked at closely at this, and yup, that's much better! I took the liberty to rewrite it to use for (const uuid of uuids) and componentAttachments.push() as well. 🥳 Thank you!

Copy link
Contributor

Choose a reason for hiding this comment

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

No worries! Sounds great to use the push method, then componentAttachments can be const instead of let. Great work! 🥳

src/layout/GenericComponent.tsx Show resolved Hide resolved
src/layout/MultipleSelect/index.tsx Show resolved Hide resolved
src/layout/Panel/PanelGroupContainer.tsx Show resolved Hide resolved
{
"files": ["src/layout/*/index.tsx"],
"rules": {
"react-hooks/rules-of-hooks": "off"
Copy link
Contributor

@framitdavid framitdavid Mar 9, 2023

Choose a reason for hiding this comment

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

It feels a little strange/painful to have to turn this rule off, but as long we are using classes I don't think this is an issue. It might be a sign that we should implement our code differently, but I'm not sure. The rule is mainly used to ensure that hooks are initialized in the top scope within the functional component, hence for classes this rule does not make sense.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yup, I know.. 😆 Little on the fence on this one as well. I guess we could separate out the methods that are meant to be used as hooks, but I'm not sure I like that approach either.

Copy link
Contributor

Choose a reason for hiding this comment

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

Sounds like a pair-programming issue to me, to discuss the different approaches that could be implemented. Anyway, this is not a task for this PR. 😃

@sonarqubecloud
Copy link

sonarqubecloud bot commented Mar 9, 2023

SonarCloud Quality Gate failed.    Quality Gate failed

Bug C 16 Bugs
Vulnerability A 0 Vulnerabilities
Security Hotspot A 0 Security Hotspots
Code Smell A 17 Code Smells

69.5% 69.5% Coverage
1.9% 1.9% Duplication

@olemartinorg olemartinorg merged commit 8162b87 into main Mar 9, 2023
@olemartinorg olemartinorg deleted the chore/node-in-genericComponent branch March 9, 2023 14:49
olemartinorg pushed a commit that referenced this pull request Mar 30, 2023
…ere I broke this functionality, and rewrote the test). The table is now in the DOM, but has no elements in it.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
kind/other Pull requests containing chores/repo structure/other changes
Projects
Archived in project
Development

Successfully merging this pull request may close these issues.

3 participants