fix issues after first review for iLert plugin
Signed-off-by: yacut <roman.frey2020@gmail.com>
This commit is contained in:
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-ilert': minor
|
||||
---
|
||||
|
||||
Add iLert plugin
|
||||
+19
-19
@@ -31,12 +31,6 @@ cd packages/app
|
||||
yarn add @backstage/plugin-ilert
|
||||
```
|
||||
|
||||
Then make sure to export the plugin in your app's [`plugins.ts`](https://github.com/backstage/backstage/blob/master/packages/app/src/plugins.ts) to enable the plugin:
|
||||
|
||||
```js
|
||||
export { plugin as ILert } from '@backstage/plugin-ilert';
|
||||
```
|
||||
|
||||
Add it to the `EntityPage.tsx`:
|
||||
|
||||
```ts
|
||||
@@ -44,13 +38,16 @@ import {
|
||||
isPluginApplicableToEntity as isILertAvailable,
|
||||
EntityILertCard,
|
||||
} from '@backstage/plugin-ilert';
|
||||
{
|
||||
isILertAvailable(entity) && (
|
||||
<Grid item md={6}>
|
||||
|
||||
// ...
|
||||
<EntitySwitch>
|
||||
<EntitySwitch.Case if={isILertAvailable}>
|
||||
<Grid item sm={6}>
|
||||
<EntityILertCard />
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
</EntitySwitch.Case>
|
||||
</EntitySwitch>;
|
||||
// ...
|
||||
```
|
||||
|
||||
> To force an iLert card for each entity just add the `<EntityILertCard />` component. An instruction card will appear if no integration key is set.
|
||||
@@ -61,11 +58,11 @@ Modify your app routes in [`App.tsx`](https://github.com/backstage/backstage/blo
|
||||
|
||||
```tsx
|
||||
import { ILertPage } from '@backstage/plugin-ilert';
|
||||
<Routes>
|
||||
<FlatRoutes>
|
||||
// ...
|
||||
<Route path="/ilert" element={<ILertPage />} />
|
||||
// ...
|
||||
</Routes>;
|
||||
</FlatRoutes>;
|
||||
```
|
||||
|
||||
Modify your sidebar in [`Root.tsx`](https://github.com/backstage/backstage/blob/master/packages/app/src/components/Root/Root.tsx) to include the icon component exported by the plugin - for example:
|
||||
@@ -104,14 +101,13 @@ proxy:
|
||||
allowedMethods: ['GET', 'POST', 'PUT']
|
||||
allowedHeaders: ['Authorization']
|
||||
headers:
|
||||
Authorization:
|
||||
$env: ILERT_AUTH_HEADER
|
||||
Authorization: ${ILERT_AUTH_HEADER}
|
||||
```
|
||||
|
||||
Then start the backend, passing the token as environment variable:
|
||||
Then start the backend, passing the authorization header (bearer token or basic auth) as environment variable:
|
||||
|
||||
```bash
|
||||
$ ILERT_AUTH_HEADER='Basic <TOKEN>' yarn start
|
||||
$ ILERT_AUTH_HEADER='<ILERT_AUTH>' yarn start
|
||||
```
|
||||
|
||||
## Integration Key
|
||||
@@ -123,6 +119,10 @@ The information displayed for each entity is based on the alert source integrati
|
||||
If you want to use this plugin for an entity, you need to label it with the below annotation:
|
||||
|
||||
```yml
|
||||
annotations:
|
||||
ilert.com/integration-key: [INTEGRATION_KEY]
|
||||
apiVersion: backstage.io/v1alpha1
|
||||
kind: Component
|
||||
metadata:
|
||||
name: example
|
||||
annotations:
|
||||
ilert.com/integration-key: [INTEGRATION_KEY]
|
||||
```
|
||||
|
||||
Vendored
+31
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2021 Spotify AB
|
||||
*
|
||||
* 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 interface Config {
|
||||
ilert: {
|
||||
/**
|
||||
* Domain used by users to access iLert web UI.
|
||||
* Example: https://my-app.ilert.com/
|
||||
* @visibility frontend
|
||||
*/
|
||||
baseUrl: string;
|
||||
|
||||
/**
|
||||
* Path to use for requests via the proxy, defaults to /ilert/api
|
||||
* @visibility frontend
|
||||
*/
|
||||
proxyPath?: string;
|
||||
};
|
||||
}
|
||||
@@ -22,16 +22,16 @@
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.7.6",
|
||||
"@backstage/core": "^0.7.7",
|
||||
"@backstage/errors": "^0.1.1",
|
||||
"@backstage/plugin-catalog-react": "^0.1.4",
|
||||
"@backstage/theme": "^0.2.6",
|
||||
"@date-io/date-fns": "1.x",
|
||||
"@date-io/luxon": "1.x",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@material-ui/lab": "4.0.0-alpha.45",
|
||||
"@material-ui/pickers": "^3.3.10",
|
||||
"date-fns": "^2.20.2",
|
||||
"moment": "^2.29.1",
|
||||
"moment-timezone": "^0.5.33",
|
||||
"humanize-duration": "^3.26.0",
|
||||
"luxon": "^1.26.0",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-use": "^15.3.3"
|
||||
@@ -49,6 +49,8 @@
|
||||
"msw": "^0.21.2"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
]
|
||||
"dist",
|
||||
"config.d.ts"
|
||||
],
|
||||
"configSchema": "config.d.ts"
|
||||
}
|
||||
|
||||
+88
-142
@@ -14,6 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { ConfigApi, createApiRef, DiscoveryApi } from '@backstage/core';
|
||||
import { AuthenticationError, ResponseError } from '@backstage/errors';
|
||||
import {
|
||||
AlertSource,
|
||||
EscalationPolicy,
|
||||
@@ -32,8 +33,7 @@ import {
|
||||
Options,
|
||||
EventRequest,
|
||||
} from './types';
|
||||
import moment from 'moment';
|
||||
import momentTimezone from 'moment-timezone';
|
||||
import { DateTime as dt } from 'luxon';
|
||||
|
||||
export const ilertApiRef = createApiRef<ILertApi>({
|
||||
id: 'plugin.ilert.service',
|
||||
@@ -41,7 +41,10 @@ export const ilertApiRef = createApiRef<ILertApi>({
|
||||
});
|
||||
|
||||
const DEFAULT_PROXY_PATH = '/ilert';
|
||||
export class UnauthorizedError extends Error {}
|
||||
const JSON_HEADERS = {
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json',
|
||||
};
|
||||
|
||||
export class ILertClient implements ILertApi {
|
||||
private readonly discoveryApi: DiscoveryApi;
|
||||
@@ -55,14 +58,15 @@ export class ILertClient implements ILertApi {
|
||||
return new ILertClient({
|
||||
discoveryApi: discoveryApi,
|
||||
baseUrl,
|
||||
proxyPath: configApi.getOptionalString('ilert.proxyPath'),
|
||||
proxyPath:
|
||||
configApi.getOptionalString('ilert.proxyPath') ?? DEFAULT_PROXY_PATH,
|
||||
});
|
||||
}
|
||||
|
||||
constructor(opts: Options) {
|
||||
this.discoveryApi = opts.discoveryApi;
|
||||
this.baseUrl = opts.baseUrl;
|
||||
this.proxyPath = opts.proxyPath ?? DEFAULT_PROXY_PATH;
|
||||
this.proxyPath = opts.proxyPath;
|
||||
}
|
||||
|
||||
private async fetch<T = any>(input: string, init?: RequestInit): Promise<T> {
|
||||
@@ -70,40 +74,37 @@ export class ILertClient implements ILertApi {
|
||||
|
||||
const response = await fetch(`${apiUrl}${input}`, init);
|
||||
if (response.status === 401) {
|
||||
throw new UnauthorizedError('');
|
||||
}
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
`Request failed with ${response.status} ${response.statusText}`,
|
||||
throw new AuthenticationError(
|
||||
'This request requires HTTP authentication.',
|
||||
);
|
||||
}
|
||||
if (!response.ok || response.status >= 400) {
|
||||
throw await ResponseError.fromResponse(response);
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
async fetchIncidents(opts?: GetIncidentsOpts): Promise<Incident[]> {
|
||||
const init = {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json',
|
||||
},
|
||||
headers: JSON_HEADERS,
|
||||
};
|
||||
const query = new URLSearchParams();
|
||||
if (opts && opts.maxResults) {
|
||||
query.append('max-results', `${opts.maxResults}`);
|
||||
if (opts?.maxResults !== undefined) {
|
||||
query.append('max-results', String(opts.maxResults));
|
||||
}
|
||||
if (opts && opts.startIndex) {
|
||||
query.append('start-index', `${opts.startIndex}`);
|
||||
if (opts?.startIndex !== undefined) {
|
||||
query.append('start-index', String(opts.startIndex));
|
||||
}
|
||||
if (opts && opts.alertSources) {
|
||||
if (opts?.alertSources !== undefined && Array.isArray(opts.alertSources)) {
|
||||
opts.alertSources.forEach((a: number) => {
|
||||
if (a) {
|
||||
query.append('alert-source', `${a}`);
|
||||
query.append('alert-source', String(a));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (opts && opts.states && Array.isArray(opts.states)) {
|
||||
if (opts?.states !== undefined && Array.isArray(opts.states)) {
|
||||
opts.states.forEach(state => {
|
||||
query.append('state', state);
|
||||
});
|
||||
@@ -118,10 +119,7 @@ export class ILertClient implements ILertApi {
|
||||
|
||||
async fetchIncidentsCount(opts?: GetIncidentsCountOpts): Promise<number> {
|
||||
const init = {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json',
|
||||
},
|
||||
headers: JSON_HEADERS,
|
||||
};
|
||||
const query = new URLSearchParams();
|
||||
if (opts && opts.states && Array.isArray(opts.states)) {
|
||||
@@ -137,14 +135,21 @@ export class ILertClient implements ILertApi {
|
||||
return response && response.count ? response.count : 0;
|
||||
}
|
||||
|
||||
async fetchIncident(id: number): Promise<Incident> {
|
||||
const init = {
|
||||
headers: JSON_HEADERS,
|
||||
};
|
||||
|
||||
const response = await this.fetch(`/api/v1/incidents/${id}`, init);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
async fetchIncidentResponders(
|
||||
incident: Incident,
|
||||
): Promise<IncidentResponder[]> {
|
||||
const init = {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json',
|
||||
},
|
||||
headers: JSON_HEADERS,
|
||||
};
|
||||
|
||||
const response = await this.fetch(
|
||||
@@ -157,10 +162,7 @@ export class ILertClient implements ILertApi {
|
||||
|
||||
async fetchIncidentActions(incident: Incident): Promise<IncidentAction[]> {
|
||||
const init = {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json',
|
||||
},
|
||||
headers: JSON_HEADERS,
|
||||
};
|
||||
|
||||
const response = await this.fetch(
|
||||
@@ -171,40 +173,42 @@ export class ILertClient implements ILertApi {
|
||||
return response;
|
||||
}
|
||||
|
||||
async acceptIncident(incident: Incident): Promise<Incident> {
|
||||
async acceptIncident(
|
||||
incident: Incident,
|
||||
userName: string,
|
||||
): Promise<Incident> {
|
||||
const init = {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ summary: 'Backstage — iLert plugin' }),
|
||||
method: 'POST',
|
||||
headers: JSON_HEADERS,
|
||||
body: JSON.stringify({
|
||||
apiKey: incident.alertSource?.integrationKey || '',
|
||||
incidentKey: incident.incidentKey,
|
||||
summary: `from ${userName} via Backstage plugin`,
|
||||
eventType: 'ACCEPT',
|
||||
}),
|
||||
};
|
||||
|
||||
const response = await this.fetch(
|
||||
`/api/v1/incidents/${incident.id}/accept`,
|
||||
init,
|
||||
);
|
||||
|
||||
return response;
|
||||
await this.fetch('/api/v1/events', init);
|
||||
return this.fetchIncident(incident.id);
|
||||
}
|
||||
|
||||
async resolveIncident(incident: Incident): Promise<Incident> {
|
||||
async resolveIncident(
|
||||
incident: Incident,
|
||||
userName: string,
|
||||
): Promise<Incident> {
|
||||
const init = {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ summary: 'Backstage — iLert plugin' }),
|
||||
method: 'POST',
|
||||
headers: JSON_HEADERS,
|
||||
body: JSON.stringify({
|
||||
apiKey: incident.alertSource?.integrationKey || '',
|
||||
incidentKey: incident.incidentKey,
|
||||
summary: `from ${userName} via Backstage plugin`,
|
||||
eventType: 'RESOLVE',
|
||||
}),
|
||||
};
|
||||
|
||||
const response = await this.fetch(
|
||||
`/api/v1/incidents/${incident.id}/resolve`,
|
||||
init,
|
||||
);
|
||||
|
||||
return response;
|
||||
await this.fetch('/api/v1/events', init);
|
||||
return this.fetchIncident(incident.id);
|
||||
}
|
||||
|
||||
async assignIncident(
|
||||
@@ -213,22 +217,19 @@ export class ILertClient implements ILertApi {
|
||||
): Promise<Incident> {
|
||||
const init = {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json',
|
||||
},
|
||||
headers: JSON_HEADERS,
|
||||
};
|
||||
|
||||
const query = new URLSearchParams();
|
||||
switch (responder.group) {
|
||||
case 'ESCALATION_POLICY':
|
||||
query.append('policy-id', `${responder.id}`);
|
||||
query.append('policy-id', String(responder.id));
|
||||
break;
|
||||
case 'ON_CALL_SCHEDULE':
|
||||
query.append('schedule-id', `${responder.id}`);
|
||||
query.append('schedule-id', String(responder.id));
|
||||
break;
|
||||
default:
|
||||
query.append('user-id', `${responder.id}`);
|
||||
query.append('user-id', String(responder.id));
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -246,10 +247,7 @@ export class ILertClient implements ILertApi {
|
||||
): Promise<void> {
|
||||
const init = {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json',
|
||||
},
|
||||
headers: JSON_HEADERS,
|
||||
body: JSON.stringify({
|
||||
webhookId: action.webhookId,
|
||||
extensionId: action.extensionId,
|
||||
@@ -264,10 +262,7 @@ export class ILertClient implements ILertApi {
|
||||
async createIncident(eventRequest: EventRequest): Promise<boolean> {
|
||||
const init = {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json',
|
||||
},
|
||||
headers: JSON_HEADERS,
|
||||
body: JSON.stringify({
|
||||
apiKey: eventRequest.integrationKey,
|
||||
summary: eventRequest.summary,
|
||||
@@ -286,15 +281,12 @@ export class ILertClient implements ILertApi {
|
||||
};
|
||||
|
||||
const response = await this.fetch('/api/v1/events', init);
|
||||
return response.responseCode === 'NEW_INCIDENT_CREATED';
|
||||
return response;
|
||||
}
|
||||
|
||||
async fetchUptimeMonitors(): Promise<UptimeMonitor[]> {
|
||||
const init = {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json',
|
||||
},
|
||||
headers: JSON_HEADERS,
|
||||
};
|
||||
|
||||
const response = await this.fetch('/api/v1/uptime-monitors', init);
|
||||
@@ -304,10 +296,7 @@ export class ILertClient implements ILertApi {
|
||||
|
||||
async fetchUptimeMonitor(id: number): Promise<UptimeMonitor> {
|
||||
const init = {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json',
|
||||
},
|
||||
headers: JSON_HEADERS,
|
||||
};
|
||||
|
||||
const response: UptimeMonitor = await this.fetch(
|
||||
@@ -323,10 +312,7 @@ export class ILertClient implements ILertApi {
|
||||
): Promise<UptimeMonitor> {
|
||||
const init = {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json',
|
||||
},
|
||||
headers: JSON_HEADERS,
|
||||
body: JSON.stringify({ ...uptimeMonitor, paused: true }),
|
||||
};
|
||||
|
||||
@@ -343,10 +329,7 @@ export class ILertClient implements ILertApi {
|
||||
): Promise<UptimeMonitor> {
|
||||
const init = {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json',
|
||||
},
|
||||
headers: JSON_HEADERS,
|
||||
body: JSON.stringify({ ...uptimeMonitor, paused: false }),
|
||||
};
|
||||
|
||||
@@ -360,10 +343,7 @@ export class ILertClient implements ILertApi {
|
||||
|
||||
async fetchAlertSources(): Promise<AlertSource[]> {
|
||||
const init = {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json',
|
||||
},
|
||||
headers: JSON_HEADERS,
|
||||
};
|
||||
|
||||
const response = await this.fetch('/api/v1/alert-sources', init);
|
||||
@@ -375,10 +355,7 @@ export class ILertClient implements ILertApi {
|
||||
idOrIntegrationKey: number | string,
|
||||
): Promise<AlertSource> {
|
||||
const init = {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json',
|
||||
},
|
||||
headers: JSON_HEADERS,
|
||||
};
|
||||
|
||||
const response = await this.fetch(
|
||||
@@ -391,16 +368,13 @@ export class ILertClient implements ILertApi {
|
||||
|
||||
async fetchAlertSourceOnCalls(alertSource: AlertSource): Promise<OnCall[]> {
|
||||
const init = {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json',
|
||||
},
|
||||
headers: JSON_HEADERS,
|
||||
};
|
||||
|
||||
const response = await this.fetch(
|
||||
`/api/v1/on-calls?policies=${
|
||||
alertSource.escalationPolicy.id
|
||||
}&expand=user&expand=escalationPolicy&timezone=${momentTimezone.tz.guess()}`,
|
||||
}&expand=user&expand=escalationPolicy&timezone=${dt.local().zoneName}`,
|
||||
init,
|
||||
);
|
||||
|
||||
@@ -410,10 +384,7 @@ export class ILertClient implements ILertApi {
|
||||
async enableAlertSource(alertSource: AlertSource): Promise<AlertSource> {
|
||||
const init = {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json',
|
||||
},
|
||||
headers: JSON_HEADERS,
|
||||
body: JSON.stringify({ ...alertSource, active: true }),
|
||||
};
|
||||
|
||||
@@ -428,10 +399,7 @@ export class ILertClient implements ILertApi {
|
||||
async disableAlertSource(alertSource: AlertSource): Promise<AlertSource> {
|
||||
const init = {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json',
|
||||
},
|
||||
headers: JSON_HEADERS,
|
||||
body: JSON.stringify({ ...alertSource, active: false }),
|
||||
};
|
||||
|
||||
@@ -449,16 +417,13 @@ export class ILertClient implements ILertApi {
|
||||
): Promise<void> {
|
||||
const init = {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json',
|
||||
},
|
||||
headers: JSON_HEADERS,
|
||||
body: JSON.stringify({
|
||||
start: moment().utc().toISOString(),
|
||||
end: moment().add(minutes, 'minutes').utc().toISOString(),
|
||||
start: dt.utc().toISO(),
|
||||
end: dt.utc().plus({ minutes }).toISO(),
|
||||
description: `Immediate maintenance window for ${minutes} minutes. Backstage — iLert plugin.`,
|
||||
createdBy: 'Backstage',
|
||||
timezone: momentTimezone.tz.guess(),
|
||||
timezone: dt.local().zoneName,
|
||||
alertSources: [{ id: alertSourceId }],
|
||||
}),
|
||||
};
|
||||
@@ -470,10 +435,7 @@ export class ILertClient implements ILertApi {
|
||||
|
||||
async fetchOnCallSchedules(): Promise<Schedule[]> {
|
||||
const init = {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json',
|
||||
},
|
||||
headers: JSON_HEADERS,
|
||||
};
|
||||
|
||||
const response = await this.fetch('/api/v1/schedules', init);
|
||||
@@ -483,10 +445,7 @@ export class ILertClient implements ILertApi {
|
||||
|
||||
async fetchUsers(): Promise<User[]> {
|
||||
const init = {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json',
|
||||
},
|
||||
headers: JSON_HEADERS,
|
||||
};
|
||||
|
||||
const response = await this.fetch('/api/v1/users', init);
|
||||
@@ -502,10 +461,7 @@ export class ILertClient implements ILertApi {
|
||||
): Promise<Schedule> {
|
||||
const init = {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json',
|
||||
},
|
||||
headers: JSON_HEADERS,
|
||||
body: JSON.stringify({ user: { id: userId }, start, end }),
|
||||
};
|
||||
|
||||
@@ -541,17 +497,7 @@ export class ILertClient implements ILertApi {
|
||||
}
|
||||
|
||||
getUserPhoneNumber(user: User | null) {
|
||||
if (!user) {
|
||||
return '';
|
||||
}
|
||||
if (user.mobile) {
|
||||
return user.mobile.number;
|
||||
}
|
||||
|
||||
if (user.landline) {
|
||||
return user.landline.number;
|
||||
}
|
||||
return '';
|
||||
return user?.mobile?.number || user?.landline?.number || '';
|
||||
}
|
||||
|
||||
getUserInitials(user: User | null) {
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { ILertClient, ilertApiRef, UnauthorizedError } from './client';
|
||||
export { ILertClient, ilertApiRef } from './client';
|
||||
export type {
|
||||
ILertApi,
|
||||
GetIncidentsCountOpts,
|
||||
|
||||
@@ -54,10 +54,11 @@ export type EventRequest = {
|
||||
export interface ILertApi {
|
||||
fetchIncidents(opts?: GetIncidentsOpts): Promise<Incident[]>;
|
||||
fetchIncidentsCount(opts?: GetIncidentsCountOpts): Promise<number>;
|
||||
fetchIncident(id: number): Promise<Incident>;
|
||||
fetchIncidentResponders(incident: Incident): Promise<IncidentResponder[]>;
|
||||
fetchIncidentActions(incident: Incident): Promise<IncidentAction[]>;
|
||||
acceptIncident(incident: Incident): Promise<Incident>;
|
||||
resolveIncident(incident: Incident): Promise<Incident>;
|
||||
acceptIncident(incident: Incident, userName: string): Promise<Incident>;
|
||||
resolveIncident(incident: Incident, userName: string): Promise<Incident>;
|
||||
assignIncident(
|
||||
incident: Incident,
|
||||
responder: IncidentResponder,
|
||||
@@ -115,5 +116,5 @@ export type Options = {
|
||||
/**
|
||||
* Path to use for requests via the proxy, defaults to /ilert/api
|
||||
*/
|
||||
proxyPath?: string;
|
||||
proxyPath: string;
|
||||
};
|
||||
|
||||
@@ -14,8 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { useApi } from '@backstage/core';
|
||||
import Link from '@material-ui/core/Link';
|
||||
import { useApi, Link } from '@backstage/core';
|
||||
import Grid from '@material-ui/core/Grid';
|
||||
import { AlertSource } from '../../types';
|
||||
import { ilertApiRef } from '../../api';
|
||||
@@ -61,7 +60,7 @@ export const AlertSourceLink = ({
|
||||
<Grid item xs={10}>
|
||||
<Link
|
||||
className={classes.link}
|
||||
href={ilertApi.getAlertSourceDetailsURL(alertSource)}
|
||||
to={ilertApi.getAlertSourceDetailsURL(alertSource)}
|
||||
>
|
||||
{alertSource.name}
|
||||
</Link>
|
||||
|
||||
@@ -14,8 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { useApi } from '@backstage/core';
|
||||
import Link from '@material-ui/core/Link';
|
||||
import { useApi, Link } from '@backstage/core';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import { EscalationPolicy } from '../../types';
|
||||
import { ilertApiRef } from '../../api';
|
||||
@@ -41,7 +40,7 @@ export const EscalationPolicyLink = ({
|
||||
return (
|
||||
<Link
|
||||
className={classes.link}
|
||||
href={ilertApi.getEscalationPolicyDetailsURL(escalationPolicy)}
|
||||
to={ilertApi.getEscalationPolicyDetailsURL(escalationPolicy)}
|
||||
>
|
||||
{escalationPolicy.name}
|
||||
</Link>
|
||||
|
||||
@@ -15,14 +15,14 @@
|
||||
*/
|
||||
import React from 'react';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { UnauthorizedError } from '../../api';
|
||||
import Alert from '@material-ui/lab/Alert';
|
||||
import { AuthenticationError } from '@backstage/errors';
|
||||
import { ResponseErrorPanel } from '@backstage/core';
|
||||
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 { ILERT_INTEGRATION_KEY } from '../../constants';
|
||||
import { ILERT_INTEGRATION_KEY_ANNOTATION } from '../../constants';
|
||||
import { MissingAuthorizationHeaderError } from '../Errors';
|
||||
import { useIncidents } from '../../hooks/useIncidents';
|
||||
import { IncidentsTable } from '../IncidentsPage';
|
||||
@@ -36,7 +36,7 @@ import { ILertCardEmptyState } from './ILertCardEmptyState';
|
||||
import { ILertCardOnCall } from './ILertCardOnCall';
|
||||
|
||||
export const isPluginApplicableToEntity = (entity: Entity) =>
|
||||
Boolean(entity.metadata.annotations?.[ILERT_INTEGRATION_KEY]);
|
||||
Boolean(entity.metadata.annotations?.[ILERT_INTEGRATION_KEY_ANNOTATION]);
|
||||
|
||||
const useStyles = makeStyles({
|
||||
content: {
|
||||
@@ -79,15 +79,11 @@ export const ILertCard = () => {
|
||||
] = React.useState(false);
|
||||
|
||||
if (error) {
|
||||
if (error instanceof UnauthorizedError) {
|
||||
if (error instanceof AuthenticationError) {
|
||||
return <MissingAuthorizationHeaderError />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Alert data-testid="error-message" severity="error">
|
||||
{error.message}
|
||||
</Alert>
|
||||
);
|
||||
return <ResponseErrorPanel error={error} />;
|
||||
}
|
||||
|
||||
if (!integrationKey) {
|
||||
|
||||
@@ -40,14 +40,12 @@ spec:
|
||||
const useStyles = makeStyles<BackstageTheme>(theme => ({
|
||||
code: {
|
||||
borderRadius: 6,
|
||||
margin: `${theme.spacing(2)}px 0px`,
|
||||
margin: theme.spacing(2, 0),
|
||||
background: theme.palette.type === 'dark' ? '#444' : '#fff',
|
||||
},
|
||||
header: {
|
||||
display: 'inline-block',
|
||||
padding: `${theme.spacing(2)}px ${theme.spacing(2)}px ${theme.spacing(
|
||||
2,
|
||||
)}px ${theme.spacing(2.5)}px`,
|
||||
padding: theme.spacing(2, 2, 2, 2.5),
|
||||
},
|
||||
}));
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ import { makeStyles } from '@material-ui/core/styles';
|
||||
import { OnCall } from '../../types';
|
||||
import { useApi } from '@backstage/core';
|
||||
import { ilertApiRef } from '../../api';
|
||||
import moment from 'moment';
|
||||
import { DateTime as dt } from 'luxon';
|
||||
|
||||
const useStyles = makeStyles({
|
||||
listItemPrimary: {
|
||||
@@ -123,8 +123,8 @@ export const ILertCardOnCallItem = ({
|
||||
<Tooltip
|
||||
title={
|
||||
'On call shift ' +
|
||||
`${moment(onCall.start).format('D MMM, HH:mm')} - ` +
|
||||
`${moment(onCall.end).format('D MMM, HH:mm')}`
|
||||
`${dt.fromISO(onCall.start).toFormat('D MMM, HH:mm')} - ` +
|
||||
`${dt.fromISO(onCall.end).toFormat('D MMM, HH:mm')}`
|
||||
}
|
||||
placement="top-start"
|
||||
>
|
||||
|
||||
@@ -14,9 +14,14 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { alertApiRef, Progress, useApi } from '@backstage/core';
|
||||
import {
|
||||
alertApiRef,
|
||||
Progress,
|
||||
useApi,
|
||||
identityApiRef,
|
||||
Link,
|
||||
} from '@backstage/core';
|
||||
import { IconButton, Menu, MenuItem, Typography } from '@material-ui/core';
|
||||
import Link from '@material-ui/core/Link';
|
||||
import MoreVertIcon from '@material-ui/icons/MoreVert';
|
||||
import { ilertApiRef } from '../../api';
|
||||
import { Incident, IncidentAction } from '../../types';
|
||||
@@ -34,6 +39,8 @@ export const IncidentActionsMenu = ({
|
||||
}) => {
|
||||
const ilertApi = useApi(ilertApiRef);
|
||||
const alertApi = useApi(alertApiRef);
|
||||
const identityApi = useApi(identityApiRef);
|
||||
const userName = identityApi.getUserId();
|
||||
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
|
||||
const callback = onIncidentChanged || ((_: Incident): void => {});
|
||||
const setProcessing = setIsLoading || ((_: boolean): void => {});
|
||||
@@ -59,7 +66,7 @@ export const IncidentActionsMenu = ({
|
||||
try {
|
||||
handleCloseMenu();
|
||||
setProcessing(true);
|
||||
const newIncident = await ilertApi.acceptIncident(incident);
|
||||
const newIncident = await ilertApi.acceptIncident(incident, userName);
|
||||
alertApi.post({ message: 'Incident accepted.' });
|
||||
|
||||
callback(newIncident);
|
||||
@@ -74,7 +81,7 @@ export const IncidentActionsMenu = ({
|
||||
try {
|
||||
handleCloseMenu();
|
||||
setProcessing(true);
|
||||
const newIncident = await ilertApi.resolveIncident(incident);
|
||||
const newIncident = await ilertApi.resolveIncident(incident, userName);
|
||||
alertApi.post({ message: 'Incident resolved.' });
|
||||
|
||||
callback(newIncident);
|
||||
@@ -179,7 +186,7 @@ export const IncidentActionsMenu = ({
|
||||
|
||||
<MenuItem key="details" onClick={handleCloseMenu}>
|
||||
<Typography variant="inherit" noWrap>
|
||||
<Link href={ilertApi.getIncidentDetailsURL(incident)}>
|
||||
<Link to={ilertApi.getIncidentDetailsURL(incident)}>
|
||||
View in iLert
|
||||
</Link>
|
||||
</Typography>
|
||||
|
||||
@@ -14,8 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { useApi } from '@backstage/core';
|
||||
import Link from '@material-ui/core/Link';
|
||||
import { useApi, Link } from '@backstage/core';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import { Incident } from '../../types';
|
||||
import { ilertApiRef } from '../../api';
|
||||
@@ -37,7 +36,7 @@ export const IncidentLink = ({ incident }: { incident: Incident | null }) => {
|
||||
return (
|
||||
<Link
|
||||
className={classes.link}
|
||||
href={ilertApi.getIncidentDetailsURL(incident)}
|
||||
to={ilertApi.getIncidentDetailsURL(incident)}
|
||||
>
|
||||
#{incident.id}
|
||||
</Link>
|
||||
|
||||
@@ -98,17 +98,15 @@ export const IncidentNewModal = ({
|
||||
setIsLoading(true);
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
const success = await ilertApi.createIncident({
|
||||
await ilertApi.createIncident({
|
||||
integrationKey,
|
||||
summary,
|
||||
details,
|
||||
userName,
|
||||
source,
|
||||
});
|
||||
if (success) {
|
||||
alertApi.post({ message: 'Incident created.' });
|
||||
refetchIncidents();
|
||||
}
|
||||
alertApi.post({ message: 'Incident created.' });
|
||||
refetchIncidents();
|
||||
} catch (err) {
|
||||
alertApi.post({ message: err, severity: 'error' });
|
||||
}
|
||||
|
||||
@@ -14,11 +14,15 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { Content, ContentHeader, SupportButton } from '@backstage/core';
|
||||
import {
|
||||
Content,
|
||||
ContentHeader,
|
||||
SupportButton,
|
||||
ResponseErrorPanel,
|
||||
} from '@backstage/core';
|
||||
import { AuthenticationError } from '@backstage/errors';
|
||||
import Button from '@material-ui/core/Button';
|
||||
import AddIcon from '@material-ui/icons/Add';
|
||||
import { UnauthorizedError } from '../../api';
|
||||
import Alert from '@material-ui/lab/Alert';
|
||||
import { IncidentsTable } from './IncidentsTable';
|
||||
import { MissingAuthorizationHeaderError } from '../Errors';
|
||||
import { useIncidents } from '../../hooks/useIncidents';
|
||||
@@ -44,14 +48,18 @@ export const IncidentsPage = () => {
|
||||
};
|
||||
|
||||
if (error) {
|
||||
if (error instanceof UnauthorizedError) {
|
||||
return <MissingAuthorizationHeaderError />;
|
||||
if (error instanceof AuthenticationError) {
|
||||
return (
|
||||
<Content>
|
||||
<MissingAuthorizationHeaderError />
|
||||
</Content>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Alert data-testid="error-message" severity="error">
|
||||
{error.message}
|
||||
</Alert>
|
||||
<Content>
|
||||
<ResponseErrorPanel error={error} />
|
||||
</Content>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,8 @@ import { StatusChip } from './StatusChip';
|
||||
import { AlertSourceLink } from '../AlertSource/AlertSourceLink';
|
||||
import { TableTitle } from './TableTitle';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import moment from 'moment';
|
||||
import { DateTime as dt, Interval } from 'luxon';
|
||||
import humanizeDuration from 'humanize-duration';
|
||||
import { IncidentActionsMenu } from '../Incident/IncidentActionsMenu';
|
||||
import { IncidentLink } from '../Incident/IncidentLink';
|
||||
|
||||
@@ -116,16 +117,24 @@ export const IncidentsTable = ({
|
||||
render: rowData => (
|
||||
<Typography noWrap>
|
||||
{(rowData as Incident).status !== 'RESOLVED'
|
||||
? moment
|
||||
.duration(moment((rowData as Incident).reportTime).diff(moment()))
|
||||
.humanize()
|
||||
: moment
|
||||
.duration(
|
||||
moment((rowData as Incident).reportTime).diff(
|
||||
moment((rowData as Incident).resolvedOn),
|
||||
),
|
||||
? humanizeDuration(
|
||||
Interval.fromDateTimes(
|
||||
dt.fromISO((rowData as Incident).reportTime),
|
||||
dt.now(),
|
||||
)
|
||||
.humanize()}
|
||||
.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),
|
||||
)
|
||||
.toDuration()
|
||||
.valueOf(),
|
||||
{ units: ['h', 'm', 's'], largest: 2, round: true },
|
||||
)}
|
||||
</Typography>
|
||||
),
|
||||
};
|
||||
|
||||
@@ -14,13 +14,12 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { useApi, ItemCardGrid, Progress } from '@backstage/core';
|
||||
import { useApi, ItemCardGrid, Progress, Link } from '@backstage/core';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import Card from '@material-ui/core/Card';
|
||||
import CardContent from '@material-ui/core/CardContent';
|
||||
import CardHeader from '@material-ui/core/CardHeader';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import Link from '@material-ui/core/Link';
|
||||
import { Schedule } from '../../types';
|
||||
import { ilertApiRef } from '../../api';
|
||||
import { OnCallShiftItem } from './OnCallShiftItem';
|
||||
@@ -132,7 +131,7 @@ export const OnCallSchedulesGrid = ({
|
||||
classes={{ content: classes.cardHeader }}
|
||||
title={
|
||||
<Link
|
||||
href={ilertApi.getScheduleDetailsURL(schedule)}
|
||||
to={ilertApi.getScheduleDetailsURL(schedule)}
|
||||
className={classes.link}
|
||||
>
|
||||
{schedule.name}
|
||||
|
||||
@@ -14,9 +14,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { Content, ContentHeader, SupportButton } from '@backstage/core';
|
||||
import { UnauthorizedError } from '../../api';
|
||||
import Alert from '@material-ui/lab/Alert';
|
||||
import {
|
||||
Content,
|
||||
ContentHeader,
|
||||
SupportButton,
|
||||
ResponseErrorPanel,
|
||||
} from '@backstage/core';
|
||||
import { AuthenticationError } from '@backstage/errors';
|
||||
import { OnCallSchedulesGrid } from './OnCallSchedulesGrid';
|
||||
import { MissingAuthorizationHeaderError } from '../Errors';
|
||||
import { useOnCallSchedules } from '../../hooks/useOnCallSchedules';
|
||||
@@ -28,14 +32,18 @@ export const OnCallSchedulesPage = () => {
|
||||
] = useOnCallSchedules();
|
||||
|
||||
if (error) {
|
||||
if (error instanceof UnauthorizedError) {
|
||||
return <MissingAuthorizationHeaderError />;
|
||||
if (error instanceof AuthenticationError) {
|
||||
return (
|
||||
<Content>
|
||||
<MissingAuthorizationHeaderError />
|
||||
</Content>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Alert data-testid="error-message" severity="error">
|
||||
{error.message}
|
||||
</Alert>
|
||||
<Content>
|
||||
<ResponseErrorPanel error={error} />
|
||||
</Content>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ import Grid from '@material-ui/core/Grid';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import RepeatIcon from '@material-ui/icons/Repeat';
|
||||
import { Shift } from '../../types';
|
||||
import moment from 'moment';
|
||||
import { DateTime as dt } from 'luxon';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import { ShiftOverrideModal } from '../Shift/ShiftOverrideModal';
|
||||
|
||||
@@ -77,9 +77,9 @@ export const OnCallShiftItem = ({
|
||||
) : null}
|
||||
<Grid item sm={12}>
|
||||
<Typography variant="subtitle2" color="textSecondary">
|
||||
{`${moment(shift.start).format('D MMM, HH:mm')} - ${moment(
|
||||
shift.end,
|
||||
).format('D MMM, HH:mm')}`}
|
||||
{`${dt.fromISO(shift.start).toFormat('D MMM, HH:mm')} - ${dt
|
||||
.fromISO(shift.end)
|
||||
.toFormat('D MMM, HH:mm')}`}
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item sm={12}>
|
||||
|
||||
@@ -28,7 +28,7 @@ import { ilertApiRef } from '../../api';
|
||||
import { useShiftOverride } from '../../hooks/useShiftOverride';
|
||||
import { Shift } from '../../types';
|
||||
import { DateTimePicker, MuiPickersUtilsProvider } from '@material-ui/pickers';
|
||||
import DateFnsUtils from '@date-io/date-fns';
|
||||
import LuxonUtils from '@date-io/luxon';
|
||||
|
||||
const useStyles = makeStyles(() => ({
|
||||
container: {
|
||||
@@ -120,7 +120,7 @@ export const ShiftOverrideModal = ({
|
||||
>
|
||||
<DialogTitle id="override-shift-form-title">Shift override</DialogTitle>
|
||||
<DialogContent>
|
||||
<MuiPickersUtilsProvider utils={DateFnsUtils}>
|
||||
<MuiPickersUtilsProvider utils={LuxonUtils}>
|
||||
<Autocomplete
|
||||
disabled={isLoading}
|
||||
options={users}
|
||||
@@ -161,7 +161,7 @@ export const ShiftOverrideModal = ({
|
||||
value={start}
|
||||
className={classes.formControl}
|
||||
onChange={date => {
|
||||
setStart(date ? date.toISOString() : '');
|
||||
setStart(date ? date.toISO() : '');
|
||||
}}
|
||||
/>
|
||||
<DateTimePicker
|
||||
@@ -173,7 +173,7 @@ export const ShiftOverrideModal = ({
|
||||
value={end}
|
||||
className={classes.formControl}
|
||||
onChange={date => {
|
||||
setEnd(date ? date.toISOString() : '');
|
||||
setEnd(date ? date.toISO() : '');
|
||||
}}
|
||||
/>
|
||||
</MuiPickersUtilsProvider>
|
||||
|
||||
@@ -14,9 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { alertApiRef, useApi } from '@backstage/core';
|
||||
import { alertApiRef, useApi, Link } from '@backstage/core';
|
||||
import { IconButton, Menu, MenuItem, Typography } from '@material-ui/core';
|
||||
import Link from '@material-ui/core/Link';
|
||||
import MoreVertIcon from '@material-ui/icons/MoreVert';
|
||||
|
||||
import { ilertApiRef } from '../../api';
|
||||
@@ -117,13 +116,15 @@ export const UptimeMonitorActionsMenu = ({
|
||||
|
||||
<MenuItem key="report" onClick={handleCloseMenu}>
|
||||
<Typography variant="inherit" noWrap>
|
||||
<Link onClick={handleOpenReport}>View Report</Link>
|
||||
<Link to="#" onClick={handleOpenReport}>
|
||||
View Report
|
||||
</Link>
|
||||
</Typography>
|
||||
</MenuItem>
|
||||
|
||||
<MenuItem key="details" onClick={handleCloseMenu}>
|
||||
<Typography variant="inherit" noWrap>
|
||||
<Link href={ilertApi.getUptimeMonitorDetailsURL(uptimeMonitor)}>
|
||||
<Link to={ilertApi.getUptimeMonitorDetailsURL(uptimeMonitor)}>
|
||||
View in iLert
|
||||
</Link>
|
||||
</Typography>
|
||||
|
||||
@@ -14,8 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { useApi } from '@backstage/core';
|
||||
import Link from '@material-ui/core/Link';
|
||||
import { useApi, Link } from '@backstage/core';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import { UptimeMonitor } from '../../types';
|
||||
import { ilertApiRef } from '../../api';
|
||||
@@ -41,7 +40,7 @@ export const UptimeMonitorLink = ({
|
||||
return (
|
||||
<Link
|
||||
className={classes.link}
|
||||
href={ilertApi.getUptimeMonitorDetailsURL(uptimeMonitor)}
|
||||
to={ilertApi.getUptimeMonitorDetailsURL(uptimeMonitor)}
|
||||
>
|
||||
#{uptimeMonitor.id}
|
||||
</Link>
|
||||
|
||||
@@ -14,9 +14,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { Content, ContentHeader, SupportButton } from '@backstage/core';
|
||||
import { UnauthorizedError } from '../../api';
|
||||
import Alert from '@material-ui/lab/Alert';
|
||||
import {
|
||||
Content,
|
||||
ContentHeader,
|
||||
SupportButton,
|
||||
ResponseErrorPanel,
|
||||
} from '@backstage/core';
|
||||
import { AuthenticationError } from '@backstage/errors';
|
||||
import { UptimeMonitorsTable } from './UptimeMonitorsTable';
|
||||
import { MissingAuthorizationHeaderError } from '../Errors';
|
||||
import { useUptimeMonitors } from '../../hooks/useUptimeMonitors';
|
||||
@@ -28,14 +32,18 @@ export const UptimeMonitorsPage = () => {
|
||||
] = useUptimeMonitors();
|
||||
|
||||
if (error) {
|
||||
if (error instanceof UnauthorizedError) {
|
||||
return <MissingAuthorizationHeaderError />;
|
||||
if (error instanceof AuthenticationError) {
|
||||
return (
|
||||
<Content>
|
||||
<MissingAuthorizationHeaderError />
|
||||
</Content>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Alert data-testid="error-message" severity="error">
|
||||
{error.message}
|
||||
</Alert>
|
||||
<Content>
|
||||
<ResponseErrorPanel error={error} />
|
||||
</Content>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,8 @@ import { TableState } from '../../api';
|
||||
import { UptimeMonitor } from '../../types';
|
||||
import { StatusChip } from './StatusChip';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import moment from 'moment';
|
||||
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';
|
||||
@@ -99,13 +100,15 @@ export const UptimeMonitorsTable = ({
|
||||
headerStyle: mdColumnStyle,
|
||||
render: rowData => (
|
||||
<Typography noWrap>
|
||||
{moment
|
||||
.duration(
|
||||
moment((rowData as UptimeMonitor).lastStatusChange).diff(
|
||||
moment(),
|
||||
),
|
||||
{humanizeDuration(
|
||||
Interval.fromDateTimes(
|
||||
dt.fromISO((rowData as UptimeMonitor).lastStatusChange),
|
||||
dt.now(),
|
||||
)
|
||||
.humanize()}
|
||||
.toDuration()
|
||||
.valueOf(),
|
||||
{ units: ['h', 'm', 's'], largest: 2, round: true },
|
||||
)}
|
||||
</Typography>
|
||||
),
|
||||
},
|
||||
|
||||
@@ -16,13 +16,16 @@
|
||||
|
||||
import { useEntity } from '@backstage/plugin-catalog-react';
|
||||
|
||||
import { ILERT_INTEGRATION_KEY } from '../constants';
|
||||
import { ILERT_INTEGRATION_KEY_ANNOTATION } from '../constants';
|
||||
|
||||
export function useILertEntity() {
|
||||
const { entity } = useEntity();
|
||||
const integrationKey =
|
||||
entity.metadata.annotations?.[ILERT_INTEGRATION_KEY] || '';
|
||||
entity.metadata.annotations?.[ILERT_INTEGRATION_KEY_ANNOTATION] || '';
|
||||
const name = entity.metadata.name;
|
||||
const identifier = `${entity.kind}:${
|
||||
entity.metadata.namespace || 'default'
|
||||
}/${entity.metadata.name}`;
|
||||
|
||||
return { integrationKey, name };
|
||||
return { integrationKey, name, identifier };
|
||||
}
|
||||
|
||||
@@ -14,8 +14,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { ilertApiRef, UnauthorizedError } from '../api';
|
||||
import { ilertApiRef } from '../api';
|
||||
import { useApi, errorApiRef } from '@backstage/core';
|
||||
import { AuthenticationError } from '@backstage/errors';
|
||||
import { useAsyncRetry } from 'react-use';
|
||||
import { AlertSource, UptimeMonitor } from '../types';
|
||||
|
||||
@@ -46,7 +47,7 @@ export const useAlertSource = (integrationKey: string) => {
|
||||
setIsAlertSourceLoading(false);
|
||||
} catch (e) {
|
||||
setIsAlertSourceLoading(false);
|
||||
if (!(e instanceof UnauthorizedError)) {
|
||||
if (!(e instanceof AuthenticationError)) {
|
||||
errorApi.post(e);
|
||||
}
|
||||
throw e;
|
||||
@@ -69,7 +70,7 @@ export const useAlertSource = (integrationKey: string) => {
|
||||
setIsUptimeMonitorLoading(false);
|
||||
} catch (e) {
|
||||
setIsUptimeMonitorLoading(false);
|
||||
if (!(e instanceof UnauthorizedError)) {
|
||||
if (!(e instanceof AuthenticationError)) {
|
||||
errorApi.post(e);
|
||||
}
|
||||
throw e;
|
||||
|
||||
@@ -14,8 +14,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { ilertApiRef, UnauthorizedError } from '../api';
|
||||
import { ilertApiRef } from '../api';
|
||||
import { useApi, errorApiRef } from '@backstage/core';
|
||||
import { AuthenticationError } from '@backstage/errors';
|
||||
import { useAsyncRetry } from 'react-use';
|
||||
import { AlertSource, OnCall } from '../types';
|
||||
|
||||
@@ -37,7 +38,7 @@ export const useAlertSourceOnCalls = (alertSource?: AlertSource | null) => {
|
||||
setIsLoading(false);
|
||||
} catch (e) {
|
||||
setIsLoading(false);
|
||||
if (!(e instanceof UnauthorizedError)) {
|
||||
if (!(e instanceof AuthenticationError)) {
|
||||
errorApi.post(e);
|
||||
}
|
||||
throw e;
|
||||
|
||||
@@ -14,8 +14,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { ilertApiRef, UnauthorizedError } from '../api';
|
||||
import { ilertApiRef } from '../api';
|
||||
import { useApi, errorApiRef } from '@backstage/core';
|
||||
import { AuthenticationError } from '@backstage/errors';
|
||||
import { useAsyncRetry } from 'react-use';
|
||||
import { Incident, IncidentResponder } from '../types';
|
||||
|
||||
@@ -49,7 +50,7 @@ export const useAssignIncident = (incident: Incident | null, open: boolean) => {
|
||||
setIncidentRespondersList(data);
|
||||
}
|
||||
} catch (e) {
|
||||
if (!(e instanceof UnauthorizedError)) {
|
||||
if (!(e instanceof AuthenticationError)) {
|
||||
errorApi.post(e);
|
||||
}
|
||||
throw e;
|
||||
|
||||
@@ -14,8 +14,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { ilertApiRef, UnauthorizedError } from '../api';
|
||||
import { ilertApiRef } from '../api';
|
||||
import { useApi, errorApiRef } from '@backstage/core';
|
||||
import { AuthenticationError } from '@backstage/errors';
|
||||
import { useAsyncRetry } from 'react-use';
|
||||
import { Incident, IncidentAction } from '../types';
|
||||
|
||||
@@ -39,7 +40,7 @@ export const useIncidentActions = (
|
||||
const data = await ilertApi.fetchIncidentActions(incident);
|
||||
setIncidentActionsList(data);
|
||||
} catch (e) {
|
||||
if (!(e instanceof UnauthorizedError)) {
|
||||
if (!(e instanceof AuthenticationError)) {
|
||||
errorApi.post(e);
|
||||
}
|
||||
throw e;
|
||||
|
||||
@@ -14,13 +14,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import {
|
||||
GetIncidentsOpts,
|
||||
ilertApiRef,
|
||||
TableState,
|
||||
UnauthorizedError,
|
||||
} from '../api';
|
||||
import { GetIncidentsOpts, ilertApiRef, TableState } from '../api';
|
||||
import { useApi, errorApiRef } from '@backstage/core';
|
||||
import { AuthenticationError } from '@backstage/errors';
|
||||
import { useAsyncRetry } from 'react-use';
|
||||
import {
|
||||
ACCEPTED,
|
||||
@@ -68,7 +64,7 @@ export const useIncidents = (
|
||||
setIncidentsList(data || []);
|
||||
setIsLoading(false);
|
||||
} catch (e) {
|
||||
if (!(e instanceof UnauthorizedError)) {
|
||||
if (!(e instanceof AuthenticationError)) {
|
||||
errorApi.post(e);
|
||||
}
|
||||
setIsLoading(false);
|
||||
@@ -81,7 +77,7 @@ export const useIncidents = (
|
||||
const count = await ilertApi.fetchIncidentsCount({ states });
|
||||
setIncidentsCount(count || 0);
|
||||
} catch (e) {
|
||||
if (!(e instanceof UnauthorizedError)) {
|
||||
if (!(e instanceof AuthenticationError)) {
|
||||
errorApi.post(e);
|
||||
}
|
||||
throw e;
|
||||
|
||||
@@ -14,8 +14,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { ilertApiRef, UnauthorizedError } from '../api';
|
||||
import { ilertApiRef } from '../api';
|
||||
import { useApi, errorApiRef } from '@backstage/core';
|
||||
import { AuthenticationError } from '@backstage/errors';
|
||||
import { useAsyncRetry } from 'react-use';
|
||||
import { AlertSource } from '../types';
|
||||
|
||||
@@ -44,7 +45,7 @@ export const useNewIncident = (
|
||||
const count = await ilertApi.fetchAlertSources();
|
||||
setAlertSourcesList(count || 0);
|
||||
} catch (e) {
|
||||
if (!(e instanceof UnauthorizedError)) {
|
||||
if (!(e instanceof AuthenticationError)) {
|
||||
errorApi.post(e);
|
||||
}
|
||||
throw e;
|
||||
|
||||
@@ -14,8 +14,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { ilertApiRef, UnauthorizedError } from '../api';
|
||||
import { ilertApiRef } from '../api';
|
||||
import { useApi, errorApiRef } from '@backstage/core';
|
||||
import { AuthenticationError } from '@backstage/errors';
|
||||
import { useAsyncRetry } from 'react-use';
|
||||
import { Schedule } from '../types';
|
||||
|
||||
@@ -36,7 +37,7 @@ export const useOnCallSchedules = () => {
|
||||
setIsLoading(false);
|
||||
} catch (e) {
|
||||
setIsLoading(false);
|
||||
if (!(e instanceof UnauthorizedError)) {
|
||||
if (!(e instanceof AuthenticationError)) {
|
||||
errorApi.post(e);
|
||||
}
|
||||
throw e;
|
||||
|
||||
@@ -14,8 +14,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { ilertApiRef, UnauthorizedError } from '../api';
|
||||
import { ilertApiRef } from '../api';
|
||||
import { useApi, errorApiRef } from '@backstage/core';
|
||||
import { AuthenticationError } from '@backstage/errors';
|
||||
import { useAsyncRetry } from 'react-use';
|
||||
import { User, Shift } from '../types';
|
||||
|
||||
@@ -38,7 +39,7 @@ export const useShiftOverride = (s: Shift, isModalOpened: boolean) => {
|
||||
setIsLoading(false);
|
||||
} catch (e) {
|
||||
setIsLoading(false);
|
||||
if (!(e instanceof UnauthorizedError)) {
|
||||
if (!(e instanceof AuthenticationError)) {
|
||||
errorApi.post(e);
|
||||
}
|
||||
throw e;
|
||||
|
||||
@@ -14,8 +14,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { ilertApiRef, TableState, UnauthorizedError } from '../api';
|
||||
import { ilertApiRef, TableState } from '../api';
|
||||
import { useApi, errorApiRef } from '@backstage/core';
|
||||
import { AuthenticationError } from '@backstage/errors';
|
||||
import { useAsyncRetry } from 'react-use';
|
||||
import { UptimeMonitor } from '../types';
|
||||
|
||||
@@ -40,7 +41,7 @@ export const useUptimeMonitors = () => {
|
||||
setIsLoading(false);
|
||||
} catch (e) {
|
||||
setIsLoading(false);
|
||||
if (!(e instanceof UnauthorizedError)) {
|
||||
if (!(e instanceof AuthenticationError)) {
|
||||
errorApi.post(e);
|
||||
}
|
||||
throw e;
|
||||
|
||||
Reference in New Issue
Block a user