1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-08-08 23:35:31 +02:00

refactor(groups): migrate groups selectors to react [EE-3842] (#8936)

This commit is contained in:
Chaim Lev-Ari 2023-06-22 21:11:10 +07:00 committed by GitHub
parent 2018529add
commit e91b4f5c83
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
38 changed files with 543 additions and 627 deletions

View file

@ -14,6 +14,7 @@ import {
import { EnvironmentGroupId } from '@/react/portainer/environments/environment-groups/types';
import {
refetchIfAnyOffline,
SortType,
useEnvironmentList,
} from '@/react/portainer/environments/queries/useEnvironmentList';
import { useGroups } from '@/react/portainer/environments/environment-groups/queries';
@ -68,7 +69,9 @@ export function EnvironmentList({ onClickBrowse, onRefresh }: Props) {
'group',
[]
);
const [sortByFilter, setSortByFilter] = useSearchBarState('sortBy');
const [sortByFilter, setSortByFilter] = useHomePageFilter<
SortType | undefined
>('sortBy', 'Name');
const [sortByDescending, setSortByDescending] = useHomePageFilter(
'sortOrder',
false
@ -342,7 +345,7 @@ export function EnvironmentList({ onClickBrowse, onRefresh }: Props) {
setConnectionTypes([]);
}
function sortOnchange(value: string) {
function sortOnchange(value?: 'Name' | 'Group' | 'Status') {
setSortByFilter(value);
setSortByButton(!!value);
}

View file

@ -6,6 +6,10 @@ import { useAgentVersionsList } from '../../environments/queries/useAgentVersion
import { EnvironmentStatus, PlatformType } from '../../environments/types';
import { isBE } from '../../feature-flags/feature-flags.service';
import { useGroups } from '../../environments/environment-groups/queries';
import {
SortOptions,
SortType,
} from '../../environments/queries/useEnvironmentList';
import { HomepageFilter } from './HomepageFilter';
import { SortbySelector } from './SortbySelector';
@ -17,7 +21,7 @@ const status = [
{ value: EnvironmentStatus.Down, label: 'Down' },
];
const sortByOptions = ['Name', 'Group', 'Status'].map((v) => ({
const sortByOptions = SortOptions.map((v) => ({
value: v,
label: v,
}));
@ -60,8 +64,8 @@ export function EnvironmentListFilters({
setAgentVersions: (value: string[]) => void;
agentVersions: string[];
sortByState: string;
sortOnChange: (value: string) => void;
sortByState?: SortType;
sortOnChange: (value: SortType) => void;
sortOnDescending: () => void;
sortByDescending: boolean;

View file

@ -3,16 +3,18 @@ import clsx from 'clsx';
import { Option, PortainerSelect } from '@@/form-components/PortainerSelect';
import { TableHeaderSortIcons } from '@@/datatables/TableHeaderSortIcons';
import { SortType } from '../../environments/queries/useEnvironmentList';
import styles from './SortbySelector.module.css';
interface Props {
filterOptions: Option<string>[];
onChange: (value: string) => void;
filterOptions: Option<SortType>[];
onChange: (value: SortType) => void;
onDescending: () => void;
placeHolder: string;
sortByDescending: boolean;
sortByButton: boolean;
value: string;
value?: SortType;
}
export function SortbySelector({
@ -30,7 +32,7 @@ export function SortbySelector({
<PortainerSelect
placeholder={placeHolder}
options={filterOptions}
onChange={(option) => onChange(option || '')}
onChange={(option: SortType) => onChange(option || '')}
isClearable
value={value}
/>

View file

@ -11,7 +11,7 @@ import { Link } from '@@/Link';
import { useTableState } from '@@/datatables/useTableState';
import { isBE } from '../../feature-flags/feature-flags.service';
import { refetchIfAnyOffline } from '../queries/useEnvironmentList';
import { isSortType, refetchIfAnyOffline } from '../queries/useEnvironmentList';
import { columns } from './columns';
import { EnvironmentListItem } from './types';
@ -36,7 +36,7 @@ export function EnvironmentsDatatable({
excludeSnapshots: true,
page: page + 1,
pageLimit: tableState.pageSize,
sort: tableState.sortBy.id,
sort: isSortType(tableState.sortBy.id) ? tableState.sortBy.id : undefined,
order: tableState.sortBy.desc ? 'desc' : 'asc',
},
{ enabled: groupsQuery.isSuccess, refetchInterval: refetchIfAnyOffline }

View file

@ -0,0 +1,61 @@
import { EnvironmentId } from '../../types';
import { GroupAssociationTable } from './GroupAssociationTable';
export function AssociatedEnvironmentsSelector({
onChange,
value,
}: {
onChange: (
value: EnvironmentId[],
meta: { type: 'add' | 'remove'; value: EnvironmentId }
) => void;
value: EnvironmentId[];
}) {
return (
<>
<div className="col-sm-12 small text-muted">
You can select which environment should be part of this group by moving
them to the associated environments table. Simply click on any
environment entry to move it from one table to the other.
</div>
<div className="col-sm-12 mt-4">
<div className="flex">
<div className="w-1/2">
<GroupAssociationTable
title="Available environments"
emptyContentLabel="No environment available"
query={{
groupIds: [1],
}}
onClickRow={(env) => {
if (!value.includes(env.Id)) {
onChange([...value, env.Id], { type: 'add', value: env.Id });
}
}}
data-cy="edgeGroupCreate-availableEndpoints"
/>
</div>
<div className="w-1/2">
<GroupAssociationTable
title="Associated environments"
emptyContentLabel="No associated environment'"
query={{
endpointIds: value,
}}
onClickRow={(env) => {
if (value.includes(env.Id)) {
onChange(
value.filter((id) => id !== env.Id),
{ type: 'remove', value: env.Id }
);
}
}}
/>
</div>
</div>
</div>
</>
);
}

View file

@ -0,0 +1,68 @@
import { createColumnHelper } from '@tanstack/react-table';
import { truncate } from 'lodash';
import { useState } from 'react';
import { useEnvironmentList } from '@/react/portainer/environments/queries';
import { Environment } from '@/react/portainer/environments/types';
import { EnvironmentsQueryParams } from '@/react/portainer/environments/environment.service';
import { AutomationTestingProps } from '@/types';
import { useTableStateWithoutStorage } from '@@/datatables/useTableState';
import { Datatable, TableRow } from '@@/datatables';
const columHelper = createColumnHelper<Environment>();
const columns = [
columHelper.accessor('Name', {
header: 'Name',
id: 'Name',
cell: ({ getValue }) => truncate(getValue(), { length: 64 }),
}),
];
export function GroupAssociationTable({
title,
query,
emptyContentLabel,
onClickRow,
'data-cy': dataCy,
}: {
title: string;
query: EnvironmentsQueryParams;
emptyContentLabel: string;
onClickRow?: (env: Environment) => void;
} & AutomationTestingProps) {
const tableState = useTableStateWithoutStorage('Name');
const [page, setPage] = useState(1);
const environmentsQuery = useEnvironmentList({
pageLimit: tableState.pageSize,
page,
search: tableState.search,
sort: tableState.sortBy.id as 'Name',
order: tableState.sortBy.desc ? 'desc' : 'asc',
...query,
});
const { environments } = environmentsQuery;
return (
<Datatable<Environment>
title={title}
columns={columns}
settingsManager={tableState}
dataset={environments}
onPageChange={setPage}
pageCount={Math.ceil(environmentsQuery.totalCount / tableState.pageSize)}
renderRow={(row) => (
<TableRow<Environment>
cells={row.getVisibleCells()}
onClick={onClickRow ? () => onClickRow(row.original) : undefined}
/>
)}
emptyContentLabel={emptyContentLabel}
data-cy={dataCy}
disableSelect
totalCount={environmentsQuery.totalCount}
/>
);
}

View file

@ -8,9 +8,11 @@ import { queryKeys } from './queries/query-keys';
export function useGroups<T = EnvironmentGroup[]>({
select,
}: { select?: (group: EnvironmentGroup[]) => T } = {}) {
enabled = true,
}: { select?: (group: EnvironmentGroup[]) => T; enabled?: boolean } = {}) {
return useQuery(queryKeys.base(), getGroups, {
select,
enabled,
});
}

View file

@ -60,7 +60,10 @@ export async function getEnvironments(
query = {},
}: GetEnvironmentsOptions = { query: {} }
) {
if (query.tagIds && query.tagIds.length === 0) {
if (
(query.tagIds && query.tagIds.length === 0) ||
(query.endpointIds && query.endpointIds.length === 0)
) {
return {
totalCount: 0,
value: <Environment[]>[],

View file

@ -12,10 +12,16 @@ import { queryKeys } from './query-keys';
export const ENVIRONMENTS_POLLING_INTERVAL = 30000; // in ms
export const SortOptions = ['Name', 'Group', 'Status'] as const;
export type SortType = (typeof SortOptions)[number];
export function isSortType(value: string): value is SortType {
return SortOptions.includes(value as SortType);
}
export type Query = EnvironmentsQueryParams & {
page?: number;
pageLimit?: number;
sort?: string;
sort?: SortType;
order?: 'asc' | 'desc';
};