remove all uses of fetchUsers() v1 (#64719)

This commit is contained in:
Yassine Bounekhla
2026-08-13 21:31:59 +00:00
committed by GitHub
parent 007a84c4c2
commit d16e99f570
10 changed files with 56 additions and 97 deletions
@@ -187,6 +187,7 @@ export function splitSelectProps<
customProps,
defaultValue,
elevated,
getOptionValue,
helperText,
inputId,
inputValue,
@@ -236,6 +237,7 @@ export function splitSelectProps<
customProps,
defaultValue,
elevated,
getOptionValue,
inputValue,
isClearable,
isDisabled,
@@ -284,6 +286,7 @@ type KeysRemovedFromOthers =
| 'customProps'
| 'defaultValue'
| 'elevated'
| 'getOptionValue'
| 'helperText'
| 'inputId'
| 'inputValue'
@@ -36,6 +36,7 @@ import type {
import { Desktop } from 'teleport/services/desktops';
import { Node } from 'teleport/services/nodes';
import { RoleResource } from 'teleport/services/resources';
import { User } from 'teleport/services/user';
import Ctx from 'teleport/teleportContext';
import useTeleport from 'teleport/useTeleport';
@@ -44,6 +45,7 @@ import { ServerSideListProps, TableWrapper } from '../common';
import { Desktops } from './Desktops';
import { Nodes } from './Nodes';
import { Roles } from './Roles';
import Users from './Users';
export function ServerSideSupportedList(props: CommonListProps) {
const ctx = useTeleport();
@@ -119,6 +121,8 @@ export function ServerSideSupportedList(props: CommonListProps) {
};
switch (props.selectedResourceKind) {
case 'user':
return <Users users={resources as User[]} {...listProps} />;
case 'role':
return <Roles roles={resources as RoleResource[]} {...listProps} />;
case 'node':
@@ -148,7 +152,11 @@ export function ServerSideSupportedList(props: CommonListProps) {
to: pageIndicators.to,
total: pageIndicators.totalCount,
}}
hideAdvancedSearch={props.selectedResourceKind === 'role'} // Roles don't support advanced search.
// Roles and users don't support advanced search.
hideAdvancedSearch={
props.selectedResourceKind === 'role' ||
props.selectedResourceKind === 'user'
}
filter={resourceFilter}
disableSearch={fetchStatus === 'loading'}
/>
@@ -194,6 +202,13 @@ function getFetchFuncForServerSidePaginating(
clusterId: string,
params: UrlResourcesParams
) => Promise<ResourcesResponse<unknown>> {
if (resourceKind === 'user') {
return async (clusterId, params) => {
const { items, startKey } = await ctx.userService.fetchUsersV2(params);
return { agents: items, startKey };
};
}
if (resourceKind === 'role') {
return async (clusterId, params) => {
const { items, startKey } = await ctx.resourceService.fetchRoles(params);
@@ -20,9 +20,11 @@ import Table, { Cell, LabelCell } from 'design/DataTable';
import { User } from 'teleport/services/user';
import { renderActionCell, SimpleListProps } from '../common';
import { renderActionCell, ServerSideListProps } from '../common';
export default function UserList(props: SimpleListProps & { users: User[] }) {
export default function UserList(
props: ServerSideListProps & { users: User[] }
) {
const {
users = [],
selectedResources,
@@ -37,31 +39,15 @@ export default function UserList(props: SimpleListProps & { users: User[] }) {
{
key: 'name',
headerText: 'Name',
isSortable: true,
},
{
key: 'roles',
headerText: 'Roles',
isSortable: true,
onSort: (a, b) => {
const aStr = a.roles.toString();
const bStr = b.roles.toString();
if (aStr < bStr) {
return -1;
}
if (aStr > bStr) {
return 1;
}
return 0;
},
render: ({ roles }) => <LabelCell data={roles} />,
},
{
key: 'authType',
headerText: 'Type',
isSortable: true,
render: ({ authType }) => (
<Cell style={{ textTransform: 'capitalize' }}>
{renderAuthType(authType)}
@@ -77,8 +63,7 @@ export default function UserList(props: SimpleListProps & { users: User[] }) {
},
]}
emptyText="No Users Found"
isSearchable
pagination={{ pageSize: props.pageSize }}
disableFilter
fetching={{
fetchStatus,
}}
@@ -19,13 +19,11 @@
import React, { useEffect, useMemo, useState } from 'react';
import { MfaDevice } from 'teleport/services/mfa';
import { User } from 'teleport/services/user';
import useTeleport from 'teleport/useTeleport';
import { CommonListProps, LockResourceKind } from '../../common';
import { SimpleListProps, TableWrapper } from '../common';
import { MfaDevices } from './MfaDevices';
import Users from './Users';
export type SimpleListOpts = {
getFetchFn(
@@ -45,9 +43,6 @@ export function SimpleList(props: CommonListProps & { opts: SimpleListOpts }) {
useEffect(() => {
let fetchFn;
switch (props.selectedResourceKind) {
case 'user':
fetchFn = ctx.userService.fetchUsers;
break;
case 'mfa_device':
fetchFn = ctx.mfaService.fetchDevices;
break;
@@ -81,8 +76,6 @@ export function SimpleList(props: CommonListProps & { opts: SimpleListOpts }) {
toggleSelectResource: props.toggleSelectResource,
};
switch (props.selectedResourceKind) {
case 'user':
return <Users users={resources as User[]} {...listProps} />;
case 'mfa_device':
return (
<MfaDevices mfaDevices={resources as MfaDevice[]} {...listProps} />
@@ -102,7 +102,7 @@ export const baseResourceKindOpts: LockResourceOption[] = [
{
value: 'user',
label: 'Users',
listKind: 'simple',
listKind: 'server-side',
},
{
value: 'role',
@@ -16,7 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { useMutation } from '@tanstack/react-query';
import { useState } from 'react';
import { Alert, Box, ButtonPrimary, ButtonSecondary } from 'design';
@@ -46,7 +46,6 @@ import userService, {
type CreateUserVariables,
type User,
} from 'teleport/services/user';
import { GetUsersQueryKey } from 'teleport/services/user/hooks';
import UserTokenLink from './../UserTokenLink';
@@ -67,8 +66,6 @@ export function UserAddEdit({
}: UserAddEditProps) {
const ctx = useTeleport();
const queryClient = useQueryClient();
const createUser = useMutation({
mutationFn: async (variables: CreateUserVariables) => {
const mfaResponse =
@@ -87,30 +84,13 @@ export function UserAddEdit({
token,
};
},
onSuccess: ({ token, user }) => {
onSuccess: ({ token }) => {
setToken(token);
queryClient.setQueryData(GetUsersQueryKey, previous => {
if (!previous) {
return [];
}
return [user, ...previous];
});
},
});
const updateUser = useMutation({
mutationFn: userService.updateUser,
onSuccess: data => {
queryClient.setQueryData(GetUsersQueryKey, previous => {
if (!previous) {
return [];
}
return [data, ...previous.filter(i => i.name !== data.name)];
});
},
});
const [name, setName] = useState(user.name);
@@ -16,7 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { useMutation } from '@tanstack/react-query';
import { Alert, ButtonSecondary, ButtonWarning, Text } from 'design';
import Dialog, {
@@ -28,7 +28,6 @@ import Dialog, {
import { ResourcesResponse } from 'teleport/services/agents';
import userService, { User } from 'teleport/services/user';
import { GetUsersQueryKey } from 'teleport/services/user/hooks';
interface UserDeleteProps {
username: string;
@@ -43,19 +42,8 @@ export function UserDelete({
onClose,
modifyFetchedData,
}: UserDeleteProps) {
const queryClient = useQueryClient();
const deleteUser = useMutation({
mutationFn: userService.deleteUser,
onSuccess: (_, name) => {
queryClient.setQueryData(GetUsersQueryKey, previous => {
if (!previous) {
return [];
}
return previous.filter(user => user.name !== name);
});
},
});
async function handleDelete() {
+1 -1
View File
@@ -1287,7 +1287,7 @@ const cfg = {
return generatePath(cfg.routes.ssoConnector.create, { connectorType });
},
// TODO(rudream): DELETE IN V21.0.0
// TODO(rudream): DELETE IN v20.0
getUsersUrl() {
return cfg.api.usersPath;
},
@@ -1,23 +0,0 @@
/**
* Teleport
* Copyright (C) 2025 Gravitational, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { createQueryHook } from 'teleport/services/queryHelpers';
import userService from 'teleport/services/user';
export const { queryKey: GetUsersQueryKey, useQuery: useGetUsers } =
createQueryHook(['users', 'get'], userService.fetchUsers);
@@ -66,11 +66,29 @@ const service = {
.then(makeUser);
},
// TODO(rudream): DELETE IN v21.0
/**
* @deprecated Use {@link service.fetchUsersV2} instead.
*/
// TODO(rudream): DELETE IN v20.0
fetchUsers(signal?: AbortSignal) {
return api.get(cfg.getUsersUrl(), signal).then(makeUsers);
},
/**
* fetchAllUsers fetches all users page by page until there are no more.
* Only use this in cases where all users are needed upfront, otherwise, use {@link service.fetchUsersV2}.
*/
async fetchAllUsers(signal?: AbortSignal): Promise<User[]> {
const allUsers: User[] = [];
let startKey = '';
do {
const page = await service.fetchUsersV2({ limit: 200, startKey }, signal);
allUsers.push(...page.items);
startKey = page.startKey;
} while (startKey);
return allUsers;
},
async fetchUsersV2(
params?: UrlListUsersParams,
signal?: AbortSignal
@@ -89,11 +107,11 @@ const service = {
.catch(err => {
// If this v2 paginated endpoint isn't found, fallback to the v1 endpoint but paginate locally in order to
// maintain compatibility with the paginated table component which expects a paginated response.
// TODO(rudream): DELETE IN v21.0
// TODO(rudream): DELETE IN v20.0
if (isPathNotFoundError(err)) {
return this.fetchUsers().then(users =>
makeUsersPageLocally(params, users)
);
return service
.fetchUsers()
.then(users => makeUsersPageLocally(params, users));
} else {
throw err;
}
@@ -202,7 +220,7 @@ function withExcludedField(user: User, excludeUserField: ExcludeUserField) {
* makeUsersPageLocally mocks a paginated response for users so that a list of all users
* can be handled by a serverside paginated table component.
*/
// TODO(rudream): DELETE IN v21.0
// TODO(rudream): DELETE IN v20.0
function makeUsersPageLocally(
params: UrlListUsersParams,
allUsers: User[]
@@ -210,18 +228,18 @@ function makeUsersPageLocally(
items: User[];
startKey: string;
} {
if (params.search) {
if (params?.search) {
allUsers = allUsers.filter(u =>
u.name.toLowerCase().includes(params.search.toLowerCase())
);
}
if (params.startKey) {
if (params?.startKey) {
const startIndex = allUsers.findIndex(p => p.name === params.startKey);
allUsers = allUsers.slice(startIndex);
}
const limit = params.limit || 200;
const limit = params?.limit || 200;
const nextKey = allUsers.at(limit)?.name;
allUsers = allUsers.slice(0, limit);