-
-
Notifications
You must be signed in to change notification settings - Fork 4.5k
ref(js): RuleListRow -> React.FC #29726
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
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 | ||||
|---|---|---|---|---|---|---|
|
|
@@ -39,28 +39,28 @@ type Props = { | |||||
| userTeams: Set<string>; | ||||||
| }; | ||||||
|
|
||||||
| type State = {}; | ||||||
|
|
||||||
| class RuleListRow extends React.Component<Props, State> { | ||||||
| /** | ||||||
| * Memoized function to find a project from a list of projects | ||||||
| */ | ||||||
| getProject = memoize((slug: string, projects: Project[]) => | ||||||
| projects.find(project => project.slug === slug) | ||||||
| ); | ||||||
|
|
||||||
| activeIncident() { | ||||||
| const {rule} = this.props; | ||||||
| return ( | ||||||
| rule.latestIncident?.status !== undefined && | ||||||
| [IncidentStatus.CRITICAL, IncidentStatus.WARNING].includes( | ||||||
| rule.latestIncident.status | ||||||
| ) | ||||||
| /** | ||||||
| * Memoized function to find a project from a list of projects | ||||||
| */ | ||||||
| const getProject = memoize((slug: string, projects: Project[]) => | ||||||
| projects.find(project => project.slug === slug) | ||||||
| ); | ||||||
|
|
||||||
| function RuleListRow({ | ||||||
| rule, | ||||||
| projectsLoaded, | ||||||
| projects, | ||||||
| orgId, | ||||||
| onDelete, | ||||||
| userTeams, | ||||||
| }: Props) { | ||||||
| const activeIncident = | ||||||
| rule.latestIncident?.status !== undefined && | ||||||
| [IncidentStatus.CRITICAL, IncidentStatus.WARNING].includes( | ||||||
| rule.latestIncident.status | ||||||
| ); | ||||||
| } | ||||||
|
|
||||||
| renderLastIncidentDate(): React.ReactNode { | ||||||
| const {rule} = this.props; | ||||||
| function renderLastIncidentDate(): React.ReactNode { | ||||||
| if (isIssueAlert(rule)) { | ||||||
| return null; | ||||||
| } | ||||||
|
|
@@ -69,7 +69,7 @@ class RuleListRow extends React.Component<Props, State> { | |||||
| return '-'; | ||||||
| } | ||||||
|
|
||||||
| if (this.activeIncident()) { | ||||||
| if (activeIncident) { | ||||||
| return ( | ||||||
| <div> | ||||||
| {t('Triggered ')} | ||||||
|
|
@@ -86,14 +86,11 @@ class RuleListRow extends React.Component<Props, State> { | |||||
| ); | ||||||
| } | ||||||
|
|
||||||
| renderAlertRuleStatus(): React.ReactNode { | ||||||
| const {rule} = this.props; | ||||||
|
|
||||||
| function renderAlertRuleStatus(): React.ReactNode { | ||||||
| if (isIssueAlert(rule)) { | ||||||
| return null; | ||||||
| } | ||||||
|
|
||||||
| const activeIncident = this.activeIncident(); | ||||||
| const criticalTrigger = rule.triggers.find(({label}) => label === 'critical'); | ||||||
| const warningTrigger = rule.triggers.find(({label}) => label === 'warning'); | ||||||
| const resolvedTrigger = rule.resolveThreshold; | ||||||
|
|
@@ -138,131 +135,107 @@ class RuleListRow extends React.Component<Props, State> { | |||||
| ); | ||||||
| } | ||||||
|
|
||||||
| render() { | ||||||
| const {rule, projectsLoaded, projects, orgId, onDelete, userTeams} = this.props; | ||||||
| const slug = rule.projects[0]; | ||||||
| const editLink = `/organizations/${orgId}/alerts/${ | ||||||
| isIssueAlert(rule) ? 'rules' : 'metric-rules' | ||||||
| }/${slug}/${rule.id}/`; | ||||||
|
|
||||||
| const detailsLink = `/organizations/${orgId}/alerts/rules/details/${rule.id}/`; | ||||||
|
|
||||||
| const ownerId = rule.owner?.split(':')[1]; | ||||||
| const teamActor = ownerId | ||||||
| ? {type: 'team' as Actor['type'], id: ownerId, name: ''} | ||||||
| : null; | ||||||
|
|
||||||
| const canEdit = ownerId ? userTeams.has(ownerId) : true; | ||||||
| const alertLink = isIssueAlert(rule) ? ( | ||||||
| rule.name | ||||||
| ) : ( | ||||||
| <TitleLink to={isIssueAlert(rule) ? editLink : detailsLink}>{rule.name}</TitleLink> | ||||||
| ); | ||||||
| const slug = rule.projects[0]; | ||||||
| const editLink = `/organizations/${orgId}/alerts/${ | ||||||
| isIssueAlert(rule) ? 'rules' : 'metric-rules' | ||||||
| }/${slug}/${rule.id}/`; | ||||||
|
|
||||||
| const IssueStatusText: Record<IncidentStatus, string> = { | ||||||
| [IncidentStatus.CRITICAL]: t('Critical'), | ||||||
| [IncidentStatus.WARNING]: t('Warning'), | ||||||
| [IncidentStatus.CLOSED]: t('Resolved'), | ||||||
| [IncidentStatus.OPENED]: t('Resolved'), | ||||||
| }; | ||||||
| const detailsLink = `/organizations/${orgId}/alerts/rules/details/${rule.id}/`; | ||||||
|
|
||||||
| return ( | ||||||
| <ErrorBoundary> | ||||||
| <AlertNameWrapper isIssueAlert={isIssueAlert(rule)}> | ||||||
| <FlexCenter> | ||||||
| <Tooltip | ||||||
| title={ | ||||||
| isIssueAlert(rule) | ||||||
| ? t('Issue Alert') | ||||||
| : tct('Metric Alert Status: [status]', { | ||||||
| status: | ||||||
| IssueStatusText[ | ||||||
| rule?.latestIncident?.status ?? IncidentStatus.CLOSED | ||||||
| ], | ||||||
| }) | ||||||
| } | ||||||
| > | ||||||
| <AlertBadge | ||||||
| status={rule?.latestIncident?.status} | ||||||
| isIssue={isIssueAlert(rule)} | ||||||
| hideText | ||||||
| /> | ||||||
| </Tooltip> | ||||||
| </FlexCenter> | ||||||
| <AlertNameAndStatus> | ||||||
| <AlertName>{alertLink}</AlertName> | ||||||
| {!isIssueAlert(rule) && this.renderLastIncidentDate()} | ||||||
| </AlertNameAndStatus> | ||||||
| </AlertNameWrapper> | ||||||
| <FlexCenter>{this.renderAlertRuleStatus()}</FlexCenter> | ||||||
| const ownerId = rule.owner?.split(':')[1]; | ||||||
| const teamActor = ownerId | ||||||
| ? {type: 'team' as Actor['type'], id: ownerId, name: ''} | ||||||
| : null; | ||||||
|
|
||||||
| <FlexCenter> | ||||||
| <ProjectBadgeContainer> | ||||||
| <ProjectBadge | ||||||
| avatarSize={18} | ||||||
| project={!projectsLoaded ? {slug} : this.getProject(slug, projects)} | ||||||
| /> | ||||||
| </ProjectBadgeContainer> | ||||||
| </FlexCenter> | ||||||
| const canEdit = ownerId ? userTeams.has(ownerId) : true; | ||||||
| const alertLink = isIssueAlert(rule) ? ( | ||||||
| rule.name | ||||||
| ) : ( | ||||||
| <TitleLink to={isIssueAlert(rule) ? editLink : detailsLink}>{rule.name}</TitleLink> | ||||||
| ); | ||||||
|
|
||||||
| const IssueStatusText: Record<IncidentStatus, string> = { | ||||||
| [IncidentStatus.CRITICAL]: t('Critical'), | ||||||
| [IncidentStatus.WARNING]: t('Warning'), | ||||||
| [IncidentStatus.CLOSED]: t('Resolved'), | ||||||
| [IncidentStatus.OPENED]: t('Resolved'), | ||||||
| }; | ||||||
|
|
||||||
| return ( | ||||||
| <ErrorBoundary> | ||||||
| <AlertNameWrapper isIssueAlert={isIssueAlert(rule)}> | ||||||
| <FlexCenter> | ||||||
| {teamActor ? <ActorAvatar actor={teamActor} size={24} /> : '-'} | ||||||
| <Tooltip | ||||||
| title={ | ||||||
| isIssueAlert(rule) | ||||||
| ? t('Issue Alert') | ||||||
| : tct('Metric Alert Status: [status]', { | ||||||
| status: | ||||||
| IssueStatusText[ | ||||||
| rule?.latestIncident?.status ?? IncidentStatus.CLOSED | ||||||
| ], | ||||||
| }) | ||||||
| } | ||||||
| > | ||||||
| <AlertBadge | ||||||
| status={rule?.latestIncident?.status} | ||||||
| isIssue={isIssueAlert(rule)} | ||||||
| hideText | ||||||
| /> | ||||||
| </Tooltip> | ||||||
| </FlexCenter> | ||||||
| <AlertNameAndStatus> | ||||||
| <AlertName>{alertLink}</AlertName> | ||||||
| {!isIssueAlert(rule) && renderLastIncidentDate()} | ||||||
| </AlertNameAndStatus> | ||||||
| </AlertNameWrapper> | ||||||
| <FlexCenter>{renderAlertRuleStatus()}</FlexCenter> | ||||||
|
|
||||||
| <FlexCenter> | ||||||
| <StyledDateTime | ||||||
| date={getDynamicText({ | ||||||
| value: rule.dateCreated, | ||||||
| fixed: new Date('2021-04-20'), | ||||||
| })} | ||||||
| format="ll" | ||||||
| <FlexCenter> | ||||||
| <ProjectBadgeContainer> | ||||||
| <ProjectBadge | ||||||
| avatarSize={18} | ||||||
| project={!projectsLoaded ? {slug} : getProject(slug, projects)} | ||||||
| /> | ||||||
| </FlexCenter> | ||||||
| <ActionsRow> | ||||||
| <Access access={['alerts:write']}> | ||||||
| {({hasAccess}) => ( | ||||||
| <React.Fragment> | ||||||
| <StyledDropdownLink> | ||||||
| <DropdownLink | ||||||
| anchorRight | ||||||
| caret={false} | ||||||
| title={ | ||||||
| <Button | ||||||
| tooltipProps={{ | ||||||
| containerDisplayMode: 'flex', | ||||||
| }} | ||||||
| size="small" | ||||||
| type="button" | ||||||
| aria-label={t('Show more')} | ||||||
| icon={<IconEllipsis size="xs" />} | ||||||
| /> | ||||||
| } | ||||||
| > | ||||||
| <li> | ||||||
| <Link to={editLink}>{t('Edit')}</Link> | ||||||
| </li> | ||||||
| <Confirm | ||||||
| disabled={!hasAccess || !canEdit} | ||||||
| message={tct( | ||||||
| "Are you sure you want to delete [name]? You won't be able to view the history of this alert once it's deleted.", | ||||||
| { | ||||||
| name: rule.name, | ||||||
| } | ||||||
| )} | ||||||
| header={t('Delete Alert Rule?')} | ||||||
| priority="danger" | ||||||
| confirmText={t('Delete Rule')} | ||||||
| onConfirm={() => onDelete(slug, rule)} | ||||||
| > | ||||||
| <MenuItemActionLink title={t('Delete')}> | ||||||
| {t('Delete')} | ||||||
| </MenuItemActionLink> | ||||||
| </Confirm> | ||||||
| </DropdownLink> | ||||||
| </StyledDropdownLink> | ||||||
|
|
||||||
| {/* Small screen actions */} | ||||||
| <StyledButtonBar gap={1}> | ||||||
| </ProjectBadgeContainer> | ||||||
| </FlexCenter> | ||||||
|
|
||||||
| <FlexCenter> | ||||||
| {teamActor ? <ActorAvatar actor={teamActor} size={24} /> : '-'} | ||||||
| </FlexCenter> | ||||||
|
|
||||||
| <FlexCenter> | ||||||
| <StyledDateTime | ||||||
| date={getDynamicText({ | ||||||
| value: rule.dateCreated, | ||||||
| fixed: new Date('2021-04-20'), | ||||||
| })} | ||||||
| format="ll" | ||||||
| /> | ||||||
| </FlexCenter> | ||||||
| <ActionsRow> | ||||||
| <Access access={['alerts:write']}> | ||||||
| {({hasAccess}) => ( | ||||||
| <React.Fragment> | ||||||
| <StyledDropdownLink> | ||||||
| <DropdownLink | ||||||
| anchorRight | ||||||
| caret={false} | ||||||
| title={ | ||||||
| <Button | ||||||
| tooltipProps={{ | ||||||
| containerDisplayMode: 'flex', | ||||||
| }} | ||||||
| size="small" | ||||||
| type="button" | ||||||
| aria-label={t('Show more')} | ||||||
|
Member
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.
Suggested change
|
||||||
| icon={<IconEllipsis size="xs" />} | ||||||
| /> | ||||||
| } | ||||||
| > | ||||||
| <li> | ||||||
| <Link to={editLink}>{t('Edit')}</Link> | ||||||
| </li> | ||||||
| <Confirm | ||||||
| disabled={!hasAccess || !canEdit} | ||||||
| message={tct( | ||||||
|
|
@@ -276,29 +249,50 @@ class RuleListRow extends React.Component<Props, State> { | |||||
| confirmText={t('Delete Rule')} | ||||||
| onConfirm={() => onDelete(slug, rule)} | ||||||
| > | ||||||
| <Button | ||||||
| type="button" | ||||||
| icon={<IconDelete />} | ||||||
| size="small" | ||||||
| title={t('Delete')} | ||||||
| /> | ||||||
| <MenuItemActionLink title={t('Delete')}> | ||||||
| {t('Delete')} | ||||||
| </MenuItemActionLink> | ||||||
| </Confirm> | ||||||
| <Tooltip title={t('Edit')}> | ||||||
| <Button | ||||||
| size="small" | ||||||
| type="button" | ||||||
| icon={<IconSettings />} | ||||||
| to={editLink} | ||||||
| /> | ||||||
| </Tooltip> | ||||||
| </StyledButtonBar> | ||||||
| </React.Fragment> | ||||||
| )} | ||||||
| </Access> | ||||||
| </ActionsRow> | ||||||
| </ErrorBoundary> | ||||||
| ); | ||||||
| } | ||||||
| </DropdownLink> | ||||||
| </StyledDropdownLink> | ||||||
|
|
||||||
| {/* Small screen actions */} | ||||||
| <StyledButtonBar gap={1}> | ||||||
| <Confirm | ||||||
| disabled={!hasAccess || !canEdit} | ||||||
| message={tct( | ||||||
| "Are you sure you want to delete [name]? You won't be able to view the history of this alert once it's deleted.", | ||||||
| { | ||||||
| name: rule.name, | ||||||
| } | ||||||
| )} | ||||||
| header={t('Delete Alert Rule?')} | ||||||
| priority="danger" | ||||||
| confirmText={t('Delete Rule')} | ||||||
| onConfirm={() => onDelete(slug, rule)} | ||||||
| > | ||||||
| <Button | ||||||
| type="button" | ||||||
| icon={<IconDelete />} | ||||||
| size="small" | ||||||
| title={t('Delete')} | ||||||
| /> | ||||||
|
Comment on lines
+274
to
+279
Member
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. Should we set the label prop so that this button is a bit more accessible? |
||||||
| </Confirm> | ||||||
| <Tooltip title={t('Edit')}> | ||||||
| <Button | ||||||
| size="small" | ||||||
|
Comment on lines
+281
to
+283
Member
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 could use the title prop on the button. Also, let's add a label prop. |
||||||
| type="button" | ||||||
| icon={<IconSettings />} | ||||||
| to={editLink} | ||||||
| /> | ||||||
| </Tooltip> | ||||||
| </StyledButtonBar> | ||||||
| </React.Fragment> | ||||||
| )} | ||||||
| </Access> | ||||||
| </ActionsRow> | ||||||
| </ErrorBoundary> | ||||||
| ); | ||||||
| } | ||||||
|
|
||||||
| const TitleLink = styled(Link)` | ||||||
|
|
||||||
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.
We could use em dash / NotAvailable component.