-
Notifications
You must be signed in to change notification settings - Fork 64
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(release): trigger release form #911
Changes from all commits
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,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(); | ||
Check warning on line 42 in src/components/ReleaseService/ReleasePlan/TriggerRelease/AddIssueSection/AddIssueModal.tsx Codecov / codecov/patchsrc/components/ReleaseService/ReleasePlan/TriggerRelease/AddIssueSection/AddIssueModal.tsx#L40-L42
|
||
}, | ||
[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> | ||
</> | ||
); | ||
}; |
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; | ||
} | ||
} |
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); | ||
Check warning on line 63 in src/components/ReleaseService/ReleasePlan/TriggerRelease/AddIssueSection/AddIssueSection.tsx Codecov / codecov/patchsrc/components/ReleaseService/ReleasePlan/TriggerRelease/AddIssueSection/AddIssueSection.tsx#L63
|
||
} | ||
// 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(''); | ||
Check warning on line 80 in src/components/ReleaseService/ReleasePlan/TriggerRelease/AddIssueSection/AddIssueSection.tsx Codecov / codecov/patchsrc/components/ReleaseService/ReleasePlan/TriggerRelease/AddIssueSection/AddIssueSection.tsx#L80
|
||
}; | ||
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); | ||
Check warning on line 104 in src/components/ReleaseService/ReleasePlan/TriggerRelease/AddIssueSection/AddIssueSection.tsx Codecov / codecov/patchsrc/components/ReleaseService/ReleasePlan/TriggerRelease/AddIssueSection/AddIssueSection.tsx#L104
|
||
}; | ||
|
||
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} /> | ||
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. |
||
</Td> | ||
{!isBug && ( | ||
<Td className={issueTableColumnClass.components}> | ||
{issue.components && | ||
Array.isArray(issue.components) && | ||
issue.components.length > 0 | ||
Check warning on line 189 in src/components/ReleaseService/ReleasePlan/TriggerRelease/AddIssueSection/AddIssueSection.tsx Codecov / codecov/patchsrc/components/ReleaseService/ReleasePlan/TriggerRelease/AddIssueSection/AddIssueSection.tsx#L188-L189
|
||
? issue.components?.map((component) => ( | ||
<span key={component} className="pf-v5-u-mr-sm"> | ||
Check warning on line 191 in src/components/ReleaseService/ReleasePlan/TriggerRelease/AddIssueSection/AddIssueSection.tsx Codecov / codecov/patchsrc/components/ReleaseService/ReleasePlan/TriggerRelease/AddIssueSection/AddIssueSection.tsx#L191
|
||
{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), | ||
Check warning on line 214 in src/components/ReleaseService/ReleasePlan/TriggerRelease/AddIssueSection/AddIssueSection.tsx Codecov / codecov/patchsrc/components/ReleaseService/ReleasePlan/TriggerRelease/AddIssueSection/AddIssueSection.tsx#L214
|
||
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> | ||
</> | ||
); | ||
}} | ||
/> | ||
); | ||
}; |
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.
Why not use the Table component that we use everywhere in list pages?
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.
FieldArray, arrayHelper & index doesn't work properly with our ../shared/Table, using standard patternfly table