Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding users -> roles tests automation #373

Merged
merged 20 commits into from
Nov 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
c5de58a
Fixed the type check logic for the getInputComponent function
pranavgoel29 Nov 11, 2024
d1895dc
Removed the duplicate code of HumanizeNumber i.e., convertToReadableS…
pranavgoel29 Nov 11, 2024
3f65f46
Merge branch 'main' of https://github.com/parseablehq/console into mi…
pranavgoel29 Nov 13, 2024
cf59735
Fix the semantic tag for roles header and modal header font-weight fo…
pranavgoel29 Nov 13, 2024
cc88541
Improved teh code to use correct TS literals
pranavgoel29 Nov 13, 2024
2f8efb1
Added the base tests for /users roles section
pranavgoel29 Nov 13, 2024
29d595f
Reset the playwright config file
pranavgoel29 Nov 13, 2024
6552ff7
Added tests for create role modal
pranavgoel29 Nov 13, 2024
b3959eb
Added an identifier for the create role button
pranavgoel29 Nov 14, 2024
5f91d4d
Added more button idetifiers to modal and cleaned the test file
pranavgoel29 Nov 14, 2024
11ddc86
Switched to playwright test-id structure
pranavgoel29 Nov 14, 2024
635a4b2
Added create and delete role tests
pranavgoel29 Nov 14, 2024
cff1041
Cleaned the create and delete role tests and added helper functions
pranavgoel29 Nov 14, 2024
6559354
Added test folder in 'tsconfig' for linting
pranavgoel29 Nov 14, 2024
4477fb1
Removed the unwanted console logs
pranavgoel29 Nov 14, 2024
88ba89c
Added jsdoc for user->roles helper functions
pranavgoel29 Nov 14, 2024
1602227
Merge branch 'parseablehq:main' into user-roles-tests
pranavgoel29 Nov 14, 2024
1ef2d20
Merge branch 'main' of https://github.com/parseablehq/console into us…
pranavgoel29 Nov 15, 2024
1935426
Removed unused page variable
pranavgoel29 Nov 15, 2024
5f2a8f6
Updated imports in users_roles.spec.ts
pranavgoel29 Nov 15, 2024
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
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
"lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"start": "vite preview --host --port 3002",
"tsCheck": "tsc --noEmit",
"pq": "pretty-quick"
"pq": "pretty-quick",
"test": "playwright test",
"test-ui": "playwright test --ui"
},
"dependencies": {
"@apache-arrow/ts": "^14.0.2",
Expand Down
3 changes: 3 additions & 0 deletions src/components/Button/IconButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import classes from './Button.module.css';
type IconButtonProps = {
onClick?: () => void;
renderIcon: () => ReactNode;
data_id?: string;
icon?: ReactNode;
active?: boolean;
tooltipLabel?: string;
Expand All @@ -17,6 +18,7 @@ const IconButton: FC<IconButtonProps> = (props) => {
return (
<Tooltip label={tooltipLabel}>
<ActionIcon
data-testId={props.data_id}
size={props.size ? props.size : 'xl'}
className={`${classes.iconBtn} ${props.active && classes.iconBtnActive}`}
onClick={props.onClick && props.onClick}>
Expand All @@ -27,6 +29,7 @@ const IconButton: FC<IconButtonProps> = (props) => {
} else {
return (
<ActionIcon
data-testId={props.data_id}
size={props.size ? props.size : 'xl'}
className={`${classes.iconBtn} ${props.active && classes.iconBtnActive}`}
onClick={props.onClick && props.onClick}>
Expand Down
4 changes: 2 additions & 2 deletions src/hooks/useGetStreamMetadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ export type MetaData = {

// until dedicated endpoint been provided - fetch one by one
export const useGetStreamMetadata = () => {
const [isLoading, setLoading] = useState<Boolean>(true);
const [error, setError] = useState<Boolean>(false);
const [isLoading, setLoading] = useState<boolean>(true);
const [error, setError] = useState<boolean>(false);
const [metaData, setMetadata] = useState<MetaData | null>(null);
const [userRoles] = useAppStore((store) => store.userRoles);

Expand Down
2 changes: 1 addition & 1 deletion src/pages/AccessManagement/PrivilegeTR.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ const PrivilegeTR: FC<PrivilegeTRProps> = (props) => {
<Button
variant="default"
className={classes.actionBtn}
aria-label="Delete user"
aria-label="Delete Role"
onClick={() => {
openDeleteRole();
}}>
Expand Down
16 changes: 13 additions & 3 deletions src/pages/AccessManagement/Roles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -167,10 +167,13 @@ const Roles: FC = () => {
return (
<Box className={classes.container}>
<Stack className={classes.header} gap={0}>
<Text style={{ fontWeight: 600 }}>Roles</Text>
<Text component="h2" style={{ fontWeight: 600 }}>
Roles
</Text>
<Stack style={{ flexDirection: 'row' }} gap={0}>
<Button
className={classes.createUserBtn}
data-testId="create-role-button"
rightSection={<IconUserPlus size={px('1rem')} stroke={1.5} />}
onClick={() => {
setModalOpen(true);
Expand All @@ -187,7 +190,7 @@ const Roles: FC = () => {
Set Default OIDC Role{' '}
</Button>
)}
<IconButton renderIcon={renderDocsIcon} onClick={navigateToDocs} tooltipLabel="Docs" />
<IconButton renderIcon={renderDocsIcon} onClick={navigateToDocs} tooltipLabel="Docs" data_id="roles-docs" />
</Stack>
</Stack>
<ScrollArea className={classes.tableContainer} type="always">
Expand All @@ -207,6 +210,7 @@ const Roles: FC = () => {
onClose={handleDefaultRoleModalClose}
title="Set default oidc role"
centered
styles={{ title: { fontWeight: 500 } }}
className={classes.modalStyle}>
<Stack>
<Select
Expand Down Expand Up @@ -310,12 +314,18 @@ const Roles: FC = () => {
<Button
variant="filled"
color="gray"
data-testId="create-role-modal-button"
className={classes.modalActionBtn}
disabled={createVaildtion()}
onClick={handleCreateRole}>
Create
</Button>
<Button onClick={handleClose} variant="outline" color="gray" className={classes.modalCancelBtn}>
<Button
onClick={handleClose}
variant="outline"
color="gray"
data-testId="cancel-role-modal-button"
className={classes.modalCancelBtn}>
Cancel
</Button>
</Group>
Expand Down
6 changes: 3 additions & 3 deletions src/pages/Dashboards/CreateTileForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -527,14 +527,14 @@ const sanitizeFormValues = (form: TileFormType, type: 'create' | 'update'): Edit
};

const defaultVizOpts = {
visualization_type: 'donut-chart' as 'donut-chart',
visualization_type: 'donut-chart' as const,
size: 'sm',
color_config: [],
tick_config: [],
circular_chart_config: {},
graph_config: { x_key: '', y_keys: [] },
orientation: 'horizontal' as 'horizontal',
graph_type: 'default' as 'default',
orientation: 'horizontal' as const,
graph_type: 'default' as const,
};

const defaultFormOpts = {
Expand Down
12 changes: 6 additions & 6 deletions src/pages/Dashboards/assets/templates.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const pmetaConfig = {
query: 'select MAX(parseable_events_ingested), address from pmeta GROUP BY address LIMIT 100',
order: 1,
visualization: {
visualization_type: 'donut-chart' as 'donut-chart',
visualization_type: 'donut-chart' as const,
circular_chart_config: {
name_key: 'address',
value_key: 'MAX(pmeta.parseable_events_ingested)',
Expand All @@ -30,7 +30,7 @@ const pmetaConfig = {
"select a.time,SUM(a.current_events) as current_events, SUM(a.lifetime_events) as lifetime_events, SUM(a.deleted_events) as deleted_events from (select MAX(parseable_events_ingested) as current_events, MAX(parseable_lifetime_events_ingested) as lifetime_events, MAX(parseable_deleted_events_ingested) as deleted_events, DATE_BIN('10 minutes', p_timestamp, NOW()) AS time from pmeta where address='http://parseable-ing-8yvv-0.parseable-ingestor-headless.demo.svc.cluster.local:8000/' group by time order by time) a GROUP BY a.time ORDER BY a.time LIMIT 100",
order: 2,
visualization: {
visualization_type: 'bar-chart' as 'bar-chart',
visualization_type: 'bar-chart' as const,
circular_chart_config: null,
graph_config: {
x_key: 'time',
Expand Down Expand Up @@ -67,7 +67,7 @@ const pmetaConfig = {
query: 'SELECT MAX(process_resident_memory_bytes) as memory_usage, address FROM pmeta GROUP BY address LIMIT 100',
order: 3,
visualization: {
visualization_type: 'pie-chart' as 'pie-chart',
visualization_type: 'pie-chart' as const,
circular_chart_config: {
name_key: 'address',
value_key: 'memory_usage',
Expand All @@ -85,7 +85,7 @@ const pmetaConfig = {
"select a.time, SUM(a.current_events_size) as current_events_size, SUM(a.lifetime_events_size) as lifetime_events_size, SUM(a.deleted_events_size) as deleted_events_size from (select MAX(parseable_events_ingested_size) as current_events_size, MAX(parseable_lifetime_events_ingested_size) as lifetime_events_size, MAX(parseable_deleted_events_ingested_size) as deleted_events_size, DATE_BIN('10 minutes', p_timestamp, NOW()) AS time from pmeta where address='http://parseable-ing-8yvv-0.parseable-ingestor-headless.demo.svc.cluster.local:8000/' group by time order by time) a GROUP BY a.time ORDER BY a.time LIMIT 100",
order: 4,
visualization: {
visualization_type: 'bar-chart' as 'bar-chart',
visualization_type: 'bar-chart' as const,
circular_chart_config: null,
graph_config: {
x_key: 'time',
Expand Down Expand Up @@ -135,7 +135,7 @@ const pmetaConfig = {
"SELECT MAX(parseable_events_ingested_size) as current_events_size, MAX(parseable_storage_size_data) as current_data_size FROM pmeta where address='http://ec2-3-15-172-147.us-east-2.compute.amazonaws.com:443/' LIMIT 100",
order: 5,
visualization: {
visualization_type: 'table' as 'table',
visualization_type: 'table' as const,
circular_chart_config: null,
graph_config: null,
size: 'sm',
Expand All @@ -159,7 +159,7 @@ const pmetaConfig = {
"select DATE_BIN('10 minutes', p_timestamp, NOW()) AS time, MAX(process_resident_memory_bytes) as process_resident_memory_bytes from pmeta where address='http://parseable-ing-8yvv-0.parseable-ingestor-headless.demo.svc.cluster.local:8000/' group by time order by time LIMIT 100",
order: 6,
visualization: {
visualization_type: 'bar-chart' as 'bar-chart',
visualization_type: 'bar-chart' as const,
circular_chart_config: null,
graph_config: {
x_key: 'time',
Expand Down
2 changes: 1 addition & 1 deletion src/pages/Stream/Views/Explore/StaticLogTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ const Table = (props: { primaryHeaderHeight: number }) => {
overflow: 'hidden',
textOverflow: 'ellipsis',
display: 'table-cell',
...(!_.includes(wrapDisabledColumns, columnName) ? { whiteSpace: 'nowrap' as 'nowrap' } : {}),
...(!_.includes(wrapDisabledColumns, columnName) ? { whiteSpace: 'nowrap' as const } : {}),
},
};
},
Expand Down
4 changes: 2 additions & 2 deletions src/pages/Stream/Views/Manage/Alerts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import ErrorView from './ErrorView';
import RestrictedView from '@/components/Misc/RestrictedView';

const defaultColumnTypeConfig = { column: '', operator: '=', value: '', repeats: 1, ignoreCase: false };
const defaultColumnTypeRule = { type: 'column' as 'column', config: defaultColumnTypeConfig };
const defaultColumnTypeRule = { type: 'column' as const, config: defaultColumnTypeConfig };
const { transformAlerts } = streamStoreReducers;

const stringOperators = [
Expand Down Expand Up @@ -574,7 +574,7 @@ const AlertsModal = (props: {
);
};

const Header = (props: { selectAlert: selectAlert; isLoading: boolean, showCreateBtn: boolean }) => {
const Header = (props: { selectAlert: selectAlert; isLoading: boolean; showCreateBtn: boolean }) => {
return (
<Stack className={classes.headerContainer} style={{ minHeight: '3rem', maxHeight: '3rem' }}>
<Text className={classes.title}>Alerts</Text>
Expand Down
17 changes: 8 additions & 9 deletions src/pages/Stream/providers/LogsProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ const getDefaultTimeRange = (duration: FixedDuration = DEFAULT_FIXED_DURATIONS)
return {
startTime: startTime.toDate(),
endTime: now.toDate(),
type: 'fixed' as 'fixed',
type: 'fixed' as const,
label,
interval: milliseconds,
shiftInterval: 1,
Expand All @@ -152,7 +152,7 @@ const defaultQuickFilters = {
};

const defaultLiveTailConfig = {
liveTailStatus: '' as '',
liveTailStatus: '' as const,
liveTailSchemaData: [],
liveTailSearchValue: '',
liveTailSearchField: '',
Expand Down Expand Up @@ -298,7 +298,7 @@ type LogsStoreReducers = {
};

const defaultSortKey = 'p_timestamp';
const defaultSortOrder = 'desc' as 'desc';
const defaultSortOrder = 'desc' as const;

const initialState: LogsStore = {
timeRange: getDefaultTimeRange(),
Expand Down Expand Up @@ -464,7 +464,7 @@ const setCustQuerySearchState = (store: LogsStore, query: string, viewMode: stri
isQuerySearchActive: true,
custSearchQuery: query,
viewMode,
activeMode: viewMode === 'filters' ? ('filters' as 'filters') : ('sql' as 'sql'),
activeMode: viewMode === 'filters' ? ('filters' as const) : ('sql' as const),
},
...getCleanStoreForRefetch(store),
timeRange,
Expand Down Expand Up @@ -757,7 +757,7 @@ const applyCustomQuery = (
endTime: endTime.toDate(),
label,
interval,
type: 'custom' as 'custom', // always
type: 'custom' as const, // always
},
};
}
Expand Down Expand Up @@ -810,7 +810,7 @@ const setAndSortData = (store: LogsStore, sortKey: string, sortOrder: 'asc' | 'd
};
};

const setAndFilterData = (store: LogsStore, filterKey: string, filterValues: string[], remove: boolean = false) => {
const setAndFilterData = (store: LogsStore, filterKey: string, filterValues: string[], remove = false) => {
const { data, tableOpts } = store;
const { sortKey, sortOrder, filters } = tableOpts;
const updatedFilters = remove ? _.omit(filters, filterKey) : { ...filters, [filterKey]: filterValues };
Expand Down Expand Up @@ -903,7 +903,7 @@ const setRetention = (_store: LogsStore, retention: { duration?: string; descrip
retention: {
duration: durationInNumber,
description: retention.description || '',
action: 'delete' as 'delete',
action: 'delete' as const,
},
};
};
Expand Down Expand Up @@ -941,8 +941,7 @@ const onToggleView = (store: LogsStore, viewMode: 'json' | 'table') => {
};

const toggleConfigViewType = (store: LogsStore) => {
const configViewType =
store.tableOpts.configViewType === 'schema' ? ('columns' as 'columns') : ('schema' as 'schema');
const configViewType: 'columns' | 'schema' = store.tableOpts.configViewType === 'schema' ? 'columns' : 'schema';

return {
tableOpts: {
Expand Down
2 changes: 1 addition & 1 deletion src/pages/Stream/providers/StreamProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ const setRetention = (_store: StreamStore, retention: { duration?: string; descr
retention: {
duration: durationInNumber,
description: retention.description || '',
action: 'delete' as 'delete',
action: 'delete' as const,
},
};
};
Expand Down
4 changes: 2 additions & 2 deletions src/pages/Systems/ServerList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ const List = () => {
const { getClusterInfoSuccess, getClusterInfoRefetch } = useClusterInfo();
const [clusterStore] = useClusterStore((store) => store);
const { ingestorMachines: ingestorsInStore, querierMachine } = clusterStore;
const querier = { ...querierMachine, type: 'querier' as 'querier' };
const ingestors = _.map(ingestorsInStore, (ingestors) => ({ ...ingestors, type: 'ingestor' as 'ingestor' }));
const querier = { ...querierMachine, type: 'querier' as const };
const ingestors = _.map(ingestorsInStore, (ingestors) => ({ ...ingestors, type: 'ingestor' as const }));

useEffect(() => {
getClusterInfoRefetch();
Expand Down
75 changes: 75 additions & 0 deletions tests/helpers/users_roles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { Page, expect } from '@playwright/test';

/**
* Creates a new role in the application.
*
* @param page - The Playwright Page object representing the browser page.
* @param ROLE_NAME - The name of the role to be created.
* @param ACCESS - The access level to be assigned to the role.
*
* @returns A promise that resolves when the role creation process is complete.
*/
const createNewRole = async (page: Page, ROLE_NAME: string, ACCESS: string) => {
const createRoleButton = page.getByRole('button', { name: 'Create Role' });
await createRoleButton.click();
const roleNameInputCreate = page.getByPlaceholder('Type the name of the Role to create');
await roleNameInputCreate.fill(ROLE_NAME);

const accessLevelDropdown = page.getByPlaceholder('Select privilege');
await accessLevelDropdown.click();
await page
.locator('.mantine-Select-option')
.filter({ hasText: `${ACCESS}` })
.click();

// Wait for one second
await page.waitForTimeout(1000);
// Check if the Create button is enabled
const createButton = page.getByTestId('create-role-modal-button');
await expect(createButton).toBeEnabled();
await createButton.click();
};

/**
* Deletes a role if it exists in the roles table on the given page.
*
* @param page - The Playwright Page object representing the browser page.
* @param ROLE_NAME - The name of the role to be deleted.
*
* @remarks
* This function locates the roles table on the page, checks if the specified role exists,
* and if it does, clicks the delete button, confirms the deletion, and verifies that the role
* is no longer visible in the table.
*/
const deleteIfRoleExists = async (page: Page, ROLE_NAME: string) => {
const rolesTable = page.locator('table').filter({
has: page
.locator('thead tr')
.filter({
has: page.locator('th').nth(0).filter({ hasText: 'Role' }),
})
.filter({
has: page.locator('th').nth(1).filter({ hasText: 'Access' }),
}),
});

const roleRow = rolesTable.locator('tbody tr').filter({ hasText: ROLE_NAME });
const roleRowCount = await roleRow.count();

if (roleRowCount !== 0) {
const deleteButton = roleRow.getByRole('button', { name: 'Delete Role' });
await deleteButton.click();

await page.waitForSelector('text=Are you sure you want to delete this role?');
const roleNameInput = page.getByPlaceholder('Please enter the Role');
await roleNameInput.fill(ROLE_NAME);

const confirmDeleteButton = page.getByRole('button', { name: 'Delete' });
await expect(confirmDeleteButton).toBeEnabled();
await confirmDeleteButton.click();

await expect(roleRow).not.toBeVisible();
}
};

export { createNewRole, deleteIfRoleExists };
Loading
Loading