Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions test/integration/service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,28 @@ test('Alerts', async ({ page, isMobile }) => {
await expect(page.getByTestId('service-recent-events')).toContainText(
'Closed',
)

// Validate availability from EP page
await page.locator('a[href^="/escalation-policies/"]').click()
await page
.getByRole('link', {
name: 'Manage alerts specific to services using this policy',
})
.click()

// Wait for data-ql=true and data-ql-ready=true
await page.waitForSelector('[data-ql="true"][data-ql-ready="true"]')
await expect(page.getByText('No results')).toBeVisible()

await page.getByRole('tab', { name: 'CLOSED' }).click()
// ensure the tab has aria-selected=true
await expect(
page.getByRole('tab', { name: 'CLOSED', selected: true }),
).toBeVisible()

await expect(
page.getByRole('link', { name: ' CLOSED ' + summary }),
).toBeVisible()
})

test('Metric', async ({ page, isMobile }) => {
Expand Down
63 changes: 48 additions & 15 deletions web/src/app/alerts/AlertsList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ import { NotificationContext } from '../main/SnackbarNotification'
import ReactGA from 'react-ga4'
import { useConfigValue } from '../util/RequireConfig'
import AlertFeedbackDialog from './components/AlertFeedbackDialog'
import { Target } from 'web/src/schema'

interface AlertsListProps {
serviceID: string
type AlertsListProps = {
secondaryActions?: ReactElement
}
} & ({ serviceID: string } | { policyID: string })

interface MutationVariables {
input: MutationVariablesInput
Expand Down Expand Up @@ -124,6 +124,9 @@ export default function AlertsList(props: AlertsListProps): React.JSX.Element {
const [fullTime] = useURLParam('fullTime', false)
const [filter] = useURLParam<string>('filter', 'active')

const serviceID = 'serviceID' in props ? props.serviceID : ''
const policyID = 'policyID' in props ? props.policyID : ''

useEffect(() => {
if (analyticsID && event.length)
ReactGA.event({ category: 'Bulk Alert Action', action: event })
Expand All @@ -139,19 +142,46 @@ export default function AlertsList(props: AlertsListProps): React.JSX.Element {
}
}
`,
variables: { id: props.serviceID || '' },
pause: !props.serviceID,
variables: { id: serviceID },
pause: !serviceID,
})

// query for current policy name if props.policyID is provided
const [policyInfoQuery] = useQuery({
query: gql`
query ($id: ID!) {
escalationPolicy(id: $id) {
id
name
assignedTo {
id
type
}
}
}
`,
variables: { id: policyID },
pause: !policyID,
})
const policyServiceIDs: string[] = (
policyInfoQuery.data?.escalationPolicy.assignedTo ?? []
)
.filter((a: Target) => a.type === 'service')
.map((a: Target) => a.id)

// alerts list query variables
const variables = {
input: {
filterByStatus: getStatusFilter(filter),
first: 25,
// default to favorites only, unless viewing alerts from a service's page
favoritesOnly: !props.serviceID && !allServices,
includeNotified: !props.serviceID, // keep service list alerts specific to that service,
filterByServiceID: props.serviceID ? [props.serviceID] : null,
// default to favorites only, unless viewing alerts from a service or policy page
favoritesOnly: !serviceID && !policyID && !allServices,
includeNotified: !serviceID && !policyID, // keep service list alerts specific to that service,
filterByServiceID: serviceID
? [serviceID]
: policyID
? policyServiceIDs
: null,
},
}

Expand Down Expand Up @@ -250,9 +280,12 @@ export default function AlertsList(props: AlertsListProps): React.JSX.Element {
return `Showing ${filter} alerts for all services.`
}

if (props.serviceID && serviceNameQuery.data?.service?.name) {
if (serviceID && serviceNameQuery.data?.service?.name) {
return `Showing ${filter} alerts for the service ${serviceNameQuery.data.service.name}.`
}
if (policyID && policyInfoQuery.data?.escalationPolicy?.name) {
return `Showing ${filter} alerts for all services using the policy ${policyInfoQuery.data.escalationPolicy.name}.`
}
}

/*
Expand Down Expand Up @@ -298,7 +331,7 @@ export default function AlertsList(props: AlertsListProps): React.JSX.Element {
return (
<React.Fragment>
<Grid container direction='column' spacing={2}>
<ServiceNotices serviceID={props.serviceID} />
<ServiceNotices serviceID={serviceID} />
<Grid item>
<QueryList
query={alertsListQuery}
Expand All @@ -311,8 +344,7 @@ export default function AlertsList(props: AlertsListProps): React.JSX.Element {
title: `${a.alertID}: ${a.status
.toUpperCase()
.replace('STATUS', '')}`,
subText:
(props.serviceID ? '' : a.service.name + ': ') + a.summary,
subText: (serviceID ? '' : a.service.name + ': ') + a.summary,
action: (
<ListItemText
className={classes.alertTimeContainer}
Expand All @@ -330,13 +362,14 @@ export default function AlertsList(props: AlertsListProps): React.JSX.Element {
variables={variables}
secondaryActions={
props?.secondaryActions ?? (
<AlertsListFilter serviceID={props.serviceID} />
<AlertsListFilter allowShowAll={!serviceID && !policyID} />
)
}
renderCreateDialog={(onClose) => (
<CreateAlertDialog
serviceID={props.serviceID}
serviceID={serviceID}
onClose={onClose}
onlyIDs={policyID ? policyServiceIDs : undefined}
/>
)}
createLabel='Alert'
Expand Down
2 changes: 2 additions & 0 deletions web/src/app/alerts/CreateAlertDialog/CreateAlertDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ const useStyles = makeStyles((theme: Theme) => ({
export default function CreateAlertDialog(props: {
onClose: () => void
serviceID?: string
onlyIDs?: string[]
}): React.JSX.Element {
const classes = useStyles()
const [step, setStep] = useState(0)
Expand Down Expand Up @@ -156,6 +157,7 @@ export default function CreateAlertDialog(props: {
onChange={(newValue: Value) => setValue(newValue)}
disabled={loading}
errors={fieldErrors(error)}
onlyIDs={props.onlyIDs}
/>
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ interface CreateAlertFormProps {
// Enables functionality to remove an incoming value at it's index from
// an array field if the new value is falsey.
removeFalseyIdxs?: boolean

onlyIDs?: string[]
}

// TODO: remove this interface once FormContainer.js has been converted to TS
Expand All @@ -44,7 +46,7 @@ export function CreateAlertForm({
<FormField
required
render={(props: CreateAlertServiceSelectProps) => (
<CreateAlertServiceSelect {...props} />
<CreateAlertServiceSelect {...props} only={otherProps.onlyIDs} />
)}
name='serviceIDs'
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ export interface CreateAlertServiceSelectProps {
value: string[]
onChange: (val: string[]) => void
error?: Error
only?: string[]
}

export function CreateAlertServiceSelect(
Expand All @@ -94,6 +95,7 @@ export function CreateAlertServiceSelect(
favoritesFirst: true,
omit: value,
first: 15,
only: props.only,
},
},
})
Expand Down
4 changes: 2 additions & 2 deletions web/src/app/alerts/components/AlertsListFilter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ const useStyles = makeStyles((theme: Theme) => ({
}))

interface AlertsListFilterProps {
serviceID: string
allowShowAll?: boolean
}

function AlertsListFilter(props: AlertsListFilterProps): React.JSX.Element {
Expand Down Expand Up @@ -74,7 +74,7 @@ function AlertsListFilter(props: AlertsListFilterProps): React.JSX.Element {

function renderFilters(): React.JSX.Element {
let favoritesFilter = null
if (!props.serviceID) {
if (props.allowShowAll) {
favoritesFilter = (
<FormControlLabel
control={
Expand Down
8 changes: 8 additions & 0 deletions web/src/app/escalation-policies/PolicyAlertsList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import React from 'react'
import AlertsList from '../alerts/AlertsList'

export default function ServiceAlerts(props: {
policyID: string
}): JSX.Element {
return <AlertsList policyID={props.policyID} />
}
45 changes: 44 additions & 1 deletion web/src/app/escalation-policies/PolicyDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ import { Edit, Delete } from '@mui/icons-material'
import PolicyStepsQuery from './PolicyStepsQuery'
import PolicyDeleteDialog from './PolicyDeleteDialog'
import { QuerySetFavoriteButton } from '../util/QuerySetFavoriteButton'
import DetailsPage from '../details/DetailsPage'
import DetailsPage, { LinkStatus } from '../details/DetailsPage'
import PolicyEditDialog from './PolicyEditDialog'
import { GenericError, ObjectNotFound } from '../error-pages'
import Spinner from '../loading/components/Spinner'
import { EPAvatar } from '../util/avatars'
import { Redirect } from 'wouter'
import { Target } from 'web/src/schema'

const query = gql`
query ($id: ID!) {
Expand All @@ -24,10 +25,38 @@ const query = gql`
message
details
}

assignedTo {
id
type
}
}
}
`
const alertStatusQuery = gql`
query policyAlertStatusQuery($serviceIDs: [ID!]!) {
alerts(
input: {
filterByStatus: [StatusAcknowledged, StatusUnacknowledged]
filterByServiceID: $serviceIDs
first: 1
}
) {
nodes {
id
status
}
}
}
`

const alertStatus = (a: { status: string }[]): LinkStatus | null => {
if (!a) return null
if (!a.length) return 'ok'
if (a[0].status === 'StatusUnacknowledged') return 'err'
return 'warn'
}

export default function PolicyDetails(props: {
policyID: string
}): React.JSX.Element {
Expand All @@ -40,6 +69,14 @@ export default function PolicyDetails(props: {
id: props.policyID,
},
})
const [alertStatusResult] = useQuery({
query: alertStatusQuery,
variables: {
serviceIDs: _data?.escalationPolicy?.assignedTo
.filter((a: Target) => a.type === 'service')
.map((a: Target) => a.id),
},
})

const data = _.get(_data, 'escalationPolicy', null)

Expand Down Expand Up @@ -80,6 +117,12 @@ export default function PolicyDetails(props: {
/>,
]}
links={[
{
label: 'Alerts',
status: alertStatus(alertStatusResult.data?.alerts?.nodes),
url: 'alerts',
subText: 'Manage alerts specific to services using this policy',
},
{
label: 'Services',
url: 'services',
Expand Down
2 changes: 1 addition & 1 deletion web/src/app/lists/QueryList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ export default function QueryList(props: QueryListProps): React.JSX.Element {
}

return (
<Grid container spacing={2}>
<Grid container spacing={2} data-ql='true' data-ql-ready={!isLoading}>
{renderList()}
{!props.infiniteScroll && (
<PageControls
Expand Down
2 changes: 2 additions & 0 deletions web/src/app/main/AppRoutes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import AdminSwitchoverGuide from '../admin/switchover/AdminSwitchoverGuide'
import UniversalKeyPage from '../services/UniversalKey/UniversalKeyPage'
import { useExpFlag } from '../util/useExpFlag'
import AdminMaint from '../admin/AdminMaint'
import PolicyAlertsList from '../escalation-policies/PolicyAlertsList'

// ParamRoute will pass route parameters as props to the route's child.
function ParamRoute(props: RouteProps): React.JSX.Element {
Expand Down Expand Up @@ -96,6 +97,7 @@ export const routes: Record<string, JSXElementConstructor<any>> = {
'/escalation-policies': PolicyList,
'/escalation-policies/:policyID': PolicyDetails,
'/escalation-policies/:policyID/services': PolicyServicesQuery,
'/escalation-policies/:policyID/alerts': PolicyAlertsList,

'/services': ServiceList,
'/services/:serviceID': ServiceDetails,
Expand Down
4 changes: 0 additions & 4 deletions web/src/app/schedules/temp-sched/TempSchedShiftsList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -189,11 +189,7 @@ export default function TempSchedShiftsList({

let diffColor = ''
const compare = (compareWith: Shift[]): boolean => {
console.log()
const res = compareWith.find((val) => {
// console.log('shiftStart: ', DateTime.fromISO(s.start))
// console.log('compareVal: ', DateTime.fromISO(val.start), '\n')

return (
DateTime.fromISO(s.start).toISO() ===
DateTime.fromISO(val.start).toISO() &&
Expand Down
2 changes: 1 addition & 1 deletion web/src/app/services/ServiceAlerts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export default function ServiceAlerts(props: {
const secondaryActions = (
<Grid className={classes.filter} container spacing={2} alignItems='center'>
<Grid item>
<AlertsListFilter serviceID={props.serviceID} />
<AlertsListFilter />
</Grid>
<Grid item>
<ButtonGroup variant='outlined'>
Expand Down
1 change: 0 additions & 1 deletion web/src/app/util/QuerySetFavoriteButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,6 @@ export function QuerySetFavoriteButton({
isFavorite={isFavorite}
loading={!data && fetching}
onClick={() => {
console.log(toTitleCase(type))
commit(
{
input: { target: { id, type }, favorite: !isFavorite },
Expand Down
Loading