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

UX Change; Move Modification icon to the side of control title #52

Merged
merged 9 commits into from
Jun 21, 2021

Conversation

hreineck
Copy link
Contributor

Changed the react components in OSCALControl.js to render the modification component (OSCALControlModification.js)
instead of it being rendered at the top of OSCALControlPart.js
This satisfies the UX change specified in EGRC-306.

Changed the react components in OSCALControl.js to render the modification component (OSCALControlModification.js)
instead of it being rendered at the top of OSCALControlPart.js
This satisfies the UX change specified in EGRC-306.
Copy link
Contributor

@rgauss rgauss left a comment

Choose a reason for hiding this comment

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

We need resolve conflicts here first, and check with @kkennedy26 on the check for whether or not to display the modification as mentioned in comments.

@@ -58,6 +59,26 @@ export default function OSCALControl(props) {
}
const classes = useStyles(props);

let modificationDisplay;
if (props.modifications) {
// Finds the control-id within alters and matches it with a resolved control
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 all of this has been moved into the OSCALControlModification component and does not need to be duplicated here.

<Tooltip title="Modifications">
<Badge
anchorOrigin={{
vertical: "top",
horizontal: "right",
horizontal: "left",
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 should stay to the right.

OSCALControlPart.js renders the modification icon for each control part.
Before, this rendered the icon under the title, which was undesirable as referenced in EGRC-306.
OSCALControl.js now renders the icon next to the title, and passes in null as the modifications argument
to its top-level control part so that the icon is not rendered in OSCALControlPart.js
Also, in OSCALControlModification.js the <div> around the icon was removed so that it would render next to the title.
@hreineck hreineck requested a review from rgauss June 11, 2021 20:03
The const topPart variable in OSCALControl.js was out of place and was causing tests to fail.
The line was deleted.
modificationDisplay = (
<OSCALControlModification
modifications={props.modifications}
controlPartId={props.control.parts[0].id}
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think we want to specify a controlPartId at all here.

control={props.control}
modifications={index !== 0 && props.modifications}
Copy link
Contributor

Choose a reason for hiding this comment

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

What is this change trying to do?

@@ -145,7 +145,7 @@ export default function OSCALControlModification(props) {
// Display if alter or controlPartObject is true
if (alter || controlPartObject) {
modificationsDisplay = (
<div>
<>
Copy link
Contributor

Choose a reason for hiding this comment

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

If we don't need a div can we remove the empty <> element?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Believe that's the only way to have a component without a div around it

Copy link
Contributor

Choose a reason for hiding this comment

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

@hreineck I was wondering what removing the <div> does here? I was just looking over it for suggestions and genuinely didn't know.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

div adds a divider between the icon and the text it's supposed to be next to, so I removed it to render in line with the text

Copy link
Contributor

Choose a reason for hiding this comment

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

Would a <span> accomplish the same thing?

Copy link
Contributor

Choose a reason for hiding this comment

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

I tested it locally, and from what I can see it works.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

True. Span would be another way of doing it inline. Maybe that would be more descriptive than the empty <>

);
} else {
modificationsDisplay = null;
}

return <div>{modificationsDisplay}</div>;
return <>{modificationsDisplay}</>;
Copy link
Contributor

Choose a reason for hiding this comment

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

Why the empty <> element here as well?

src/OSCALControlPart.js Show resolved Hide resolved
if (!parameter) {
return;
}
// TODO parse select parameters
return `< ${parameter.label} >`;
}

replacedProse = props.prose.replace(/\{\{ insert: param, ([0-9a-zA-B-_.]*) \}\}/g, getParameterLabel);
replacedProse = props.prose.replace(
Copy link
Contributor

Choose a reason for hiding this comment

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

Were these types of changes from linter complaints?

Moved the call to OSCALControlModification in OSCALControlParts into the typography component
of the replaced prose so that it renders next to the control part.

Fixed some issues with rendering the top level control modifications:
OSCALControl now correctly passes in its own control id when displaying its modifications.

Also some minor cleanup in OSCALControlModification.js

These changes modify the UX to satisfy EGRC-306.
@rgauss
Copy link
Contributor

rgauss commented Jun 17, 2021

@hreineck, there may have been some overlapping changes with what was going on in #53.

Can you sync merge EGRC-261 into this branch and make sure everything still looks right?

Copy link
Contributor

@tuckerzp tuckerzp left a comment

Choose a reason for hiding this comment

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

I believe places with empty <> can be replaced with a <span>. <div> is a block level element while <span> is an inline element.

@rgauss
Copy link
Contributor

rgauss commented Jun 17, 2021

@hreineck and @tuckerzp, I did a sync merge.

The top-level modifications are repeating for each control part as well.

I think the call to OSCALControlModification now needs to send in controlId instead of control, and we may not need any of the changes in OSCALControlPart?

<OSCALControlModification
modifications={props.modifications}
controlPartId={props.control.id}
control={props.control}
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 now needs to be controlId?

src/OSCALControlPart.js Show resolved Hide resolved
Changed OSCALModification to distinguish between top-level modifications and control parts.
Some profiles do not include a "by-id" field on top-level modifications;
added a check to get around this so that only the control title renders the top-level icon.
@hreineck hreineck requested review from rgauss and tuckerzp June 17, 2021 18:29
@@ -78,8 +90,8 @@ export default function OSCALControl(props) {
parameters={props.control.params}
implReqStatements={props.implReqStatements}
componentId={props.componentId}
modifications={props.modifications}
control={props.control}
Copy link
Contributor

Choose a reason for hiding this comment

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

This can be changed to use the id rather than the entire control now. That would also make check in OSCALControlPart unneeded.

@@ -122,6 +122,7 @@ export default function OSCALControlModification(props) {
if (alter.adds) {
[addsDisplay, len] = getModifications(
props.controlPartId,
props.controlPartId === props.controlId,
Copy link
Contributor

@tuckerzp tuckerzp Jun 17, 2021

Choose a reason for hiding this comment

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

Would it be cleaner to just pass the controlId? I feel like that would give us more relevant information later on, for things like debugging, than just a bool. Either way would probably work

Copy link
Contributor

Choose a reason for hiding this comment

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

You would just push the check to isRelevantId where we are already doing logic checking similar to that

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, this is what I had done as well, see above comment on isRelevantId.

@@ -137,7 +138,7 @@ export default function OSCALControlModification(props) {
// Display if altered is true
if (modLength) {
modificationsDisplay = (
<div>
<span>
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice!

@@ -61,11 +61,11 @@ const getAlterAddsOrRemovesDisplay = (addsElements, addsLabel) => {
* @param {String} field Field of Add/Remove element to check
* @returns true if element matches controlID
*/
const isRelevantId = (controlPartId, element, field) =>
const isRelevantId = (controlPartId, element, field, isTopLevel) =>
Copy link
Contributor

Choose a reason for hiding this comment

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

Changing the documentation to show change in parameters might be good to do now rather than figure it out in documentation issue. Same for getModifications

Copy link
Contributor

Choose a reason for hiding this comment

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

Let's instead send in the controlId (maybe as the first argument) and a null controlPartId when calling from a top-level control, and yes, let's update the docs.

@@ -61,11 +61,11 @@ const getAlterAddsOrRemovesDisplay = (addsElements, addsLabel) => {
* @param {String} field Field of Add/Remove element to check
* @returns true if element matches controlID
*/
const isRelevantId = (controlPartId, element, field) =>
const isRelevantId = (controlPartId, element, field, isTopLevel) =>
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's instead send in the controlId (maybe as the first argument) and a null controlPartId when calling from a top-level control, and yes, let's update the docs.

@@ -122,6 +122,7 @@ export default function OSCALControlModification(props) {
if (alter.adds) {
[addsDisplay, len] = getModifications(
props.controlPartId,
props.controlPartId === props.controlId,
Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, this is what I had done as well, see above comment on isRelevantId.

Removed the redundant props passed into OSCALModification from OSCALControl
Changed the structure of the isRelevantId function to be more self-describing
Updated function docs
@hreineck hreineck requested review from tuckerzp and rgauss June 17, 2021 19:39
@tuckerzp tuckerzp dismissed their stale review June 17, 2021 20:24

My concerns have been addressed

tuckerzp
tuckerzp previously approved these changes Jun 18, 2021
@@ -69,7 +80,7 @@ export default function OSCALControl(props) {
className={classes.OSCALControlChildLevelTitle}
>
<span className={classes.OSCALControlId}>{props.control.id}</span>{" "}
{props.control.title}
{props.control.title} {modificationDisplay}
Copy link
Contributor

Choose a reason for hiding this comment

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

What happens if (!props.modifications) above and therefore and modificationDisplay == undefined? Not reviewing, just asking a React question. Does it just not render this bit?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It should just not render anything

// TODO: Differences in how NIST and FedRAMP implement adds makes
// this check needed. See if we can clean this up at some point.
// Assumes the difference is that NIST does not have a "control-id" field
!element[field] || element[field] === controlPartId;
// Assumes the difference is that NIST does not have a "by-id" field
Copy link
Contributor

Choose a reason for hiding this comment

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

This doesn't really make that assumption does it? It allows callers to make that assumption? Basically, the old comment was incorrect as well right? Can we remove it?

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 think more accurately there should be a comment where we call this check with"by-id" as an argument

);
} else {
modificationsDisplay = null;
}

return <div>{modificationsDisplay}</div>;
return modificationsDisplay;
Copy link
Contributor

Choose a reason for hiding this comment

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

With the removal of the <div> you, can now just return directly in the if above without having to keep this extra mutable var.

if (!modLength) {
  return null;
}
return (
  <span>...

Comment on lines 69 to 70
(!element[field] && controlPartId === controlId) ||
element[field] === controlPartId;
Copy link
Contributor

Choose a reason for hiding this comment

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

Now I feel guilty for having helped shorten this earlier as the number of variables and conditions begins to grow. I am losing understanding of the implementation here. Could we do something like:

const nistCheck = (controlPartId, controlId, element, field) => ...
const fedrampCheck = (controlPartId, controlId, element, field) => ...

return nistCheck(...) || fedrampCheck(...)

or even just some well-written if statements with early returns that are well commented.

It sounds like each of them defines things in a certain way. But we talk a lot about how we have to do this check because they're different. And then we lose all sight of how they differ in the implementation. But we also want to do something extensible because if NIST and FedRAMP differ here, then is it possible for Agency C to come along and do it differently as well?

Copy link
Contributor

Choose a reason for hiding this comment

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

These variations aren't specific to NIST or FedRAMP, just different ways to represent control modifications in the OSCAL spec, so I don't think we want to start naming methods around their implementations.

I'm fine with splitting things out into separate conditionals for better readability if need be though.

Copy link
Contributor

Choose a reason for hiding this comment

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

So are these the only ways to express the control modifications? Or would there be others? If these two are the only ways we're going to have to do it, then yeah, separate conditionals explaining would make sense. But the surrounding comments right now are very name-focused (NIST & FedRAMP) and not data structure focused (how it relates to the spec) so that was the idea behind using the names.

But I think the fact there's ambiguity there at all with the comments and complexity of the expression is a good indication we should try to clean it up at some point. @hreineck if there's a good way to clean this up during this PR that doesn't creep the scope of what you're actually working on, that'd be cool to see but I want to make sure I am not asking for something beyond what you're already doing.

@tuckerzp tuckerzp dismissed their stale review June 21, 2021 12:40

I would like to see Kyle's reccomendations considered

Edited comments in OSCALControlModification.js to more accurately explain
the isRelevantID check, which is needed for different ways to represent
control modifications in OSCAL.
Cleaned up the modification React component; removed an uncessesary variable.
@hreineck hreineck requested a review from kylelaker June 21, 2021 15:31
@kylelaker
Copy link
Contributor

Thanks @hreineck for the changes! Good stuff!

@hreineck hreineck merged commit d883a6d into EGRC-261 Jun 21, 2021
@kylelaker kylelaker deleted the EGRC-306 branch June 21, 2021 16:05
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.

None yet

5 participants