123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398 |
- import React from 'react';
- import { useHistory } from 'react-router';
- import {
- TopicWithDetailedInfo,
- ClusterName,
- TopicName,
- } from 'redux/interfaces';
- import { useParams } from 'react-router-dom';
- import { clusterTopicCopyPath, clusterTopicNewPath } from 'lib/paths';
- import usePagination from 'lib/hooks/usePagination';
- import ClusterContext from 'components/contexts/ClusterContext';
- import PageLoader from 'components/common/PageLoader/PageLoader';
- import ConfirmationModal from 'components/common/ConfirmationModal/ConfirmationModal';
- import {
- CleanUpPolicy,
- GetTopicsRequest,
- SortOrder,
- TopicColumnsToSort,
- } from 'generated-sources';
- import Search from 'components/common/Search/Search';
- import { PER_PAGE } from 'lib/constants';
- import { Button } from 'components/common/Button/Button';
- import PageHeading from 'components/common/PageHeading/PageHeading';
- import { ControlPanelWrapper } from 'components/common/ControlPanel/ControlPanel.styled';
- import Switch from 'components/common/Switch/Switch';
- import { SmartTable } from 'components/common/SmartTable/SmartTable';
- import {
- TableCellProps,
- TableColumn,
- } from 'components/common/SmartTable/TableColumn';
- import { useTableState } from 'lib/hooks/useTableState';
- import Dropdown from 'components/common/Dropdown/Dropdown';
- import VerticalElipsisIcon from 'components/common/Icons/VerticalElipsisIcon';
- import DropdownItem from 'components/common/Dropdown/DropdownItem';
- import {
- MessagesCell,
- OutOfSyncReplicasCell,
- TitleCell,
- TopicSizeCell,
- } from './TopicsTableCells';
- import { ActionsTd } from './List.styled';
- export interface TopicsListProps {
- areTopicsFetching: boolean;
- topics: TopicWithDetailedInfo[];
- totalPages: number;
- fetchTopicsList(props: GetTopicsRequest): void;
- deleteTopic(topicName: TopicName, clusterName: ClusterName): void;
- deleteTopics(topicName: TopicName, clusterNames: ClusterName[]): void;
- recreateTopic(topicName: TopicName, clusterName: ClusterName): void;
- clearTopicsMessages(topicName: TopicName, clusterNames: ClusterName[]): void;
- clearTopicMessages(
- topicName: TopicName,
- clusterName: ClusterName,
- partitions?: number[]
- ): void;
- search: string;
- orderBy: TopicColumnsToSort | null;
- sortOrder: SortOrder;
- setTopicsSearch(search: string): void;
- setTopicsOrderBy(orderBy: TopicColumnsToSort | null): void;
- }
- const List: React.FC<TopicsListProps> = ({
- areTopicsFetching,
- topics,
- totalPages,
- fetchTopicsList,
- deleteTopic,
- deleteTopics,
- recreateTopic,
- clearTopicMessages,
- clearTopicsMessages,
- search,
- orderBy,
- sortOrder,
- setTopicsSearch,
- setTopicsOrderBy,
- }) => {
- const { isReadOnly, isTopicDeletionAllowed } =
- React.useContext(ClusterContext);
- const { clusterName } = useParams<{ clusterName: ClusterName }>();
- const { page, perPage, pathname } = usePagination();
- const [showInternal, setShowInternal] = React.useState<boolean>(true);
- const [cachedPage, setCachedPage] = React.useState<number | null>(null);
- const history = useHistory();
- React.useEffect(() => {
- fetchTopicsList({
- clusterName,
- page,
- perPage,
- orderBy: orderBy || undefined,
- sortOrder,
- search,
- showInternal,
- });
- }, [
- fetchTopicsList,
- clusterName,
- page,
- perPage,
- orderBy,
- sortOrder,
- search,
- showInternal,
- ]);
- const tableState = useTableState<
- TopicWithDetailedInfo,
- string,
- TopicColumnsToSort
- >(
- topics,
- {
- idSelector: (topic) => topic.name,
- totalPages,
- isRowSelectable: (topic) => !topic.internal,
- },
- {
- handleOrderBy: setTopicsOrderBy,
- orderBy,
- sortOrder,
- }
- );
- const getSelectedTopic = (): string => {
- const name = Array.from(tableState.selectedIds)[0];
- const selectedTopic =
- tableState.data.find(
- (topic: TopicWithDetailedInfo) => topic.name === name
- ) || {};
- return Object.keys(selectedTopic)
- .map((x: string) => {
- const value = selectedTopic[x as keyof typeof selectedTopic];
- return value && x !== 'partitions' ? `${x}=${value}` : null;
- })
- .join('&');
- };
- const handleSwitch = React.useCallback(() => {
- setShowInternal(!showInternal);
- history.push(`${pathname}?page=1&perPage=${perPage || PER_PAGE}`);
- }, [history, pathname, perPage, showInternal]);
- const [confirmationModal, setConfirmationModal] = React.useState<
- '' | 'deleteTopics' | 'purgeMessages'
- >('');
- const [confirmationModalText, setConfirmationModalText] =
- React.useState<string>('');
- const closeConfirmationModal = () => {
- setConfirmationModal('');
- };
- const clearSelectedTopics = React.useCallback(() => {
- tableState.toggleSelection(false);
- }, [tableState]);
- const searchHandler = React.useCallback(
- (searchString: string) => {
- setTopicsSearch(searchString);
- setCachedPage(page || null);
- const newPageQuery = !searchString && cachedPage ? cachedPage : 1;
- history.push(
- `${pathname}?page=${newPageQuery}&perPage=${perPage || PER_PAGE}`
- );
- },
- [setTopicsSearch, history, pathname, perPage, page]
- );
- const deleteOrPurgeConfirmationHandler = React.useCallback(() => {
- const selectedIds = Array.from(tableState.selectedIds);
- if (confirmationModal === 'deleteTopics') {
- deleteTopics(clusterName, selectedIds);
- } else {
- clearTopicsMessages(clusterName, selectedIds);
- }
- closeConfirmationModal();
- clearSelectedTopics();
- }, [
- confirmationModal,
- clearSelectedTopics,
- clusterName,
- deleteTopics,
- clearTopicsMessages,
- tableState.selectedIds,
- ]);
- const ActionsCell = React.memo<TableCellProps<TopicWithDetailedInfo, string>>(
- ({ hovered, dataItem: { internal, cleanUpPolicy, name } }) => {
- const [
- isDeleteTopicConfirmationVisible,
- setDeleteTopicConfirmationVisible,
- ] = React.useState(false);
- const [
- isRecreateTopicConfirmationVisible,
- setRecreateTopicConfirmationVisible,
- ] = React.useState(false);
- const isHidden = internal || isReadOnly || !hovered;
- const deleteTopicHandler = React.useCallback(() => {
- deleteTopic(clusterName, name);
- }, [name]);
- const clearTopicMessagesHandler = React.useCallback(() => {
- clearTopicMessages(clusterName, name);
- }, [name]);
- const recreateTopicHandler = React.useCallback(() => {
- recreateTopic(clusterName, name);
- setRecreateTopicConfirmationVisible(false);
- }, [name]);
- return (
- <>
- <div className="has-text-right">
- {!isHidden && (
- <Dropdown label={<VerticalElipsisIcon />} right>
- {cleanUpPolicy === CleanUpPolicy.DELETE && (
- <DropdownItem onClick={clearTopicMessagesHandler} danger>
- Clear Messages
- </DropdownItem>
- )}
- {isTopicDeletionAllowed && (
- <DropdownItem
- onClick={() => setDeleteTopicConfirmationVisible(true)}
- danger
- >
- Remove Topic
- </DropdownItem>
- )}
- <DropdownItem
- onClick={() => setRecreateTopicConfirmationVisible(true)}
- danger
- >
- Recreate Topic
- </DropdownItem>
- </Dropdown>
- )}
- </div>
- <ConfirmationModal
- isOpen={isDeleteTopicConfirmationVisible}
- onCancel={() => setDeleteTopicConfirmationVisible(false)}
- onConfirm={deleteTopicHandler}
- >
- Are you sure want to remove <b>{name}</b> topic?
- </ConfirmationModal>
- <ConfirmationModal
- isOpen={isRecreateTopicConfirmationVisible}
- onCancel={() => setRecreateTopicConfirmationVisible(false)}
- onConfirm={recreateTopicHandler}
- >
- Are you sure to recreate <b>{name}</b> topic?
- </ConfirmationModal>
- </>
- );
- }
- );
- return (
- <div>
- <div>
- <PageHeading text="All Topics">
- {!isReadOnly && (
- <Button
- buttonType="primary"
- buttonSize="M"
- isLink
- to={clusterTopicNewPath(clusterName)}
- >
- <i className="fas fa-plus" /> Add a Topic
- </Button>
- )}
- </PageHeading>
- <ControlPanelWrapper hasInput>
- <div>
- <Search
- handleSearch={searchHandler}
- placeholder="Search by Topic Name"
- value={search}
- />
- </div>
- <div>
- <Switch
- name="ShowInternalTopics"
- checked={showInternal}
- onChange={handleSwitch}
- />
- <label>Show Internal Topics</label>
- </div>
- </ControlPanelWrapper>
- </div>
- {areTopicsFetching ? (
- <PageLoader />
- ) : (
- <div>
- {tableState.selectedCount > 0 && (
- <>
- <ControlPanelWrapper data-testid="delete-buttons">
- <Button
- buttonSize="M"
- buttonType="secondary"
- onClick={() => {
- setConfirmationModal('deleteTopics');
- setConfirmationModalText(
- 'Are you sure you want to remove selected topics?'
- );
- }}
- >
- Delete selected topics
- </Button>
- {tableState.selectedCount === 1 && (
- <Button
- buttonSize="M"
- buttonType="secondary"
- isLink
- to={{
- pathname: clusterTopicCopyPath(clusterName),
- search: `?${getSelectedTopic()}`,
- }}
- >
- Copy selected topic
- </Button>
- )}
- <Button
- buttonSize="M"
- buttonType="secondary"
- onClick={() => {
- setConfirmationModal('purgeMessages');
- setConfirmationModalText(
- 'Are you sure you want to purge messages of selected topics?'
- );
- }}
- >
- Purge messages of selected topics
- </Button>
- </ControlPanelWrapper>
- <ConfirmationModal
- isOpen={confirmationModal !== ''}
- onCancel={closeConfirmationModal}
- onConfirm={deleteOrPurgeConfirmationHandler}
- >
- {confirmationModalText}
- </ConfirmationModal>
- </>
- )}
- <SmartTable
- selectable={!isReadOnly}
- tableState={tableState}
- placeholder="No topics found"
- isFullwidth
- paginated
- hoverable
- >
- <TableColumn
- maxWidth="350px"
- title="Topic Name"
- cell={TitleCell}
- orderValue={TopicColumnsToSort.NAME}
- />
- <TableColumn
- title="Total Partitions"
- field="partitions.length"
- orderValue={TopicColumnsToSort.TOTAL_PARTITIONS}
- />
- <TableColumn
- title="Out of sync replicas"
- cell={OutOfSyncReplicasCell}
- orderValue={TopicColumnsToSort.OUT_OF_SYNC_REPLICAS}
- />
- <TableColumn title="Replication Factor" field="replicationFactor" />
- <TableColumn title="Number of messages" cell={MessagesCell} />
- <TableColumn
- title="Size"
- cell={TopicSizeCell}
- orderValue={TopicColumnsToSort.SIZE}
- />
- <TableColumn
- maxWidth="4%"
- cell={ActionsCell}
- customTd={ActionsTd}
- />
- </SmartTable>
- </div>
- )}
- </div>
- );
- };
- export default List;
|