Improve live tailing. (#1898)
* disable filter options when live tailing mode is enabled * prevent seek direction change when stop loading is pressed on live mode * disable submit button while tailing * write tests for MultiSelect.styled component to achieve 100% coverage
This commit is contained in:
parent
247fd23bc0
commit
521ba0cb2f
4 changed files with 88 additions and 8 deletions
|
@ -129,6 +129,8 @@ const Filters: React.FC<FiltersProps> = ({
|
||||||
: MessageFilterType.STRING_CONTAINS
|
: MessageFilterType.STRING_CONTAINS
|
||||||
);
|
);
|
||||||
const [query, setQuery] = React.useState<string>(searchParams.get('q') || '');
|
const [query, setQuery] = React.useState<string>(searchParams.get('q') || '');
|
||||||
|
const [isTailing, setIsTailing] = React.useState<boolean>(isLive);
|
||||||
|
|
||||||
const isSeekTypeControlVisible = React.useMemo(
|
const isSeekTypeControlVisible = React.useMemo(
|
||||||
() => selectedPartitions.length > 0,
|
() => selectedPartitions.length > 0,
|
||||||
[selectedPartitions]
|
[selectedPartitions]
|
||||||
|
@ -136,11 +138,13 @@ const Filters: React.FC<FiltersProps> = ({
|
||||||
|
|
||||||
const isSubmitDisabled = React.useMemo(() => {
|
const isSubmitDisabled = React.useMemo(() => {
|
||||||
if (isSeekTypeControlVisible) {
|
if (isSeekTypeControlVisible) {
|
||||||
return currentSeekType === SeekType.TIMESTAMP && !timestamp;
|
return (
|
||||||
|
(currentSeekType === SeekType.TIMESTAMP && !timestamp) || isTailing
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}, [isSeekTypeControlVisible, currentSeekType, timestamp]);
|
}, [isSeekTypeControlVisible, currentSeekType, timestamp, isTailing]);
|
||||||
|
|
||||||
const partitionMap = React.useMemo(
|
const partitionMap = React.useMemo(
|
||||||
() =>
|
() =>
|
||||||
|
@ -345,6 +349,10 @@ const Filters: React.FC<FiltersProps> = ({
|
||||||
handleFiltersSubmit(offset);
|
handleFiltersSubmit(offset);
|
||||||
}, [handleFiltersSubmit, seekDirection]);
|
}, [handleFiltersSubmit, seekDirection]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
setIsTailing(isLive);
|
||||||
|
}, [isLive]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<S.FiltersWrapper>
|
<S.FiltersWrapper>
|
||||||
<div>
|
<div>
|
||||||
|
@ -352,6 +360,7 @@ const Filters: React.FC<FiltersProps> = ({
|
||||||
<Search
|
<Search
|
||||||
placeholder="Search"
|
placeholder="Search"
|
||||||
value={query}
|
value={query}
|
||||||
|
disabled={isTailing}
|
||||||
handleSearch={(value: string) => setQuery(value)}
|
handleSearch={(value: string) => setQuery(value)}
|
||||||
/>
|
/>
|
||||||
<S.SeekTypeSelectorWrapper>
|
<S.SeekTypeSelectorWrapper>
|
||||||
|
@ -362,7 +371,7 @@ const Filters: React.FC<FiltersProps> = ({
|
||||||
selectSize="M"
|
selectSize="M"
|
||||||
minWidth="100px"
|
minWidth="100px"
|
||||||
options={SeekTypeOptions}
|
options={SeekTypeOptions}
|
||||||
disabled={isLive}
|
disabled={isTailing}
|
||||||
/>
|
/>
|
||||||
{currentSeekType === SeekType.OFFSET ? (
|
{currentSeekType === SeekType.OFFSET ? (
|
||||||
<Input
|
<Input
|
||||||
|
@ -373,7 +382,7 @@ const Filters: React.FC<FiltersProps> = ({
|
||||||
className="offset-selector"
|
className="offset-selector"
|
||||||
placeholder="Offset"
|
placeholder="Offset"
|
||||||
onChange={({ target: { value } }) => setOffset(value)}
|
onChange={({ target: { value } }) => setOffset(value)}
|
||||||
disabled={isLive}
|
disabled={isTailing}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<DatePicker
|
<DatePicker
|
||||||
|
@ -384,7 +393,7 @@ const Filters: React.FC<FiltersProps> = ({
|
||||||
dateFormat="MMMM d, yyyy HH:mm"
|
dateFormat="MMMM d, yyyy HH:mm"
|
||||||
className="date-picker"
|
className="date-picker"
|
||||||
placeholderText="Select timestamp"
|
placeholderText="Select timestamp"
|
||||||
disabled={isLive}
|
disabled={isTailing}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</S.SeekTypeSelectorWrapper>
|
</S.SeekTypeSelectorWrapper>
|
||||||
|
@ -397,6 +406,7 @@ const Filters: React.FC<FiltersProps> = ({
|
||||||
value={selectedPartitions}
|
value={selectedPartitions}
|
||||||
onChange={setSelectedPartitions}
|
onChange={setSelectedPartitions}
|
||||||
labelledBy="Select partitions"
|
labelledBy="Select partitions"
|
||||||
|
disabled={isTailing}
|
||||||
/>
|
/>
|
||||||
<S.ClearAll onClick={handleClearAllFilters}>Clear all</S.ClearAll>
|
<S.ClearAll onClick={handleClearAllFilters}>Clear all</S.ClearAll>
|
||||||
{isFetching ? (
|
{isFetching ? (
|
||||||
|
@ -465,13 +475,13 @@ const Filters: React.FC<FiltersProps> = ({
|
||||||
isFetching &&
|
isFetching &&
|
||||||
phaseMessage}
|
phaseMessage}
|
||||||
</p>
|
</p>
|
||||||
<S.MessageLoading isLive={isLive}>
|
<S.MessageLoading isLive={isTailing}>
|
||||||
<S.MessageLoadingSpinner isFetching={isFetching} />
|
<S.MessageLoadingSpinner isFetching={isFetching} />
|
||||||
Loading messages.
|
Loading messages.
|
||||||
<S.StopLoading
|
<S.StopLoading
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
changeSeekDirection(SeekDirection.FORWARD);
|
handleSSECancel();
|
||||||
setIsFetching(false);
|
setIsTailing(false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Stop loading
|
Stop loading
|
||||||
|
|
|
@ -9,8 +9,14 @@ const MultiSelect = styled(ReactMultiSelect)<{ minWidth?: string }>`
|
||||||
& > .dropdown-container {
|
& > .dropdown-container {
|
||||||
height: 32px;
|
height: 32px;
|
||||||
|
|
||||||
|
* {
|
||||||
|
cursor: ${({ disabled }) => (disabled ? 'not-allowed' : 'pointer')};
|
||||||
|
}
|
||||||
|
|
||||||
& > .dropdown-heading {
|
& > .dropdown-heading {
|
||||||
height: 32px;
|
height: 32px;
|
||||||
|
color: ${({ disabled, theme }) =>
|
||||||
|
disabled ? theme.select.color.disabled : theme.select.color.active};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { render } from 'lib/testHelpers';
|
||||||
|
import MultiSelect from 'components/common/MultiSelect/MultiSelect.styled';
|
||||||
|
import { ISelectProps } from 'react-multi-select-component/dist/lib/interfaces';
|
||||||
|
|
||||||
|
const Option1 = { value: 1, label: 'option 1' };
|
||||||
|
const Option2 = { value: 2, label: 'option 2' };
|
||||||
|
|
||||||
|
interface IMultiSelectProps extends ISelectProps {
|
||||||
|
minWidth?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DefaultProps: IMultiSelectProps = {
|
||||||
|
options: [Option1, Option2],
|
||||||
|
labelledBy: 'multi-select',
|
||||||
|
value: [Option1, Option2],
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('MultiSelect.Styled', () => {
|
||||||
|
const setUpComponent = (props: IMultiSelectProps = DefaultProps) => {
|
||||||
|
const { container } = render(<MultiSelect {...props} />);
|
||||||
|
const multiSelect = container.firstChild;
|
||||||
|
const dropdownContainer = multiSelect?.firstChild?.firstChild;
|
||||||
|
|
||||||
|
return { container, multiSelect, dropdownContainer };
|
||||||
|
};
|
||||||
|
|
||||||
|
it('should have 200px minWidth by default', () => {
|
||||||
|
const { container } = setUpComponent();
|
||||||
|
const multiSelect = container.firstChild;
|
||||||
|
|
||||||
|
expect(multiSelect).toHaveStyle('min-width: 200px');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have the provided minWidth in styles', () => {
|
||||||
|
const minWidth = '400px';
|
||||||
|
const { container } = setUpComponent({ ...DefaultProps, minWidth });
|
||||||
|
const multiSelect = container.firstChild;
|
||||||
|
|
||||||
|
expect(multiSelect).toHaveStyle(`min-width: ${minWidth}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when not disabled', () => {
|
||||||
|
it('should have cursor pointer', () => {
|
||||||
|
const { dropdownContainer } = setUpComponent();
|
||||||
|
|
||||||
|
expect(dropdownContainer).toHaveStyle(`cursor: pointer`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when disabled', () => {
|
||||||
|
it('should have cursor not-allowed', () => {
|
||||||
|
const { dropdownContainer } = setUpComponent({
|
||||||
|
...DefaultProps,
|
||||||
|
disabled: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(dropdownContainer).toHaveStyle(`cursor: not-allowed`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -6,12 +6,14 @@ interface SearchProps {
|
||||||
handleSearch: (value: string) => void;
|
handleSearch: (value: string) => void;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
value: string;
|
value: string;
|
||||||
|
disabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Search: React.FC<SearchProps> = ({
|
const Search: React.FC<SearchProps> = ({
|
||||||
handleSearch,
|
handleSearch,
|
||||||
placeholder = 'Search',
|
placeholder = 'Search',
|
||||||
value,
|
value,
|
||||||
|
disabled = false,
|
||||||
}) => {
|
}) => {
|
||||||
const onChange = useDebouncedCallback(
|
const onChange = useDebouncedCallback(
|
||||||
(e) => handleSearch(e.target.value),
|
(e) => handleSearch(e.target.value),
|
||||||
|
@ -26,6 +28,7 @@ const Search: React.FC<SearchProps> = ({
|
||||||
defaultValue={value}
|
defaultValue={value}
|
||||||
leftIcon="fas fa-search"
|
leftIcon="fas fa-search"
|
||||||
inputSize="M"
|
inputSize="M"
|
||||||
|
disabled={disabled}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
Loading…
Add table
Reference in a new issue