Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -680,6 +680,7 @@ test.describe('SSO Configuration Tests', () => {
page,
}) => {
await selectSSOProvider(page, 'ldap');
await page.waitForResponse('/api/v1/roles/search?*');

const addMappingButton = page.getByTestId('add-mapping-btn');
const ldapGroupInputs = page.locator(
Expand Down Expand Up @@ -747,6 +748,7 @@ test.describe('SSO Configuration Tests', () => {
page,
}) => {
await selectSSOProvider(page, 'ldap');
await page.waitForResponse('/api/v1/roles/search?*');

const field = page.getByTestId(
'sso-configuration-form-array-field-template-authReassignRoles'
Expand All @@ -760,8 +762,13 @@ test.describe('SSO Configuration Tests', () => {
// Opening the dropdown shows API-fetched role options
await field.click();
await expect(dropdown).toBeVisible();
await field.locator('input').fill('');
await page.waitForResponse('/api/v1/roles/search?*');
await expect(dropdown.locator('.ant-select-item-option')).not.toHaveCount(
0
0,
{
timeout: 15000,
}
);

// Select the first available role — it appears as a selection tag
Expand All @@ -780,15 +787,20 @@ test.describe('SSO Configuration Tests', () => {
// Typing filters the visible options
await field.click();
await field.locator('input').fill('Data');
await page.waitForResponse('/api/v1/roles/search?*');
await expect(
dropdown.locator(
'.ant-select-item-option:not(.ant-select-item-option-disabled)'
)
).not.toHaveCount(0);
).not.toHaveCount(0, { timeout: 15000 });

// Pressing Enter on a non-existent value does not create an arbitrary tag
await field.locator('input').clear();
const missingRoleSearchResponse = page.waitForResponse(
'/api/v1/roles/search?*'
);
await field.locator('input').fill('NonExistentRoleXYZ123');
await missingRoleSearchResponse;
await field.locator('input').press('Enter');
await expect(field.locator('.ant-select-selection-item')).toHaveCount(0);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,9 @@ test.describe.serial('Add role and assign it to the user', () => {
test('Create new user and assign new role to him', async ({ page }) => {
await settingClick(page, GlobalSettingOptions.USERS);

const initialRolesResponse = page.waitForResponse('/api/v1/roles/search?*');
await page.click('[data-testid="add-user"]');
await initialRolesResponse;

await page.fill('[data-testid="email"]', user.email);
await page.fill('[data-testid="displayName"]', userDisplayName);
Expand All @@ -96,7 +98,9 @@ test.describe.serial('Add role and assign it to the user', () => {
await page.locator('.ant-select-dropdown').waitFor({
state: 'visible',
});
const rolesSearchResponse = page.waitForResponse('/api/v1/roles/search?*');
await page.fill('#roles', roleName);
await rolesSearchResponse;
await page.click(`[title="${roleName}"]`);

await page.keyboard.press('Escape');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2714,7 +2714,9 @@ test.describe('Domains Rbac', () => {

// Add domain role to the user
await visitUserProfilePage(page, user1.responseData.name);
const initialRolesResponse = page.waitForResponse('/api/v1/roles/search?*');
await page.getByTestId('edit-roles-button').click();
await initialRolesResponse;

await page.locator('[data-testid="user-profile-edit-popover"]').isVisible();
const rolesCombobox = page.locator('input[role="combobox"]').nth(1);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -501,7 +501,11 @@ test.describe('User with different Roles', () => {

await expect(adminPage.getByTestId('user-profile-roles')).toBeVisible();

const initialRolesResponse = adminPage.waitForResponse(
'/api/v1/roles/search?*'
);
await adminPage.getByTestId('edit-roles-button').click();
await initialRolesResponse;

await expect(
adminPage.getByTestId('profile-edit-roles-select')
Expand All @@ -511,6 +515,15 @@ test.describe('User with different Roles', () => {
state: 'visible',
});

await adminPage
.getByTestId('profile-edit-roles-select')
.locator('input')
.fill('Application');
await adminPage.waitForResponse('/api/v1/roles/search?*');
await adminPage
.locator('.ant-select-item-option-content')
.getByText('Application bot role', { exact: true })
.waitFor({ state: 'visible' });
await adminPage
.locator('.ant-select-item-option-content')
.getByText('Application bot role', { exact: true })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -710,7 +710,7 @@ export const addUser = async (
await waitForAllLoadersToDisappear(page);
await page.click('[data-testid="add-user"]');

await page.waitForResponse('/api/v1/roles?default=false&limit=100&fields=');
await page.waitForResponse('/api/v1/roles/search?*');
await page.fill('[data-testid="email"]', email);

await page.fill('[data-testid="displayName"]', name);
Expand All @@ -726,7 +726,9 @@ export const addUser = async (
.getByRole('combobox');
await expect(rolesCombobox).toBeVisible({ timeout: 120000 });
await rolesCombobox.click();
const rolesSearchResponse = page.waitForResponse('/api/v1/roles/search?*');
await rolesCombobox.fill(role);
await rolesSearchResponse;
const roleOption = page
.locator('.ant-select-item-option-content')
.filter({ hasText: new RegExp(`^${role}$`) })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,16 @@
import { CheckOutlined, CloseOutlined } from '@ant-design/icons';
import { Button, Card, Col, Input, Row, Typography } from 'antd';
import { AxiosError } from 'axios';
import { toLower } from 'lodash';
import { FC, useEffect, useMemo, useState } from 'react';
import { debounce, toLower, uniqBy } from 'lodash';
import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { ReactComponent as IconBotProfile } from '../../../../assets/svg/bot-profile.svg';
import { PAGE_SIZE_LARGE, TERM_ADMIN } from '../../../../constants/constants';
import { TERM_ADMIN } from '../../../../constants/constants';
import { GlobalSettingOptions } from '../../../../constants/GlobalSettings.constants';
import { useLimitStore } from '../../../../context/LimitsProvider/useLimitsStore';
import { EntityType } from '../../../../enums/entity.enum';
import { Role } from '../../../../generated/entity/teams/role';
import { getAllRoles } from '../../../../rest/rolesAPIV1';
import { searchRoles } from '../../../../rest/rolesAPIV1';
import { getEntityName } from '../../../../utils/EntityUtils';
import { getSettingPath } from '../../../../utils/RouterUtils';
import { showErrorToast } from '../../../../utils/ToastUtils';
Expand All @@ -48,6 +48,8 @@ const BotDetails: FC<BotsDetailProps> = ({
const [isDisplayNameEdit, setIsDisplayNameEdit] = useState(false);
const [selectedRoles, setSelectedRoles] = useState<Array<string>>([]);
const [roles, setRoles] = useState<Array<Role>>([]);
const [isRolesLoading, setIsRolesLoading] = useState(false);
const selectedRolesRef = useRef<string[]>([]);
const { getResourceLimit, config } = useLimitStore();

const [disableFields, setDisableFields] = useState<string[]>(['token']);
Expand All @@ -74,15 +76,29 @@ const BotDetails: FC<BotsDetailProps> = ({
}
};

const fetchRoles = async () => {
const fetchRoles = useCallback(async (query = '') => {
setIsRolesLoading(true);

try {
const data = await getAllRoles('', false, PAGE_SIZE_LARGE);
setRoles(data);
const data = await searchRoles(query);
setRoles((prevRoles) => {
const selectedRoleOptions = prevRoles.filter((role) =>
selectedRolesRef.current.includes(role.id)
);

return uniqBy([...selectedRoleOptions, ...data], 'id');
});
} catch (err) {
setRoles([]);
showErrorToast(err as AxiosError);
} finally {
setIsRolesLoading(false);
}
};
}, []);

const debouncedFetchRoles = useMemo(
() => debounce(fetchRoles, 300),
[fetchRoles]
);

const onDisplayNameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setDisplayName(e.target.value);
Expand Down Expand Up @@ -190,7 +206,9 @@ const BotDetails: FC<BotsDetailProps> = ({
</Col>
<Col span={24}>
<RolesCard
isRolesLoading={isRolesLoading}
roles={roles}
searchRolesOptions={debouncedFetchRoles}
selectedRoles={selectedRoles}
setSelectedRoles={(selectedRoles) =>
setSelectedRoles(selectedRoles)
Expand All @@ -206,13 +224,36 @@ const BotDetails: FC<BotsDetailProps> = ({
);
};

useEffect(() => {
selectedRolesRef.current = selectedRoles;
}, [selectedRoles]);

useEffect(() => {
fetchRoles();
initLimits();
}, []);

useEffect(() => {
return () => {
debouncedFetchRoles.cancel();
};
}, [debouncedFetchRoles]);

useEffect(() => {
prepareSelectedRoles();
setRoles((prevRoles) =>
uniqBy(
[
...prevRoles,
...((botUserData.roles ?? []).map((role) => ({
id: role.id,
name: role.name ?? '',
displayName: role.displayName,
})) as Role[]),
],
'id'
)
);
}, [botUserData]);

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import { act, render, screen } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import { OperationPermission } from '../../../../context/PermissionProvider/PermissionProvider.interface';
import { searchRoles } from '../../../../rest/rolesAPIV1';
import { getAuthMechanismForBotUser } from '../../../../rest/userAPI';
import AccessTokenCard from '../../Users/AccessTokenCard/AccessTokenCard.component';
import BotDetails from './BotDetails.component';
Expand Down Expand Up @@ -93,6 +94,10 @@ jest.mock('../../../../utils/PermissionsUtils', () => ({
checkPermission: jest.fn().mockReturnValue(true),
}));

jest.mock('../../../../rest/rolesAPIV1', () => ({
searchRoles: jest.fn().mockResolvedValue([]),
}));

const mockGetResourceLimit = jest.fn().mockResolvedValue({
configuredLimit: { disabledFields: [] },
});
Expand Down Expand Up @@ -146,6 +151,10 @@ jest.mock('../../../../context/LimitsProvider/useLimitsStore', () => ({
}));

describe('Test BotsDetail Component', () => {
beforeEach(() => {
(searchRoles as jest.Mock).mockResolvedValue([]);
});

it('Should render all child elements', async () => {
await act(async () => {
render(<BotDetails {...mockProp} />, {
Expand Down
Loading
Loading