Skip to content

Commit

Permalink
Merge branch 'master' into stalenessKeys
Browse files Browse the repository at this point in the history
  • Loading branch information
adonispuente authored Dec 11, 2023
2 parents 2cc5e53 + 64c5f6c commit 7c737b9
Show file tree
Hide file tree
Showing 10 changed files with 186 additions and 45 deletions.
3 changes: 2 additions & 1 deletion config/overrideChrome.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ const chromeMock = {
isBeta: () => false,
appAction: () => {},
appObjectId: () => {},
on: () => {},
on: () => () => {},
getApp: () => 'inventory',
getBundle: () => 'insights',
getUserPermissions: () => [{ permission: 'inventory:*:*' }],
Expand All @@ -25,6 +25,7 @@ const chromeMock = {
},
}),
},
hideGlobalFilter: () => {},
};

export default () => chromeMock;
Expand Down
1 change: 1 addition & 0 deletions config/setupTests.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ jest.mock('@redhat-cloud-services/frontend-components/useChrome', () => ({
getUserPermissions: () => Promise.resolve(['inventory:*:*']),
getApp: jest.fn(),
getBundle: jest.fn(),
hideGlobalFilter: jest.fn(),
}),
}));

Expand Down
7 changes: 6 additions & 1 deletion src/components/GroupSystems/GroupSystems.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import { clearEntitiesAction } from '../../store/actions';
import { useBulkSelectConfig } from '../../Utilities/hooks/useBulkSelectConfig';
import difference from 'lodash/difference';
import map from 'lodash/map';
import useGlobalFilter from '../filters/useGlobalFilter';

export const prepareColumns = (
initialColumns,
hideGroupColumn,
Expand Down Expand Up @@ -74,6 +76,7 @@ export const prepareColumns = (

const GroupSystems = ({ groupName, groupId }) => {
const dispatch = useDispatch();
const globalFilter = useGlobalFilter();
const [removeHostsFromGroupModalOpen, setRemoveHostsFromGroupModalOpen] =
useState(false);
const [currentSystem, setCurrentSystem] = useState([]);
Expand Down Expand Up @@ -104,7 +107,7 @@ const GroupSystems = ({ groupName, groupId }) => {

const bulkSelectConfig = useBulkSelectConfig(
selected,
null,
globalFilter,
total,
rows,
true,
Expand Down Expand Up @@ -220,6 +223,8 @@ const GroupSystems = ({ groupName, groupId }) => {
showTags
ref={inventory}
showCentosVersions
customFilters={{ globalFilter }}
autoRefresh
/>
)}
</div>
Expand Down
8 changes: 1 addition & 7 deletions src/components/InventoryGroupDetail/index.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,9 @@
import useChrome from '@redhat-cloud-services/frontend-components/useChrome';
import React, { useEffect } from 'react';
import React from 'react';
import { useParams } from 'react-router-dom';
import InventoryGroupDetail from './InventoryGroupDetail';

const InventoryGroupDetailWrapper = () => {
const { groupId } = useParams();
const chrome = useChrome();

useEffect(() => {
chrome?.hideGlobalFilter?.();
}, []);

return <InventoryGroupDetail groupId={groupId} />;
};
Expand Down
6 changes: 5 additions & 1 deletion src/components/InventoryGroups/Modals/CreateGroupModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ import { createGroup, validateGroupName } from '../utils/api';
import { useDispatch } from 'react-redux';
import awesomeDebouncePromise from 'awesome-debounce-promise';

export const validate = async (value) => {
export const validate = async (value = '') => {
if (value.length === 0) {
return undefined; // the input is empty
}

const results = await validateGroupName(value.trim());
if (results === true) {
throw 'Group name already exists';
Expand Down
125 changes: 122 additions & 3 deletions src/components/InventoryGroups/Modals/CreateGroupModal.test.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,144 @@
import { render, screen, waitFor } from '@testing-library/react';
import React from 'react';
import { validateGroupName } from '../utils/api';
import { validate } from './CreateGroupModal';
import CreateGroupModal, { validate } from './CreateGroupModal';
import userEvent from '@testing-library/user-event';
import '@testing-library/jest-dom';

jest.mock('../utils/api');
jest.mock('react-redux');

describe('validate function', () => {
afterEach(() => {
jest.clearAllMocks();
});

it('works with basic input', async () => {
const result = await validate('test');

expect(result).toBe(undefined);
expect(result).toBeUndefined();
expect(validateGroupName).toHaveBeenCalledWith('test');
});

it('trims input', async () => {
const result = await validate(' test ');

expect(result).toBe(undefined);
expect(result).toBeUndefined();
expect(validateGroupName).toHaveBeenCalledWith('test');
});

it('throws error if the name is present', async () => {
validateGroupName.mockResolvedValue(true);

await expect(validate('test')).rejects.toBe('Group name already exists');
});

it('does not check on undefined input', async () => {
const result = await validate(undefined);

expect(result).toBeUndefined();
expect(validateGroupName).not.toHaveBeenCalled();
});

it('does not check on empty input', async () => {
const result = await validate('');

expect(result).toBeUndefined();
expect(validateGroupName).not.toHaveBeenCalled();
});
});

describe('create group modal', () => {
afterEach(() => {
jest.clearAllMocks();
});

const setIsModalOpen = jest.fn();
const reloadData = jest.fn();

it('create button is initially disabled', () => {
render(
<CreateGroupModal
isModalOpen
setIsModalOpen={setIsModalOpen}
reloadData={reloadData}
/>
);

expect(
screen.getByRole('button', {
name: /create/i,
})
).toBeDisabled();
});

it('can create a group with new name', async () => {
validateGroupName.mockResolvedValue(false);

render(
<CreateGroupModal
isModalOpen
setIsModalOpen={setIsModalOpen}
reloadData={reloadData}
/>
);

await userEvent.type(
screen.getByRole('textbox', {
name: /group name/i,
}),
'_abc'
);

await waitFor(() => {
expect(
screen.getByRole('button', {
name: /create/i,
})
).toBeEnabled();
});
});

it('cannot create a group with incorrect name', async () => {
render(
<CreateGroupModal
isModalOpen
setIsModalOpen={setIsModalOpen}
reloadData={reloadData}
/>
);

expect(
screen.getByRole('button', {
name: /create/i,
})
).toBeDisabled();

await userEvent.type(
screen.getByRole('textbox', {
name: /group name/i,
}),
'###'
);

expect(
screen.getByRole('button', {
name: /create/i,
})
).toBeDisabled();

await userEvent.click(
screen.getByRole('button', {
name: /create/i,
})
); // must change focus for the hint to appear (DDF implementation)

await waitFor(() => {
expect(
screen.getByText(
'Valid characters include letters, numbers, spaces, hyphens ( - ), and underscores ( _ ).'
)
).toBeVisible();
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const createGroupSchema = (namePresenceValidator) => ({
name: 'name',
label: 'Group name',
helperText:
'Can only contain letters, numbers, spaces, hyphens ( - ), and underscores( _ ).',
'Can only contain letters, numbers, spaces, hyphens ( - ), and underscores ( _ ).',
isRequired: true,
autoFocus: true,
validate: [
Expand Down
4 changes: 2 additions & 2 deletions src/components/InventoryGroups/helpers/validate.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import validatorTypes from '@data-driven-forms/react-form-renderer/validator-typ

export const nameValidator = {
type: validatorTypes.PATTERN,
pattern: /^[A-Za-z0-9]+[A-Za-z0-9_\-\s]*$/,
pattern: /^[A-Za-z0-9_\-\s]+[A-Za-z0-9_\-\s]*$/,
message:
'Must start with a letter or number. Valid characters include lowercase letters, numbers, hyphens ( - ), and underscores ( _ ).',
'Valid characters include letters, numbers, spaces, hyphens ( - ), and underscores ( _ ).',
};
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import { generateFilter } from '../../../Utilities/constants';
import { InventoryTable as InventoryTableCmp } from '../../InventoryTable';
import useChrome from '@redhat-cloud-services/frontend-components/useChrome';
import AddSelectedHostsToGroupModal from '../../InventoryGroups/Modals/AddSelectedHostsToGroupModal';
import useFeatureFlag from '../../../Utilities/useFeatureFlag';
import { useBulkSelectConfig } from '../../../Utilities/hooks/useBulkSelectConfig';
import RemoveHostsFromGroupModal from '../../InventoryGroups/Modals/RemoveHostsFromGroupModal';
import {
Expand All @@ -27,6 +26,7 @@ import uniq from 'lodash/uniq';
import useInsightsNavigate from '@redhat-cloud-services/frontend-components-utilities/useInsightsNavigate/useInsightsNavigate';
import useTableActions from './useTableActions';
import { calculateFilters, calculatePagination } from './Utilities';
import useGlobalFilter from '../../filters/useGlobalFilter';

const BulkDeleteButton = ({ selectedSystems, ...props }) => {
const requiredPermissions = selectedSystems.map(({ groups }) =>
Expand Down Expand Up @@ -87,7 +87,7 @@ const ConventionalSystemsTab = ({
const [addHostGroupModalOpen, setAddHostGroupModalOpen] = useState(false);
const [removeHostsFromGroupModalOpen, setRemoveHostsFromGroupModalOpen] =
useState(false);
const [globalFilter, setGlobalFilter] = useState();
const globalFilter = useGlobalFilter();
const rows = useSelector(({ entities }) => entities?.rows, shallowEqual);
const loaded = useSelector(({ entities }) => entities?.loaded);
const selected = useSelector(({ entities }) => entities?.selected);
Expand Down Expand Up @@ -118,37 +118,10 @@ const ConventionalSystemsTab = ({
}
};

const EdgeParityFilterDeviceEnabled = useFeatureFlag(
'edgeParity.inventory-list-filter'
);

useEffect(() => {
chrome.updateDocumentTitle('Systems | Red Hat Insights');
chrome?.hideGlobalFilter?.(false);
chrome.appAction('system-list');
chrome.appObjectId();
chrome.on('GLOBAL_FILTER_UPDATE', ({ data }) => {
const [workloads, SID, tags] = chrome.mapGlobalFilter(data, false, true);
setGlobalFilter({
tags,
filter: {
...globalFilter?.filter,
system_profile: {
...globalFilter?.filter?.system_profile,
...(workloads?.SAP?.isSelected && { sap_system: true }),
...(workloads &&
workloads['Ansible Automation Platform']?.isSelected && {
ansible: 'not_nil',
}),
...(workloads?.['Microsoft SQL']?.isSelected && {
mssql: 'not_nil',
}),
...(EdgeParityFilterDeviceEnabled && { host_type: 'nil' }),
...(SID?.length > 0 && { sap_sids: SID }),
},
},
});
});
dispatch(actions.clearNotifications());

if (perPage || page) {
Expand Down
44 changes: 44 additions & 0 deletions src/components/filters/useGlobalFilter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { useEffect, useState } from 'react';
import useFeatureFlag from '../../Utilities/useFeatureFlag';
import useChrome from '@redhat-cloud-services/frontend-components/useChrome';

const useGlobalFilter = () => {
const chrome = useChrome();
const edgeParityFilterDeviceEnabled = useFeatureFlag(
'edgeParity.inventory-list-filter'
);
const [globalFilter, setGlobalFilter] = useState();

useEffect(() => {
chrome.hideGlobalFilter(false);
const unlisten = chrome.on('GLOBAL_FILTER_UPDATE', ({ data }) => {
const [workloads, SID, tags] = chrome.mapGlobalFilter(data, false, true);

setGlobalFilter({
tags,
filter: {
...globalFilter?.filter,
system_profile: {
...globalFilter?.filter?.system_profile,
...(workloads?.SAP?.isSelected && { sap_system: true }),
...(workloads &&
workloads['Ansible Automation Platform']?.isSelected && {
ansible: 'not_nil',
}),
...(workloads?.['Microsoft SQL']?.isSelected && {
mssql: 'not_nil',
}),
...(edgeParityFilterDeviceEnabled && { host_type: 'nil' }),
...(SID?.length > 0 && { sap_sids: SID }),
},
},
});
});

return () => unlisten();
}, [edgeParityFilterDeviceEnabled]);

return globalFilter;
};

export default useGlobalFilter;

0 comments on commit 7c737b9

Please sign in to comment.