-
Notifications
You must be signed in to change notification settings - Fork 64
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(release): trigger release form (#911)
* feat(release): trigger release plan * feat(release): add bug to release * feat(releases): bugs * feat(releases): add cve
- Loading branch information
1 parent
d24cb92
commit cc55262
Showing
32 changed files
with
2,155 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
83 changes: 83 additions & 0 deletions
83
src/components/ReleaseService/ReleasePlan/TriggerRelease/AddIssueSection/AddIssueModal.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
import * as React from 'react'; | ||
import { Button, Modal, ModalVariant } from '@patternfly/react-core'; | ||
import { Formik } from 'formik'; | ||
import * as yup from 'yup'; | ||
import { URL_ERROR_MSG, urlRegex } from '../../../../ImportForm/utils/validation-utils'; | ||
import { ComponentProps } from '../../../../modal/createModalLauncher'; | ||
import BugFormContent from './BugFormContent'; | ||
import CVEFormContent from './CVEFormContent'; | ||
import { dateFormat } from './UploadDate'; | ||
|
||
export enum IssueType { | ||
BUG = 'bug', | ||
CVE = 'cve', | ||
} | ||
|
||
type AddIssueModalProps = ComponentProps & { | ||
bugArrayHelper: (values) => void; | ||
issueType: IssueType; | ||
}; | ||
|
||
const IssueFormSchema = yup.object({ | ||
key: yup.string().required('Required'), | ||
url: yup.string().matches(urlRegex, URL_ERROR_MSG).required('Required'), | ||
}); | ||
|
||
export const AddIssueModal: React.FC<React.PropsWithChildren<AddIssueModalProps>> = ({ | ||
onClose, | ||
bugArrayHelper, | ||
issueType, | ||
}) => { | ||
const [isModalOpen, setIsModalOpen] = React.useState(false); | ||
|
||
const isBug = issueType === IssueType.BUG; | ||
|
||
const handleModalToggle = () => { | ||
setIsModalOpen(!isModalOpen); | ||
}; | ||
|
||
const setValues = React.useCallback( | ||
(fields) => { | ||
bugArrayHelper(fields); | ||
onClose(); | ||
}, | ||
[onClose, bugArrayHelper], | ||
); | ||
|
||
return ( | ||
<> | ||
<Button variant="primary" onClick={handleModalToggle} data-test="modal-launch-btn"> | ||
{isBug ? 'Add a bug' : 'Add a CVE'} | ||
</Button> | ||
<Modal | ||
variant={ModalVariant.medium} | ||
title={isBug ? 'Add a bug fix' : 'Add a CVE'} | ||
isOpen={isModalOpen} | ||
onClose={handleModalToggle} | ||
data-test="add-issue-modal" | ||
> | ||
<Formik | ||
onSubmit={setValues} | ||
initialValues={ | ||
isBug | ||
? { key: '', url: '', summary: '', uploadDate: dateFormat(new Date()) } | ||
: { | ||
key: '', | ||
components: [], | ||
url: '', | ||
summary: '', | ||
uploadDate: dateFormat(new Date()), | ||
} | ||
} | ||
validationSchema={IssueFormSchema} | ||
> | ||
{isBug ? ( | ||
<BugFormContent modalToggle={handleModalToggle} /> | ||
) : ( | ||
<CVEFormContent modalToggle={handleModalToggle} /> | ||
)} | ||
</Formik> | ||
</Modal> | ||
</> | ||
); | ||
}; |
9 changes: 9 additions & 0 deletions
9
...components/ReleaseService/ReleasePlan/TriggerRelease/AddIssueSection/AddIssueSection.scss
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
.add-bug-section { | ||
&__emptyMsg { | ||
padding: 0; | ||
margin: 0; | ||
padding-top: var(--pf-v5-global--spacer--sm); | ||
width: 100%; | ||
text-align: center; | ||
} | ||
} |
236 changes: 236 additions & 0 deletions
236
src/components/ReleaseService/ReleasePlan/TriggerRelease/AddIssueSection/AddIssueSection.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,236 @@ | ||
import * as React from 'react'; | ||
import { | ||
EmptyState, | ||
EmptyStateBody, | ||
SearchInput, | ||
TextContent, | ||
TextVariants, | ||
Toolbar, | ||
ToolbarContent, | ||
ToolbarGroup, | ||
ToolbarItem, | ||
Text, | ||
EmptyStateVariant, | ||
Truncate, | ||
} from '@patternfly/react-core'; | ||
import { Table, Tbody, Td, Th, Thead, Tr } from '@patternfly/react-table'; | ||
import { FieldArray, useField } from 'formik'; | ||
import { debounce } from 'lodash-es'; | ||
import { useSearchParam } from '../../../../../hooks/useSearchParam'; | ||
import ActionMenu from '../../../../../shared/components/action-menu/ActionMenu'; | ||
import FilteredEmptyState from '../../../../../shared/components/empty-state/FilteredEmptyState'; | ||
import { AddIssueModal, IssueType } from './AddIssueModal'; | ||
|
||
import './AddIssueSection.scss'; | ||
|
||
interface AddIssueSectionProps { | ||
field: string; | ||
issueType: IssueType; | ||
} | ||
|
||
export interface IssueObject { | ||
key: string; | ||
summary: string; | ||
url?: string; | ||
components?: string[]; | ||
uploadDate?: string; | ||
status?: string; | ||
} | ||
|
||
export const issueTableColumnClass = { | ||
issueKey: 'pf-m-width-15 wrap-column ', | ||
bugUrl: 'pf-m-width-20 ', | ||
cveUrl: 'pf-m-width-15 ', | ||
components: 'pf-m-width-15 ', | ||
summary: 'pf-m-width-20 pf-m-width-15-on-xl ', | ||
uploadDate: 'pf-m-width-15 pf-m-width-10-on-xl ', | ||
status: 'pf-m-hidden pf-m-visible-on-xl pf-m-width-15 ', | ||
kebab: 'pf-v5-c-table__action', | ||
}; | ||
|
||
export const AddIssueSection: React.FC<React.PropsWithChildren<AddIssueSectionProps>> = ({ | ||
field, | ||
issueType, | ||
}) => { | ||
const [nameFilter, setNameFilter] = useSearchParam(field, ''); | ||
const [{ value: issues }, ,] = useField<IssueObject[]>(field); | ||
|
||
const isBug = issueType === IssueType.BUG; | ||
|
||
const [onLoadName, setOnLoadName] = React.useState(nameFilter); | ||
React.useEffect(() => { | ||
if (nameFilter) { | ||
setOnLoadName(nameFilter); | ||
} | ||
// eslint-disable-next-line react-hooks/exhaustive-deps | ||
}, []); | ||
|
||
const filteredIssues = React.useMemo( | ||
() => | ||
issues && Array.isArray(issues) | ||
? issues?.filter( | ||
(bug) => !nameFilter || bug.key.toLowerCase().indexOf(nameFilter.toLowerCase()) >= 0, | ||
) | ||
: [], | ||
[issues, nameFilter], | ||
); | ||
|
||
const onClearFilters = () => { | ||
onLoadName.length && setOnLoadName(''); | ||
setNameFilter(''); | ||
}; | ||
const onNameInput = debounce((n: string) => { | ||
n.length === 0 && onLoadName.length && setOnLoadName(''); | ||
|
||
setNameFilter(n); | ||
}, 600); | ||
|
||
const EmptyMsg = (type) => | ||
nameFilter ? ( | ||
<FilteredEmptyState onClearFilters={onClearFilters} variant={EmptyStateVariant.xs} /> | ||
) : ( | ||
<EmptyState className="pf-v5-u-m-0 pf-v5-u-p-0" variant={EmptyStateVariant.xs}> | ||
<EmptyStateBody className="pf-v5-u-m-0 pf-v5-u-p-0"> | ||
{type === IssueType.BUG ? 'No Bugs found' : 'No CVEs found'} | ||
</EmptyStateBody> | ||
</EmptyState> | ||
); | ||
|
||
return ( | ||
<FieldArray | ||
name={field} | ||
render={(arrayHelper) => { | ||
const addNewBug = (bug) => { | ||
arrayHelper.push(bug); | ||
}; | ||
|
||
return ( | ||
<> | ||
<TextContent className="pf-v5-u-mt-xs"> | ||
<Text component={TextVariants.h4} className="pf-v5-u-mt-0 pf-v5-u-pt-0"> | ||
{isBug | ||
? 'Are there any bug fixes you would like to add to this release?' | ||
: 'Are there any CVEs you would like to add to this release?'} | ||
</Text> | ||
</TextContent> | ||
<Toolbar | ||
data-test="pipelinerun-list-toolbar" | ||
clearAllFilters={onClearFilters} | ||
className="pf-v5-u-mb-0 pf-v5-u-pb-0 pf-v5-u-pl-0" | ||
> | ||
<ToolbarContent> | ||
<ToolbarGroup align={{ default: 'alignLeft' }}> | ||
<ToolbarItem className="pf-v5-u-ml-0"> | ||
<SearchInput | ||
name="nameInput" | ||
data-test={`${field}-input-filter`} | ||
type="search" | ||
aria-label="name filter" | ||
placeholder="Filter by name..." | ||
onChange={(e, n) => onNameInput(n)} | ||
value={nameFilter} | ||
/> | ||
</ToolbarItem> | ||
<ToolbarItem> | ||
<AddIssueModal bugArrayHelper={addNewBug} issueType={issueType} /> | ||
</ToolbarItem> | ||
</ToolbarGroup> | ||
</ToolbarContent> | ||
</Toolbar> | ||
<div className="pf-v5-u-mb-md"> | ||
<Table | ||
aria-label="Simple table" | ||
variant="compact" | ||
borders | ||
className="pf-v5-u-m-0 pf-v5-u-p-0" | ||
> | ||
{isBug ? ( | ||
<Thead> | ||
<Tr> | ||
<Th className={issueTableColumnClass.issueKey}>Bug issue key</Th> | ||
<Th className={issueTableColumnClass.bugUrl}>URL</Th> | ||
<Th className={issueTableColumnClass.summary}>Summary</Th> | ||
<Th className={issueTableColumnClass.uploadDate}>Last updated</Th> | ||
<Th className={issueTableColumnClass.status}>Status</Th> | ||
</Tr> | ||
</Thead> | ||
) : ( | ||
<Thead> | ||
<Tr> | ||
<Th className={issueTableColumnClass.issueKey}>CVE key</Th> | ||
<Th className={issueTableColumnClass.cveUrl}>URL</Th> | ||
<Th className={issueTableColumnClass.components}>Components</Th> | ||
<Th className={issueTableColumnClass.summary}>Summary</Th> | ||
<Th className={issueTableColumnClass.uploadDate}>Last updated</Th> | ||
<Th className={issueTableColumnClass.status}>Status</Th> | ||
</Tr> | ||
</Thead> | ||
)} | ||
|
||
{Array.isArray(filteredIssues) && filteredIssues.length > 0 && ( | ||
<Tbody data-test="issue-table-body"> | ||
{filteredIssues.map((issue, i) => ( | ||
<Tr key={issue.key}> | ||
<Td className={issueTableColumnClass.issueKey} data-test="issue-key"> | ||
{issue.key ?? '-'} | ||
</Td> | ||
<Td | ||
className={ | ||
isBug ? issueTableColumnClass.bugUrl : issueTableColumnClass.bugUrl | ||
} | ||
data-test="issue-url" | ||
> | ||
<Truncate content={issue.url} /> | ||
</Td> | ||
{!isBug && ( | ||
<Td className={issueTableColumnClass.components}> | ||
{issue.components && | ||
Array.isArray(issue.components) && | ||
issue.components.length > 0 | ||
? issue.components?.map((component) => ( | ||
<span key={component} className="pf-v5-u-mr-sm"> | ||
{component} | ||
</span> | ||
)) | ||
: '-'} | ||
</Td> | ||
)} | ||
<Td className={issueTableColumnClass.summary} data-test="issue-summary"> | ||
{issue.summary ? <Truncate content={issue.summary} /> : '-'} | ||
</Td> | ||
<Td | ||
className={issueTableColumnClass.uploadDate} | ||
data-test="issue-uploadDate" | ||
> | ||
{issue.uploadDate ?? '-'} | ||
</Td> | ||
<Td className={issueTableColumnClass.status} data-test="issue-status"> | ||
{issue.status ?? '-'} | ||
</Td> | ||
<Td className={issueTableColumnClass.kebab}> | ||
<ActionMenu | ||
actions={[ | ||
{ | ||
cta: () => arrayHelper.remove(i), | ||
id: 'delete-bug', | ||
label: isBug ? 'Delete bug' : 'Delete CVE', | ||
}, | ||
]} | ||
/> | ||
</Td> | ||
</Tr> | ||
))} | ||
</Tbody> | ||
)} | ||
</Table> | ||
{!filteredIssues || | ||
(filteredIssues?.length === 0 && ( | ||
<div className="add-issue-section__emptyMsg">{EmptyMsg(issueType)}</div> | ||
))} | ||
</div> | ||
</> | ||
); | ||
}} | ||
/> | ||
); | ||
}; |
Oops, something went wrong.