Skip to content

Commit

Permalink
Merge pull request #384 from bartoval/global_refresh_button
Browse files Browse the repository at this point in the history
Global refresh button
  • Loading branch information
bartoval authored Feb 19, 2024
2 parents 55ba736 + 01f793d commit eec23a2
Show file tree
Hide file tree
Showing 9 changed files with 104 additions and 375 deletions.
16 changes: 14 additions & 2 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { Suspense } from 'react';

import { Page } from '@patternfly/react-core';
import { Page, Toolbar, ToolbarContent, ToolbarGroup, ToolbarItem } from '@patternfly/react-core';
import { AnimatePresence } from 'framer-motion';

import SkBreadcrumb from '@core/components/SkBreadcrumb';
import SkUpdateDataButton from '@core/components/SkUpdateDataButton';
import { getThemePreference, reflectThemePreference } from '@core/utils/isDarkTheme';
import SkHeader from '@layout/Header';
import RouteContainer from '@layout/RouteContainer';
Expand All @@ -21,7 +22,18 @@ const App = function () {
<Page
header={<SkHeader />}
sidebar={<SkSidebar />}
breadcrumb={<SkBreadcrumb />}
breadcrumb={
<Toolbar style={{ padding: 0 }}>
<ToolbarContent style={{ padding: 0 }}>
<ToolbarItem>
<SkBreadcrumb />
</ToolbarItem>
<ToolbarGroup align={{ default: 'alignRight' }}>
<SkUpdateDataButton />
</ToolbarGroup>
</ToolbarContent>
</Toolbar>
}
isManagedSidebar
isBreadcrumbGrouped
additionalGroupedContent={
Expand Down
2 changes: 1 addition & 1 deletion src/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export const SMALL_PAGINATION_SIZE = 10;
export const brandLogo = process.env.BRAND_APP_LOGO ? require(process.env.BRAND_APP_LOGO) : Logo;

/** General config: contains various global settings and constants */
export const UPDATE_INTERVAL = 10 * 1000; // Time in milliseconds to request updated data from the backend
export const UPDATE_INTERVAL = 0 * 1000; // Time in milliseconds to request updated data from the backend
export const MSG_TIMEOUT_ERROR = 'The request to fetch the data has timed out.'; // Error message to display when request times out
export const TOAST_VISIBILITY_TIMEOUT = 2000; // Time in milliseconds to display toast messages
/** Tests */
Expand Down
19 changes: 0 additions & 19 deletions src/core/components/SkUpdateDataButton/SkUpdateDataButton.css

This file was deleted.

46 changes: 0 additions & 46 deletions src/core/components/SkUpdateDataButton/SkUpdateDataButton.spec.tsx

This file was deleted.

161 changes: 77 additions & 84 deletions src/core/components/SkUpdateDataButton/index.tsx
Original file line number Diff line number Diff line change
@@ -1,128 +1,135 @@
import { FC, useMemo, MouseEvent as ReactMouseEvent, useState, useCallback, Ref } from 'react';
import { FC, useMemo, MouseEvent as ReactMouseEvent, useState, useCallback, Ref, useRef } from 'react';

import {
Dropdown,
DropdownItem,
DropdownList,
Button,
MenuToggle,
MenuToggleAction,
MenuToggleElement,
Spinner
Select,
SelectList,
SelectOption,
debounce
} from '@patternfly/react-core';
import { SyncIcon } from '@patternfly/react-icons';

import './SkUpdateDataButton.css';
import { useIsFetching, useQueryClient } from '@tanstack/react-query';

interface SkUpdateDataButtonProps {
isLoading?: boolean;
isDisabled?: boolean;
onClick: Function;
onRefreshIntervalSelected: Function;
onClick?: Function;
onRefreshIntervalSelected?: Function;
refreshIntervalDefault?: number;
}

const REFETCH_DATA_LABEL = 'Refresh';
export const refreshDataIntervalMap = [
{
key: 'pause',
value: 0,
label: 'Off'
key: 'Refresh off',
value: 0
},
{
key: '20s',
value: 20 * 1000,
label: '20s'
key: '15s',
value: 15 * 1000
},
{
key: '40s',
value: 40 * 1000,
label: '40s'
key: '30s',
value: 30 * 1000
},
{
key: '60s',
value: 60 * 1000,
label: '1m'
value: 60 * 1000
},
{
key: '120s',
value: 120 * 1000,
label: '2m'
value: 120 * 1000
}
];

const SkUpdateDataButton: FC<SkUpdateDataButtonProps> = function ({
isLoading = false,
isDisabled = false,
onClick,
onRefreshIntervalSelected,
refreshIntervalDefault
}) {
const queryClient = useQueryClient();
const fetchNumber = useIsFetching();

const [isSelectOpen, setSelectOpen] = useState(false);
const [refreshIntervalSelected, setSelectIntervalSelected] = useState<string | undefined>(
findRefreshDataIntervalLabelFromValue(refreshIntervalDefault)
);
const refreshIntervalId = useRef<number>();

const refreshIntervalOptions = useMemo(
() =>
refreshDataIntervalMap.map(({ label, key }, index) => (
<DropdownItem key={index} value={key}>
{label}
</DropdownItem>
refreshDataIntervalMap.map(({ key }, index) => (
<SelectOption key={index} value={key}>
{key}
</SelectOption>
)),
[]
);

const revalidateLiveQueries = useCallback(() => {
queryClient.invalidateQueries({
refetchType: 'active',
predicate: (query) => query.queryKey[0] !== 'QueriesGetUser' && query.queryKey[0] !== 'QueryLogout'
});

if (onClick) {
onClick();
}
}, [onClick, queryClient]);

const handleSelectRefreshInterval = useCallback(
(_event: ReactMouseEvent<Element, MouseEvent> | undefined, selection: string | number | undefined) => {
(_: ReactMouseEvent<Element, MouseEvent> | undefined, selection: string | number | undefined) => {
const refreshDataIntervalSelected = selection as string;

setSelectIntervalSelected(refreshDataIntervalSelected);
setSelectOpen(false);

const refreshInterval = findRefreshDataIntervalValueFromLabel(refreshDataIntervalSelected);
clearInterval(refreshIntervalId.current);

if (refreshInterval) {
refreshIntervalId.current = window.setInterval(() => {
revalidateLiveQueries();
}, refreshInterval);
}

if (onRefreshIntervalSelected) {
onRefreshIntervalSelected(findRefreshDataIntervalValueFromLabel(refreshDataIntervalSelected));
}
},
[onRefreshIntervalSelected]
[onRefreshIntervalSelected, revalidateLiveQueries]
);

const isRefreshIntervalSelected =
!refreshIntervalSelected || refreshIntervalSelected === refreshDataIntervalMap[0].key;

return (
<Dropdown
isOpen={isSelectOpen}
onSelect={handleSelectRefreshInterval}
toggle={(toggleRef: Ref<MenuToggleElement>) => (
<MenuToggle
isDisabled={isDisabled}
data-testid="update-data-dropdown"
className={isLoading ? 'button-toggle-dropdown-loading' : ''}
ref={toggleRef}
variant="primary"
onClick={() => setSelectOpen(!isSelectOpen)}
isExpanded={isSelectOpen}
splitButtonOptions={{
variant: 'action',
items: [
<MenuToggleAction
className={getDropdownClassName({ isLoading, isDisabled })}
key="split-action-primary"
data-testid="update-data-click"
onClick={() => onClick()}
>
{isLoading ? <Spinner isInline className="button-toggle-spinner" /> : <SyncIcon />}
<span className="button-toggle-spinner-text">{REFETCH_DATA_LABEL}</span>{' '}
{isRefreshIntervalSelected ? ' ' : refreshIntervalSelected}
</MenuToggleAction>
]
}}
/>
)}
shouldFocusToggleOnSelect
>
<DropdownList>{refreshIntervalOptions}</DropdownList>
</Dropdown>
<>
<Select
isOpen={isSelectOpen}
onSelect={handleSelectRefreshInterval}
toggle={(toggleRef: Ref<MenuToggleElement>) => (
<MenuToggle
data-testid="update-data-dropdown"
ref={toggleRef}
onClick={() => setSelectOpen(!isSelectOpen)}
isExpanded={isSelectOpen}
>
{refreshIntervalSelected || refreshDataIntervalMap[0].key}
</MenuToggle>
)}
shouldFocusToggleOnSelect
>
<SelectList>{refreshIntervalOptions}</SelectList>
</Select>

<Button
key="split-action-primary"
data-testid="update-data-click"
onClick={debounce(revalidateLiveQueries, 750)}
style={{ borderTopLeftRadius: 0, borderBottomLeftRadius: 0 }}
isLoading={fetchNumber > 0}
>
<SyncIcon />
</Button>
</>
);
};

Expand All @@ -136,20 +143,6 @@ function findRefreshDataIntervalLabelFromValue(valueSelected: number | undefined
return (
// value !== refreshDataIntervalMap[0].value. We don't want to show the label "off" when we select this value from the button
refreshDataIntervalMap.find(({ value }) => value === valueSelected && value !== refreshDataIntervalMap[0].value)
?.label || ''
?.key || ''
);
}

function getDropdownClassName({ isLoading, isDisabled }: { isLoading: boolean; isDisabled: boolean }) {
let dropdownClassName = '';

if (isLoading) {
dropdownClassName = 'button-toggle-loading';
}

if (isDisabled) {
dropdownClassName = 'button-toggle-off';
}

return dropdownClassName;
}
5 changes: 4 additions & 1 deletion src/layout/MainContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { FC, ReactElement, Suspense } from 'react';
import {
Divider,
Flex,
FlexItem,
PageGroup,
PageNavigation,
PageSection,
Expand Down Expand Up @@ -54,7 +55,9 @@ const MainContainer: FC<MainContainerProps> = function ({
<Title headingLevel="h1">{title}</Title>
{description && <Text component={TextVariants.p}>{description}</Text>}
</TextContent>
{link && <NavigationViewLink link={link} linkLabel={linkLabel} />}
<Flex>
<FlexItem>{link && <NavigationViewLink link={link} linkLabel={linkLabel} />}</FlexItem>
</Flex>
</Flex>
</PageSection>
)}
Expand Down
5 changes: 1 addition & 4 deletions src/pages/shared/Metrics/__tests__/Traffic.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import LoadingPage from '@pages/shared/Loading';
import Traffic from '../components/Traffic';
import { MetricsLabels } from '../Metrics.enum';

let component;
const processResult = processesData.results[0] as ProcessResponse;

describe('Traffic component', () => {
Expand All @@ -33,7 +32,7 @@ describe('Traffic component', () => {
it('should render the Traffic section of the metric', async () => {
const handleGetisSectionExpanded = jest.fn();

component = render(
render(
<Wrapper>
<Suspense fallback={<LoadingPage />}>
<Traffic
Expand All @@ -56,8 +55,6 @@ describe('Traffic component', () => {

fireEvent.click(document.querySelector('.pf-v5-c-card__header-toggle')?.querySelector('button')!);
expect(handleGetisSectionExpanded).toHaveBeenCalledTimes(1);

expect(component).toMatchSnapshot();
});

it('should render the Traffic section and display the no metric found message', async () => {
Expand Down
Loading

0 comments on commit eec23a2

Please sign in to comment.