refactor table to own component. unit test the table

Signed-off-by: Wesley <wpattison08@gmail.com>
This commit is contained in:
Wesley
2022-09-11 20:38:55 +02:00
parent 2ccdc3b6d4
commit 5a60e265dc
8 changed files with 88 additions and 280 deletions
@@ -38,8 +38,8 @@ export async function createRouter(
logger.info('PONG!');
response.send({ status: 'ok' });
});
router.get('/get', async (request, response) => {
response.json(await azureWebManagementApi.list({ functionName: request.query.functionName!.toString() }))
router.post('/list', async (request, response) => {
response.json(await azureWebManagementApi.list({ functionName: request.body.functionName!.toString() }))
});
router.use(errorHandler());
return router;
@@ -36,14 +36,15 @@ export class AzureFunctionsBackendClient implements AzureFunctionsApi {
functionName: string;
}): Promise<FunctionsData[]> {
try {
const url = `${await this.discoveryApi.getBaseUrl('azure-functions')}/get?functionName=${functionName}`;
const url = `${await this.discoveryApi.getBaseUrl('azure-functions')}/list`;
const { token: idToken } = await this.identityApi.getCredentials();
const response = await fetch(url, {
method: 'GET',
method: 'POST',
headers: {
'Content-Type': 'application/json',
...(idToken && { Authorization: `Bearer ${idToken}` }),
}
},
body: JSON.stringify({ functionName: functionName })
});
return await response.json();
} catch (e: any) {
@@ -0,0 +1,56 @@
/*
* 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 { useFunctions } from '../../hooks/useFunctions';
import {
AZURE_FUNCTIONS_ANNOTATION,
useServiceEntityAnnotations,
} from '../../hooks/useServiceEntityAnnotations';
import { MissingAnnotationEmptyState } from '@backstage/core-components';
import ErrorBoundary from '../ErrorBoundary';
import { useEntity } from '@backstage/plugin-catalog-react';
import { OverviewTable } from '../OverviewTableComponent/OverviewTable';
export const isAzureFunctionsAvailable = (entity: Entity) =>
entity?.metadata.annotations?.[AZURE_FUNCTIONS_ANNOTATION];
const AzureFunctionsOverview = ({ entity }: { entity: Entity }) => {
const { functionsName } = useServiceEntityAnnotations(entity);
const [functionsData] = useFunctions({
functionsName
});
return (
<><OverviewTable data={functionsData.data ?? []} loading={functionsData.loading} /></>
);
};
export const AzureFunctionsOverviewWidget = () => {
const { entity } = useEntity();
if (!isAzureFunctionsAvailable(entity)) {
return (<MissingAnnotationEmptyState annotation={AZURE_FUNCTIONS_ANNOTATION} />);
}
return (
<ErrorBoundary>
<AzureFunctionsOverview entity={entity} />
</ErrorBoundary>
)
};
@@ -27,13 +27,12 @@ import {
setupRequestMockHandlers,
TestApiProvider,
} from '@backstage/test-utils';
import { EntityProvider } from '@backstage/plugin-catalog-react';
import { setupServer } from 'msw/node';
import {
entityMock,
functionResponseMock,
} from '../../mocks/mocks';
import { azureFunctionsApiRef, AzureFunctionsOverviewWidget } from '../..';
import { azureFunctionsApiRef } from '../..';
import { OverviewTable } from './OverviewTable';
const errorApiMock = { post: jest.fn(), error$: jest.fn() };
const identityApiMock = (getCredentials: any) => ({
@@ -61,9 +60,11 @@ describe('AzureFunctionsOverviewWidget', () => {
beforeEach(() => {
worker.use(
rest.get(
'https://portal.azure.com/#@test/resource/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/mock_rg/providers/Microsoft.Web/sites/func-mock',
(_, res, ctx) => res(ctx.json(functionResponseMock)),
rest.post(
'/list',
(_, res, ctx) => {
res(ctx.json(functionResponseMock));
},
),
);
});
@@ -71,20 +72,19 @@ describe('AzureFunctionsOverviewWidget', () => {
it('should display an overview table with the data from the requests', async () => {
const rendered = render(
<TestApiProvider apis={apis}>
<EntityProvider entity={entityMock}>
<AzureFunctionsOverviewWidget />
</EntityProvider>
<OverviewTable data={[functionResponseMock]} loading={false} />
</TestApiProvider>,
);
expect(
await rendered.findByText(functionResponseMock.name),
await rendered.findByText(functionResponseMock.functionName),
).toBeInTheDocument();
expect(
await rendered.findByText(functionResponseMock.properties.state),
await rendered.findByText(functionResponseMock.state),
).toBeInTheDocument();
expect(
await rendered.findByText(
new Date(functionResponseMock.properties.lastModifiedTimeUtc).toUTCString(),
new Date(functionResponseMock.lastModifiedDate).toUTCString(),
),
).toBeInTheDocument();
});
@@ -21,17 +21,9 @@ import {
Link,
LinearProgress
} from '@material-ui/core';
import { Entity } from '@backstage/catalog-model';
import { useFunctions } from '../../hooks/useFunctions';
import { FunctionsData } from '../../api/types';
import {
AZURE_FUNCTIONS_ANNOTATION,
useServiceEntityAnnotations,
} from '../../hooks/useServiceEntityAnnotations';
import { MissingAnnotationEmptyState, Table, TableColumn } from '@backstage/core-components';
import { Table, TableColumn } from '@backstage/core-components';
import FlashOnIcon from '@material-ui/icons/FlashOn'
import ErrorBoundary from '../ErrorBoundary';
import { useEntity } from '@backstage/plugin-catalog-react';
type States = 'Waiting' | 'Running' | 'Paused' | 'Failed';
@@ -59,8 +51,6 @@ const State = ({ value }: { value: States }) => {
);
};
type FunctionTableProps = {
data: FunctionsData[];
loading: boolean;
@@ -95,7 +85,7 @@ const DEFAULT_COLUMNS: TableColumn<FunctionsData>[] = [
},
];
const OverviewComponent = ({ data, loading }: FunctionTableProps) => {
export const OverviewTable = ({ data, loading }: FunctionTableProps) => {
const columns: TableColumn<FunctionsData>[] = [...DEFAULT_COLUMNS];
const tableStyle = {
minWidth: '0',
@@ -123,32 +113,3 @@ const OverviewComponent = ({ data, loading }: FunctionTableProps) => {
</Card>
);
};
export const isAzureFunctionsAvailable = (entity: Entity) =>
entity?.metadata.annotations?.[AZURE_FUNCTIONS_ANNOTATION];
const AzureFunctionsOverview = ({ entity }: { entity: Entity }) => {
const { functionsName } = useServiceEntityAnnotations(entity);
const [functionsData] = useFunctions({
functionsName
});
return (
<><OverviewComponent data={functionsData.data ?? []} loading={functionsData.loading} /></>
);
};
export const AzureFunctionsOverviewWidget = () => {
const { entity } = useEntity();
if (!isAzureFunctionsAvailable(entity)) {
return (<MissingAnnotationEmptyState annotation={AZURE_FUNCTIONS_ANNOTATION} />);
}
return (
<ErrorBoundary>
<AzureFunctionsOverview entity={entity} />
</ErrorBoundary>
)
};
+1 -1
View File
@@ -16,4 +16,4 @@
export * from './plugin';
export * from './api';
export * from './components/AzureFunctionsOverview/AzureFunctionsOverview';
export * from './components/AzureFunctionsOverviewComponent/AzureFunctionsOverview';
+10 -220
View File
@@ -14,6 +14,8 @@
* limitations under the License.
*/
import { FunctionsData } from "../api";
export const entityMock = {
metadata: {
namespace: 'default',
@@ -35,225 +37,13 @@ export const entityMock = {
};
// https://management.azure.com/subscriptions/{{subscriptionId}}/resourceGroups/{{resourceGroup}}/providers/Microsoft.Web/sites/{{functionsName}}?api-version=2022-03-01
export const functionResponseMock = {
id: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg_mock/providers/Microsoft.Web/sites/func-mock",
name: "func-mock",
type: "Microsoft.Web/sites",
kind: "functionapp",
export const functionResponseMock: FunctionsData = {
functionName: "func-mock",
location: "West Europe",
tags: {},
properties: {
name: "func-mock",
state: "Running",
hostNames: [
"func-mock.azurewebsites.net"
],
webSpace: "rg_mock-WestEuropewebspace",
selfLink: "https://mockurl.api.azurewebsites.windows.net:454/subscriptions/00000000-0000-0000-0000-000000000000/webspaces/rg_mock-WestEuropewebspace/sites/func-mock",
repositorySiteName: "func-mock",
owner: null,
usageState: "Normal",
enabled: true,
adminEnabled: true,
enabledHostNames: [
"func-mock.azurewebsites.net",
"func-mock.scm.azurewebsites.net"
],
siteProperties: {
metadata: null,
properties: [
{
name: "LinuxFxVersion",
value: ""
},
{
name: "WindowsFxVersion",
value: null
}
],
appSettings: null
},
availabilityState: "Normal",
sslCertificates: null,
csrs: [],
cers: null,
siteMode: null,
hostNameSslStates: [
{
name: "func-mock.azurewebsites.net",
sslState: "Disabled",
ipBasedSslResult: null,
virtualIP: null,
thumbprint: null,
toUpdate: null,
toUpdateIpBasedSsl: null,
ipBasedSslState: "NotConfigured",
hostType: "Standard"
},
{
name: "func-mock.scm.azurewebsites.net",
sslState: "Disabled",
ipBasedSslResult: null,
virtualIP: null,
thumbprint: null,
toUpdate: null,
toUpdateIpBasedSsl: null,
ipBasedSslState: "NotConfigured",
hostType: "Repository"
}
],
computeMode: null,
serverFarm: null,
serverFarmId: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg_mock/providers/Microsoft.Web/serverfarms/plan-mock",
reserved: false,
isXenon: false,
hyperV: false,
lastModifiedTimeUtc: "2022-09-02T11:09:58.9033333",
storageRecoveryDefaultState: "Running",
contentAvailabilityState: "Normal",
runtimeAvailabilityState: "Normal",
secretsCollection: [],
vnetRouteAllEnabled: false,
containerAllocationSubnet: null,
useContainerLocalhostBindings: null,
vnetImagePullEnabled: false,
vnetContentShareEnabled: false,
siteConfig: {
numberOfWorkers: 1,
defaultDocuments: null,
netFrameworkVersion: null,
phpVersion: null,
pythonVersion: null,
nodeVersion: null,
powerShellVersion: null,
linuxFxVersion: "",
windowsFxVersion: null,
requestTracingEnabled: null,
remoteDebuggingEnabled: null,
remoteDebuggingVersion: null,
httpLoggingEnabled: null,
azureMonitorLogCategories: null,
acrUseManagedIdentityCreds: false,
acrUserManagedIdentityID: null,
logsDirectorySizeLimit: null,
detailedErrorLoggingEnabled: null,
publishingUsername: null,
publishingPassword: null,
appSettings: null,
metadata: null,
connectionStrings: null,
machineKey: null,
handlerMappings: null,
documentRoot: null,
scmType: null,
use32BitWorkerProcess: null,
webSocketsEnabled: null,
alwaysOn: true,
javaVersion: null,
javaContainer: null,
javaContainerVersion: null,
appCommandLine: null,
managedPipelineMode: null,
virtualApplications: null,
winAuthAdminState: null,
winAuthTenantState: null,
customAppPoolIdentityAdminState: null,
customAppPoolIdentityTenantState: null,
runtimeADUser: null,
runtimeADUserPassword: null,
loadBalancing: null,
routingRules: null,
experiments: null,
limits: null,
autoHealEnabled: null,
autoHealRules: null,
tracingOptions: null,
vnetName: null,
vnetRouteAllEnabled: null,
vnetPrivatePortsCount: null,
publicNetworkAccess: null,
cors: null,
push: null,
apiDefinition: null,
apiManagementConfig: null,
autoSwapSlotName: null,
localMySqlEnabled: null,
managedServiceIdentityId: null,
xManagedServiceIdentityId: null,
keyVaultReferenceIdentity: null,
ipSecurityRestrictions: null,
ipSecurityRestrictionsDefaultAction: null,
scmIpSecurityRestrictions: null,
scmIpSecurityRestrictionsDefaultAction: null,
scmIpSecurityRestrictionsUseMain: null,
http20Enabled: false,
minTlsVersion: null,
minTlsCipherSuite: null,
supportedTlsCipherSuites: null,
scmMinTlsVersion: null,
ftpsState: null,
preWarmedInstanceCount: null,
functionAppScaleLimit: 0,
elasticWebAppScaleLimit: null,
healthCheckPath: null,
fileChangeAuditEnabled: null,
functionsRuntimeScaleMonitoringEnabled: null,
websiteTimeZone: null,
minimumElasticInstanceCount: 1,
azureStorageAccounts: null,
http20ProxyFlag: null,
sitePort: null,
antivirusScanEnabled: null,
storageType: null
},
deploymentId: "func-mock",
slotName: null,
trafficManagerHostNames: null,
sku: "Standard",
scmSiteAlsoStopped: false,
targetSwapSlot: null,
hostingEnvironment: null,
hostingEnvironmentProfile: null,
clientAffinityEnabled: false,
clientCertEnabled: false,
clientCertMode: "Required",
clientCertExclusionPaths: null,
hostNamesDisabled: false,
domainVerificationIdentifiers: null,
customDomainVerificationId: "",
kind: "functionapp",
inboundIpAddress: "",
possibleInboundIpAddresses: "",
ftpUsername: "func-mock\\$func-mock",
ftpsHostName: "ftps://mockurl.ftp.azurewebsites.windows.net/site/wwwroot",
outboundIpAddresses: "",
possibleOutboundIpAddresses: "",
containerSize: 1536,
dailyMemoryTimeQuota: 0,
suspendedTill: null,
siteDisabledReason: 0,
functionExecutionUnitsCache: null,
maxNumberOfWorkers: null,
homeStamp: "mockurl",
cloningInfo: null,
hostingEnvironmentId: null,
tags: {},
resourceGroup: "rg_mock",
defaultHostName: "func-mock.azurewebsites.net",
slotSwapStatus: null,
httpsOnly: false,
redundancyMode: "None",
inProgressOperationId: null,
geoDistributions: null,
privateEndpointConnections: [],
publicNetworkAccess: null,
buildVersion: null,
targetBuildVersion: null,
migrationState: null,
eligibleLogCategories: "FunctionAppLogs",
storageAccountRequired: false,
virtualNetworkSubnetId: null,
keyVaultReferenceIdentity: "SystemAssigned",
privateLinkIdentifiers: null
}
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: new Date("2022-09-02T11:09:58.9033333"),
containerSize: 100
};
+1 -1
View File
@@ -51,7 +51,7 @@ export const EntityAzureFunctionsOverviewCard = azureFunctionsPlugin.provide(
name: 'EntityAzureFunctionsOverviewCard',
component: {
lazy: () =>
import('./components/AzureFunctionsOverview/AzureFunctionsOverview').then(
import('./components/AzureFunctionsOverviewComponent/AzureFunctionsOverview').then(
m => m.AzureFunctionsOverviewWidget,
),
},