rename azure-functions to azure-sites
add drop down for actions show kind on table Signed-off-by: Wesley Pattison <wesley.pattison@friss.com>
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2022 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 { createApiRef } from '@backstage/core-plugin-api';
|
||||
import {
|
||||
AzureSiteListRequest,
|
||||
AzureSiteListResponse,
|
||||
AzureSiteStartStopRequest,
|
||||
} from '@backstage/plugin-azure-sites-common';
|
||||
|
||||
/** @public */
|
||||
export const azureSiteApiRef = createApiRef<AzureSitesApi>({
|
||||
id: 'plugin.azure-sites.service',
|
||||
});
|
||||
|
||||
/** @public */
|
||||
export type AzureSitesApi = {
|
||||
list: (request: AzureSiteListRequest) => Promise<AzureSiteListResponse>;
|
||||
start: (request: AzureSiteStartStopRequest) => Promise<void>;
|
||||
stop: (request: AzureSiteStartStopRequest) => Promise<void>;
|
||||
};
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright 2022 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 { AzureSitesApi } from './AzureSitesApi';
|
||||
import {
|
||||
AzureSiteListRequest,
|
||||
AzureSiteListResponse,
|
||||
AzureSiteStartStopRequest,
|
||||
} from '@backstage/plugin-azure-sites-common';
|
||||
import { DiscoveryApi, IdentityApi } from '@backstage/core-plugin-api';
|
||||
|
||||
/** @public */
|
||||
export class AzureSitesApiBackendClient implements AzureSitesApi {
|
||||
private readonly identityApi: IdentityApi;
|
||||
private readonly discoveryApi: DiscoveryApi;
|
||||
constructor(options: {
|
||||
discoveryApi: DiscoveryApi;
|
||||
identityApi: IdentityApi;
|
||||
}) {
|
||||
this.discoveryApi = options.discoveryApi;
|
||||
this.identityApi = options.identityApi;
|
||||
}
|
||||
|
||||
async stop(request: AzureSiteStartStopRequest): Promise<void> {
|
||||
try {
|
||||
const url = `${await this.discoveryApi.getBaseUrl('azure-functions')}/${
|
||||
request.subscription
|
||||
}/${request.resourceGroup}/${request.name}/stop`;
|
||||
const { token: accessToken } = await this.identityApi.getCredentials();
|
||||
await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...(accessToken && { Authorization: `Bearer ${accessToken}` }),
|
||||
},
|
||||
});
|
||||
} catch (e: any) {
|
||||
throw new Error(e);
|
||||
}
|
||||
}
|
||||
async start(request: AzureSiteStartStopRequest): Promise<void> {
|
||||
try {
|
||||
const url = `${await this.discoveryApi.getBaseUrl('azure-functions')}/${
|
||||
request.subscription
|
||||
}/${request.resourceGroup}/${request.name}/start`;
|
||||
const { token: accessToken } = await this.identityApi.getCredentials();
|
||||
await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...(accessToken && { Authorization: `Bearer ${accessToken}` }),
|
||||
},
|
||||
});
|
||||
} catch (e: any) {
|
||||
throw new Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
async list(request: AzureSiteListRequest): Promise<AzureSiteListResponse> {
|
||||
try {
|
||||
const url = `${await this.discoveryApi.getBaseUrl('azure-sites')}/list/${
|
||||
request.name
|
||||
}`;
|
||||
const { token: accessToken } = await this.identityApi.getCredentials();
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...(accessToken && { Authorization: `Bearer ${accessToken}` }),
|
||||
},
|
||||
});
|
||||
return await response.json();
|
||||
} catch (e: any) {
|
||||
throw new Error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright 2022 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 './AzureSitesApi';
|
||||
export * from './AzureSitesApiBackendClient';
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright 2022 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 { Entity } from '@backstage/catalog-model';
|
||||
import { useSites } from '../../hooks/useSites';
|
||||
import {
|
||||
AZURE_WEB_SITE_NAME_ANNOTATION,
|
||||
useServiceEntityAnnotations,
|
||||
} from '../../hooks/useServiceEntityAnnotations';
|
||||
import {
|
||||
ErrorBoundary,
|
||||
MissingAnnotationEmptyState,
|
||||
ResponseErrorPanel,
|
||||
} from '@backstage/core-components';
|
||||
import { useEntity } from '@backstage/plugin-catalog-react';
|
||||
import { AzureSitesOverviewTable } from '../AzureSitesOverviewTableComponent/AzureSitesOverviewTable';
|
||||
|
||||
/** @public */
|
||||
export const isAzureWebSiteNameAvailable = (entity: Entity) =>
|
||||
entity?.metadata.annotations?.[AZURE_WEB_SITE_NAME_ANNOTATION];
|
||||
|
||||
const AzureSitesOverview = ({ entity }: { entity: Entity }) => {
|
||||
const { webSiteName } = useServiceEntityAnnotations(entity);
|
||||
|
||||
const [sites] = useSites({
|
||||
name: webSiteName,
|
||||
});
|
||||
|
||||
if (sites.error) {
|
||||
return (
|
||||
<div>
|
||||
<ResponseErrorPanel error={sites.error} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<AzureSitesOverviewTable
|
||||
data={sites.data?.items ?? []}
|
||||
loading={sites.loading}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
/** @public */
|
||||
export const AzureSitesOverviewWidget = () => {
|
||||
const { entity } = useEntity();
|
||||
|
||||
if (!isAzureWebSiteNameAvailable(entity)) {
|
||||
return (
|
||||
<MissingAnnotationEmptyState
|
||||
annotation={AZURE_WEB_SITE_NAME_ANNOTATION}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ErrorBoundary>
|
||||
<AzureSitesOverview entity={entity} />
|
||||
</ErrorBoundary>
|
||||
);
|
||||
};
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright 2022 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 { render } from '@testing-library/react';
|
||||
import {
|
||||
AnyApiRef,
|
||||
configApiRef,
|
||||
errorApiRef,
|
||||
identityApiRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { rest } from 'msw';
|
||||
import {
|
||||
setupRequestMockHandlers,
|
||||
TestApiProvider,
|
||||
} from '@backstage/test-utils';
|
||||
import { setupServer } from 'msw/node';
|
||||
import { DateTime } from 'luxon';
|
||||
import { siteMock } from '../../mocks/mocks';
|
||||
import { azureSiteApiRef } from '../..';
|
||||
import { AzureSitesOverviewTable } from './AzureSitesOverviewTable';
|
||||
|
||||
const errorApiMock = { post: jest.fn(), error$: jest.fn() };
|
||||
const identityApiMock = (getCredentials: any) => ({
|
||||
signOut: jest.fn(),
|
||||
getProfileInfo: jest.fn(),
|
||||
getBackstageIdentity: jest.fn(),
|
||||
getCredentials,
|
||||
});
|
||||
const azureSitesApiMock = {};
|
||||
|
||||
const config = {
|
||||
getString: (_: string) => 'https://test-url',
|
||||
};
|
||||
|
||||
const apis: [AnyApiRef, Partial<unknown>][] = [
|
||||
[errorApiRef, errorApiMock],
|
||||
[configApiRef, config],
|
||||
[azureSiteApiRef, azureSitesApiMock],
|
||||
[identityApiRef, identityApiMock],
|
||||
];
|
||||
|
||||
describe('AzureSitesOverviewWidget', () => {
|
||||
const worker = setupServer();
|
||||
setupRequestMockHandlers(worker);
|
||||
|
||||
beforeEach(() => {
|
||||
worker.use(
|
||||
rest.get('/list/func-mock', (_, res, ctx) => {
|
||||
res(ctx.json(siteMock));
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should display an overview table with the data from the requests', async () => {
|
||||
const rendered = render(
|
||||
<TestApiProvider apis={apis}>
|
||||
<AzureSitesOverviewTable data={[siteMock]} loading={false} />
|
||||
</TestApiProvider>,
|
||||
);
|
||||
|
||||
expect(await rendered.findByText(siteMock.name)).toBeInTheDocument();
|
||||
expect(await rendered.findByText(siteMock.location)).toBeInTheDocument();
|
||||
expect(await rendered.findByText(siteMock.state)).toBeInTheDocument();
|
||||
expect(
|
||||
await rendered.findByText(
|
||||
DateTime.fromISO(siteMock.lastModifiedDate).toLocaleString(
|
||||
DateTime.DATETIME_MED,
|
||||
),
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
+271
@@ -0,0 +1,271 @@
|
||||
/*
|
||||
* Copyright 2022 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, { Dispatch, useEffect, useState } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Card,
|
||||
IconButton,
|
||||
LinearProgress,
|
||||
Link,
|
||||
Menu,
|
||||
MenuItem,
|
||||
Snackbar,
|
||||
Tooltip,
|
||||
} from '@material-ui/core';
|
||||
import { default as MuiAlert } from '@material-ui/lab/Alert';
|
||||
import { AzureSite } from '@backstage/plugin-azure-sites-common';
|
||||
import { Table, TableColumn } from '@backstage/core-components';
|
||||
import FlashOnIcon from '@material-ui/icons/FlashOn';
|
||||
import PublicIcon from '@material-ui/icons/Public';
|
||||
import MoreVertIcon from '@material-ui/icons/MoreVert';
|
||||
import StartIcon from '@material-ui/icons/PlayArrow';
|
||||
import StopIcon from '@material-ui/icons/Stop';
|
||||
import OpenInNewIcon from '@material-ui/icons/OpenInNew';
|
||||
import { DateTime } from 'luxon';
|
||||
import { useApi } from '@backstage/core-plugin-api';
|
||||
import { azureSiteApiRef } from '../../api';
|
||||
|
||||
type States = 'Waiting' | 'Running' | 'Paused' | 'Failed' | 'Stopped';
|
||||
type Kinds = 'app' | 'functionapp';
|
||||
|
||||
const State = ({ value }: { value: States }) => {
|
||||
const colorMap = {
|
||||
Waiting: '#dcbc21',
|
||||
Running: 'green',
|
||||
Paused: 'black',
|
||||
Failed: 'red',
|
||||
Stopped: 'black',
|
||||
};
|
||||
return (
|
||||
<Box display="flex" alignItems="center">
|
||||
<span
|
||||
style={{
|
||||
display: 'block',
|
||||
width: '8px',
|
||||
height: '8px',
|
||||
borderRadius: '50%',
|
||||
backgroundColor: colorMap[value],
|
||||
marginRight: '5px',
|
||||
}}
|
||||
/>
|
||||
{value}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const Kind = ({ value }: { value: Kinds }) => {
|
||||
const iconMap = {
|
||||
app: <PublicIcon />,
|
||||
functionapp: <FlashOnIcon />,
|
||||
};
|
||||
return (
|
||||
<Box display="flex" alignItems="center">
|
||||
<Tooltip title={value}>{iconMap[value]}</Tooltip>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
type TableProps = {
|
||||
data: AzureSite[];
|
||||
loading: boolean;
|
||||
};
|
||||
|
||||
const ActionButtons = ({
|
||||
value,
|
||||
onMenuItemClick,
|
||||
}: {
|
||||
value: AzureSite;
|
||||
onMenuItemClick: Dispatch<React.SetStateAction<string | null>>;
|
||||
}) => {
|
||||
const azureApi = useApi(azureSiteApiRef);
|
||||
|
||||
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
||||
const open = Boolean(anchorEl);
|
||||
|
||||
const handleOpen = (event: React.MouseEvent<HTMLElement>) => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
};
|
||||
const handleClose = () => {
|
||||
setAnchorEl(null);
|
||||
};
|
||||
const start = () => {
|
||||
azureApi.start({
|
||||
name: value.name,
|
||||
resourceGroup: value.resourceGroup,
|
||||
subscription: value.subscription,
|
||||
});
|
||||
onMenuItemClick('Starting, this may take some time...');
|
||||
handleClose();
|
||||
};
|
||||
const stop = () => {
|
||||
azureApi.stop({
|
||||
name: value.name,
|
||||
resourceGroup: value.resourceGroup,
|
||||
subscription: value.subscription,
|
||||
});
|
||||
onMenuItemClick('Stopping, this may take some time...');
|
||||
handleClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<IconButton
|
||||
aria-label="more"
|
||||
id="long-button"
|
||||
aria-controls={open ? 'long-menu' : undefined}
|
||||
aria-expanded={open ? 'true' : undefined}
|
||||
aria-haspopup="true"
|
||||
onClick={handleOpen}
|
||||
>
|
||||
<MoreVertIcon />
|
||||
</IconButton>
|
||||
<Menu
|
||||
id="long-menu"
|
||||
MenuListProps={{
|
||||
'aria-labelledby': 'long-button',
|
||||
}}
|
||||
anchorEl={anchorEl}
|
||||
open={open}
|
||||
onClose={handleClose}
|
||||
PaperProps={{
|
||||
style: {
|
||||
maxHeight: 48 * 4.5,
|
||||
width: '20ch',
|
||||
},
|
||||
}}
|
||||
>
|
||||
{value.state !== 'Running' && (
|
||||
<MenuItem key="start" onClick={start}>
|
||||
<StartIcon />
|
||||
Start
|
||||
</MenuItem>
|
||||
)}
|
||||
{value.state !== 'Stopped' && (
|
||||
<MenuItem key="stop" onClick={stop}>
|
||||
<StopIcon />
|
||||
Stop
|
||||
</MenuItem>
|
||||
)}
|
||||
<MenuItem
|
||||
component="a"
|
||||
href={value.logstreamHref}
|
||||
target="_blank"
|
||||
key="logStream"
|
||||
onClick={handleClose}
|
||||
>
|
||||
<OpenInNewIcon />
|
||||
Log Stream
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
/** @public */
|
||||
export const AzureSitesOverviewTable = ({ data, loading }: TableProps) => {
|
||||
const [snackbarMessage, setSnackbarMessage] = useState<null | string>(null);
|
||||
const [isSnackbarOpen, setSnackbarOpen] = useState(false);
|
||||
|
||||
const onSnackbarClose = () => {
|
||||
setSnackbarMessage(null);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setSnackbarOpen(!!snackbarMessage);
|
||||
}, [snackbarMessage]);
|
||||
|
||||
const columns: TableColumn<AzureSite>[] = [
|
||||
{
|
||||
width: '25px',
|
||||
field: 'kind',
|
||||
render: (func: AzureSite) => <Kind value={func.kind as Kinds} />,
|
||||
},
|
||||
{
|
||||
title: 'name',
|
||||
field: 'name',
|
||||
highlight: true,
|
||||
render: (func: AzureSite) => {
|
||||
return (
|
||||
<Link href={func.href} target="_blank">
|
||||
{func.name}
|
||||
</Link>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'location',
|
||||
field: 'location',
|
||||
render: (func: AzureSite) => func.location ?? 'unknown',
|
||||
},
|
||||
{
|
||||
title: 'status',
|
||||
field: 'status',
|
||||
render: (func: AzureSite) => <State value={func.state as States} />,
|
||||
},
|
||||
{
|
||||
title: 'last modified',
|
||||
field: 'lastModifiedDate',
|
||||
render: (func: AzureSite) =>
|
||||
DateTime.fromISO(func.lastModifiedDate).toLocaleString(
|
||||
DateTime.DATETIME_MED,
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'actions',
|
||||
align: 'right',
|
||||
sorting: false,
|
||||
field: 'actions',
|
||||
render: (func: AzureSite) => (
|
||||
<ActionButtons value={func} onMenuItemClick={setSnackbarMessage} />
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const tableStyle = {
|
||||
minWidth: '0',
|
||||
width: '100%',
|
||||
};
|
||||
|
||||
return (
|
||||
<Card style={tableStyle}>
|
||||
<Table
|
||||
title={
|
||||
<Box display="flex" alignItems="center">
|
||||
<FlashOnIcon style={{ fontSize: 30 }} />
|
||||
<Box mr={1} />
|
||||
Azure Sites
|
||||
</Box>
|
||||
}
|
||||
options={{ paging: true, search: false, pageSize: 10 }}
|
||||
data={data}
|
||||
emptyContent={<LinearProgress />}
|
||||
isLoading={loading}
|
||||
columns={columns}
|
||||
/>
|
||||
<Snackbar
|
||||
open={isSnackbarOpen}
|
||||
autoHideDuration={6_000}
|
||||
anchorOrigin={{ vertical: 'top', horizontal: 'center' }}
|
||||
onClose={onSnackbarClose}
|
||||
>
|
||||
<MuiAlert onClose={onSnackbarClose} severity="info">
|
||||
{snackbarMessage}
|
||||
</MuiAlert>
|
||||
</Snackbar>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright 2022 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 { Entity } from '@backstage/catalog-model';
|
||||
|
||||
export const AZURE_WEB_SITE_NAME_ANNOTATION = 'azure.com/microsoft-web-sites';
|
||||
export const useServiceEntityAnnotations = (entity: Entity) => {
|
||||
const webSiteName =
|
||||
entity?.metadata.annotations?.[AZURE_WEB_SITE_NAME_ANNOTATION] ?? '';
|
||||
|
||||
return {
|
||||
webSiteName,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright 2022 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 useInterval from 'react-use/lib/useInterval';
|
||||
import useAsyncRetry from 'react-use/lib/useAsyncRetry';
|
||||
import { useApi, errorApiRef } from '@backstage/core-plugin-api';
|
||||
import { AzureSiteListResponse } from '@backstage/plugin-azure-sites-common';
|
||||
import { azureSiteApiRef } from '../api';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
const POLLING_INTERVAL = 15000;
|
||||
|
||||
export function useSites({ name }: { name: string }) {
|
||||
const azureApi = useApi(azureSiteApiRef);
|
||||
const errorApi = useApi(errorApiRef);
|
||||
|
||||
const list = useCallback(
|
||||
async () => {
|
||||
return await azureApi.list({
|
||||
name: name,
|
||||
});
|
||||
},
|
||||
[name], // eslint-disable-line react-hooks/exhaustive-deps
|
||||
);
|
||||
const {
|
||||
loading,
|
||||
value: data,
|
||||
error,
|
||||
retry,
|
||||
} = useAsyncRetry<AzureSiteListResponse | null>(async () => {
|
||||
try {
|
||||
const sites = await list();
|
||||
return sites;
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
if (e?.message === 'AbortError') {
|
||||
errorApi.post(
|
||||
new Error(
|
||||
'Timeout reaching backend plugin, please add azure-backend plugin',
|
||||
),
|
||||
);
|
||||
}
|
||||
errorApi.post(e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}, []);
|
||||
|
||||
useInterval(() => retry(), POLLING_INTERVAL);
|
||||
|
||||
return [
|
||||
{
|
||||
loading,
|
||||
data,
|
||||
error,
|
||||
retry,
|
||||
},
|
||||
] as const;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright 2022 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 './plugin';
|
||||
export * from './api';
|
||||
export * from './components/AzureSitesOverviewComponent/AzureSitesOverview';
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2022 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 { AzureSite } from '@backstage/plugin-azure-sites-common';
|
||||
|
||||
export const entityMock = {
|
||||
metadata: {
|
||||
namespace: 'default',
|
||||
annotations: {
|
||||
'azure.com/microsoft-web-sites': 'func-mock',
|
||||
},
|
||||
name: 'sample-azure-service',
|
||||
description: 'A service for testing Backstage functionality.',
|
||||
uid: 'c009b513-d053-4b3f-9429-8433a145e943',
|
||||
},
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'Component',
|
||||
spec: {
|
||||
type: 'service',
|
||||
owner: 'dev_group@example.com',
|
||||
lifecycle: 'experimental',
|
||||
},
|
||||
};
|
||||
|
||||
// https://management.azure.com/subscriptions/{{subscriptionId}}/resourceGroups/{{resourceGroup}}/providers/Microsoft.Web/sites/{{functionsName}}?api-version=2022-03-01
|
||||
export const siteMock: AzureSite = {
|
||||
name: 'func-mock',
|
||||
kind: 'functionapp',
|
||||
resourceGroup: 'mock-resourcegroup',
|
||||
subscription: '00000000-0000-0000-0000-000000000000',
|
||||
tags: {
|
||||
isMock: true,
|
||||
},
|
||||
location: 'West Europe',
|
||||
state: 'Running',
|
||||
href: 'https://mockurl.api.azurewebsites.windows.net:454/subscriptions/00000000-0000-0000-0000-000000000000/webspaces/rg_mock-WestEuropewebspace/sites/func-mock',
|
||||
logstreamHref:
|
||||
'https://mockurl.api.azurewebsites.windows.net:454/subscriptions/00000000-0000-0000-0000-000000000000/webspaces/rg_mock-WestEuropewebspace/sites/func-mock/logStream',
|
||||
usageState: 'Normal',
|
||||
lastModifiedDate: '2022-09-02T11:09:58.9033333',
|
||||
containerSize: 100,
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright 2022 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 { azureSitesPlugin } from './plugin';
|
||||
|
||||
describe('azure', () => {
|
||||
it('should export plugin', () => {
|
||||
expect(azureSitesPlugin).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright 2022 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 {
|
||||
createApiFactory,
|
||||
createComponentExtension,
|
||||
createPlugin,
|
||||
createRouteRef,
|
||||
discoveryApiRef,
|
||||
identityApiRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { azureSiteApiRef, AzureSitesApiBackendClient } from './api';
|
||||
|
||||
/** @public */
|
||||
export const entityContentRouteRef = createRouteRef({
|
||||
id: 'Azure Entity Content',
|
||||
});
|
||||
|
||||
/** @public */
|
||||
export const azureSitesPlugin = createPlugin({
|
||||
id: 'azureFunctions',
|
||||
apis: [
|
||||
createApiFactory({
|
||||
api: azureSiteApiRef,
|
||||
deps: {
|
||||
discoveryApi: discoveryApiRef,
|
||||
identityApi: identityApiRef,
|
||||
},
|
||||
factory: ({ discoveryApi, identityApi }) =>
|
||||
new AzureSitesApiBackendClient({ discoveryApi, identityApi }),
|
||||
}),
|
||||
],
|
||||
routes: {
|
||||
entityContent: entityContentRouteRef,
|
||||
},
|
||||
});
|
||||
|
||||
/** @public */
|
||||
export const EntityAzureSitesOverviewWidget = azureSitesPlugin.provide(
|
||||
createComponentExtension({
|
||||
name: 'EntityAzureSitesOverviewWidget',
|
||||
component: {
|
||||
lazy: () =>
|
||||
import(
|
||||
'./components/AzureSitesOverviewComponent/AzureSitesOverview'
|
||||
).then(m => m.AzureSitesOverviewWidget),
|
||||
},
|
||||
}),
|
||||
);
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright 2022 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 { createRouteRef } from '@backstage/core-plugin-api';
|
||||
|
||||
export const rootRouteRef = createRouteRef({
|
||||
id: 'azure-sites',
|
||||
});
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright 2022 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 '@testing-library/jest-dom';
|
||||
import 'cross-fetch/polyfill';
|
||||
Reference in New Issue
Block a user