-
Notifications
You must be signed in to change notification settings - Fork 44
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
✨ Add facts modal/code viewer & detail drawer tab #1057
Changes from all commits
20425e9
1bcf1ee
c1795c6
114d4f4
c221947
657f20f
7cbf77e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
import { | ||
FilterCategory, | ||
FilterToolbar, | ||
FilterType, | ||
} from "@app/shared/components/FilterToolbar"; | ||
import { | ||
Button, | ||
Toolbar, | ||
ToolbarContent, | ||
ToolbarItem, | ||
ToolbarToggleGroup, | ||
} from "@patternfly/react-core"; | ||
import React from "react"; | ||
import FilterIcon from "@patternfly/react-icons/dist/esm/icons/filter-icon"; | ||
import spacing from "@patternfly/react-styles/css/utilities/Spacing/spacing"; | ||
import { useTranslation } from "react-i18next"; | ||
import { useLegacyFilterState } from "@app/shared/hooks/useLegacyFilterState"; | ||
import { Fact } from "@app/api/models"; | ||
import { FactDetailModal } from "./fact-detail-modal/fact-detail-modal"; | ||
|
||
export interface IApplicationRiskProps { | ||
facts: Fact[]; | ||
} | ||
|
||
export const ApplicationFacts: React.FC<IApplicationRiskProps> = ({ | ||
facts, | ||
}) => { | ||
const { t } = useTranslation(); | ||
const sources = new Set<string>(); | ||
|
||
// TODO: work through how to store sources for facts in the ui for sorting | ||
// facts.forEach((fact) => sources.add(fact.source || "")); | ||
|
||
const filterCategories: FilterCategory<Fact, "source">[] = [ | ||
{ | ||
key: "source", | ||
title: t("terms.source"), | ||
type: FilterType.multiselect, | ||
placeholderText: t("terms.source"), | ||
// getItemValue: (fact) => fact.source || "Source default name", | ||
selectOptions: Array.from(sources) | ||
//TODO: Custom sorting for facts may be required | ||
// .sort(compareSources) | ||
.map((source) => source || "Source default name") | ||
.map((source) => ({ key: source, value: source })), | ||
logicOperator: "OR", | ||
}, | ||
]; | ||
|
||
const { | ||
filterValues, | ||
setFilterValues, | ||
filteredItems: filteredFacts, | ||
} = useLegacyFilterState(facts, filterCategories); | ||
|
||
const [selectedFactForDetailModal, setSelectedFactForDetailModal] = | ||
React.useState<Fact | null>(null); | ||
|
||
return ( | ||
<> | ||
<Toolbar | ||
clearAllFilters={() => setFilterValues({})} | ||
clearFiltersButtonText={t("actions.clearAllFilters")} | ||
> | ||
<ToolbarContent className={spacing.p_0}> | ||
<ToolbarItem>Filter by:</ToolbarItem> | ||
<ToolbarToggleGroup toggleIcon={<FilterIcon />} breakpoint="xl"> | ||
<FilterToolbar | ||
filterCategories={filterCategories} | ||
filterValues={filterValues} | ||
setFilterValues={setFilterValues} | ||
showFiltersSideBySide | ||
/> | ||
</ToolbarToggleGroup> | ||
</ToolbarContent> | ||
</Toolbar> | ||
{filteredFacts.map((fact) => { | ||
return ( | ||
<div> | ||
<Button | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This flat list of link-buttons with no real layout aside from line breaks really bothers me for some reason, but that's a design issue. Maybe we can revisit this with Justin. I think even just a compact table with one unlabeled column, or maybe a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Opened an issue for this & linked in the description. |
||
variant="link" | ||
isInline | ||
onClick={() => setSelectedFactForDetailModal(fact)} | ||
> | ||
{fact.name} | ||
</Button> | ||
</div> | ||
); | ||
})} | ||
{selectedFactForDetailModal ? ( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You use the form There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was trying to match the style we used in issue-affected-files-table for consistency's sake. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We're not consistent about it in this repo, but I've always tried to favor the ternary because |
||
<FactDetailModal | ||
fact={selectedFactForDetailModal} | ||
onClose={() => setSelectedFactForDetailModal(null)} | ||
/> | ||
) : null} | ||
</> | ||
); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import * as React from "react"; | ||
import { CodeEditor, Language } from "@patternfly/react-code-editor"; | ||
import { Fact } from "@app/api/models"; | ||
import yaml from "js-yaml"; | ||
export interface IFactCodeSnipViewerProps { | ||
fact: Fact; | ||
} | ||
|
||
export const FactCodeSnipViewer: React.FC<IFactCodeSnipViewerProps> = ({ | ||
fact, | ||
}) => { | ||
return ( | ||
<CodeEditor | ||
isReadOnly | ||
isDarkTheme | ||
isLineNumbersVisible | ||
language={Language.json} | ||
height="450px" | ||
code={yaml.dump(fact.data, { skipInvalid: true })} | ||
/> | ||
); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import * as React from "react"; | ||
import { Button, Modal } from "@patternfly/react-core"; | ||
import { FactCodeSnipViewer } from "./fact-code-snip-viewer"; | ||
import { Fact } from "@app/api/models"; | ||
|
||
export interface IFactDetailModalProps { | ||
fact: Fact; | ||
onClose: () => void; | ||
} | ||
|
||
export const FactDetailModal: React.FC<IFactDetailModalProps> = ({ | ||
fact, | ||
onClose, | ||
}) => { | ||
return ( | ||
<Modal | ||
title={fact.name} | ||
variant="large" | ||
isOpen | ||
onClose={onClose} | ||
actions={[ | ||
<Button key="close" variant="primary" onClick={onClose}> | ||
Close | ||
</Button>, | ||
]} | ||
> | ||
<FactCodeSnipViewer fact={fact}></FactCodeSnipViewer> | ||
</Modal> | ||
); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from "./fact-detail-modal"; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import { useQuery } from "@tanstack/react-query"; | ||
|
||
import { getFacts } from "@app/api/rest"; | ||
import { AxiosError } from "axios"; | ||
import { Fact } from "@app/api/models"; | ||
|
||
export const FactsQueryKey = "facts"; | ||
|
||
export const useFetchFacts = (applicationID: number | string | undefined) => { | ||
const { data, isLoading, error, refetch } = useQuery( | ||
[FactsQueryKey, applicationID], | ||
{ | ||
queryFn: () => getFacts(applicationID), | ||
enabled: !!applicationID, | ||
onError: (error: AxiosError) => console.log("error, ", error), | ||
select: (facts): Fact[] => | ||
Object.keys(facts).map((fact) => ({ name: fact, data: facts[fact] })), | ||
} | ||
); | ||
|
||
return { | ||
facts: data || [], | ||
isFetching: isLoading, | ||
fetchError: error, | ||
refetch, | ||
}; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was going to suggest we replace
any
withunknown
here to prevent any issues with code assuming it's a string (JSON.stringify won't care what it is), but since we're going to revisit this to support structured facts it's probably fine to leave it as-is (and it stands out as something to be fixed anyway which is maybe good)