diff --git a/.changeset/popular-mails-wave.md b/.changeset/popular-mails-wave.md new file mode 100644 index 0000000000..23517f0608 --- /dev/null +++ b/.changeset/popular-mails-wave.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-ilert': minor +--- + +Added support for multiple responders in alert list, added new tab with list to support iLert resource 'service', added new tab with list to support iLert resource 'status page' diff --git a/plugins/ilert/api-report.md b/plugins/ilert/api-report.md index 96576a8358..6cc0c12a8f 100644 --- a/plugins/ilert/api-report.md +++ b/plugins/ilert/api-report.md @@ -16,11 +16,96 @@ import { RouteRef } from '@backstage/core-plugin-api'; // @public (undocumented) export const ACCEPTED = 'ACCEPTED'; +// @public (undocumented) +export interface Alert { + // (undocumented) + alertKey: string; + // (undocumented) + alertSource: AlertSource | null; + // (undocumented) + assignedTo: User | null; + // (undocumented) + commentPublishToSubscribers: boolean; + // (undocumented) + commentText: string; + // (undocumented) + details: string; + // (undocumented) + id: number; + // (undocumented) + images: Image_2[]; + // (undocumented) + links: Link[]; + // (undocumented) + logEntries: LogEntry[]; + // (undocumented) + priority: AlertPriority; + // (undocumented) + reportTime: string; + // (undocumented) + resolvedOn: string; + // (undocumented) + responders: Responder[]; + // (undocumented) + status: AlertStatus; + // (undocumented) + subscribers: Subscriber[]; + // (undocumented) + summary: string; +} + +// @public (undocumented) +export interface AlertAction { + // (undocumented) + extensionId?: string; + // (undocumented) + history?: AlertActionHistory[]; + // (undocumented) + name: string; + // (undocumented) + type: string; + // (undocumented) + webhookId: string; +} + +// @public (undocumented) +export interface AlertActionHistory { + // (undocumented) + actor: User; + // (undocumented) + alertId: number; + // (undocumented) + id: string; + // (undocumented) + success: boolean; + // (undocumented) + webhookId: string; +} + +// @public (undocumented) +export type AlertPriority = 'HIGH' | 'LOW'; + +// @public (undocumented) +export interface AlertResponder { + // (undocumented) + disabled: boolean; + // (undocumented) + group: 'SUGGESTED' | 'USER' | 'ESCALATION_POLICY' | 'ON_CALL_SCHEDULE'; + // (undocumented) + id: number; + // (undocumented) + name: string; +} + // @public (undocumented) export interface AlertSource { // (undocumented) active?: boolean; // (undocumented) + alertCreation?: AlertSourceAlertCreation; + // (undocumented) + alertPriorityRule?: AlertSourceAlertPriorityRule; + // (undocumented) autoResolutionTimeout?: string; // (undocumented) autotaskMetadata?: AlertSourceAutotaskMetadata; @@ -45,10 +130,6 @@ export interface AlertSource { // (undocumented) id: number; // (undocumented) - incidentCreation?: AlertSourceIncidentCreation; - // (undocumented) - incidentPriorityRule?: AlertSourceIncidentPriorityRule; - // (undocumented) integrationKey?: string; // (undocumented) integrationType: AlertSourceIntegrationType; @@ -66,6 +147,21 @@ export interface AlertSource { teams: TeamShort[]; } +// @public (undocumented) +export type AlertSourceAlertCreation = + | 'ONE_ALERT_PER_EMAIL' + | 'ONE_ALERT_PER_EMAIL_SUBJECT' + | 'ONE_PENDING_ALERT_ALLOWED' + | 'ONE_OPEN_ALERT_ALLOWED' + | 'OPEN_RESOLVE_ON_EXTRACTION'; + +// @public (undocumented) +export type AlertSourceAlertPriorityRule = + | 'HIGH' + | 'LOW' + | 'HIGH_DURING_SUPPORT_HOURS' + | 'LOW_DURING_SUPPORT_HOURS'; + // @public (undocumented) export interface AlertSourceAutotaskMetadata { // (undocumented) @@ -109,21 +205,6 @@ export interface AlertSourceHeartbeat { summary: string; } -// @public (undocumented) -export type AlertSourceIncidentCreation = - | 'ONE_INCIDENT_PER_EMAIL' - | 'ONE_INCIDENT_PER_EMAIL_SUBJECT' - | 'ONE_PENDING_INCIDENT_ALLOWED' - | 'ONE_OPEN_INCIDENT_ALLOWED' - | 'OPEN_RESOLVE_ON_EXTRACTION'; - -// @public (undocumented) -export type AlertSourceIncidentPriorityRule = - | 'HIGH' - | 'LOW' - | 'HIGH_DURING_SUPPORT_HOURS' - | 'LOW_DURING_SUPPORT_HOURS'; - // @public (undocumented) export type AlertSourceIntegrationType = | 'NAGIOS' @@ -192,7 +273,7 @@ export interface AlertSourceSupportDay { // @public (undocumented) export interface AlertSourceSupportHours { // (undocumented) - autoRaiseIncidents: boolean; + autoRaiseAlerts: boolean; // (undocumented) supportDays: { MONDAY: AlertSourceSupportDay; @@ -214,6 +295,12 @@ export type AlertSourceTimeZone = | 'America/Los_Angeles' | 'Asia/Istanbul'; +// @public (undocumented) +export type AlertStatus = typeof PENDING | typeof ACCEPTED | typeof RESOLVED; + +// @public (undocumented) +export const DEGRADED = 'DEGRADED'; + // @public (undocumented) export const EntityILertCard: () => JSX.Element; @@ -255,72 +342,85 @@ export type EventRequest = { }; // @public (undocumented) -export type GetIncidentsCountOpts = { - states?: IncidentStatus[]; +export type GetAlertsCountOpts = { + states?: AlertStatus[]; }; // @public (undocumented) -export type GetIncidentsOpts = { +export type GetAlertsOpts = { maxResults?: number; startIndex?: number; - states?: IncidentStatus[]; + states?: AlertStatus[]; alertSources?: number[]; }; +// @public (undocumented) +export type GetServicesOpts = { + maxResults?: number; + startIndex?: number; +}; + +// @public (undocumented) +export type GetStatusPagesOpts = { + maxResults?: number; + startIndex?: number; +}; + // @public (undocumented) export interface ILertApi { // (undocumented) - acceptIncident(incident: Incident, userName: string): Promise; + acceptAlert(alert: Alert, userName: string): Promise; // (undocumented) addImmediateMaintenance( alertSourceId: number, minutes: number, ): Promise; // (undocumented) - assignIncident( - incident: Incident, - responder: IncidentResponder, - ): Promise; + assignAlert(alert: Alert, responder: AlertResponder): Promise; // (undocumented) - createIncident(eventRequest: EventRequest): Promise; + createAlert(eventRequest: EventRequest): Promise; // (undocumented) disableAlertSource(alertSource: AlertSource): Promise; // (undocumented) enableAlertSource(alertSource: AlertSource): Promise; // (undocumented) + fetchAlert(id: number): Promise; + // (undocumented) + fetchAlertActions(alert: Alert): Promise; + // (undocumented) + fetchAlertResponders(alert: Alert): Promise; + // (undocumented) + fetchAlerts(opts?: GetAlertsOpts): Promise; + // (undocumented) + fetchAlertsCount(opts?: GetAlertsCountOpts): Promise; + // (undocumented) fetchAlertSource(idOrIntegrationKey: number | string): Promise; // (undocumented) fetchAlertSourceOnCalls(alertSource: AlertSource): Promise; // (undocumented) fetchAlertSources(): Promise; // (undocumented) - fetchIncident(id: number): Promise; - // (undocumented) - fetchIncidentActions(incident: Incident): Promise; - // (undocumented) - fetchIncidentResponders(incident: Incident): Promise; - // (undocumented) - fetchIncidents(opts?: GetIncidentsOpts): Promise; - // (undocumented) - fetchIncidentsCount(opts?: GetIncidentsCountOpts): Promise; - // (undocumented) fetchOnCallSchedules(): Promise; // (undocumented) - fetchUptimeMonitor(id: number): Promise; + fetchServices(opts?: GetServicesOpts): Promise; // (undocumented) - fetchUptimeMonitors(): Promise; + fetchStatusPages(opts?: GetStatusPagesOpts): Promise; // (undocumented) fetchUsers(): Promise; // (undocumented) + getAlertDetailsURL(alert: Alert): string; + // (undocumented) getAlertSourceDetailsURL(alertSource: AlertSource | null): string; // (undocumented) getEscalationPolicyDetailsURL(escalationPolicy: EscalationPolicy): string; // (undocumented) - getIncidentDetailsURL(incident: Incident): string; - // (undocumented) getScheduleDetailsURL(schedule: Schedule): string; // (undocumented) - getUptimeMonitorDetailsURL(uptimeMonitor: UptimeMonitor): string; + getServiceDetailsURL(service: Service): string; + // (undocumented) + getStatusPageDetailsURL(statusPage: StatusPage): string; + // (undocumented) + getStatusPageURL(statusPage: StatusPage): string; // (undocumented) getUserInitials(user: User | null): string; // (undocumented) @@ -333,16 +433,9 @@ export interface ILertApi { end: string, ): Promise; // (undocumented) - pauseUptimeMonitor(uptimeMonitor: UptimeMonitor): Promise; + resolveAlert(alert: Alert, userName: string): Promise; // (undocumented) - resolveIncident(incident: Incident, userName: string): Promise; - // (undocumented) - resumeUptimeMonitor(uptimeMonitor: UptimeMonitor): Promise; - // (undocumented) - triggerIncidentAction( - incident: Incident, - action: IncidentAction, - ): Promise; + triggerAlertAction(alert: Alert, action: AlertAction): Promise; } // @public (undocumented) @@ -359,45 +452,42 @@ export class ILertClient implements ILertApi { proxyPath: string; }); // (undocumented) - acceptIncident(incident: Incident, userName: string): Promise; + acceptAlert(alert: Alert, userName: string): Promise; // (undocumented) addImmediateMaintenance( alertSourceId: number, minutes: number, ): Promise; // (undocumented) - assignIncident( - incident: Incident, - responder: IncidentResponder, - ): Promise; + assignAlert(alert: Alert, responder: AlertResponder): Promise; // (undocumented) - createIncident(eventRequest: EventRequest): Promise; + createAlert(eventRequest: EventRequest): Promise; // (undocumented) disableAlertSource(alertSource: AlertSource): Promise; // (undocumented) enableAlertSource(alertSource: AlertSource): Promise; // (undocumented) + fetchAlert(id: number): Promise; + // (undocumented) + fetchAlertActions(alert: Alert): Promise; + // (undocumented) + fetchAlertResponders(alert: Alert): Promise; + // (undocumented) + fetchAlerts(opts?: GetAlertsOpts): Promise; + // (undocumented) + fetchAlertsCount(opts?: GetAlertsCountOpts): Promise; + // (undocumented) fetchAlertSource(idOrIntegrationKey: number | string): Promise; // (undocumented) fetchAlertSourceOnCalls(alertSource: AlertSource): Promise; // (undocumented) fetchAlertSources(): Promise; // (undocumented) - fetchIncident(id: number): Promise; - // (undocumented) - fetchIncidentActions(incident: Incident): Promise; - // (undocumented) - fetchIncidentResponders(incident: Incident): Promise; - // (undocumented) - fetchIncidents(opts?: GetIncidentsOpts): Promise; - // (undocumented) - fetchIncidentsCount(opts?: GetIncidentsCountOpts): Promise; - // (undocumented) fetchOnCallSchedules(): Promise; // (undocumented) - fetchUptimeMonitor(id: number): Promise; + fetchServices(opts?: GetServicesOpts): Promise; // (undocumented) - fetchUptimeMonitors(): Promise; + fetchStatusPages(opts?: GetStatusPagesOpts): Promise; // (undocumented) fetchUsers(): Promise; // (undocumented) @@ -406,15 +496,19 @@ export class ILertClient implements ILertApi { discoveryApi: DiscoveryApi, ): ILertClient; // (undocumented) + getAlertDetailsURL(alert: Alert): string; + // (undocumented) getAlertSourceDetailsURL(alertSource: AlertSource | null): string; // (undocumented) getEscalationPolicyDetailsURL(escalationPolicy: EscalationPolicy): string; // (undocumented) - getIncidentDetailsURL(incident: Incident): string; - // (undocumented) getScheduleDetailsURL(schedule: Schedule): string; // (undocumented) - getUptimeMonitorDetailsURL(uptimeMonitor: UptimeMonitor): string; + getServiceDetailsURL(service: Service): string; + // (undocumented) + getStatusPageDetailsURL(statusPage: StatusPage): string; + // (undocumented) + getStatusPageURL(statusPage: StatusPage): string; // (undocumented) getUserInitials(user: User | null): string; // (undocumented) @@ -427,16 +521,9 @@ export class ILertClient implements ILertApi { end: string, ): Promise; // (undocumented) - pauseUptimeMonitor(uptimeMonitor: UptimeMonitor): Promise; + resolveAlert(alert: Alert, userName: string): Promise; // (undocumented) - resolveIncident(incident: Incident, userName: string): Promise; - // (undocumented) - resumeUptimeMonitor(uptimeMonitor: UptimeMonitor): Promise; - // (undocumented) - triggerIncidentAction( - incident: Incident, - action: IncidentAction, - ): Promise; + triggerAlertAction(alert: Alert, action: AlertAction): Promise; } // @public (undocumented) @@ -470,88 +557,6 @@ interface Image_2 { } export { Image_2 as Image }; -// @public (undocumented) -export interface Incident { - // (undocumented) - alertSource: AlertSource | null; - // (undocumented) - assignedTo: User | null; - // (undocumented) - commentPublishToSubscribers: boolean; - // (undocumented) - commentText: string; - // (undocumented) - details: string; - // (undocumented) - id: number; - // (undocumented) - images: Image_2[]; - // (undocumented) - incidentKey: string; - // (undocumented) - links: Link[]; - // (undocumented) - logEntries: LogEntry[]; - // (undocumented) - priority: IncidentPriority; - // (undocumented) - reportTime: string; - // (undocumented) - resolvedOn: string; - // (undocumented) - status: IncidentStatus; - // (undocumented) - subscribers: Subscriber[]; - // (undocumented) - summary: string; -} - -// @public (undocumented) -export interface IncidentAction { - // (undocumented) - extensionId?: string; - // (undocumented) - history?: IncidentActionHistory[]; - // (undocumented) - name: string; - // (undocumented) - type: string; - // (undocumented) - webhookId: string; -} - -// @public (undocumented) -export interface IncidentActionHistory { - // (undocumented) - actor: User; - // (undocumented) - id: string; - // (undocumented) - incidentId: number; - // (undocumented) - success: boolean; - // (undocumented) - webhookId: string; -} - -// @public (undocumented) -export type IncidentPriority = 'HIGH' | 'LOW'; - -// @public (undocumented) -export interface IncidentResponder { - // (undocumented) - disabled: boolean; - // (undocumented) - group: 'SUGGESTED' | 'USER' | 'ESCALATION_POLICY' | 'ON_CALL_SCHEDULE'; - // (undocumented) - id: number; - // (undocumented) - name: string; -} - -// @public (undocumented) -export type IncidentStatus = typeof PENDING | typeof ACCEPTED | typeof RESOLVED; - // @public (undocumented) const isPluginApplicableToEntity: (entity: Entity) => boolean; export { isPluginApplicableToEntity as isILertAvailable }; @@ -570,6 +575,8 @@ export interface Link { // @public (undocumented) export interface LogEntry { + // (undocumented) + alertId?: number; // (undocumented) filterTypes?: string[]; // (undocumented) @@ -579,8 +586,6 @@ export interface LogEntry { // (undocumented) id: number; // (undocumented) - incidentId?: number; - // (undocumented) logEntryType: string; // (undocumented) text: string; @@ -588,6 +593,9 @@ export interface LogEntry { timestamp: string; } +// @public (undocumented) +export const MAJOR_OUTAGE = 'MAJOR_OUTAGE'; + // @public (undocumented) export interface OnCall { // (undocumented) @@ -604,6 +612,12 @@ export interface OnCall { user: User; } +// @public (undocumented) +export const OPERATIONAL = 'OPERATIONAL'; + +// @public (undocumented) +export const PARTIAL_OUTAGE = 'PARTIAL_OUTAGE'; + // @public (undocumented) export const PENDING = 'PENDING'; @@ -615,9 +629,25 @@ export interface Phone { regionCode: string; } +// @public (undocumented) +export const PRIVATE = 'PRIVATE'; + +// @public (undocumented) +export const PUBLIC = 'PUBLIC'; + // @public (undocumented) export const RESOLVED = 'RESOLVED'; +// @public (undocumented) +export interface Responder { + // (undocumented) + acceptedAt?: string; + // (undocumented) + status: string; + // (undocumented) + user: User; +} + // @public (undocumented) export const Router: () => JSX.Element; @@ -643,6 +673,26 @@ export interface Schedule { timezone: string; } +// @public (undocumented) +export interface Service { + // (undocumented) + id: number; + // (undocumented) + name: string; + // (undocumented) + status: ServiceStatus; + // (undocumented) + uptime: Uptime; +} + +// @public (undocumented) +export type ServiceStatus = + | typeof OPERATIONAL + | typeof UNDER_MAINTENANCE + | typeof DEGRADED + | typeof PARTIAL_OUTAGE + | typeof MAJOR_OUTAGE; + // @public (undocumented) export interface Shift { // (undocumented) @@ -653,6 +703,25 @@ export interface Shift { user: User; } +// @public (undocumented) +export interface StatusPage { + // (undocumented) + domain: string; + // (undocumented) + id: number; + // (undocumented) + name: string; + // (undocumented) + status: ServiceStatus; + // (undocumented) + subdomain: string; + // (undocumented) + visibility: StatusPageVisibility; +} + +// @public (undocumented) +export type StatusPageVisibility = typeof PRIVATE | typeof PUBLIC; + // @public (undocumented) export interface Subscriber { // (undocumented) @@ -689,47 +758,18 @@ export interface TeamShort { } // @public (undocumented) -export interface UptimeMonitor { +export const UNDER_MAINTENANCE = 'UNDER_MAINTENANCE'; + +// @public (undocumented) +export interface Uptime { // (undocumented) - checkParams: UptimeMonitorCheckParams; - // (undocumented) - checkType: 'http' | 'tcp' | 'udp' | 'ping'; - // (undocumented) - createIncidentAfterFailedChecks: number; - // (undocumented) - embedUrl: string; - // (undocumented) - escalationPolicy: EscalationPolicy; - // (undocumented) - id: number; - // (undocumented) - intervalSec: number; - // (undocumented) - lastStatusChange: string; - // (undocumented) - name: string; - // (undocumented) - paused: boolean; - // (undocumented) - region: 'EU' | 'US'; - // (undocumented) - shareUrl: string; - // (undocumented) - status: string; - // (undocumented) - teams: TeamShort[]; - // (undocumented) - timeoutMs: number; + uptimePercentage: UptimePercentage; } // @public (undocumented) -export interface UptimeMonitorCheckParams { +export interface UptimePercentage { // (undocumented) - host?: string; - // (undocumented) - port?: number; - // (undocumented) - url?: string; + p90: number; } // @public (undocumented) diff --git a/plugins/ilert/src/api/client.ts b/plugins/ilert/src/api/client.ts index 7ab9a6d08c..9eb60c8c6a 100644 --- a/plugins/ilert/src/api/client.ts +++ b/plugins/ilert/src/api/client.ts @@ -13,30 +13,33 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { AuthenticationError, ResponseError } from '@backstage/errors'; -import { - AlertSource, - EscalationPolicy, - Incident, - IncidentAction, - IncidentResponder, - OnCall, - Schedule, - UptimeMonitor, - User, -} from '../types'; -import { - ILertApi, - GetIncidentsOpts, - GetIncidentsCountOpts, - EventRequest, -} from './types'; -import { DateTime as dt } from 'luxon'; import { ConfigApi, createApiRef, DiscoveryApi, } from '@backstage/core-plugin-api'; +import { AuthenticationError, ResponseError } from '@backstage/errors'; +import { DateTime as dt } from 'luxon'; +import { + Alert, + AlertAction, + AlertResponder, + AlertSource, + EscalationPolicy, + OnCall, + Schedule, + Service, + StatusPage, + User, +} from '../types'; +import { + EventRequest, + GetAlertsCountOpts, + GetAlertsOpts, + GetServicesOpts, + GetStatusPagesOpts, + ILertApi, +} from './types'; /** @public */ export const ilertApiRef = createApiRef({ @@ -102,7 +105,7 @@ export class ILertClient implements ILertApi { return await response.json(); } - async fetchIncidents(opts?: GetIncidentsOpts): Promise { + async fetchAlerts(opts?: GetAlertsOpts): Promise { const init = { headers: JSON_HEADERS, }; @@ -126,15 +129,11 @@ export class ILertClient implements ILertApi { query.append('state', state); }); } - const response = await this.fetch( - `/api/v1/incidents?${query.toString()}`, - init, - ); - + const response = await this.fetch(`/api/alerts?${query.toString()}`, init); return response; } - async fetchIncidentsCount(opts?: GetIncidentsCountOpts): Promise { + async fetchAlertsCount(opts?: GetAlertsCountOpts): Promise { const init = { headers: JSON_HEADERS, }; @@ -145,96 +144,85 @@ export class ILertClient implements ILertApi { }); } const response = await this.fetch( - `/api/v1/incidents/count?${query.toString()}`, + `/api/alerts/count?${query.toString()}`, init, ); return response && response.count ? response.count : 0; } - async fetchIncident(id: number): Promise { + async fetchAlert(id: number): Promise { const init = { headers: JSON_HEADERS, }; const response = await this.fetch( - `/api/v1/incidents/${encodeURIComponent(id)}`, + `/api/alerts/${encodeURIComponent(id)}`, init, ); return response; } - async fetchIncidentResponders( - incident: Incident, - ): Promise { + async fetchAlertResponders(alert: Alert): Promise { const init = { headers: JSON_HEADERS, }; const response = await this.fetch( - `/api/v1/incidents/${encodeURIComponent(incident.id)}/responders`, + `/api/alerts/${encodeURIComponent(alert.id)}/responders`, init, ); return response; } - async fetchIncidentActions(incident: Incident): Promise { + async fetchAlertActions(alert: Alert): Promise { const init = { headers: JSON_HEADERS, }; const response = await this.fetch( - `/api/v1/incidents/${encodeURIComponent(incident.id)}/actions`, + `/api/alerts/${encodeURIComponent(alert.id)}/actions`, init, ); return response; } - async acceptIncident( - incident: Incident, - userName: string, - ): Promise { + async acceptAlert(alert: Alert, userName: string): Promise { const init = { method: 'POST', headers: JSON_HEADERS, body: JSON.stringify({ - apiKey: incident.alertSource?.integrationKey || '', - incidentKey: incident.incidentKey, + apiKey: alert.alertSource?.integrationKey || '', + alertKey: alert.alertKey, summary: `from ${userName} via Backstage plugin`, eventType: 'ACCEPT', }), }; - await this.fetch('/api/v1/events', init); - return this.fetchIncident(incident.id); + await this.fetch('/api/events', init); + return this.fetchAlert(alert.id); } - async resolveIncident( - incident: Incident, - userName: string, - ): Promise { + async resolveAlert(alert: Alert, userName: string): Promise { const init = { method: 'POST', headers: JSON_HEADERS, body: JSON.stringify({ - apiKey: incident.alertSource?.integrationKey || '', - incidentKey: incident.incidentKey, + apiKey: alert.alertSource?.integrationKey || '', + alertKey: alert.alertKey, summary: `from ${userName} via Backstage plugin`, eventType: 'RESOLVE', }), }; - await this.fetch('/api/v1/events', init); - return this.fetchIncident(incident.id); + await this.fetch('/api/events', init); + return this.fetchAlert(alert.id); } - async assignIncident( - incident: Incident, - responder: IncidentResponder, - ): Promise { + async assignAlert(alert: Alert, responder: AlertResponder): Promise { const init = { method: 'PUT', headers: JSON_HEADERS, @@ -254,19 +242,14 @@ export class ILertClient implements ILertApi { } const response = await this.fetch( - `/api/v1/incidents/${encodeURIComponent( - incident.id, - )}/assign?${query.toString()}`, + `/api/alerts/${encodeURIComponent(alert.id)}/assign?${query.toString()}`, init, ); return response; } - async triggerIncidentAction( - incident: Incident, - action: IncidentAction, - ): Promise { + async triggerAlertAction(alert: Alert, action: AlertAction): Promise { const init = { method: 'POST', headers: JSON_HEADERS, @@ -279,12 +262,12 @@ export class ILertClient implements ILertApi { }; await this.fetch( - `/api/v1/incidents/${encodeURIComponent(incident.id)}/actions`, + `/api/alerts/${encodeURIComponent(alert.id)}/actions`, init, ); } - async createIncident(eventRequest: EventRequest): Promise { + async createAlert(eventRequest: EventRequest): Promise { const init = { method: 'POST', headers: JSON_HEADERS, @@ -305,64 +288,7 @@ export class ILertClient implements ILertApi { }), }; - const response = await this.fetch('/api/v1/events', init); - return response; - } - - async fetchUptimeMonitors(): Promise { - const init = { - headers: JSON_HEADERS, - }; - - const response = await this.fetch('/api/v1/uptime-monitors', init); - - return response; - } - - async fetchUptimeMonitor(id: number): Promise { - const init = { - headers: JSON_HEADERS, - }; - - const response: UptimeMonitor = await this.fetch( - `/api/v1/uptime-monitors/${encodeURIComponent(id)}`, - init, - ); - - return response; - } - - async pauseUptimeMonitor( - uptimeMonitor: UptimeMonitor, - ): Promise { - const init = { - method: 'PUT', - headers: JSON_HEADERS, - body: JSON.stringify({ ...uptimeMonitor, paused: true }), - }; - - const response = await this.fetch( - `/api/v1/uptime-monitors/${encodeURIComponent(uptimeMonitor.id)}`, - init, - ); - - return response; - } - - async resumeUptimeMonitor( - uptimeMonitor: UptimeMonitor, - ): Promise { - const init = { - method: 'PUT', - headers: JSON_HEADERS, - body: JSON.stringify({ ...uptimeMonitor, paused: false }), - }; - - const response = await this.fetch( - `/api/v1/uptime-monitors/${encodeURIComponent(uptimeMonitor.id)}`, - init, - ); - + const response = await this.fetch('/api/events', init); return response; } @@ -371,7 +297,7 @@ export class ILertClient implements ILertApi { headers: JSON_HEADERS, }; - const response = await this.fetch('/api/v1/alert-sources', init); + const response = await this.fetch('/api/alert-sources', init); return response; } @@ -384,7 +310,7 @@ export class ILertClient implements ILertApi { }; const response = await this.fetch( - `/api/v1/alert-sources/${encodeURIComponent(idOrIntegrationKey)}`, + `/api/alert-sources/${encodeURIComponent(idOrIntegrationKey)}`, init, ); @@ -397,7 +323,7 @@ export class ILertClient implements ILertApi { }; const response = await this.fetch( - `/api/v1/on-calls?policies=${encodeURIComponent( + `/api/on-calls?policies=${encodeURIComponent( alertSource.escalationPolicy.id, )}&expand=user&expand=escalationPolicy&timezone=${dt.local().zoneName}`, init, @@ -414,7 +340,7 @@ export class ILertClient implements ILertApi { }; const response = await this.fetch( - `/api/v1/alert-sources/${encodeURIComponent(alertSource.id)}`, + `/api/alert-sources/${encodeURIComponent(alertSource.id)}`, init, ); @@ -429,7 +355,7 @@ export class ILertClient implements ILertApi { }; const response = await this.fetch( - `/api/v1/alert-sources/${encodeURIComponent(alertSource.id)}`, + `/api/alert-sources/${encodeURIComponent(alertSource.id)}`, init, ); @@ -453,7 +379,7 @@ export class ILertClient implements ILertApi { }), }; - const response = await this.fetch('/api/v1/maintenance-windows', init); + const response = await this.fetch('/api/maintenance-windows', init); return response; } @@ -463,7 +389,10 @@ export class ILertClient implements ILertApi { headers: JSON_HEADERS, }; - const response = await this.fetch('/api/v1/schedules', init); + const response = await this.fetch( + '/api/schedules?include=currentShift&include=nextShift', + init, + ); return response; } @@ -473,7 +402,7 @@ export class ILertClient implements ILertApi { headers: JSON_HEADERS, }; - const response = await this.fetch('/api/v1/users', init); + const response = await this.fetch('/api/users', init); return response; } @@ -491,17 +420,57 @@ export class ILertClient implements ILertApi { }; const response = await this.fetch( - `/api/v1/schedules/${encodeURIComponent(scheduleId)}/overrides`, + `/api/schedules/${encodeURIComponent(scheduleId)}/overrides`, init, ); return response; } - getIncidentDetailsURL(incident: Incident): string { - return `${this.baseUrl}/incident/view.jsf?id=${encodeURIComponent( - incident.id, - )}`; + async fetchServices(opts?: GetServicesOpts): Promise { + const init = { + headers: JSON_HEADERS, + }; + const query = new URLSearchParams(); + if (opts?.maxResults !== undefined) { + query.append('max-results', String(opts.maxResults)); + } + if (opts?.startIndex !== undefined) { + query.append('start-index', String(opts.startIndex)); + } + + query.append('include', 'uptime'); + + const response = await this.fetch( + `/api/services?${query.toString()}`, + init, + ); + return response; + } + + async fetchStatusPages(opts?: GetStatusPagesOpts): Promise { + const init = { + headers: JSON_HEADERS, + }; + const query = new URLSearchParams(); + if (opts?.maxResults !== undefined) { + query.append('max-results', String(opts.maxResults)); + } + if (opts?.startIndex !== undefined) { + query.append('start-index', String(opts.startIndex)); + } + + query.append('include', 'subscribed'); + + const response = await this.fetch( + `/api/status-pages?${query.toString()}`, + init, + ); + return response; + } + + getAlertDetailsURL(alert: Alert): string { + return `${this.baseUrl}/alert/view.jsf?id=${encodeURIComponent(alert.id)}`; } getAlertSourceDetailsURL(alertSource: AlertSource | null): string { @@ -519,18 +488,28 @@ export class ILertClient implements ILertApi { )}`; } - getUptimeMonitorDetailsURL(uptimeMonitor: UptimeMonitor): string { - return `${this.baseUrl}/uptime/view.jsf?id=${encodeURIComponent( - uptimeMonitor.id, - )}`; - } - getScheduleDetailsURL(schedule: Schedule): string { return `${this.baseUrl}/schedule/view.jsf?id=${encodeURIComponent( schedule.id, )}`; } + getServiceDetailsURL(service: Service): string { + return `${this.baseUrl}/service/view.jsf?id=${encodeURIComponent( + service.id, + )}`; + } + + getStatusPageDetailsURL(statusPage: StatusPage): string { + return `${this.baseUrl}/status-page/view.jsf?id=${encodeURIComponent( + statusPage.id, + )}`; + } + + getStatusPageURL(statusPage: StatusPage): string { + return statusPage.domain ? statusPage.domain : statusPage.subdomain; + } + getUserPhoneNumber(user: User | null) { return user?.mobile?.number || user?.landline?.number || ''; } @@ -542,7 +521,7 @@ export class ILertClient implements ILertApi { if (!user.firstName && !user.lastName) { return user.username; } - return `${user.firstName} ${user.lastName} (${user.username})`; + return `${user.firstName} ${user.lastName}`; } private async apiUrl() { diff --git a/plugins/ilert/src/api/index.ts b/plugins/ilert/src/api/index.ts index e1163090ec..30120225a4 100644 --- a/plugins/ilert/src/api/index.ts +++ b/plugins/ilert/src/api/index.ts @@ -14,11 +14,13 @@ * limitations under the License. */ -export { ILertClient, ilertApiRef } from './client'; +export { ilertApiRef, ILertClient } from './client'; export type { + EventRequest as EventRequest, + GetAlertsCountOpts, + GetAlertsOpts, + GetServicesOpts, + GetStatusPagesOpts, ILertApi, - EventRequest, - GetIncidentsCountOpts, - GetIncidentsOpts, TableState, } from './types'; diff --git a/plugins/ilert/src/api/types.ts b/plugins/ilert/src/api/types.ts index 2e9ea30f2f..959d7975ac 100644 --- a/plugins/ilert/src/api/types.ts +++ b/plugins/ilert/src/api/types.ts @@ -15,16 +15,17 @@ */ import { + Alert, + AlertAction, + AlertResponder, AlertSource, - Incident, - User, - IncidentStatus, - UptimeMonitor, + AlertStatus, EscalationPolicy, - Schedule, - IncidentResponder, - IncidentAction, OnCall, + Schedule, + Service, + StatusPage, + User, } from '../types'; /** @public */ @@ -34,16 +35,28 @@ export type TableState = { }; /** @public */ -export type GetIncidentsOpts = { +export type GetAlertsOpts = { maxResults?: number; startIndex?: number; - states?: IncidentStatus[]; + states?: AlertStatus[]; alertSources?: number[]; }; /** @public */ -export type GetIncidentsCountOpts = { - states?: IncidentStatus[]; +export type GetAlertsCountOpts = { + states?: AlertStatus[]; +}; + +/** @public */ +export type GetServicesOpts = { + maxResults?: number; + startIndex?: number; +}; + +/** @public */ +export type GetStatusPagesOpts = { + maxResults?: number; + startIndex?: number; }; /** @public */ @@ -57,27 +70,16 @@ export type EventRequest = { /** @public */ export interface ILertApi { - fetchIncidents(opts?: GetIncidentsOpts): Promise; - fetchIncidentsCount(opts?: GetIncidentsCountOpts): Promise; - fetchIncident(id: number): Promise; - fetchIncidentResponders(incident: Incident): Promise; - fetchIncidentActions(incident: Incident): Promise; - acceptIncident(incident: Incident, userName: string): Promise; - resolveIncident(incident: Incident, userName: string): Promise; - assignIncident( - incident: Incident, - responder: IncidentResponder, - ): Promise; - createIncident(eventRequest: EventRequest): Promise; - triggerIncidentAction( - incident: Incident, - action: IncidentAction, - ): Promise; - - fetchUptimeMonitors(): Promise; - pauseUptimeMonitor(uptimeMonitor: UptimeMonitor): Promise; - resumeUptimeMonitor(uptimeMonitor: UptimeMonitor): Promise; - fetchUptimeMonitor(id: number): Promise; + fetchAlerts(opts?: GetAlertsOpts): Promise; + fetchAlertsCount(opts?: GetAlertsCountOpts): Promise; + fetchAlert(id: number): Promise; + fetchAlertResponders(alert: Alert): Promise; + fetchAlertActions(alert: Alert): Promise; + acceptAlert(alert: Alert, userName: string): Promise; + resolveAlert(alert: Alert, userName: string): Promise; + assignAlert(alert: Alert, responder: AlertResponder): Promise; + createAlert(eventRequest: EventRequest): Promise; + triggerAlertAction(alert: Alert, action: AlertAction): Promise; fetchAlertSources(): Promise; fetchAlertSource(idOrIntegrationKey: number | string): Promise; @@ -100,11 +102,17 @@ export interface ILertApi { end: string, ): Promise; - getIncidentDetailsURL(incident: Incident): string; + fetchServices(opts?: GetServicesOpts): Promise; + + fetchStatusPages(opts?: GetStatusPagesOpts): Promise; + + getAlertDetailsURL(alert: Alert): string; getAlertSourceDetailsURL(alertSource: AlertSource | null): string; getEscalationPolicyDetailsURL(escalationPolicy: EscalationPolicy): string; - getUptimeMonitorDetailsURL(uptimeMonitor: UptimeMonitor): string; getScheduleDetailsURL(schedule: Schedule): string; + getServiceDetailsURL(service: Service): string; + getStatusPageDetailsURL(statusPage: StatusPage): string; + getStatusPageURL(statusPage: StatusPage): string; getUserPhoneNumber(user: User | null): string; getUserInitials(user: User | null): string; } diff --git a/plugins/ilert/src/components/Incident/IncidentActionsMenu.tsx b/plugins/ilert/src/components/Alert/AlertActionsMenu.tsx similarity index 73% rename from plugins/ilert/src/components/Incident/IncidentActionsMenu.tsx rename to plugins/ilert/src/components/Alert/AlertActionsMenu.tsx index 909e4dc362..ff20e39b24 100644 --- a/plugins/ilert/src/components/Incident/IncidentActionsMenu.tsx +++ b/plugins/ilert/src/components/Alert/AlertActionsMenu.tsx @@ -13,42 +13,42 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; import { IconButton, Menu, MenuItem, Typography } from '@material-ui/core'; import MoreVertIcon from '@material-ui/icons/MoreVert'; +import React from 'react'; import { ilertApiRef } from '../../api'; -import { Incident, IncidentAction } from '../../types'; -import { IncidentAssignModal } from './IncidentAssignModal'; -import { useIncidentActions } from '../../hooks/useIncidentActions'; +import { useAlertActions } from '../../hooks/useAlertActions'; +import { Alert, AlertAction } from '../../types'; +import { AlertAssignModal } from './AlertAssignModal'; +import { DEFAULT_NAMESPACE, parseEntityRef } from '@backstage/catalog-model'; +import { Link, Progress } from '@backstage/core-components'; import { alertApiRef, - useApi, identityApiRef, + useApi, } from '@backstage/core-plugin-api'; -import { Progress, Link } from '@backstage/core-components'; -import { DEFAULT_NAMESPACE, parseEntityRef } from '@backstage/catalog-model'; -export const IncidentActionsMenu = ({ - incident, - onIncidentChanged, +export const AlertActionsMenu = ({ + alert, + onAlertChanged, setIsLoading, }: { - incident: Incident; - onIncidentChanged?: (incident: Incident) => void; + alert: Alert; + onAlertChanged?: (alert: Alert) => void; setIsLoading?: (isLoading: boolean) => void; }) => { const ilertApi = useApi(ilertApiRef); const alertApi = useApi(alertApiRef); const identityApi = useApi(identityApiRef); const [anchorEl, setAnchorEl] = React.useState(null); - const callback = onIncidentChanged || ((_: Incident): void => {}); + const callback = onAlertChanged || ((_: Alert): void => {}); const setProcessing = setIsLoading || ((_: boolean): void => {}); - const [isAssignIncidentModalOpened, setIsAssignIncidentModalOpened] = + const [isAssignAlertModalOpened, setIsAssignAlertModalOpened] = React.useState(false); - const [{ incidentActions, isLoading }] = useIncidentActions( - incident, + const [{ alertActions, isLoading }] = useAlertActions( + alert, Boolean(anchorEl), ); @@ -70,10 +70,10 @@ export const IncidentActionsMenu = ({ defaultKind: 'User', defaultNamespace: DEFAULT_NAMESPACE, }); - const newIncident = await ilertApi.acceptIncident(incident, userName); - alertApi.post({ message: 'Incident accepted.' }); + const newAlert = await ilertApi.acceptAlert(alert, userName); + alertApi.post({ message: 'Alert accepted.' }); - callback(newIncident); + callback(newAlert); setProcessing(false); } catch (err) { setProcessing(false); @@ -90,10 +90,10 @@ export const IncidentActionsMenu = ({ defaultKind: 'User', defaultNamespace: DEFAULT_NAMESPACE, }); - const newIncident = await ilertApi.resolveIncident(incident, userName); - alertApi.post({ message: 'Incident resolved.' }); + const newAlert = await ilertApi.resolveAlert(alert, userName); + alertApi.post({ message: 'Alert resolved.' }); - callback(newIncident); + callback(newAlert); setProcessing(false); } catch (err) { setProcessing(false); @@ -103,15 +103,15 @@ export const IncidentActionsMenu = ({ const handleAssign = () => { handleCloseMenu(); - setIsAssignIncidentModalOpened(true); + setIsAssignAlertModalOpened(true); }; - const handleTriggerAction = (action: IncidentAction) => async () => { + const handleTriggerAction = (action: AlertAction) => async () => { try { handleCloseMenu(); setProcessing(true); - await ilertApi.triggerIncidentAction(incident, action); - alertApi.post({ message: 'Incident action triggered.' }); + await ilertApi.triggerAlertAction(alert, action); + alertApi.post({ message: 'Alert action triggered.' }); setProcessing(false); } catch (err) { setProcessing(false); @@ -119,7 +119,7 @@ export const IncidentActionsMenu = ({ } }; - const actions: React.ReactNode[] = incidentActions.map(a => { + const actions: React.ReactNode[] = alertActions.map(a => { const successTrigger = a.history ? a.history.find(h => h.success) : undefined; @@ -152,7 +152,7 @@ export const IncidentActionsMenu = ({ - {incident.status === 'PENDING' ? ( + {alert.status === 'PENDING' ? ( Accept @@ -169,7 +169,7 @@ export const IncidentActionsMenu = ({ ) : null} - {incident.status !== 'RESOLVED' ? ( + {alert.status !== 'RESOLVED' ? ( Resolve @@ -177,7 +177,7 @@ export const IncidentActionsMenu = ({ ) : null} - {incident.status !== 'RESOLVED' ? ( + {alert.status !== 'RESOLVED' ? ( Assign @@ -195,17 +195,15 @@ export const IncidentActionsMenu = ({ - - View in iLert - + View in iLert - ); diff --git a/plugins/ilert/src/components/Incident/IncidentAssignModal.tsx b/plugins/ilert/src/components/Alert/AlertAssignModal.tsx similarity index 76% rename from plugins/ilert/src/components/Incident/IncidentAssignModal.tsx rename to plugins/ilert/src/components/Alert/AlertAssignModal.tsx index 5fd8a97df3..d1ecefafa1 100644 --- a/plugins/ilert/src/components/Incident/IncidentAssignModal.tsx +++ b/plugins/ilert/src/components/Alert/AlertAssignModal.tsx @@ -13,21 +13,21 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; -import { makeStyles } from '@material-ui/core/styles'; -import Alert from '@material-ui/lab/Alert'; +import { alertApiRef, useApi } from '@backstage/core-plugin-api'; +import { Typography } from '@material-ui/core'; import Button from '@material-ui/core/Button'; -import TextField from '@material-ui/core/TextField'; import Dialog from '@material-ui/core/Dialog'; import DialogActions from '@material-ui/core/DialogActions'; import DialogContent from '@material-ui/core/DialogContent'; import DialogTitle from '@material-ui/core/DialogTitle'; +import { makeStyles } from '@material-ui/core/styles'; +import TextField from '@material-ui/core/TextField'; +import MUIAlert from '@material-ui/lab/Alert'; import Autocomplete from '@material-ui/lab/Autocomplete'; -import { useAssignIncident } from '../../hooks/useAssignIncident'; -import { Typography } from '@material-ui/core'; +import React from 'react'; import { ilertApiRef } from '../../api'; -import { Incident } from '../../types'; -import { alertApiRef, useApi } from '@backstage/core-plugin-api'; +import { useAssignAlert } from '../../hooks/useAssignAlert'; +import { Alert } from '../../types'; const useStyles = makeStyles(() => ({ container: { @@ -55,45 +55,42 @@ const useStyles = makeStyles(() => ({ }, })); -export const IncidentAssignModal = ({ - incident, +export const AlertAssignModal = ({ + alert, isModalOpened, setIsModalOpened, - onIncidentChanged, + onAlertChanged, }: { - incident: Incident | null; + alert: Alert | null; isModalOpened: boolean; setIsModalOpened: (open: boolean) => void; - onIncidentChanged?: (incident: Incident) => void; + onAlertChanged?: (alert: Alert) => void; }) => { const [ - { incidentRespondersList, incidentResponder, isLoading }, - { setIsLoading, setIncidentResponder, setIncidentRespondersList }, - ] = useAssignIncident(incident, isModalOpened); - const callback = onIncidentChanged || ((_: Incident): void => {}); + { alertRespondersList, alertResponder, isLoading }, + { setIsLoading, setAlertResponder, setAlertRespondersList }, + ] = useAssignAlert(alert, isModalOpened); + const callback = onAlertChanged || ((_: Alert): void => {}); const ilertApi = useApi(ilertApiRef); const alertApi = useApi(alertApiRef); const classes = useStyles(); const handleClose = () => { - setIncidentRespondersList([]); + setAlertRespondersList([]); setIsModalOpened(false); }; const handleAssign = () => { - if (!incident || !incidentResponder) { + if (!alert || !alertResponder) { return; } setIsLoading(true); - setIncidentRespondersList([]); + setAlertRespondersList([]); setTimeout(async () => { try { - const newIncident = await ilertApi.assignIncident( - incident, - incidentResponder, - ); - callback(newIncident); - alertApi.post({ message: 'Incident assigned.' }); + const newAlert = await ilertApi.assignAlert(alert, alertResponder); + callback(newAlert); + alertApi.post({ message: 'Alert assigned.' }); } catch (err) { alertApi.post({ message: err, severity: 'error' }); } @@ -102,33 +99,33 @@ export const IncidentAssignModal = ({ }, 250); }; - const canAssign = !!incidentResponder; + const canAssign = !!alertResponder; return ( - + Select responder to assign - + - This action will assign the incident to the selected responder. + This action will assign the alert to the selected responder. - + { - setIncidentResponder(newValue); + setAlertResponder(newValue); }} autoHighlight groupBy={option => { diff --git a/plugins/ilert/src/components/Alert/AlertLink.tsx b/plugins/ilert/src/components/Alert/AlertLink.tsx new file mode 100644 index 0000000000..4382d6608b --- /dev/null +++ b/plugins/ilert/src/components/Alert/AlertLink.tsx @@ -0,0 +1,31 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; +import { ilertApiRef } from '../../api'; +import { Alert } from '../../types'; + +import { Link } from '@backstage/core-components'; +import { useApi } from '@backstage/core-plugin-api'; + +export const AlertLink = ({ alert }: { alert: Alert | null }) => { + const ilertApi = useApi(ilertApiRef); + + if (!alert) { + return null; + } + + return #{alert.id}; +}; diff --git a/plugins/ilert/src/components/Incident/IncidentNewModal.tsx b/plugins/ilert/src/components/Alert/AlertNewModal.tsx similarity index 92% rename from plugins/ilert/src/components/Incident/IncidentNewModal.tsx rename to plugins/ilert/src/components/Alert/AlertNewModal.tsx index 561047d45e..66928e07e3 100644 --- a/plugins/ilert/src/components/Incident/IncidentNewModal.tsx +++ b/plugins/ilert/src/components/Alert/AlertNewModal.tsx @@ -13,27 +13,27 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; import { DEFAULT_NAMESPACE, parseEntityRef } from '@backstage/catalog-model'; -import { makeStyles } from '@material-ui/core/styles'; -import Alert from '@material-ui/lab/Alert'; -import Button from '@material-ui/core/Button'; -import TextField from '@material-ui/core/TextField'; -import Dialog from '@material-ui/core/Dialog'; -import DialogActions from '@material-ui/core/DialogActions'; -import DialogContent from '@material-ui/core/DialogContent'; -import DialogTitle from '@material-ui/core/DialogTitle'; -import Autocomplete from '@material-ui/lab/Autocomplete'; -import { useNewIncident } from '../../hooks/useNewIncident'; -import { Typography } from '@material-ui/core'; -import useMediaQuery from '@material-ui/core/useMediaQuery'; -import { ilertApiRef } from '../../api'; -import { AlertSource } from '../../types'; import { alertApiRef, identityApiRef, useApi, } from '@backstage/core-plugin-api'; +import { Typography } from '@material-ui/core'; +import Button from '@material-ui/core/Button'; +import Dialog from '@material-ui/core/Dialog'; +import DialogActions from '@material-ui/core/DialogActions'; +import DialogContent from '@material-ui/core/DialogContent'; +import DialogTitle from '@material-ui/core/DialogTitle'; +import { makeStyles } from '@material-ui/core/styles'; +import TextField from '@material-ui/core/TextField'; +import useMediaQuery from '@material-ui/core/useMediaQuery'; +import Alert from '@material-ui/lab/Alert'; +import Autocomplete from '@material-ui/lab/Autocomplete'; +import React from 'react'; +import { ilertApiRef } from '../../api'; +import { useNewAlert } from '../../hooks/useNewAlert'; +import { AlertSource } from '../../types'; const useStyles = makeStyles(() => ({ container: { @@ -61,23 +61,23 @@ const useStyles = makeStyles(() => ({ }, })); -export const IncidentNewModal = ({ +export const AlertNewModal = ({ isModalOpened, setIsModalOpened, - refetchIncidents, + refetchAlerts, initialAlertSource, entityName, }: { isModalOpened: boolean; setIsModalOpened: (open: boolean) => void; - refetchIncidents: () => void; + refetchAlerts: () => void; initialAlertSource?: AlertSource | null; entityName?: string; }) => { const [ { alertSources, alertSource, summary, details, isLoading }, { setAlertSource, setSummary, setDetails, setIsLoading }, - ] = useNewIncident(isModalOpened, initialAlertSource); + ] = useNewAlert(isModalOpened, initialAlertSource); const ilertApi = useApi(ilertApiRef); const alertApi = useApi(alertApiRef); const identityApi = useApi(identityApiRef); @@ -107,15 +107,15 @@ export const IncidentNewModal = ({ defaultKind: 'User', defaultNamespace: DEFAULT_NAMESPACE, }); - await ilertApi.createIncident({ + await ilertApi.createAlert({ integrationKey, summary, details, userName, source, }); - alertApi.post({ message: 'Incident created.' }); - refetchIncidents(); + alertApi.post({ message: 'Alert created.' }); + refetchAlerts(); } catch (err) { alertApi.post({ message: err, severity: 'error' }); } @@ -129,16 +129,16 @@ export const IncidentNewModal = ({ - + {entityName ? (
- This action will trigger an incident for{' '} + This action will trigger an alert for{' '} "{entityName}".
) : ( - 'New incident' + 'New alert' )}
diff --git a/plugins/ilert/src/components/UptimeMonitor/index.ts b/plugins/ilert/src/components/Alert/index.ts similarity index 93% rename from plugins/ilert/src/components/UptimeMonitor/index.ts rename to plugins/ilert/src/components/Alert/index.ts index a4aa2fb88c..265fa23f81 100644 --- a/plugins/ilert/src/components/UptimeMonitor/index.ts +++ b/plugins/ilert/src/components/Alert/index.ts @@ -13,4 +13,4 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export * from './UptimeMonitorActionsMenu'; +export * from './AlertActionsMenu'; diff --git a/plugins/ilert/src/components/IncidentsPage/IncidentsPage.tsx b/plugins/ilert/src/components/AlertsPage/AlertsPage.tsx similarity index 68% rename from plugins/ilert/src/components/IncidentsPage/IncidentsPage.tsx rename to plugins/ilert/src/components/AlertsPage/AlertsPage.tsx index 3dc0907777..053b5a4267 100644 --- a/plugins/ilert/src/components/IncidentsPage/IncidentsPage.tsx +++ b/plugins/ilert/src/components/AlertsPage/AlertsPage.tsx @@ -13,42 +13,41 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; -import { AuthenticationError } from '@backstage/errors'; -import Button from '@material-ui/core/Button'; -import AddIcon from '@material-ui/icons/Add'; -import { IncidentsTable } from './IncidentsTable'; -import { MissingAuthorizationHeaderError } from '../Errors'; -import { useIncidents } from '../../hooks/useIncidents'; -import { IncidentNewModal } from '../Incident/IncidentNewModal'; import { Content, ContentHeader, - SupportButton, ResponseErrorPanel, + SupportButton, } from '@backstage/core-components'; +import Button from '@material-ui/core/Button'; +import AddIcon from '@material-ui/icons/Add'; +import React from 'react'; +import { useAlerts } from '../../hooks/useAlerts'; +import { AlertNewModal } from '../Alert/AlertNewModal'; +import { MissingAuthorizationHeaderError } from '../Errors'; +import { AlertsTable } from './AlertsTable'; -export const IncidentsPage = () => { +export const AlertsPage = () => { const [ - { tableState, states, incidents, incidentsCount, isLoading, error }, + { tableState, states, alerts, alertsCount, isLoading, error }, { - onIncidentStatesChange, + onAlertStatesChange, onChangePage, onChangeRowsPerPage, - onIncidentChanged, - refetchIncidents, + onAlertChanged, + refetchAlerts, setIsLoading, }, - ] = useIncidents(true); + ] = useAlerts(true); const [isModalOpened, setIsModalOpened] = React.useState(false); - const handleCreateNewIncidentClick = () => { + const handleCreateNewAlertClick = () => { setIsModalOpened(true); }; if (error) { - if (error instanceof AuthenticationError) { + if (error.name === 'AuthenticationError') { return ( @@ -65,32 +64,32 @@ export const IncidentsPage = () => { return ( - + - This helps you to bring iLert into your developer portal. - ({ }, })); -export const IncidentsTable = ({ - incidents, - incidentsCount, +export const AlertsTable = ({ + alerts, + alertsCount, tableState, states, isLoading, - onIncidentChanged, + onAlertChanged, setIsLoading, - onIncidentStatesChange, + onAlertStatesChange, onChangePage, onChangeRowsPerPage, compact, }: { - incidents: Incident[]; - incidentsCount: number; + alerts: Alert[]; + alertsCount: number; tableState: TableState; - states: IncidentStatus[]; + states: AlertStatus[]; isLoading: boolean; - onIncidentChanged: (incident: Incident) => void; + onAlertChanged: (alert: Alert) => void; setIsLoading: (isLoading: boolean) => void; - onIncidentStatesChange: (states: IncidentStatus[]) => void; + onAlertStatesChange: (states: AlertStatus[]) => void; onChangePage: (page: number) => void; onChangeRowsPerPage: (pageSize: number) => void; compact?: boolean; @@ -86,31 +86,29 @@ export const IncidentsTable = ({ maxWidth: '30%', }; - const idColumn: TableColumn = { + const idColumn: TableColumn = { title: 'ID', field: 'id', highlight: true, cellStyle: smColumnStyle, headerStyle: smColumnStyle, - render: rowData => , + render: rowData => , }; - const summaryColumn: TableColumn = { + const summaryColumn: TableColumn = { title: 'Summary', field: 'summary', cellStyle: !compact ? xlColumnStyle : undefined, headerStyle: !compact ? xlColumnStyle : undefined, - render: rowData => {(rowData as Incident).summary}, + render: rowData => {rowData.summary}, }; - const sourceColumn: TableColumn = { + const sourceColumn: TableColumn = { title: 'Source', field: 'source', cellStyle: mdColumnStyle, headerStyle: mdColumnStyle, - render: rowData => ( - - ), + render: rowData => , }; - const durationColumn: TableColumn = { + const durationColumn: TableColumn = { title: 'Duration', field: 'reportTime', type: 'datetime', @@ -118,20 +116,17 @@ export const IncidentsTable = ({ headerStyle: smColumnStyle, render: rowData => ( - {(rowData as Incident).status !== 'RESOLVED' + {rowData.status !== 'RESOLVED' ? humanizeDuration( - Interval.fromDateTimes( - dt.fromISO((rowData as Incident).reportTime), - dt.now(), - ) + Interval.fromDateTimes(dt.fromISO(rowData.reportTime), dt.now()) .toDuration() .valueOf(), { units: ['h', 'm', 's'], largest: 2, round: true }, ) : humanizeDuration( Interval.fromDateTimes( - dt.fromISO((rowData as Incident).reportTime), - dt.fromISO((rowData as Incident).resolvedOn), + dt.fromISO(rowData.reportTime), + dt.fromISO(rowData.resolvedOn), ) .toDuration() .valueOf(), @@ -140,54 +135,59 @@ export const IncidentsTable = ({ ), }; - const assignedToColumn: TableColumn = { - title: 'Assigned to', - field: 'assignedTo', + const respondersColumn: TableColumn = { + title: 'Responders', + field: 'responders', cellStyle: !compact ? mdColumnStyle : lgColumnStyle, headerStyle: !compact ? mdColumnStyle : lgColumnStyle, render: rowData => ( - - {ilertApi.getUserInitials((rowData as Incident).assignedTo)} + + {rowData.responders.map((value, i, arr) => { + return ( + ilertApi.getUserInitials(value.user) + + (arr.length - 1 !== i ? ', ' : '') + ); + })} ), }; - const priorityColumn: TableColumn = { + const priorityColumn: TableColumn = { title: 'Priority', field: 'priority', cellStyle: smColumnStyle, headerStyle: smColumnStyle, render: rowData => ( - {(rowData as Incident).priority === 'HIGH' ? 'High' : 'Low'} + {rowData.priority === 'HIGH' ? 'High' : 'Low'} ), }; - const statusColumn: TableColumn = { + const statusColumn: TableColumn = { title: 'Status', field: 'status', cellStyle: xsColumnStyle, headerStyle: xsColumnStyle, - render: rowData => , + render: rowData => , }; - const actionsColumn: TableColumn = { + const actionsColumn: TableColumn = { title: '', field: '', cellStyle: xsColumnStyle, headerStyle: xsColumnStyle, render: rowData => ( - ), }; - const columns: TableColumn[] = compact + const columns: TableColumn[] = compact ? [ summaryColumn, durationColumn, - assignedToColumn, + respondersColumn, statusColumn, actionsColumn, ] @@ -196,31 +196,14 @@ export const IncidentsTable = ({ summaryColumn, sourceColumn, durationColumn, - assignedToColumn, + respondersColumn, priorityColumn, statusColumn, actionsColumn, ]; - let tableStyle: React.CSSProperties = {}; - if (compact) { - tableStyle = { - width: '100%', - maxWidth: '100%', - minWidth: '0', - height: 'calc(100% - 10px)', - boxShadow: 'none !important', - borderRadius: 'none !important', - }; - } else { - tableStyle = { - width: '100%', - maxWidth: '100%', - }; - } return ( - No incidents right now + No alerts right now } title={ !compact ? ( ) : ( - INCIDENTS + ALERTS ) } page={tableState.page} - totalCount={incidentsCount} + totalCount={alertsCount} onPageChange={onChangePage} onRowsPerPageChange={onChangeRowsPerPage} - // localization={{ header: { actions: undefined } }} columns={columns} - data={incidents} + data={alerts} isLoading={isLoading} /> ); diff --git a/plugins/ilert/src/components/IncidentsPage/StatusChip.tsx b/plugins/ilert/src/components/AlertsPage/StatusChip.tsx similarity index 79% rename from plugins/ilert/src/components/IncidentsPage/StatusChip.tsx rename to plugins/ilert/src/components/AlertsPage/StatusChip.tsx index 98521234de..55fe46d810 100644 --- a/plugins/ilert/src/components/IncidentsPage/StatusChip.tsx +++ b/plugins/ilert/src/components/AlertsPage/StatusChip.tsx @@ -13,10 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; import { Chip, withStyles } from '@material-ui/core'; -import { Incident, PENDING, ACCEPTED, RESOLVED } from '../../types'; -import { incidentStatusLabels } from '../Incident/IncidentStatus'; +import React from 'react'; +import { ACCEPTED, Alert, PENDING, RESOLVED } from '../../types'; const ResolvedChip = withStyles({ root: { @@ -41,10 +40,16 @@ const PendingChip = withStyles({ }, })(Chip); -export const StatusChip = ({ incident }: { incident: Incident }) => { - const label = `${incidentStatusLabels[incident.status]}`; +export const alertStatusLabels = { + [RESOLVED]: 'Resolved', + [ACCEPTED]: 'Accepted', + [PENDING]: 'Pending', +} as Record; - switch (incident.status) { +export const StatusChip = ({ alert }: { alert: Alert }) => { + const label = `${alertStatusLabels[alert.status]}`; + + switch (alert.status) { case RESOLVED: return ; case ACCEPTED: diff --git a/plugins/ilert/src/components/IncidentsPage/TableTitle.tsx b/plugins/ilert/src/components/AlertsPage/TableTitle.tsx similarity index 76% rename from plugins/ilert/src/components/IncidentsPage/TableTitle.tsx rename to plugins/ilert/src/components/AlertsPage/TableTitle.tsx index 8b8fde87f7..9aab95c6f3 100644 --- a/plugins/ilert/src/components/IncidentsPage/TableTitle.tsx +++ b/plugins/ilert/src/components/AlertsPage/TableTitle.tsx @@ -13,16 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; -import { PENDING, ACCEPTED, RESOLVED, IncidentStatus } from '../../types'; -import { incidentStatusLabels } from '../Incident/IncidentStatus'; +import Checkbox from '@material-ui/core/Checkbox'; import FormControl from '@material-ui/core/FormControl'; import ListItemText from '@material-ui/core/ListItemText'; -import Select from '@material-ui/core/Select'; -import Typography from '@material-ui/core/Typography'; import MenuItem from '@material-ui/core/MenuItem'; -import Checkbox from '@material-ui/core/Checkbox'; +import Select from '@material-ui/core/Select'; import { makeStyles } from '@material-ui/core/styles'; +import Typography from '@material-ui/core/Typography'; +import React from 'react'; +import { ACCEPTED, AlertStatus, PENDING, RESOLVED } from '../../types'; +import { alertStatusLabels } from './StatusChip'; const ITEM_HEIGHT = 48; const ITEM_PADDING_TOP = 8; @@ -53,15 +53,15 @@ const useStyles = makeStyles({ }); export const TableTitle = ({ - incidentStates, - onIncidentStatesChange, + alertStates, + onAlertStatesChange, }: { - incidentStates: IncidentStatus[]; - onIncidentStatesChange: (states: IncidentStatus[]) => void; + alertStates: AlertStatus[]; + onAlertStatesChange: (states: AlertStatus[]) => void; }) => { const classes = useStyles(); - const handleIncidentStatusSelectChange = (event: any) => { - onIncidentStatesChange(event.target.value); + const handleAlertStatusSelectChange = (event: any) => { + onAlertStatesChange(event.target.value); }; return ( @@ -75,19 +75,19 @@ export const TableTitle = ({ size="small" > diff --git a/plugins/ilert/src/components/IncidentsPage/index.ts b/plugins/ilert/src/components/AlertsPage/index.ts similarity index 90% rename from plugins/ilert/src/components/IncidentsPage/index.ts rename to plugins/ilert/src/components/AlertsPage/index.ts index a5ad4e65e6..c42d558dca 100644 --- a/plugins/ilert/src/components/IncidentsPage/index.ts +++ b/plugins/ilert/src/components/AlertsPage/index.ts @@ -13,5 +13,5 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export * from './IncidentsPage'; -export * from './IncidentsTable'; +export * from './AlertsPage'; +export * from './AlertsTable'; diff --git a/plugins/ilert/src/components/ILertCard/ILertCard.tsx b/plugins/ilert/src/components/ILertCard/ILertCard.tsx index 6330189a01..204c32369c 100644 --- a/plugins/ilert/src/components/ILertCard/ILertCard.tsx +++ b/plugins/ilert/src/components/ILertCard/ILertCard.tsx @@ -13,27 +13,27 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; import { Entity } from '@backstage/catalog-model'; +import { ResponseErrorPanel } from '@backstage/core-components'; import { AuthenticationError } from '@backstage/errors'; import Card from '@material-ui/core/Card'; import CardContent from '@material-ui/core/CardContent'; import CardHeader from '@material-ui/core/CardHeader'; import Divider from '@material-ui/core/Divider'; import { makeStyles } from '@material-ui/core/styles'; +import React from 'react'; import { ILERT_INTEGRATION_KEY_ANNOTATION } from '../../constants'; -import { MissingAuthorizationHeaderError } from '../Errors'; -import { useIncidents } from '../../hooks/useIncidents'; -import { IncidentsTable } from '../IncidentsPage'; -import { IncidentNewModal } from '../Incident/IncidentNewModal'; -import { ILertCardActionsHeader } from './ILertCardActionsHeader'; -import { useAlertSource } from '../../hooks/useAlertSource'; import { useILertEntity } from '../../hooks'; +import { useAlerts } from '../../hooks/useAlerts'; +import { useAlertSource } from '../../hooks/useAlertSource'; +import { AlertNewModal } from '../Alert/AlertNewModal'; +import { AlertsTable } from '../AlertsPage'; +import { MissingAuthorizationHeaderError } from '../Errors'; +import { ILertCardActionsHeader } from './ILertCardActionsHeader'; +import { ILertCardEmptyState } from './ILertCardEmptyState'; import { ILertCardHeaderStatus } from './ILertCardHeaderStatus'; import { ILertCardMaintenanceModal } from './ILertCardMaintenanceModal'; -import { ILertCardEmptyState } from './ILertCardEmptyState'; import { ILertCardOnCall } from './ILertCardOnCall'; -import { ResponseErrorPanel } from '@backstage/core-components'; /** @public */ export const isPluginApplicableToEntity = (entity: Entity) => @@ -55,23 +55,21 @@ const useStyles = makeStyles({ export const ILertCard = () => { const classes = useStyles(); const { integrationKey, name } = useILertEntity(); + const [{ alertSource }, { setAlertSource, refetchAlertSource }] = + useAlertSource(integrationKey); const [ - { alertSource, uptimeMonitor }, - { setAlertSource, refetchAlertSource }, - ] = useAlertSource(integrationKey); - const [ - { tableState, states, incidents, incidentsCount, isLoading, error }, + { tableState, states, alerts, alertsCount, isLoading, error }, { - onIncidentStatesChange, + onAlertStatesChange, onChangePage, onChangeRowsPerPage, - onIncidentChanged, - refetchIncidents, + onAlertChanged, + refetchAlerts, setIsLoading, }, - ] = useIncidents(false, true, alertSource); + ] = useAlerts(false, true, alertSource); - const [isNewIncidentModalOpened, setIsNewIncidentModalOpened] = + const [isNewAlertModalOpened, setIsNewAlertModalOpened] = React.useState(false); const [isMaintenanceModalOpened, setIsMaintenanceModalOpened] = React.useState(false); @@ -97,9 +95,8 @@ export const ILertCard = () => { } action={} @@ -107,13 +104,13 @@ export const ILertCard = () => { - { /> - diff --git a/plugins/ilert/src/components/ILertCard/ILertCardActionsHeader.tsx b/plugins/ilert/src/components/ILertCard/ILertCardActionsHeader.tsx index 4e0b562369..85118b29b9 100644 --- a/plugins/ilert/src/components/ILertCard/ILertCardActionsHeader.tsx +++ b/plugins/ilert/src/components/ILertCard/ILertCardActionsHeader.tsx @@ -13,49 +13,46 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; -import Alert from '@material-ui/lab/Alert'; import Button from '@material-ui/core/Button'; import Dialog from '@material-ui/core/Dialog'; import DialogActions from '@material-ui/core/DialogActions'; import DialogContent from '@material-ui/core/DialogContent'; import DialogTitle from '@material-ui/core/DialogTitle'; +import Typography from '@material-ui/core/Typography'; import AlarmAddIcon from '@material-ui/icons/AlarmAdd'; import BuildIcon from '@material-ui/icons/Build'; import PauseIcon from '@material-ui/icons/Pause'; import PlayArrowIcon from '@material-ui/icons/PlayArrow'; -import TimelineIcon from '@material-ui/icons/Timeline'; import WebIcon from '@material-ui/icons/Web'; -import Typography from '@material-ui/core/Typography'; +import Alert from '@material-ui/lab/Alert'; +import React from 'react'; import { ilertApiRef } from '../../api'; -import { AlertSource, UptimeMonitor } from '../../types'; +import { AlertSource } from '../../types'; import { HeaderIconLinkRow, IconLinkVerticalProps, } from '@backstage/core-components'; -import { useApi, alertApiRef } from '@backstage/core-plugin-api'; +import { alertApiRef, useApi } from '@backstage/core-plugin-api'; export const ILertCardActionsHeader = ({ alertSource, setAlertSource, - setIsNewIncidentModalOpened, + setIsNewAlertModalOpened, setIsMaintenanceModalOpened, - uptimeMonitor, }: { alertSource: AlertSource | null; setAlertSource: (alertSource: AlertSource) => void; - setIsNewIncidentModalOpened: (isOpen: boolean) => void; + setIsNewAlertModalOpened: (isOpen: boolean) => void; setIsMaintenanceModalOpened: (isOpen: boolean) => void; - uptimeMonitor: UptimeMonitor | null; }) => { const ilertApi = useApi(ilertApiRef); const alertApi = useApi(alertApiRef); const [isLoading, setIsLoading] = React.useState(false); const [isDisableModalOpened, setIsDisableModalOpened] = React.useState(false); - const handleCreateNewIncident = () => { - setIsNewIncidentModalOpened(true); + const handleCreateNewAlert = () => { + setIsNewAlertModalOpened(true); }; const handleEnableAlertSource = async () => { @@ -108,9 +105,9 @@ export const ILertCardActionsHeader = ({ icon: , }; - const createIncidentLink: IconLinkVerticalProps = { - label: 'Create Incident', - onClick: handleCreateNewIncident, + const createAlertLink: IconLinkVerticalProps = { + label: 'Create Alert', + onClick: handleCreateNewAlert, icon: , color: 'secondary', disabled: @@ -140,25 +137,14 @@ export const ILertCardActionsHeader = ({ disabled: !alertSource || isLoading, }; - const uptimeMonitorReportLink: IconLinkVerticalProps = { - label: 'Uptime Report', - href: uptimeMonitor ? uptimeMonitor.shareUrl : '', - icon: , - disabled: !alertSource || !uptimeMonitor || isLoading, - }; - const links: IconLinkVerticalProps[] = [ alertSourceLink, - createIncidentLink, + createAlertLink, alertSource && alertSource.active ? disableAlertSourceLink : enableAlertSourceLink, ]; - if (alertSource && alertSource.integrationType === 'MONITOR') { - links.push(uptimeMonitorReportLink); - } - if (alertSource && alertSource.status !== 'IN_MAINTENANCE') { links.push(maintenanceAlertSourceLink); } @@ -178,7 +164,7 @@ export const ILertCardActionsHeader = ({ Do you really want to disable this alert source? A disabled alert - source cannot create new incidents. + source cannot create new alerts. diff --git a/plugins/ilert/src/components/ILertPage/ILertPage.tsx b/plugins/ilert/src/components/ILertPage/ILertPage.tsx index 89dae215e4..5b95677ce8 100644 --- a/plugins/ilert/src/components/ILertPage/ILertPage.tsx +++ b/plugins/ilert/src/components/ILertPage/ILertPage.tsx @@ -13,34 +13,38 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; -import { IncidentsPage } from '../IncidentsPage'; -import { UptimeMonitorsPage } from '../UptimeMonitorsPage'; -import { OnCallSchedulesPage } from '../OnCallSchedulesPage'; import { - Page, - Header, - HeaderTabs, - HeaderLabel, Content, + Header, + HeaderLabel, + HeaderTabs, + Page, } from '@backstage/core-components'; +import React from 'react'; +import { AlertsPage } from '../AlertsPage'; +import { OnCallSchedulesPage } from '../OnCallSchedulesPage'; +import { ServicesPage } from '../ServicesPage'; +import { StatusPagesPage } from '../StatusPagePage'; /** @public */ export const ILertPage = () => { const [selectedTab, setSelectedTab] = React.useState(0); const tabs = [ { label: 'Who is on call?' }, - { label: 'Incidents' }, - { label: 'Uptime Monitors' }, + { label: 'Alerts' }, + { label: 'Services' }, + { label: 'Status pages' }, ]; const renderTab = () => { switch (selectedTab) { case 0: return ; case 1: - return ; + return ; case 2: - return ; + return ; + case 3: + return ; default: return null; } diff --git a/plugins/ilert/src/components/Incident/IncidentStatus.tsx b/plugins/ilert/src/components/Incident/IncidentStatus.tsx deleted file mode 100644 index e6eb1525f7..0000000000 --- a/plugins/ilert/src/components/Incident/IncidentStatus.tsx +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import React from 'react'; -import { makeStyles } from '@material-ui/core/styles'; -import Tooltip from '@material-ui/core/Tooltip'; -import { ACCEPTED, Incident, PENDING, RESOLVED } from '../../types'; -import { StatusError, StatusOK } from '@backstage/core-components'; - -const useStyles = makeStyles({ - denseListIcon: { - marginRight: 0, - display: 'flex', - flexDirection: 'column', - alignItems: 'center', - justifyContent: 'center', - }, -}); - -export const incidentStatusLabels = { - [RESOLVED]: 'Resolved', - [ACCEPTED]: 'Accepted', - [PENDING]: 'Pending', -} as Record; - -export const IncidentStatus = ({ incident }: { incident: Incident }) => { - const classes = useStyles(); - - return ( - -
- {incident.status === 'PENDING' ? : } -
-
- ); -}; diff --git a/plugins/ilert/src/components/OnCallSchedulesPage/OnCallShiftItem.tsx b/plugins/ilert/src/components/OnCallSchedulesPage/OnCallShiftItem.tsx index e0fa82fe85..8ef5a990ec 100644 --- a/plugins/ilert/src/components/OnCallSchedulesPage/OnCallShiftItem.tsx +++ b/plugins/ilert/src/components/OnCallSchedulesPage/OnCallShiftItem.tsx @@ -13,14 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; +import { useApi } from '@backstage/core-plugin-api'; import Button from '@material-ui/core/Button'; import Grid from '@material-ui/core/Grid'; +import { makeStyles } from '@material-ui/core/styles'; import Typography from '@material-ui/core/Typography'; import RepeatIcon from '@material-ui/icons/Repeat'; -import { Shift } from '../../types'; import { DateTime as dt } from 'luxon'; -import { makeStyles } from '@material-ui/core/styles'; +import React from 'react'; +import { ilertApiRef } from '../../api'; +import { Shift } from '../../types'; import { ShiftOverrideModal } from '../Shift/ShiftOverrideModal'; const useStyles = makeStyles({ @@ -48,6 +50,7 @@ export const OnCallShiftItem = ({ refetchOnCallSchedules: () => void; }) => { const classes = useStyles(); + const ilertApi = useApi(ilertApiRef); const [isModalOpened, setIsModalOpened] = React.useState(false); const handleOverride = () => { @@ -71,7 +74,7 @@ export const OnCallShiftItem = ({ {shift && shift.user ? ( - {`${shift.user.firstName} ${shift.user.lastName} (${shift.user.username})`} + {ilertApi.getUserInitials(shift.user)} ) : null} diff --git a/plugins/ilert/src/components/Service/ServiceActionsMenu.tsx b/plugins/ilert/src/components/Service/ServiceActionsMenu.tsx new file mode 100644 index 0000000000..096cc391ee --- /dev/null +++ b/plugins/ilert/src/components/Service/ServiceActionsMenu.tsx @@ -0,0 +1,68 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { IconButton, Menu, MenuItem, Typography } from '@material-ui/core'; +import MoreVertIcon from '@material-ui/icons/MoreVert'; +import React from 'react'; +import { ilertApiRef } from '../../api'; +import { Service } from '../../types'; + +import { Link } from '@backstage/core-components'; +import { useApi } from '@backstage/core-plugin-api'; + +export const ServiceActionsMenu = ({ service }: { service: Service }) => { + const ilertApi = useApi(ilertApiRef); + const [anchorEl, setAnchorEl] = React.useState(null); + + const handleClick = (event: React.MouseEvent) => { + setAnchorEl(event.currentTarget); + }; + + const handleCloseMenu = () => { + setAnchorEl(null); + }; + + return ( + <> + + + + + + + + View in iLert + + + + + + ); +}; diff --git a/plugins/ilert/src/components/Service/ServiceLink.tsx b/plugins/ilert/src/components/Service/ServiceLink.tsx new file mode 100644 index 0000000000..8e7868a2d2 --- /dev/null +++ b/plugins/ilert/src/components/Service/ServiceLink.tsx @@ -0,0 +1,31 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; +import { ilertApiRef } from '../../api'; +import { Service } from '../../types'; + +import { Link } from '@backstage/core-components'; +import { useApi } from '@backstage/core-plugin-api'; + +export const ServiceLink = ({ service }: { service: Service | null }) => { + const ilertApi = useApi(ilertApiRef); + + if (!service) { + return null; + } + + return #{service.id}; +}; diff --git a/plugins/ilert/src/components/Incident/index.ts b/plugins/ilert/src/components/Service/index.ts similarity index 89% rename from plugins/ilert/src/components/Incident/index.ts rename to plugins/ilert/src/components/Service/index.ts index 5065cb1de9..11b2148046 100644 --- a/plugins/ilert/src/components/Incident/index.ts +++ b/plugins/ilert/src/components/Service/index.ts @@ -13,5 +13,4 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export * from './IncidentActionsMenu'; -export * from './IncidentStatus'; +export * from './ServiceActionsMenu'; diff --git a/plugins/ilert/src/components/UptimeMonitorsPage/UptimeMonitorsPage.tsx b/plugins/ilert/src/components/ServicesPage/ServicesPage.tsx similarity index 70% rename from plugins/ilert/src/components/UptimeMonitorsPage/UptimeMonitorsPage.tsx rename to plugins/ilert/src/components/ServicesPage/ServicesPage.tsx index 33612d492b..0e012b07a2 100644 --- a/plugins/ilert/src/components/UptimeMonitorsPage/UptimeMonitorsPage.tsx +++ b/plugins/ilert/src/components/ServicesPage/ServicesPage.tsx @@ -13,26 +13,25 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; -import { AuthenticationError } from '@backstage/errors'; -import { UptimeMonitorsTable } from './UptimeMonitorsTable'; -import { MissingAuthorizationHeaderError } from '../Errors'; -import { useUptimeMonitors } from '../../hooks/useUptimeMonitors'; import { Content, ContentHeader, - SupportButton, ResponseErrorPanel, + SupportButton, } from '@backstage/core-components'; +import React from 'react'; +import { useServices } from '../../hooks/useServices'; +import { MissingAuthorizationHeaderError } from '../Errors'; +import { ServicesTable } from './ServicesTable'; -export const UptimeMonitorsPage = () => { +export const ServicesPage = () => { const [ - { tableState, uptimeMonitors, isLoading, error }, - { onChangePage, onChangeRowsPerPage, onUptimeMonitorChanged }, - ] = useUptimeMonitors(); + { tableState, services, isLoading, error }, + { onChangePage, onChangeRowsPerPage, setIsLoading }, + ] = useServices(true); if (error) { - if (error instanceof AuthenticationError) { + if (error.name === 'AuthenticationError') { return ( @@ -49,18 +48,18 @@ export const UptimeMonitorsPage = () => { return ( - + This helps you to bring iLert into your developer portal. - ); diff --git a/plugins/ilert/src/components/ServicesPage/ServicesTable.tsx b/plugins/ilert/src/components/ServicesPage/ServicesTable.tsx new file mode 100644 index 0000000000..30a83e4cec --- /dev/null +++ b/plugins/ilert/src/components/ServicesPage/ServicesTable.tsx @@ -0,0 +1,137 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { makeStyles } from '@material-ui/core/styles'; +import Typography from '@material-ui/core/Typography'; +import React from 'react'; +import { TableState } from '../../api'; +import { Service } from '../../types'; +import { StatusChip } from './StatusChip'; + +import { Table, TableColumn } from '@backstage/core-components'; +import { ServiceActionsMenu } from '../Service/ServiceActionsMenu'; +import { ServiceLink } from '../Service/ServiceLink'; + +const useStyles = makeStyles(theme => ({ + empty: { + padding: theme.spacing(2), + display: 'flex', + justifyContent: 'center', + }, +})); + +export const ServicesTable = ({ + services, + tableState, + isLoading, + onChangePage, + onChangeRowsPerPage, + compact, +}: { + services: Service[]; + tableState: TableState; + isLoading: boolean; + setIsLoading: (isLoading: boolean) => void; + onChangePage: (page: number) => void; + onChangeRowsPerPage: (pageSize: number) => void; + compact?: boolean; +}) => { + const classes = useStyles(); + + const smColumnStyle = { + width: '10%', + maxWidth: '10%', + }; + const xlColumnStyle = { + width: '30%', + maxWidth: '30%', + }; + const idColumn: TableColumn = { + title: 'ID', + field: 'id', + highlight: true, + cellStyle: smColumnStyle, + headerStyle: smColumnStyle, + render: rowData => , + }; + const nameColumn: TableColumn = { + title: 'Name', + field: 'name', + cellStyle: !compact ? xlColumnStyle : undefined, + headerStyle: !compact ? xlColumnStyle : undefined, + render: rowData => {rowData.name}, + }; + const statusColumn: TableColumn = { + title: 'Status', + field: 'status', + cellStyle: smColumnStyle, + headerStyle: smColumnStyle, + render: rowData => , + }; + const uptimeColumn: TableColumn = { + title: 'Uptime in the last 90 days', + field: 'uptimePercentage', + cellStyle: smColumnStyle, + headerStyle: smColumnStyle, + render: rowData => ( + {rowData.uptime.uptimePercentage.p90} + ), + }; + const actionsColumn: TableColumn = { + title: '', + field: '', + cellStyle: smColumnStyle, + headerStyle: smColumnStyle, + render: rowData => , + }; + + const columns: TableColumn[] = compact + ? [nameColumn, statusColumn, uptimeColumn, actionsColumn] + : [idColumn, nameColumn, statusColumn, uptimeColumn, actionsColumn]; + + return ( +
+ No services + + } + title={ + + SERVICES + + } + page={tableState.page} + onPageChange={onChangePage} + onRowsPerPageChange={onChangeRowsPerPage} + columns={columns} + data={services} + isLoading={isLoading} + /> + ); +}; diff --git a/plugins/ilert/src/components/ServicesPage/StatusChip.tsx b/plugins/ilert/src/components/ServicesPage/StatusChip.tsx new file mode 100644 index 0000000000..873bfbfb28 --- /dev/null +++ b/plugins/ilert/src/components/ServicesPage/StatusChip.tsx @@ -0,0 +1,89 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { Chip, withStyles } from '@material-ui/core'; +import React from 'react'; +import { + DEGRADED, + MAJOR_OUTAGE, + OPERATIONAL, + PARTIAL_OUTAGE, + Service, + UNDER_MAINTENANCE, +} from '../../types'; + +const OperationalChip = withStyles({ + root: { + backgroundColor: '#388E3D', + color: 'white', + margin: 0, + }, +})(Chip); + +const UnderMaintenanceChip = withStyles({ + root: { + backgroundColor: '#616161', + color: 'white', + margin: 0, + }, +})(Chip); +const DegradedChip = withStyles({ + root: { + backgroundColor: '#FBC02D', + color: 'white', + margin: 0, + }, +})(Chip); +const PartialOutageChip = withStyles({ + root: { + backgroundColor: '#F57C02', + color: 'white', + margin: 0, + }, +})(Chip); +const MajorOutageChip = withStyles({ + root: { + backgroundColor: '#D22F2E', + color: 'white', + margin: 0, + }, +})(Chip); + +const serviceStatusLabels = { + [OPERATIONAL]: 'Operational', + [UNDER_MAINTENANCE]: 'Under maintenance', + [DEGRADED]: 'Degraded', + [PARTIAL_OUTAGE]: 'Partial outage', + [MAJOR_OUTAGE]: 'Major outage', +} as Record; + +export const StatusChip = ({ service }: { service: Service }) => { + const label = `${serviceStatusLabels[service.status]}`; + + switch (service.status) { + case OPERATIONAL: + return ; + case UNDER_MAINTENANCE: + return ; + case DEGRADED: + return ; + case PARTIAL_OUTAGE: + return ; + case MAJOR_OUTAGE: + return ; + default: + return ; + } +}; diff --git a/plugins/ilert/src/components/UptimeMonitorsPage/index.ts b/plugins/ilert/src/components/ServicesPage/index.ts similarity index 88% rename from plugins/ilert/src/components/UptimeMonitorsPage/index.ts rename to plugins/ilert/src/components/ServicesPage/index.ts index 865783b5e6..6c4eef8100 100644 --- a/plugins/ilert/src/components/UptimeMonitorsPage/index.ts +++ b/plugins/ilert/src/components/ServicesPage/index.ts @@ -13,5 +13,5 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export * from './UptimeMonitorsPage'; -export * from './UptimeMonitorsTable'; +export * from './ServicesPage'; +export * from './ServicesTable'; diff --git a/plugins/ilert/src/components/StatusPage/StatusPageActionsMenu.tsx b/plugins/ilert/src/components/StatusPage/StatusPageActionsMenu.tsx new file mode 100644 index 0000000000..8fe94eb2e1 --- /dev/null +++ b/plugins/ilert/src/components/StatusPage/StatusPageActionsMenu.tsx @@ -0,0 +1,79 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { IconButton, Menu, MenuItem, Typography } from '@material-ui/core'; +import MoreVertIcon from '@material-ui/icons/MoreVert'; +import React from 'react'; +import { ilertApiRef } from '../../api'; +import { StatusPage } from '../../types'; + +import { Link } from '@backstage/core-components'; +import { useApi } from '@backstage/core-plugin-api'; + +export const StatusPageActionsMenu = ({ + statusPage, +}: { + statusPage: StatusPage; +}) => { + const ilertApi = useApi(ilertApiRef); + const [anchorEl, setAnchorEl] = React.useState(null); + + const handleClick = (event: React.MouseEvent) => { + setAnchorEl(event.currentTarget); + }; + + const handleCloseMenu = () => { + setAnchorEl(null); + }; + + return ( + <> + + + + + + + + View in iLert + + + + + + + View status page + + + + + + ); +}; diff --git a/plugins/ilert/src/components/Incident/IncidentLink.tsx b/plugins/ilert/src/components/StatusPage/StatusPageLink.tsx similarity index 67% rename from plugins/ilert/src/components/Incident/IncidentLink.tsx rename to plugins/ilert/src/components/StatusPage/StatusPageLink.tsx index 381a656b72..edc6922e0d 100644 --- a/plugins/ilert/src/components/Incident/IncidentLink.tsx +++ b/plugins/ilert/src/components/StatusPage/StatusPageLink.tsx @@ -14,33 +14,26 @@ * limitations under the License. */ import React from 'react'; -import { makeStyles } from '@material-ui/core/styles'; -import { Incident } from '../../types'; import { ilertApiRef } from '../../api'; +import { StatusPage } from '../../types'; -import { useApi } from '@backstage/core-plugin-api'; import { Link } from '@backstage/core-components'; +import { useApi } from '@backstage/core-plugin-api'; -const useStyles = makeStyles({ - link: { - lineHeight: '22px', - }, -}); - -export const IncidentLink = ({ incident }: { incident: Incident | null }) => { +export const StatusPageLink = ({ + statusPage, +}: { + statusPage: StatusPage | null; +}) => { const ilertApi = useApi(ilertApiRef); - const classes = useStyles(); - if (!incident) { + if (!statusPage) { return null; } return ( - - #{incident.id} + + #{statusPage.id} ); }; diff --git a/plugins/ilert/src/components/UptimeMonitor/UptimeMonitorLink.tsx b/plugins/ilert/src/components/StatusPage/StatusPageURL.tsx similarity index 63% rename from plugins/ilert/src/components/UptimeMonitor/UptimeMonitorLink.tsx rename to plugins/ilert/src/components/StatusPage/StatusPageURL.tsx index c70a8a40f0..1aaf209770 100644 --- a/plugins/ilert/src/components/UptimeMonitor/UptimeMonitorLink.tsx +++ b/plugins/ilert/src/components/StatusPage/StatusPageURL.tsx @@ -14,37 +14,24 @@ * limitations under the License. */ import React from 'react'; -import { makeStyles } from '@material-ui/core/styles'; -import { UptimeMonitor } from '../../types'; import { ilertApiRef } from '../../api'; +import { StatusPage } from '../../types'; -import { useApi } from '@backstage/core-plugin-api'; import { Link } from '@backstage/core-components'; +import { useApi } from '@backstage/core-plugin-api'; -const useStyles = makeStyles({ - link: { - lineHeight: '22px', - }, -}); - -export const UptimeMonitorLink = ({ - uptimeMonitor, +export const StatusPageURL = ({ + statusPage, }: { - uptimeMonitor: UptimeMonitor | null; + statusPage: StatusPage | null; }) => { const ilertApi = useApi(ilertApiRef); - const classes = useStyles(); - if (!uptimeMonitor) { + if (!statusPage) { return null; } - return ( - - #{uptimeMonitor.id} - - ); + const url = ilertApi.getStatusPageURL(statusPage); + + return {url}; }; diff --git a/plugins/ilert/src/components/StatusPage/index.ts b/plugins/ilert/src/components/StatusPage/index.ts new file mode 100644 index 0000000000..d805811d90 --- /dev/null +++ b/plugins/ilert/src/components/StatusPage/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export * from './StatusPageActionsMenu'; diff --git a/plugins/ilert/src/components/StatusPagePage/StatusChip.tsx b/plugins/ilert/src/components/StatusPagePage/StatusChip.tsx new file mode 100644 index 0000000000..5cd785c4cd --- /dev/null +++ b/plugins/ilert/src/components/StatusPagePage/StatusChip.tsx @@ -0,0 +1,89 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { Chip, withStyles } from '@material-ui/core'; +import React from 'react'; +import { + DEGRADED, + MAJOR_OUTAGE, + OPERATIONAL, + PARTIAL_OUTAGE, + StatusPage, + UNDER_MAINTENANCE, +} from '../../types'; + +const OperationalChip = withStyles({ + root: { + backgroundColor: '#388E3D', + color: 'white', + margin: 0, + }, +})(Chip); + +const UnderMaintenanceChip = withStyles({ + root: { + backgroundColor: '#616161', + color: 'white', + margin: 0, + }, +})(Chip); +const DegradedChip = withStyles({ + root: { + backgroundColor: '#FBC02D', + color: 'white', + margin: 0, + }, +})(Chip); +const PartialOutageChip = withStyles({ + root: { + backgroundColor: '#F57C02', + color: 'white', + margin: 0, + }, +})(Chip); +const MajorOutageChip = withStyles({ + root: { + backgroundColor: '#D22F2E', + color: 'white', + margin: 0, + }, +})(Chip); + +const statusPageStatusLabels = { + [OPERATIONAL]: 'Operational', + [UNDER_MAINTENANCE]: 'Under maintenance', + [DEGRADED]: 'Degraded', + [PARTIAL_OUTAGE]: 'Partial outage', + [MAJOR_OUTAGE]: 'Major outage', +} as Record; + +export const StatusChip = ({ statusPage }: { statusPage: StatusPage }) => { + const label = `${statusPageStatusLabels[statusPage.status]}`; + + switch (statusPage.status) { + case OPERATIONAL: + return ; + case UNDER_MAINTENANCE: + return ; + case DEGRADED: + return ; + case PARTIAL_OUTAGE: + return ; + case MAJOR_OUTAGE: + return ; + default: + return ; + } +}; diff --git a/plugins/ilert/src/components/StatusPagePage/StatusPagesPage.tsx b/plugins/ilert/src/components/StatusPagePage/StatusPagesPage.tsx new file mode 100644 index 0000000000..9d709c5e73 --- /dev/null +++ b/plugins/ilert/src/components/StatusPagePage/StatusPagesPage.tsx @@ -0,0 +1,66 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + Content, + ContentHeader, + ResponseErrorPanel, + SupportButton, +} from '@backstage/core-components'; +import React from 'react'; +import { useStatusPages } from '../../hooks/useStatusPages'; +import { MissingAuthorizationHeaderError } from '../Errors'; +import { StatusPagesTable } from './StatusPagesTable'; + +export const StatusPagesPage = () => { + const [ + { tableState, statusPages, isLoading, error }, + { onChangePage, onChangeRowsPerPage, setIsLoading }, + ] = useStatusPages(true); + + if (error) { + if (error.name === 'AuthenticationError') { + return ( + + + + ); + } + + return ( + + + + ); + } + + return ( + + + + This helps you to bring iLert into your developer portal. + + + + + ); +}; diff --git a/plugins/ilert/src/components/StatusPagePage/StatusPagesTable.tsx b/plugins/ilert/src/components/StatusPagePage/StatusPagesTable.tsx new file mode 100644 index 0000000000..8e965b42de --- /dev/null +++ b/plugins/ilert/src/components/StatusPagePage/StatusPagesTable.tsx @@ -0,0 +1,152 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { makeStyles } from '@material-ui/core/styles'; +import Typography from '@material-ui/core/Typography'; +import React from 'react'; +import { TableState } from '../../api'; +import { StatusPage } from '../../types'; +import { VisibilityChip } from './VisibilityChip'; + +import { Table, TableColumn } from '@backstage/core-components'; +import { StatusPageActionsMenu } from '../StatusPage/StatusPageActionsMenu'; +import { StatusPageLink } from '../StatusPage/StatusPageLink'; +import { StatusPageURL } from '../StatusPage/StatusPageURL'; +import { StatusChip } from './StatusChip'; + +const useStyles = makeStyles(theme => ({ + empty: { + padding: theme.spacing(2), + display: 'flex', + justifyContent: 'center', + }, +})); + +export const StatusPagesTable = ({ + statusPages, + tableState, + isLoading, + onChangePage, + onChangeRowsPerPage, + compact, +}: { + statusPages: StatusPage[]; + tableState: TableState; + isLoading: boolean; + setIsLoading: (isLoading: boolean) => void; + onChangePage: (page: number) => void; + onChangeRowsPerPage: (pageSize: number) => void; + compact?: boolean; +}) => { + const classes = useStyles(); + + const smColumnStyle = { + width: '10%', + maxWidth: '10%', + }; + const xlColumnStyle = { + width: '30%', + maxWidth: '30%', + }; + + const idColumn: TableColumn = { + title: 'ID', + field: 'id', + highlight: true, + cellStyle: smColumnStyle, + headerStyle: smColumnStyle, + render: rowData => , + }; + const nameColumn: TableColumn = { + title: 'Name', + field: 'name', + cellStyle: !compact ? xlColumnStyle : undefined, + headerStyle: !compact ? xlColumnStyle : undefined, + render: rowData => {rowData.name}, + }; + const urlColumn: TableColumn = { + title: 'URL', + field: 'url', + cellStyle: smColumnStyle, + headerStyle: smColumnStyle, + render: rowData => , + }; + const visibilityColumn: TableColumn = { + title: 'Visibility', + field: 'visibility', + cellStyle: smColumnStyle, + headerStyle: smColumnStyle, + render: rowData => , + }; + const statusColumn: TableColumn = { + title: 'Status', + field: 'status', + cellStyle: smColumnStyle, + headerStyle: smColumnStyle, + render: rowData => , + }; + const actionsColumn: TableColumn = { + title: '', + field: '', + cellStyle: smColumnStyle, + headerStyle: smColumnStyle, + render: rowData => , + }; + + const columns: TableColumn[] = compact + ? [nameColumn, statusColumn, urlColumn, actionsColumn] + : [ + idColumn, + nameColumn, + statusColumn, + urlColumn, + visibilityColumn, + actionsColumn, + ]; + + return ( +
+ No status pages + + } + title={ + + STATUS PAGES + + } + page={tableState.page} + onPageChange={onChangePage} + onRowsPerPageChange={onChangeRowsPerPage} + columns={columns} + data={statusPages} + isLoading={isLoading} + /> + ); +}; diff --git a/plugins/ilert/src/components/StatusPagePage/VisibilityChip.tsx b/plugins/ilert/src/components/StatusPagePage/VisibilityChip.tsx new file mode 100644 index 0000000000..04e8019d3d --- /dev/null +++ b/plugins/ilert/src/components/StatusPagePage/VisibilityChip.tsx @@ -0,0 +1,52 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { Chip, withStyles } from '@material-ui/core'; +import React from 'react'; +import { PRIVATE, PUBLIC, StatusPage } from '../../types'; + +const PrivateChip = withStyles({ + root: { + backgroundColor: '#4caf50', + color: 'white', + margin: 0, + }, +})(Chip); + +const PublicChip = withStyles({ + root: { + backgroundColor: '#ffb74d', + color: 'white', + margin: 0, + }, +})(Chip); + +const statusPageVisibilityLabels = { + [PUBLIC]: 'Public', + [PRIVATE]: 'Private', +} as Record; + +export const VisibilityChip = ({ statusPage }: { statusPage: StatusPage }) => { + const label = `${statusPageVisibilityLabels[statusPage.visibility]}`; + + switch (statusPage.visibility) { + case PRIVATE: + return ; + case PUBLIC: + return ; + default: + return ; + } +}; diff --git a/plugins/ilert/src/components/StatusPagePage/index.ts b/plugins/ilert/src/components/StatusPagePage/index.ts new file mode 100644 index 0000000000..8bc923ffe1 --- /dev/null +++ b/plugins/ilert/src/components/StatusPagePage/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export * from './StatusPagesPage'; +export * from './StatusPagesTable'; diff --git a/plugins/ilert/src/components/UptimeMonitor/UptimeMonitorActionsMenu.tsx b/plugins/ilert/src/components/UptimeMonitor/UptimeMonitorActionsMenu.tsx deleted file mode 100644 index 2d25f8e50e..0000000000 --- a/plugins/ilert/src/components/UptimeMonitor/UptimeMonitorActionsMenu.tsx +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import React from 'react'; -import { IconButton, Menu, MenuItem, Typography } from '@material-ui/core'; -import MoreVertIcon from '@material-ui/icons/MoreVert'; - -import { ilertApiRef } from '../../api'; -import { UptimeMonitor } from '../../types'; - -import { alertApiRef, useApi } from '@backstage/core-plugin-api'; -import { Link } from '@backstage/core-components'; - -export const UptimeMonitorActionsMenu = ({ - uptimeMonitor, - onUptimeMonitorChanged, -}: { - uptimeMonitor: UptimeMonitor; - onUptimeMonitorChanged?: (uptimeMonitor: UptimeMonitor) => void; -}) => { - const ilertApi = useApi(ilertApiRef); - const alertApi = useApi(alertApiRef); - const [anchorEl, setAnchorEl] = React.useState(null); - const callback = onUptimeMonitorChanged || ((_: UptimeMonitor): void => {}); - - const handleClick = (event: React.MouseEvent) => { - setAnchorEl(event.currentTarget); - }; - - const handleCloseMenu = () => { - setAnchorEl(null); - }; - - const handlePause = async (): Promise => { - try { - const newUptimeMonitor = await ilertApi.pauseUptimeMonitor(uptimeMonitor); - handleCloseMenu(); - alertApi.post({ message: 'Uptime monitor paused.' }); - - callback(newUptimeMonitor); - } catch (err) { - alertApi.post({ message: err, severity: 'error' }); - } - }; - - const handleResume = async (): Promise => { - try { - const newUptimeMonitor = await ilertApi.resumeUptimeMonitor( - uptimeMonitor, - ); - handleCloseMenu(); - alertApi.post({ message: 'Uptime monitor resumed.' }); - - callback(newUptimeMonitor); - } catch (err) { - alertApi.post({ message: err, severity: 'error' }); - } - }; - - const handleOpenReport = async (): Promise => { - try { - const um = await ilertApi.fetchUptimeMonitor(uptimeMonitor.id); - handleCloseMenu(); - window.open(um.shareUrl, '_blank'); - } catch (err) { - alertApi.post({ message: err, severity: 'error' }); - } - }; - - return ( - <> - - - - - {uptimeMonitor.paused ? ( - - - Resume - - - ) : null} - - {!uptimeMonitor.paused ? ( - - - Pause - - - ) : null} - - - - - View Report - - - - - - - - View in iLert - - - - - - ); -}; diff --git a/plugins/ilert/src/components/UptimeMonitorsPage/StatusChip.tsx b/plugins/ilert/src/components/UptimeMonitorsPage/StatusChip.tsx deleted file mode 100644 index 39f3ef250b..0000000000 --- a/plugins/ilert/src/components/UptimeMonitorsPage/StatusChip.tsx +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import React from 'react'; -import Chip from '@material-ui/core/Chip'; -import { withStyles } from '@material-ui/core/styles'; -import { UptimeMonitor } from '../../types'; - -const UpChip = withStyles({ - root: { - backgroundColor: '#4caf50', - color: 'white', - margin: 0, - }, -})(Chip); - -const DownChip = withStyles({ - root: { - backgroundColor: '#d32f2f', - color: 'white', - margin: 0, - }, -})(Chip); - -const UnknownChip = withStyles({ - root: { - backgroundColor: '#92949c', - color: 'white', - margin: 0, - }, -})(Chip); - -export const uptimeMonitorStatusLabels = { - ['up']: 'Up', - ['down']: 'Down', - ['unknown']: 'Unknown', -} as Record; - -export const StatusChip = ({ - uptimeMonitor, -}: { - uptimeMonitor: UptimeMonitor; -}) => { - let label = `${uptimeMonitorStatusLabels[uptimeMonitor.status]}`; - - if (uptimeMonitor.paused) { - label = 'Paused'; - return ; - } - - switch (uptimeMonitor.status) { - case 'up': - return ; - case 'down': - return ; - case 'unknown': - return ; - default: - return ; - } -}; diff --git a/plugins/ilert/src/components/UptimeMonitorsPage/UptimeMonitorCheckType.tsx b/plugins/ilert/src/components/UptimeMonitorsPage/UptimeMonitorCheckType.tsx deleted file mode 100644 index a39901836f..0000000000 --- a/plugins/ilert/src/components/UptimeMonitorsPage/UptimeMonitorCheckType.tsx +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React from 'react'; -import { UptimeMonitor } from '../../types'; -import Typography from '@material-ui/core/Typography'; - -export const UptimeMonitorCheckType = ({ - uptimeMonitor, -}: { - uptimeMonitor: UptimeMonitor; -}) => { - switch (uptimeMonitor.region) { - case 'EU': - return ( - {`${uptimeMonitor.checkType.toUpperCase()} πŸ‡©πŸ‡ͺ`} - ); - default: - return ( - {`${uptimeMonitor.checkType.toUpperCase()} πŸ‡ΊπŸ‡Έ`} - ); - } -}; diff --git a/plugins/ilert/src/components/UptimeMonitorsPage/UptimeMonitorsTable.tsx b/plugins/ilert/src/components/UptimeMonitorsPage/UptimeMonitorsTable.tsx deleted file mode 100644 index 3099018e91..0000000000 --- a/plugins/ilert/src/components/UptimeMonitorsPage/UptimeMonitorsTable.tsx +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import React from 'react'; -import { makeStyles } from '@material-ui/core/styles'; -import { TableState } from '../../api'; -import { UptimeMonitor } from '../../types'; -import { StatusChip } from './StatusChip'; -import Typography from '@material-ui/core/Typography'; -import { DateTime as dt, Interval } from 'luxon'; -import humanizeDuration from 'humanize-duration'; -import { EscalationPolicyLink } from '../EscalationPolicy/EscalationPolicyLink'; -import { UptimeMonitorCheckType } from './UptimeMonitorCheckType'; -import { UptimeMonitorActionsMenu } from '../UptimeMonitor/UptimeMonitorActionsMenu'; -import { UptimeMonitorLink } from '../UptimeMonitor/UptimeMonitorLink'; -import { Table, TableColumn } from '@backstage/core-components'; - -const useStyles = makeStyles(theme => ({ - empty: { - padding: theme.spacing(2), - display: 'flex', - justifyContent: 'center', - }, -})); - -export const UptimeMonitorsTable = ({ - uptimeMonitors, - tableState, - isLoading, - onChangePage, - onChangeRowsPerPage, - onUptimeMonitorChanged, -}: { - uptimeMonitors: UptimeMonitor[]; - tableState: TableState; - isLoading: boolean; - onChangePage: (page: number) => void; - onChangeRowsPerPage: (pageSize: number) => void; - onUptimeMonitorChanged: (uptimeMonitor: UptimeMonitor) => void; -}) => { - const classes = useStyles(); - - const smColumnStyle = { - width: '5%', - maxWidth: '5%', - }; - const mdColumnStyle = { - width: '10%', - maxWidth: '10%', - }; - const lgColumnStyle = { - width: '15%', - maxWidth: '15%', - }; - - const columns: TableColumn[] = [ - { - title: 'ID', - field: 'id', - highlight: true, - cellStyle: mdColumnStyle, - headerStyle: mdColumnStyle, - render: rowData => ( - - ), - }, - { - title: 'Name', - field: 'name', - render: rowData => ( - {(rowData as UptimeMonitor).name} - ), - }, - { - title: 'Check Type', - field: 'checkType', - cellStyle: lgColumnStyle, - headerStyle: lgColumnStyle, - render: rowData => ( - - ), - }, - { - title: 'Last state change', - field: 'lastStatusChange', - type: 'datetime', - cellStyle: mdColumnStyle, - headerStyle: mdColumnStyle, - render: rowData => ( - - {humanizeDuration( - Interval.fromDateTimes( - dt.fromISO((rowData as UptimeMonitor).lastStatusChange), - dt.now(), - ) - .toDuration() - .valueOf(), - { units: ['h', 'm', 's'], largest: 2, round: true }, - )} - - ), - }, - { - title: 'Escalation policy', - field: 'assignedTo', - cellStyle: lgColumnStyle, - headerStyle: lgColumnStyle, - render: rowData => ( - - ), - }, - { - title: 'Status', - field: 'status', - cellStyle: smColumnStyle, - headerStyle: smColumnStyle, - render: rowData => ( - - ), - }, - { - title: '', - field: '', - cellStyle: smColumnStyle, - headerStyle: smColumnStyle, - render: rowData => ( - - ), - }, - ]; - - return ( -
- No uptime monitor - - } - page={tableState.page} - onPageChange={onChangePage} - onRowsPerPageChange={onChangeRowsPerPage} - localization={{ header: { actions: undefined } }} - isLoading={isLoading} - columns={columns} - data={uptimeMonitors} - /> - ); -}; diff --git a/plugins/ilert/src/hooks/useIncidentActions.ts b/plugins/ilert/src/hooks/useAlertActions.ts similarity index 69% rename from plugins/ilert/src/hooks/useIncidentActions.ts rename to plugins/ilert/src/hooks/useAlertActions.ts index 75621f67c3..875a8099f3 100644 --- a/plugins/ilert/src/hooks/useIncidentActions.ts +++ b/plugins/ilert/src/hooks/useAlertActions.ts @@ -13,48 +13,45 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; -import { ilertApiRef } from '../api'; +import { errorApiRef, useApi } from '@backstage/core-plugin-api'; import { AuthenticationError } from '@backstage/errors'; +import React from 'react'; import useAsyncRetry from 'react-use/lib/useAsyncRetry'; -import { Incident, IncidentAction } from '../types'; -import { useApi, errorApiRef } from '@backstage/core-plugin-api'; +import { ilertApiRef } from '../api'; +import { Alert, AlertAction } from '../types'; -export const useIncidentActions = ( - incident: Incident | null, - open: boolean, -) => { +export const useAlertActions = (alert: Alert | null, open: boolean) => { const ilertApi = useApi(ilertApiRef); const errorApi = useApi(errorApiRef); - const [incidentActionsList, setIncidentActionsList] = React.useState< - IncidentAction[] - >([]); + const [alertActionsList, setAlertActionsList] = React.useState( + [], + ); const [isLoading, setIsLoading] = React.useState(false); const { error, retry } = useAsyncRetry(async () => { try { - if (!incident || !open) { + if (!alert || !open) { return; } - const data = await ilertApi.fetchIncidentActions(incident); - setIncidentActionsList(data); + const data = await ilertApi.fetchAlertActions(alert); + setAlertActionsList(data); } catch (e) { if (!(e instanceof AuthenticationError)) { errorApi.post(e); } throw e; } - }, [incident, open]); + }, [alert, open]); return [ { - incidentActions: incidentActionsList, + alertActions: alertActionsList, error, isLoading, }, { - setIncidentActionsList, + setAlertActionsList, setIsLoading, retry, }, diff --git a/plugins/ilert/src/hooks/useAlertSource.ts b/plugins/ilert/src/hooks/useAlertSource.ts index 279150a724..9048ee3031 100644 --- a/plugins/ilert/src/hooks/useAlertSource.ts +++ b/plugins/ilert/src/hooks/useAlertSource.ts @@ -13,12 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; -import { ilertApiRef } from '../api'; +import { errorApiRef, useApi } from '@backstage/core-plugin-api'; import { AuthenticationError } from '@backstage/errors'; +import React from 'react'; import useAsyncRetry from 'react-use/lib/useAsyncRetry'; -import { AlertSource, UptimeMonitor } from '../types'; -import { useApi, errorApiRef } from '@backstage/core-plugin-api'; +import { ilertApiRef } from '../api'; +import { AlertSource } from '../types'; export const useAlertSource = (integrationKey: string) => { const ilertApi = useApi(ilertApiRef); @@ -28,10 +28,6 @@ export const useAlertSource = (integrationKey: string) => { null, ); const [isAlertSourceLoading, setIsAlertSourceLoading] = React.useState(false); - const [uptimeMonitor, setUptimeMonitor] = - React.useState(null); - const [isUptimeMonitorLoading, setIsUptimeMonitorLoading] = - React.useState(false); const fetchAlertSourceCall = async () => { try { @@ -56,38 +52,13 @@ export const useAlertSource = (integrationKey: string) => { [integrationKey], ); - const fetchUptimeMonitorCall = async () => { - try { - if (!alertSource || alertSource.integrationType !== 'MONITOR') { - return; - } - setIsUptimeMonitorLoading(true); - const data = await ilertApi.fetchUptimeMonitor(alertSource.id); - setUptimeMonitor(data || null); - setIsUptimeMonitorLoading(false); - } catch (e) { - setIsUptimeMonitorLoading(false); - if (!(e instanceof AuthenticationError)) { - errorApi.post(e); - } - throw e; - } - }; - - const { error: uptimeMonitorError, retry: uptimeMonitorRetry } = - useAsyncRetry(fetchUptimeMonitorCall, [alertSource]); - - const retry = () => { - alertSourceRetry(); - uptimeMonitorRetry(); - }; + const retry = () => alertSourceRetry(); return [ { alertSource, - uptimeMonitor, - error: alertSourceError || uptimeMonitorError, - isLoading: isAlertSourceLoading || isUptimeMonitorLoading, + error: alertSourceError, + isLoading: isAlertSourceLoading, }, { retry, diff --git a/plugins/ilert/src/hooks/useAlertSourceOnCalls.ts b/plugins/ilert/src/hooks/useAlertSourceOnCalls.ts index 5a804e37fd..2884e890db 100644 --- a/plugins/ilert/src/hooks/useAlertSourceOnCalls.ts +++ b/plugins/ilert/src/hooks/useAlertSourceOnCalls.ts @@ -13,12 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; -import { ilertApiRef } from '../api'; +import { errorApiRef, useApi } from '@backstage/core-plugin-api'; import { AuthenticationError } from '@backstage/errors'; +import React from 'react'; import useAsyncRetry from 'react-use/lib/useAsyncRetry'; +import { ilertApiRef } from '../api'; import { AlertSource, OnCall } from '../types'; -import { useApi, errorApiRef } from '@backstage/core-plugin-api'; export const useAlertSourceOnCalls = (alertSource?: AlertSource | null) => { const ilertApi = useApi(ilertApiRef); diff --git a/plugins/ilert/src/hooks/useIncidents.ts b/plugins/ilert/src/hooks/useAlerts.ts similarity index 57% rename from plugins/ilert/src/hooks/useIncidents.ts rename to plugins/ilert/src/hooks/useAlerts.ts index 45f28937c5..8acabe0086 100644 --- a/plugins/ilert/src/hooks/useIncidents.ts +++ b/plugins/ilert/src/hooks/useAlerts.ts @@ -13,20 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; -import { GetIncidentsOpts, ilertApiRef, TableState } from '../api'; +import { errorApiRef, useApi } from '@backstage/core-plugin-api'; import { AuthenticationError } from '@backstage/errors'; +import React from 'react'; import useAsyncRetry from 'react-use/lib/useAsyncRetry'; -import { - ACCEPTED, - PENDING, - Incident, - IncidentStatus, - AlertSource, -} from '../types'; -import { useApi, errorApiRef } from '@backstage/core-plugin-api'; +import { GetAlertsOpts, ilertApiRef, TableState } from '../api'; +import { ACCEPTED, Alert, AlertSource, AlertStatus, PENDING } from '../types'; -export const useIncidents = ( +export const useAlerts = ( paging: boolean, singleSource?: boolean, alertSource?: AlertSource | null, @@ -38,21 +32,21 @@ export const useIncidents = ( page: 0, pageSize: 10, }); - const [states, setStates] = React.useState([ + const [states, setStates] = React.useState([ ACCEPTED, PENDING, ]); - const [incidentsList, setIncidentsList] = React.useState([]); - const [incidentsCount, setIncidentsCount] = React.useState(0); + const [alertsList, setAlertsList] = React.useState([]); + const [alertsCount, setAlertsCount] = React.useState(0); const [isLoading, setIsLoading] = React.useState(false); - const fetchIncidentsCall = async () => { + const fetchAlertsCall = async () => { try { if (singleSource && !alertSource) { return; } setIsLoading(true); - const opts: GetIncidentsOpts = { + const opts: GetAlertsOpts = { states, alertSources: alertSource ? [alertSource.id] : [], }; @@ -60,8 +54,8 @@ export const useIncidents = ( opts.maxResults = tableState.pageSize; opts.startIndex = tableState.page * tableState.pageSize; } - const data = await ilertApi.fetchIncidents(opts); - setIncidentsList(data || []); + const data = await ilertApi.fetchAlerts(opts); + setAlertsList(data || []); setIsLoading(false); } catch (e) { if (!(e instanceof AuthenticationError)) { @@ -72,10 +66,10 @@ export const useIncidents = ( } }; - const fetchIncidentsCountCall = async () => { + const fetchAlertsCountCall = async () => { try { - const count = await ilertApi.fetchIncidentsCount({ states }); - setIncidentsCount(count || 0); + const count = await ilertApi.fetchAlertsCount({ states }); + setAlertsCount(count || 0); } catch (e) { if (!(e instanceof AuthenticationError)) { errorApi.post(e); @@ -83,44 +77,44 @@ export const useIncidents = ( throw e; } }; - const fetchIncidents = useAsyncRetry(fetchIncidentsCall, [ + const fetchAlerts = useAsyncRetry(fetchAlertsCall, [ tableState, states, singleSource, alertSource, ]); - const refetchIncidents = () => { + const refetchAlerts = () => { setTableState({ ...tableState, page: 0 }); - Promise.all([fetchIncidentsCall(), fetchIncidentsCountCall()]); + Promise.all([fetchAlertsCall(), fetchAlertsCountCall()]); }; - const fetchIncidentsCount = useAsyncRetry(fetchIncidentsCountCall, [states]); + const fetchAlertsCount = useAsyncRetry(fetchAlertsCountCall, [states]); - const error = fetchIncidents.error || fetchIncidentsCount.error; + const error = fetchAlerts.error || fetchAlertsCount.error; const retry = () => { - fetchIncidents.retry(); - fetchIncidentsCount.retry(); + fetchAlerts.retry(); + fetchAlertsCount.retry(); }; - const onIncidentChanged = (newIncident: Incident) => { - let shouldRefetchIncidents = false; - setIncidentsList( - incidentsList.reduce((acc: Incident[], incident: Incident) => { - if (newIncident.id === incident.id) { - if (states.includes(newIncident.status)) { - acc.push(newIncident); + const onAlertChanged = (newAlert: Alert) => { + let shouldRefetchAlerts = false; + setAlertsList( + alertsList.reduce((acc: Alert[], alert: Alert) => { + if (newAlert.id === alert.id) { + if (states.includes(newAlert.status)) { + acc.push(newAlert); } else { - shouldRefetchIncidents = true; + shouldRefetchAlerts = true; } return acc; } - acc.push(incident); + acc.push(alert); return acc; }, []), ); - if (shouldRefetchIncidents) { - refetchIncidents(); + if (shouldRefetchAlerts) { + refetchAlerts(); } }; @@ -130,7 +124,7 @@ export const useIncidents = ( const onChangeRowsPerPage = (p: number) => { setTableState({ ...tableState, pageSize: p }); }; - const onIncidentStatesChange = (s: IncidentStatus[]) => { + const onAlertStatesChange = (s: AlertStatus[]) => { setStates(s); }; @@ -138,22 +132,22 @@ export const useIncidents = ( { tableState, states, - incidents: incidentsList, - incidentsCount, + alerts: alertsList, + alertsCount, error, isLoading, }, { setTableState, setStates, - setIncidentsList, + setAlertsList, setIsLoading, retry, - onIncidentChanged, - refetchIncidents, + onAlertChanged, + refetchAlerts, onChangePage, onChangeRowsPerPage, - onIncidentStatesChange, + onAlertStatesChange, }, ] as const; }; diff --git a/plugins/ilert/src/hooks/useAssignIncident.ts b/plugins/ilert/src/hooks/useAssignAlert.ts similarity index 68% rename from plugins/ilert/src/hooks/useAssignIncident.ts rename to plugins/ilert/src/hooks/useAssignAlert.ts index b760f31e9c..f81eae724f 100644 --- a/plugins/ilert/src/hooks/useAssignIncident.ts +++ b/plugins/ilert/src/hooks/useAssignAlert.ts @@ -13,30 +13,30 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; -import { ilertApiRef } from '../api'; +import { errorApiRef, useApi } from '@backstage/core-plugin-api'; import { AuthenticationError } from '@backstage/errors'; +import React from 'react'; import useAsyncRetry from 'react-use/lib/useAsyncRetry'; -import { Incident, IncidentResponder } from '../types'; -import { useApi, errorApiRef } from '@backstage/core-plugin-api'; +import { ilertApiRef } from '../api'; +import { Alert, AlertResponder } from '../types'; -export const useAssignIncident = (incident: Incident | null, open: boolean) => { +export const useAssignAlert = (alert: Alert | null, open: boolean) => { const ilertApi = useApi(ilertApiRef); const errorApi = useApi(errorApiRef); - const [incidentRespondersList, setIncidentRespondersList] = React.useState< - IncidentResponder[] + const [alertRespondersList, setAlertRespondersList] = React.useState< + AlertResponder[] >([]); - const [incidentResponder, setIncidentResponder] = - React.useState(null); + const [alertResponder, setAlertResponder] = + React.useState(null); const [isLoading, setIsLoading] = React.useState(false); const { error, retry } = useAsyncRetry(async () => { try { - if (!incident || !open) { + if (!alert || !open) { return; } - const data = await ilertApi.fetchIncidentResponders(incident); + const data = await ilertApi.fetchAlertResponders(alert); if (data && Array.isArray(data)) { const groups = [ 'SUGGESTED', @@ -45,7 +45,7 @@ export const useAssignIncident = (incident: Incident | null, open: boolean) => { 'ON_CALL_SCHEDULE', ]; data.sort((a, b) => groups.indexOf(a.group) - groups.indexOf(b.group)); - setIncidentRespondersList(data); + setAlertRespondersList(data); } } catch (e) { if (!(e instanceof AuthenticationError)) { @@ -53,18 +53,18 @@ export const useAssignIncident = (incident: Incident | null, open: boolean) => { } throw e; } - }, [incident, open]); + }, [alert, open]); return [ { - incidentRespondersList, - incidentResponder, + alertRespondersList, + alertResponder, error, isLoading, }, { - setIncidentRespondersList, - setIncidentResponder, + setAlertRespondersList, + setAlertResponder, setIsLoading, retry, }, diff --git a/plugins/ilert/src/hooks/useNewIncident.ts b/plugins/ilert/src/hooks/useNewAlert.ts similarity index 95% rename from plugins/ilert/src/hooks/useNewIncident.ts rename to plugins/ilert/src/hooks/useNewAlert.ts index f2bb3a8cd3..a560c1b035 100644 --- a/plugins/ilert/src/hooks/useNewIncident.ts +++ b/plugins/ilert/src/hooks/useNewAlert.ts @@ -13,14 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; -import { ilertApiRef } from '../api'; +import { errorApiRef, useApi } from '@backstage/core-plugin-api'; import { AuthenticationError } from '@backstage/errors'; +import React from 'react'; import useAsyncRetry from 'react-use/lib/useAsyncRetry'; +import { ilertApiRef } from '../api'; import { AlertSource } from '../types'; -import { useApi, errorApiRef } from '@backstage/core-plugin-api'; -export const useNewIncident = ( +export const useNewAlert = ( open: boolean, initialAlertSource?: AlertSource | null, ) => { diff --git a/plugins/ilert/src/hooks/useUptimeMonitors.ts b/plugins/ilert/src/hooks/useServices.ts similarity index 58% rename from plugins/ilert/src/hooks/useUptimeMonitors.ts rename to plugins/ilert/src/hooks/useServices.ts index c71d5f02af..b6ea120f80 100644 --- a/plugins/ilert/src/hooks/useUptimeMonitors.ts +++ b/plugins/ilert/src/hooks/useServices.ts @@ -13,14 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; -import { ilertApiRef, TableState } from '../api'; +import { errorApiRef, useApi } from '@backstage/core-plugin-api'; import { AuthenticationError } from '@backstage/errors'; +import React from 'react'; import useAsyncRetry from 'react-use/lib/useAsyncRetry'; -import { UptimeMonitor } from '../types'; -import { useApi, errorApiRef } from '@backstage/core-plugin-api'; +import { GetServicesOpts, ilertApiRef, TableState } from '../api'; +import { Service } from '../types'; -export const useUptimeMonitors = () => { +export const useServices = (paging: boolean) => { const ilertApi = useApi(ilertApiRef); const errorApi = useApi(errorApiRef); @@ -28,60 +28,64 @@ export const useUptimeMonitors = () => { page: 0, pageSize: 10, }); - const [uptimeMonitorsList, setUptimeMonitorsList] = React.useState< - UptimeMonitor[] - >([]); + + const [servicesList, setServicesList] = React.useState([]); const [isLoading, setIsLoading] = React.useState(false); - const { error, retry } = useAsyncRetry(async () => { + const fetchServicesCall = async () => { try { setIsLoading(true); - const data = await ilertApi.fetchUptimeMonitors(); - setUptimeMonitorsList(data || []); + const opts: GetServicesOpts = {}; + if (paging) { + opts.maxResults = tableState.pageSize; + opts.startIndex = tableState.page * tableState.pageSize; + } + const data = await ilertApi.fetchServices(opts); + setServicesList(data || []); setIsLoading(false); } catch (e) { - setIsLoading(false); if (!(e instanceof AuthenticationError)) { errorApi.post(e); } + setIsLoading(false); throw e; } - }, [tableState]); + }; - const onUptimeMonitorChanged = (newUptimeMonitor: UptimeMonitor) => { - setUptimeMonitorsList( - uptimeMonitorsList.map((uptimeMonitor: UptimeMonitor): UptimeMonitor => { - if (newUptimeMonitor.id === uptimeMonitor.id) { - return newUptimeMonitor; - } + const fetchServices = useAsyncRetry(fetchServicesCall, [tableState]); - return uptimeMonitor; - }), - ); + const refetchServices = () => { + setTableState({ ...tableState, page: 0 }); + Promise.all([fetchServicesCall()]); + }; + + const error = fetchServices.error; + const retry = () => { + fetchServices.retry(); }; const onChangePage = (page: number) => { setTableState({ ...tableState, page }); }; - const onChangeRowsPerPage = (pageSize: number) => { - setTableState({ ...tableState, pageSize }); + const onChangeRowsPerPage = (p: number) => { + setTableState({ ...tableState, pageSize: p }); }; return [ { tableState, - uptimeMonitors: uptimeMonitorsList, - error, + services: servicesList, isLoading, + error, }, { setTableState, - setUptimeMonitorsList, + setServicesList, + setIsLoading, retry, - onUptimeMonitorChanged, + refetchServices, onChangePage, onChangeRowsPerPage, - setIsLoading, }, ] as const; }; diff --git a/plugins/ilert/src/hooks/useStatusPages.ts b/plugins/ilert/src/hooks/useStatusPages.ts new file mode 100644 index 0000000000..740e0f515f --- /dev/null +++ b/plugins/ilert/src/hooks/useStatusPages.ts @@ -0,0 +1,93 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { errorApiRef, useApi } from '@backstage/core-plugin-api'; +import { AuthenticationError } from '@backstage/errors'; +import React from 'react'; +import useAsyncRetry from 'react-use/lib/useAsyncRetry'; +import { GetStatusPagesOpts, ilertApiRef, TableState } from '../api'; +import { StatusPage } from '../types'; + +export const useStatusPages = (paging: boolean) => { + const ilertApi = useApi(ilertApiRef); + const errorApi = useApi(errorApiRef); + + const [tableState, setTableState] = React.useState({ + page: 0, + pageSize: 10, + }); + + const [statusPagesList, setStatusPagesList] = React.useState( + [], + ); + const [isLoading, setIsLoading] = React.useState(false); + + const fetchStatusPagesCall = async () => { + try { + setIsLoading(true); + const opts: GetStatusPagesOpts = {}; + if (paging) { + opts.maxResults = tableState.pageSize; + opts.startIndex = tableState.page * tableState.pageSize; + } + const data = await ilertApi.fetchStatusPages(opts); + setStatusPagesList(data || []); + setIsLoading(false); + } catch (e) { + if (!(e instanceof AuthenticationError)) { + errorApi.post(e); + } + setIsLoading(false); + throw e; + } + }; + + const fetchStatusPages = useAsyncRetry(fetchStatusPagesCall, [tableState]); + + const refetchStatusPages = () => { + setTableState({ ...tableState, page: 0 }); + Promise.all([fetchStatusPagesCall()]); + }; + + const error = fetchStatusPages.error; + const retry = () => { + fetchStatusPages.retry(); + }; + + const onChangePage = (page: number) => { + setTableState({ ...tableState, page }); + }; + const onChangeRowsPerPage = (p: number) => { + setTableState({ ...tableState, pageSize: p }); + }; + + return [ + { + tableState, + statusPages: statusPagesList, + isLoading, + error, + }, + { + setTableState, + setStatusPagesList, + setIsLoading, + retry, + refetchStatusPages, + onChangePage, + onChangeRowsPerPage, + }, + ] as const; +}; diff --git a/plugins/ilert/src/types.ts b/plugins/ilert/src/types.ts index 3d1531329a..e55abb67a5 100644 --- a/plugins/ilert/src/types.ts +++ b/plugins/ilert/src/types.ts @@ -15,17 +15,18 @@ */ /** @public */ -export interface Incident { +export interface Alert { id: number; summary: string; details: string; reportTime: string; resolvedOn: string; - status: IncidentStatus; - priority: IncidentPriority; - incidentKey: string; + status: AlertStatus; + priority: AlertPriority; + alertKey: string; alertSource: AlertSource | null; assignedTo: User | null; + responders: Responder[]; logEntries: LogEntry[]; links: Link[]; images: Image[]; @@ -42,10 +43,10 @@ export const ACCEPTED = 'ACCEPTED'; export const RESOLVED = 'RESOLVED'; /** @public */ -export type IncidentStatus = typeof PENDING | typeof ACCEPTED | typeof RESOLVED; +export type AlertStatus = typeof PENDING | typeof ACCEPTED | typeof RESOLVED; /** @public */ -export type IncidentPriority = 'HIGH' | 'LOW'; +export type AlertPriority = 'HIGH' | 'LOW'; /** @public */ export interface Link { @@ -76,7 +77,7 @@ export interface LogEntry { timestamp: string; logEntryType: string; text: string; - incidentId?: number; + alertId?: number; iconName?: string; iconClass?: string; filterTypes?: string[]; @@ -99,6 +100,13 @@ export interface User { department: string; } +/** @public */ +export interface Responder { + acceptedAt?: string; + status: string; + user: User; +} + /** @public */ export type UserRole = | 'USER' @@ -125,8 +133,8 @@ export interface AlertSource { iconUrl?: string; lightIconUrl?: string; darkIconUrl?: string; - incidentCreation?: AlertSourceIncidentCreation; - incidentPriorityRule?: AlertSourceIncidentPriorityRule; + alertCreation?: AlertSourceAlertCreation; + alertPriorityRule?: AlertSourceAlertPriorityRule; emailFiltered?: boolean; emailResolveFiltered?: boolean; active?: boolean; @@ -209,20 +217,48 @@ export type AlertSourceIntegrationType = | 'CORTEXXSOAR' | string; /** @public */ -export type AlertSourceIncidentCreation = - | 'ONE_INCIDENT_PER_EMAIL' - | 'ONE_INCIDENT_PER_EMAIL_SUBJECT' - | 'ONE_PENDING_INCIDENT_ALLOWED' - | 'ONE_OPEN_INCIDENT_ALLOWED' +export type AlertSourceAlertCreation = + | 'ONE_ALERT_PER_EMAIL' + | 'ONE_ALERT_PER_EMAIL_SUBJECT' + | 'ONE_PENDING_ALERT_ALLOWED' + | 'ONE_OPEN_ALERT_ALLOWED' | 'OPEN_RESOLVE_ON_EXTRACTION'; /** @public */ export type AlertSourceFilterOperator = 'AND' | 'OR'; /** @public */ -export type AlertSourceIncidentPriorityRule = +export type AlertSourceAlertPriorityRule = | 'HIGH' | 'LOW' | 'HIGH_DURING_SUPPORT_HOURS' | 'LOW_DURING_SUPPORT_HOURS'; + +/** @public */ +export const OPERATIONAL = 'OPERATIONAL'; +/** @public */ +export const UNDER_MAINTENANCE = 'UNDER_MAINTENANCE'; +/** @public */ +export const DEGRADED = 'DEGRADED'; +/** @public */ +export const PARTIAL_OUTAGE = 'PARTIAL_OUTAGE'; +/** @public */ +export const MAJOR_OUTAGE = 'MAJOR_OUTAGE'; + +/** @public */ +export type ServiceStatus = + | typeof OPERATIONAL + | typeof UNDER_MAINTENANCE + | typeof DEGRADED + | typeof PARTIAL_OUTAGE + | typeof MAJOR_OUTAGE; + +/** @public */ +export const PRIVATE = 'PRIVATE'; +/** @public */ +export const PUBLIC = 'PUBLIC'; + +/** @public */ +export type StatusPageVisibility = typeof PRIVATE | typeof PUBLIC; + /** @public */ export interface AlertSourceEmailPredicate { field: 'EMAIL_FROM' | 'EMAIL_SUBJECT' | 'EMAIL_BODY'; @@ -251,7 +287,7 @@ export interface AlertSourceSupportDay { /** @public */ export interface AlertSourceSupportHours { timezone: AlertSourceTimeZone; - autoRaiseIncidents: boolean; + autoRaiseAlerts: boolean; supportDays: { MONDAY: AlertSourceSupportDay; TUESDAY: AlertSourceSupportDay; @@ -316,33 +352,7 @@ export interface Shift { } /** @public */ -export interface UptimeMonitor { - id: number; - name: string; - region: 'EU' | 'US'; - checkType: 'http' | 'tcp' | 'udp' | 'ping'; - checkParams: UptimeMonitorCheckParams; - intervalSec: number; - timeoutMs: number; - createIncidentAfterFailedChecks: number; - paused: boolean; - embedUrl: string; - shareUrl: string; - status: string; - lastStatusChange: string; - escalationPolicy: EscalationPolicy; - teams: TeamShort[]; -} - -/** @public */ -export interface UptimeMonitorCheckParams { - host?: string; - port?: number; - url?: string; -} - -/** @public */ -export interface IncidentResponder { +export interface AlertResponder { group: 'SUGGESTED' | 'USER' | 'ESCALATION_POLICY' | 'ON_CALL_SCHEDULE'; id: number; name: string; @@ -350,19 +360,19 @@ export interface IncidentResponder { } /** @public */ -export interface IncidentAction { +export interface AlertAction { name: string; type: string; webhookId: string; extensionId?: string; - history?: IncidentActionHistory[]; + history?: AlertActionHistory[]; } /** @public */ -export interface IncidentActionHistory { +export interface AlertActionHistory { id: string; webhookId: string; - incidentId: number; + alertId: number; actor: User; success: boolean; } @@ -376,3 +386,31 @@ export interface OnCall { end: string; escalationLevel: number; } + +/** @public */ +export interface Service { + id: number; + name: string; + status: ServiceStatus; + uptime: Uptime; +} + +/** @public */ +export interface Uptime { + uptimePercentage: UptimePercentage; +} + +/** @public */ +export interface UptimePercentage { + p90: number; +} + +/** @public */ +export interface StatusPage { + id: number; + name: string; + domain: string; + subdomain: string; + visibility: StatusPageVisibility; + status: ServiceStatus; +}