mirror of https://github.com/portainer/portainer
refactor(azure/aci): migrate sidebar to react [EE-2569] (#6593)
* refactor(azure/aci): migrate sidebar to react [EE-2569] * add test files * add story * fix(sidebar): get styles from sidebar * make suggested changes + update icon story * use template in second story + change some english * use camel case in test * use icon instead of span * refactor(types): use existing environmentid type Co-authored-by: Chaim Lev-Ari <chiptus@gmail.com>pull/6651/head
parent
5188ead870
commit
f2c48409e0
|
@ -0,0 +1,34 @@
|
||||||
|
import { render, within } from '@/react-tools/test-utils';
|
||||||
|
|
||||||
|
import { AzureSidebar } from './AzureSidebar';
|
||||||
|
|
||||||
|
test('dashboard items should render correctly', () => {
|
||||||
|
const { getByLabelText } = renderComponent();
|
||||||
|
const dashboardItem = getByLabelText('Dashboard');
|
||||||
|
expect(dashboardItem).toBeVisible();
|
||||||
|
expect(dashboardItem).toHaveTextContent('Dashboard');
|
||||||
|
|
||||||
|
const dashboardItemElements = within(dashboardItem);
|
||||||
|
expect(dashboardItemElements.getByLabelText('itemIcon')).toBeVisible();
|
||||||
|
expect(dashboardItemElements.getByLabelText('itemIcon')).toHaveClass(
|
||||||
|
'fa-tachometer-alt',
|
||||||
|
'fa-fw'
|
||||||
|
);
|
||||||
|
|
||||||
|
const containerInstancesItem = getByLabelText('ContainerInstances');
|
||||||
|
expect(containerInstancesItem).toBeVisible();
|
||||||
|
expect(containerInstancesItem).toHaveTextContent('Container instances');
|
||||||
|
|
||||||
|
const containerInstancesItemElements = within(containerInstancesItem);
|
||||||
|
expect(
|
||||||
|
containerInstancesItemElements.getByLabelText('itemIcon')
|
||||||
|
).toBeVisible();
|
||||||
|
expect(containerInstancesItemElements.getByLabelText('itemIcon')).toHaveClass(
|
||||||
|
'fa-cubes',
|
||||||
|
'fa-fw'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
function renderComponent() {
|
||||||
|
return render(<AzureSidebar environmentId={1} />);
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
import { r2a } from '@/react-tools/react2angular';
|
||||||
|
import { SidebarMenuItem } from '@/portainer/components/sidebar/SidebarMenuItem';
|
||||||
|
import type { EnvironmentId } from '@/portainer/environments/types';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
environmentId: EnvironmentId;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AzureSidebar({ environmentId }: Props) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SidebarMenuItem
|
||||||
|
path="azure.dashboard"
|
||||||
|
pathParams={{ endpointId: environmentId }}
|
||||||
|
iconClass="fa-tachometer-alt fa-fw"
|
||||||
|
className="sidebar-list"
|
||||||
|
itemName="Dashboard"
|
||||||
|
data-cy="azureSidebar-dashboard"
|
||||||
|
>
|
||||||
|
Dashboard
|
||||||
|
</SidebarMenuItem>
|
||||||
|
<SidebarMenuItem
|
||||||
|
path="azure.containerinstances"
|
||||||
|
pathParams={{ endpointId: environmentId }}
|
||||||
|
iconClass="fa-cubes fa-fw"
|
||||||
|
className="sidebar-list"
|
||||||
|
itemName="ContainerInstances"
|
||||||
|
data-cy="azureSidebar-containerInstances"
|
||||||
|
>
|
||||||
|
Container instances
|
||||||
|
</SidebarMenuItem>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AzureSidebarAngular = r2a(AzureSidebar, ['environmentId']);
|
|
@ -0,0 +1 @@
|
||||||
|
export { AzureSidebar, AzureSidebarAngular } from './AzureSidebar';
|
|
@ -1,5 +1,6 @@
|
||||||
import angular from 'angular';
|
import angular from 'angular';
|
||||||
|
|
||||||
|
import { AzureSidebarAngular } from './AzureSidebar/AzureSidebar';
|
||||||
import { DashboardViewAngular } from './Dashboard/DashboardView';
|
import { DashboardViewAngular } from './Dashboard/DashboardView';
|
||||||
import { containerInstancesModule } from './ContainerInstances';
|
import { containerInstancesModule } from './ContainerInstances';
|
||||||
|
|
||||||
|
@ -82,4 +83,5 @@ angular
|
||||||
$stateRegistryProvider.register(dashboard);
|
$stateRegistryProvider.register(dashboard);
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
.component('dashboardView', DashboardViewAngular).name;
|
.component('azureSidebar', AzureSidebarAngular)
|
||||||
|
.component('dashboardView', DashboardViewAngular);
|
||||||
|
|
|
@ -1,19 +0,0 @@
|
||||||
<sidebar-menu-item
|
|
||||||
path="azure.dashboard"
|
|
||||||
path-params="{ endpointId: $ctrl.endpointId }"
|
|
||||||
icon-class="fa-tachometer-alt fa-fw"
|
|
||||||
class-name="sidebar-list"
|
|
||||||
data-cy="azureSidebar-dashboard"
|
|
||||||
>
|
|
||||||
Dashboard
|
|
||||||
</sidebar-menu-item>
|
|
||||||
|
|
||||||
<sidebar-menu-item
|
|
||||||
path="azure.containerinstances"
|
|
||||||
path-params="{ endpointId: $ctrl.endpointId }"
|
|
||||||
icon-class="fa-cubes fa-fw"
|
|
||||||
class-name="sidebar-list"
|
|
||||||
data-cy="azureSidebar-containerInstances"
|
|
||||||
>
|
|
||||||
Container instances
|
|
||||||
</sidebar-menu-item>
|
|
|
@ -1,8 +0,0 @@
|
||||||
import angular from 'angular';
|
|
||||||
|
|
||||||
angular.module('portainer.azure').component('azureSidebar', {
|
|
||||||
templateUrl: './azure-sidebar.html',
|
|
||||||
bindings: {
|
|
||||||
endpointId: '<',
|
|
||||||
},
|
|
||||||
});
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
.sidebar-menu-item > a {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
import { Meta, Story } from '@storybook/react';
|
||||||
|
|
||||||
|
import { SidebarMenuItem } from './SidebarMenuItem';
|
||||||
|
|
||||||
|
const meta: Meta = {
|
||||||
|
title: 'Components/SidebarMenuItem',
|
||||||
|
component: SidebarMenuItem,
|
||||||
|
};
|
||||||
|
export default meta;
|
||||||
|
|
||||||
|
interface StoryProps {
|
||||||
|
iconClass?: string;
|
||||||
|
className: string;
|
||||||
|
itemName: string;
|
||||||
|
linkName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function Template({ iconClass, className, itemName, linkName }: StoryProps) {
|
||||||
|
return (
|
||||||
|
<ul className="sidebar">
|
||||||
|
<div className="sidebar-list">
|
||||||
|
<SidebarMenuItem
|
||||||
|
path="example.path"
|
||||||
|
pathParams={{ endpointId: 1 }}
|
||||||
|
iconClass={iconClass}
|
||||||
|
className={className}
|
||||||
|
itemName={itemName}
|
||||||
|
>
|
||||||
|
{linkName}
|
||||||
|
</SidebarMenuItem>
|
||||||
|
</div>
|
||||||
|
</ul>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Primary: Story<StoryProps> = Template.bind({});
|
||||||
|
Primary.args = {
|
||||||
|
iconClass: 'fa-tachometer-alt fa-fw',
|
||||||
|
className: 'exampleItemClass',
|
||||||
|
itemName: 'ExampleItem',
|
||||||
|
linkName: 'Item with icon',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WithoutIcon: Story<StoryProps> = Template.bind({});
|
||||||
|
WithoutIcon.args = {
|
||||||
|
className: 'exampleItemClass',
|
||||||
|
itemName: 'ExampleItem',
|
||||||
|
linkName: 'Item without icon',
|
||||||
|
};
|
|
@ -0,0 +1,51 @@
|
||||||
|
import { render } from '@/react-tools/test-utils';
|
||||||
|
|
||||||
|
import { SidebarMenuItem } from './SidebarMenuItem';
|
||||||
|
|
||||||
|
test('should be visible & have expected class', () => {
|
||||||
|
const { getByLabelText } = renderComponent('testClass');
|
||||||
|
const listItem = getByLabelText('sidebarItem');
|
||||||
|
expect(listItem).toBeVisible();
|
||||||
|
expect(listItem).toHaveClass('testClass');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('icon should with correct icon if iconClass is provided', () => {
|
||||||
|
const { getByLabelText } = renderComponent('', 'testIconClass');
|
||||||
|
const sidebarIcon = getByLabelText('itemIcon');
|
||||||
|
expect(sidebarIcon).toBeVisible();
|
||||||
|
expect(sidebarIcon).toHaveClass('testIconClass');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('icon should not be rendered if iconClass is not provided', () => {
|
||||||
|
const { queryByLabelText } = renderComponent();
|
||||||
|
expect(queryByLabelText('itemIcon')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should render children', () => {
|
||||||
|
const { getByLabelText } = renderComponent('', '', 'Test');
|
||||||
|
expect(getByLabelText('sidebarItem')).toHaveTextContent('Test');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('li element should have correct accessibility label', () => {
|
||||||
|
const { queryByLabelText } = renderComponent('', '', '', 'testItemLabel');
|
||||||
|
expect(queryByLabelText('testItemLabel')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
function renderComponent(
|
||||||
|
className = '',
|
||||||
|
iconClass = '',
|
||||||
|
linkText = '',
|
||||||
|
itemName = 'sidebarItem'
|
||||||
|
) {
|
||||||
|
return render(
|
||||||
|
<SidebarMenuItem
|
||||||
|
path=""
|
||||||
|
pathParams={{ endpointId: 1 }}
|
||||||
|
iconClass={iconClass}
|
||||||
|
className={className}
|
||||||
|
itemName={itemName}
|
||||||
|
>
|
||||||
|
{linkText}
|
||||||
|
</SidebarMenuItem>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
import { PropsWithChildren } from 'react';
|
||||||
|
import clsx from 'clsx';
|
||||||
|
import { UISrefActive } from '@uirouter/react';
|
||||||
|
|
||||||
|
import { Link } from '@/portainer/components/Link';
|
||||||
|
|
||||||
|
import '../sidebar.css';
|
||||||
|
import styles from './SidebarMenuItem.module.css';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
path: string;
|
||||||
|
pathParams: object;
|
||||||
|
iconClass?: string;
|
||||||
|
className: string;
|
||||||
|
itemName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SidebarMenuItem({
|
||||||
|
path,
|
||||||
|
pathParams,
|
||||||
|
iconClass,
|
||||||
|
className,
|
||||||
|
itemName,
|
||||||
|
children,
|
||||||
|
}: PropsWithChildren<Props>) {
|
||||||
|
return (
|
||||||
|
<li
|
||||||
|
className={clsx('sidebar-menu-item', styles.sidebarMenuItem, className)}
|
||||||
|
aria-label={itemName}
|
||||||
|
>
|
||||||
|
<UISrefActive class="active">
|
||||||
|
<Link to={path} params={pathParams} title={itemName}>
|
||||||
|
{children}
|
||||||
|
{iconClass && (
|
||||||
|
<i
|
||||||
|
className={clsx('menu-icon fa', iconClass)}
|
||||||
|
aria-label="itemIcon"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Link>
|
||||||
|
</UISrefActive>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
export { SidebarMenuItem } from './SidebarMenuItem';
|
|
@ -23,7 +23,7 @@
|
||||||
admin-access="isAdmin"
|
admin-access="isAdmin"
|
||||||
></kubernetes-sidebar>
|
></kubernetes-sidebar>
|
||||||
|
|
||||||
<azure-sidebar ng-if="applicationState.endpoint.mode.provider === 'AZURE'" endpoint-id="endpointId"> </azure-sidebar>
|
<azure-sidebar ng-if="applicationState.endpoint.mode.provider === 'AZURE'" environment-id="endpointId"> </azure-sidebar>
|
||||||
|
|
||||||
<docker-sidebar
|
<docker-sidebar
|
||||||
ng-if="applicationState.endpoint.mode.provider !== 'AZURE' && applicationState.endpoint.mode.provider !== 'KUBERNETES'"
|
ng-if="applicationState.endpoint.mode.provider !== 'AZURE' && applicationState.endpoint.mode.provider !== 'KUBERNETES'"
|
||||||
|
|
Loading…
Reference in New Issue