mirror of
https://github.com/portainer/portainer.git
synced 2025-08-04 21:35:23 +02:00
refactor(azure): migrate module to react [EE-2782] (#6689)
* refactor(azure): migrate module to react [EE-2782] * fix(azure): remove optional chain * feat(azure): apply new icons in dashboard * feat(azure): apply new icons in dashboard * feat(ui): allow single string for breadcrumbs * refactor(azure/containers): use Table.content * feat(azure/containers): implement new ui [EE-3538] * fix(azure/containers): use correct icon * chore(tests): mock svg as component * fix(azure): fix tests Co-authored-by: matias.spinarolli <matias.spinarolli@portainer.io>
This commit is contained in:
parent
b059641c80
commit
82b848af0c
97 changed files with 1723 additions and 1430 deletions
142
app/react/azure/DashboardView/DashboardView.test.tsx
Normal file
142
app/react/azure/DashboardView/DashboardView.test.tsx
Normal file
|
@ -0,0 +1,142 @@
|
|||
import { renderWithQueryClient, within } from '@/react-tools/test-utils';
|
||||
import { UserContext } from '@/portainer/hooks/useUser';
|
||||
import { UserViewModel } from '@/portainer/models/user';
|
||||
import { server, rest } from '@/setup-tests/server';
|
||||
import {
|
||||
createMockResourceGroups,
|
||||
createMockSubscriptions,
|
||||
} from '@/react-tools/test-mocks';
|
||||
|
||||
import { DashboardView } from './DashboardView';
|
||||
|
||||
jest.mock('@uirouter/react', () => ({
|
||||
...jest.requireActual('@uirouter/react'),
|
||||
useCurrentStateAndParams: jest.fn(() => ({
|
||||
params: { endpointId: 1 },
|
||||
})),
|
||||
}));
|
||||
|
||||
test('dashboard items should render correctly', async () => {
|
||||
const { findByLabelText } = await renderComponent();
|
||||
|
||||
const subscriptionsItem = await findByLabelText('Subscription');
|
||||
expect(subscriptionsItem).toBeVisible();
|
||||
|
||||
const subscriptionElements = within(subscriptionsItem);
|
||||
expect(subscriptionElements.getByLabelText('value')).toBeVisible();
|
||||
|
||||
expect(subscriptionElements.getByLabelText('resourceType')).toHaveTextContent(
|
||||
'Subscriptions'
|
||||
);
|
||||
|
||||
const resourceGroupsItem = await findByLabelText('Resource group');
|
||||
expect(resourceGroupsItem).toBeVisible();
|
||||
|
||||
const resourceGroupElements = within(resourceGroupsItem);
|
||||
expect(resourceGroupElements.getByLabelText('value')).toBeVisible();
|
||||
|
||||
expect(
|
||||
resourceGroupElements.getByLabelText('resourceType')
|
||||
).toHaveTextContent('Resource groups');
|
||||
});
|
||||
|
||||
test('when there are no subscriptions, should show 0 subscriptions and 0 resource groups', async () => {
|
||||
const { findByLabelText } = await renderComponent();
|
||||
|
||||
const subscriptionElements = within(await findByLabelText('Subscription'));
|
||||
expect(subscriptionElements.getByLabelText('value')).toHaveTextContent('0');
|
||||
|
||||
const resourceGroupElements = within(await findByLabelText('Resource group'));
|
||||
expect(resourceGroupElements.getByLabelText('value')).toHaveTextContent('0');
|
||||
});
|
||||
|
||||
test('when there is subscription & resource group data, should display these', async () => {
|
||||
const { findByLabelText } = await renderComponent(1, { 'subscription-1': 2 });
|
||||
|
||||
const subscriptionElements = within(await findByLabelText('Subscription'));
|
||||
expect(subscriptionElements.getByLabelText('value')).toHaveTextContent('1');
|
||||
|
||||
const resourceGroupElements = within(await findByLabelText('Resource group'));
|
||||
expect(resourceGroupElements.getByLabelText('value')).toHaveTextContent('2');
|
||||
});
|
||||
|
||||
test('should correctly show total number of resource groups across multiple subscriptions', async () => {
|
||||
const { findByLabelText } = await renderComponent(2, {
|
||||
'subscription-1': 2,
|
||||
'subscription-2': 3,
|
||||
});
|
||||
|
||||
const resourceGroupElements = within(await findByLabelText('Resource group'));
|
||||
expect(resourceGroupElements.getByLabelText('value')).toHaveTextContent('5');
|
||||
});
|
||||
|
||||
test("when only subscriptions fail to load, don't show the dashboard", async () => {
|
||||
const { queryByLabelText } = await renderComponent(
|
||||
1,
|
||||
{ 'subscription-1': 1 },
|
||||
500,
|
||||
200
|
||||
);
|
||||
expect(queryByLabelText('Subscription')).not.toBeInTheDocument();
|
||||
expect(queryByLabelText('Resource group')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('when only resource groups fail to load, still show the subscriptions', async () => {
|
||||
const { queryByLabelText, findByLabelText } = await renderComponent(
|
||||
1,
|
||||
{ 'subscription-1': 1 },
|
||||
200,
|
||||
500
|
||||
);
|
||||
await expect(findByLabelText('Subscription')).resolves.toBeInTheDocument();
|
||||
expect(queryByLabelText('Resource group')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
async function renderComponent(
|
||||
subscriptionsCount = 0,
|
||||
resourceGroups: Record<string, number> = {},
|
||||
subscriptionsStatus = 200,
|
||||
resourceGroupsStatus = 200
|
||||
) {
|
||||
const user = new UserViewModel({ Username: 'user' });
|
||||
const state = { user };
|
||||
|
||||
server.use(
|
||||
rest.get(
|
||||
'/api/endpoints/:endpointId/azure/subscriptions',
|
||||
(req, res, ctx) =>
|
||||
res(
|
||||
ctx.json(createMockSubscriptions(subscriptionsCount)),
|
||||
ctx.status(subscriptionsStatus)
|
||||
)
|
||||
),
|
||||
rest.get(
|
||||
'/api/endpoints/:endpointId/azure/subscriptions/:subscriptionId/resourcegroups',
|
||||
(req, res, ctx) => {
|
||||
if (typeof req.params.subscriptionId !== 'string') {
|
||||
throw new Error("Provided subscriptionId must be of type: 'string'");
|
||||
}
|
||||
|
||||
const { subscriptionId } = req.params;
|
||||
return res(
|
||||
ctx.json(
|
||||
createMockResourceGroups(
|
||||
req.params.subscriptionId,
|
||||
resourceGroups[subscriptionId] || 0
|
||||
)
|
||||
),
|
||||
ctx.status(resourceGroupsStatus)
|
||||
);
|
||||
}
|
||||
)
|
||||
);
|
||||
const renderResult = renderWithQueryClient(
|
||||
<UserContext.Provider value={state}>
|
||||
<DashboardView />
|
||||
</UserContext.Provider>
|
||||
);
|
||||
|
||||
await expect(renderResult.findByText(/Home/)).resolves.toBeVisible();
|
||||
|
||||
return renderResult;
|
||||
}
|
53
app/react/azure/DashboardView/DashboardView.tsx
Normal file
53
app/react/azure/DashboardView/DashboardView.tsx
Normal file
|
@ -0,0 +1,53 @@
|
|||
import { Package } from 'react-feather';
|
||||
|
||||
import { useEnvironmentId } from '@/portainer/hooks/useEnvironmentId';
|
||||
|
||||
import { PageHeader } from '@@/PageHeader';
|
||||
import { DashboardItem } from '@@/DashboardItem';
|
||||
import { DashboardGrid } from '@@/DashboardItem/DashboardGrid';
|
||||
|
||||
import { useResourceGroups } from '../queries/useResourceGroups';
|
||||
import { useSubscriptions } from '../queries/useSubscriptions';
|
||||
|
||||
import SubscriptionsIcon from './icon-subscription.svg?c';
|
||||
|
||||
export function DashboardView() {
|
||||
const environmentId = useEnvironmentId();
|
||||
|
||||
const subscriptionsQuery = useSubscriptions(environmentId);
|
||||
|
||||
const resourceGroupsQuery = useResourceGroups(
|
||||
environmentId,
|
||||
subscriptionsQuery.data
|
||||
);
|
||||
|
||||
const subscriptionsCount = subscriptionsQuery.data?.length;
|
||||
const resourceGroupsCount = Object.values(
|
||||
resourceGroupsQuery.resourceGroups
|
||||
).flatMap((x) => Object.values(x)).length;
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageHeader title="Home" breadcrumbs={[{ label: 'Dashboard' }]} />
|
||||
|
||||
<div className="mx-4">
|
||||
{subscriptionsQuery.data && (
|
||||
<DashboardGrid>
|
||||
<DashboardItem
|
||||
value={subscriptionsCount as number}
|
||||
icon={SubscriptionsIcon}
|
||||
type="Subscription"
|
||||
/>
|
||||
{!resourceGroupsQuery.isError && !resourceGroupsQuery.isLoading && (
|
||||
<DashboardItem
|
||||
value={resourceGroupsCount}
|
||||
icon={Package}
|
||||
type="Resource group"
|
||||
/>
|
||||
)}
|
||||
</DashboardGrid>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
3
app/react/azure/DashboardView/icon-subscription.svg
Normal file
3
app/react/azure/DashboardView/icon-subscription.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg width="22" height="20" viewBox="0 0 22 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M20.8641 8.08333H1.69743M10.3224 16.7083L17.7974 16.7083C18.8709 16.7083 19.4076 16.7083 19.8176 16.4994C20.1782 16.3157 20.4714 16.0225 20.6552 15.6618C20.8641 15.2518 20.8641 14.7151 20.8641 13.6417V6.35833C20.8641 5.2849 20.8641 4.74818 20.6552 4.33819C20.4714 3.97754 20.1782 3.68433 19.8176 3.50057C19.4076 3.29167 18.8709 3.29167 17.7974 3.29167H16.0724M10.3224 16.7083L12.2391 18.625M10.3224 16.7083L12.2391 14.7917M6.48909 16.7083H4.76409C3.69066 16.7083 3.15394 16.7083 2.74394 16.4994C2.3833 16.3157 2.09009 16.0225 1.90633 15.6618C1.69743 15.2518 1.69743 14.7151 1.69743 13.6417V6.35833C1.69743 5.2849 1.69743 4.74818 1.90633 4.33818C2.09009 3.97754 2.3833 3.68433 2.74394 3.50057C3.15394 3.29167 3.69066 3.29167 4.76409 3.29167H12.2391M12.2391 3.29167L10.3224 5.20833M12.2391 3.29167L10.3224 1.375" stroke="#344054" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1,008 B |
1
app/react/azure/DashboardView/index.ts
Normal file
1
app/react/azure/DashboardView/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export { DashboardView } from './DashboardView';
|
Loading…
Add table
Add a link
Reference in a new issue