|
@@ -0,0 +1,197 @@
|
|
|
|
+import React from 'react';
|
|
|
|
+import { useNavigate } from 'react-router-dom';
|
|
|
|
+import {
|
|
|
|
+ ConsumerGroupDetails,
|
|
|
|
+ ConsumerGroupOffsetsReset,
|
|
|
|
+ ConsumerGroupOffsetsResetType,
|
|
|
|
+} from 'generated-sources';
|
|
|
|
+import { ClusterGroupParam } from 'lib/paths';
|
|
|
|
+import {
|
|
|
|
+ Controller,
|
|
|
|
+ FormProvider,
|
|
|
|
+ useFieldArray,
|
|
|
|
+ useForm,
|
|
|
|
+} from 'react-hook-form';
|
|
|
|
+import { MultiSelect, Option } from 'react-multi-select-component';
|
|
|
|
+import 'react-datepicker/dist/react-datepicker.css';
|
|
|
|
+import { ErrorMessage } from '@hookform/error-message';
|
|
|
|
+import { InputLabel } from 'components/common/Input/InputLabel.styled';
|
|
|
|
+import { Button } from 'components/common/Button/Button';
|
|
|
|
+import Input from 'components/common/Input/Input';
|
|
|
|
+import { FormError } from 'components/common/Input/Input.styled';
|
|
|
|
+import useAppParams from 'lib/hooks/useAppParams';
|
|
|
|
+import { useResetConsumerGroupOffsetsMutation } from 'lib/hooks/api/consumers';
|
|
|
|
+import { FlexFieldset, StyledForm } from 'components/common/Form/Form.styled';
|
|
|
|
+import ControlledSelect from 'components/common/Select/ControlledSelect';
|
|
|
|
+
|
|
|
|
+import * as S from './ResetOffsets.styled';
|
|
|
|
+
|
|
|
|
+interface FormProps {
|
|
|
|
+ defaultValues: ConsumerGroupOffsetsReset;
|
|
|
|
+ topics: string[];
|
|
|
|
+ partitions: ConsumerGroupDetails['partitions'];
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+const resetTypeOptions = Object.values(ConsumerGroupOffsetsResetType).map(
|
|
|
|
+ (value) => ({ value, label: value })
|
|
|
|
+);
|
|
|
|
+
|
|
|
|
+const Form: React.FC<FormProps> = ({ defaultValues, partitions, topics }) => {
|
|
|
|
+ const navigate = useNavigate();
|
|
|
|
+ const routerParams = useAppParams<ClusterGroupParam>();
|
|
|
|
+ const reset = useResetConsumerGroupOffsetsMutation(routerParams);
|
|
|
|
+ const topicOptions = React.useMemo(
|
|
|
|
+ () => topics.map((value) => ({ value, label: value })),
|
|
|
|
+ [topics]
|
|
|
|
+ );
|
|
|
|
+ const methods = useForm<ConsumerGroupOffsetsReset>({
|
|
|
|
+ mode: 'onChange',
|
|
|
|
+ defaultValues,
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ const {
|
|
|
|
+ handleSubmit,
|
|
|
|
+ setValue,
|
|
|
|
+ watch,
|
|
|
|
+ control,
|
|
|
|
+ formState: { errors },
|
|
|
|
+ } = methods;
|
|
|
|
+ const { fields } = useFieldArray({
|
|
|
|
+ control,
|
|
|
|
+ name: 'partitionsOffsets',
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ const resetTypeValue = watch('resetType');
|
|
|
|
+ const topicValue = watch('topic');
|
|
|
|
+ const offsetsValue = watch('partitionsOffsets');
|
|
|
|
+ const partitionsValue = watch('partitions') || [];
|
|
|
|
+
|
|
|
|
+ const partitionOptions =
|
|
|
|
+ partitions
|
|
|
|
+ ?.filter((p) => p.topic === topicValue)
|
|
|
|
+ .map((p) => ({
|
|
|
|
+ label: `Partition #${p.partition.toString()}`,
|
|
|
|
+ value: p.partition,
|
|
|
|
+ })) || [];
|
|
|
|
+
|
|
|
|
+ const onSelectedPartitionsChange = (selected: Option[]) => {
|
|
|
|
+ setValue(
|
|
|
|
+ 'partitions',
|
|
|
|
+ selected.map(({ value }) => value)
|
|
|
|
+ );
|
|
|
|
+
|
|
|
|
+ setValue(
|
|
|
|
+ 'partitionsOffsets',
|
|
|
|
+ selected.map(({ value }) => {
|
|
|
|
+ const currentOffset = offsetsValue?.find(
|
|
|
|
+ ({ partition }) => partition === value
|
|
|
|
+ );
|
|
|
|
+ return { offset: currentOffset?.offset, partition: value };
|
|
|
|
+ })
|
|
|
|
+ );
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ React.useEffect(() => {
|
|
|
|
+ onSelectedPartitionsChange([]);
|
|
|
|
+ // eslint-disable-next-line react-hooks/exhaustive-deps
|
|
|
|
+ }, [topicValue]);
|
|
|
|
+
|
|
|
|
+ const onSubmit = async (data: ConsumerGroupOffsetsReset) => {
|
|
|
|
+ await reset.mutateAsync(data);
|
|
|
|
+ navigate('../');
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ return (
|
|
|
|
+ <FormProvider {...methods}>
|
|
|
|
+ <StyledForm onSubmit={handleSubmit(onSubmit)}>
|
|
|
|
+ <FlexFieldset>
|
|
|
|
+ <ControlledSelect
|
|
|
|
+ name="topic"
|
|
|
|
+ label="Topic"
|
|
|
|
+ placeholder="Select Topic"
|
|
|
|
+ options={topicOptions}
|
|
|
|
+ />
|
|
|
|
+ <ControlledSelect
|
|
|
|
+ name="resetType"
|
|
|
|
+ label="Reset Type"
|
|
|
|
+ placeholder="Select Reset Type"
|
|
|
|
+ options={resetTypeOptions}
|
|
|
|
+ />
|
|
|
|
+ <div>
|
|
|
|
+ <InputLabel>Partitions</InputLabel>
|
|
|
|
+ <MultiSelect
|
|
|
|
+ options={partitionOptions}
|
|
|
|
+ value={partitionsValue.map((p) => ({
|
|
|
|
+ value: p,
|
|
|
|
+ label: String(p),
|
|
|
|
+ }))}
|
|
|
|
+ onChange={onSelectedPartitionsChange}
|
|
|
|
+ labelledBy="Select partitions"
|
|
|
|
+ />
|
|
|
|
+ </div>
|
|
|
|
+ {resetTypeValue === ConsumerGroupOffsetsResetType.TIMESTAMP &&
|
|
|
|
+ partitionsValue.length > 0 && (
|
|
|
|
+ <div>
|
|
|
|
+ <InputLabel>Timestamp</InputLabel>
|
|
|
|
+ <Controller
|
|
|
|
+ control={control}
|
|
|
|
+ name="resetToTimestamp"
|
|
|
|
+ rules={{
|
|
|
|
+ required: 'Timestamp is required',
|
|
|
|
+ }}
|
|
|
|
+ render={({ field: { onChange, onBlur, value, ref } }) => (
|
|
|
|
+ <S.DatePickerInput
|
|
|
|
+ ref={ref}
|
|
|
|
+ selected={new Date(value as number)}
|
|
|
|
+ onChange={(e: Date | null) => onChange(e?.getTime())}
|
|
|
|
+ onBlur={onBlur}
|
|
|
|
+ />
|
|
|
|
+ )}
|
|
|
|
+ />
|
|
|
|
+ <ErrorMessage
|
|
|
|
+ errors={errors}
|
|
|
|
+ name="resetToTimestamp"
|
|
|
|
+ render={({ message }) => <FormError>{message}</FormError>}
|
|
|
|
+ />
|
|
|
|
+ </div>
|
|
|
|
+ )}
|
|
|
|
+
|
|
|
|
+ {resetTypeValue === ConsumerGroupOffsetsResetType.OFFSET &&
|
|
|
|
+ partitionsValue.length > 0 && (
|
|
|
|
+ <S.OffsetsWrapper>
|
|
|
|
+ {fields.map((field, index) => (
|
|
|
|
+ <Input
|
|
|
|
+ key={field.id}
|
|
|
|
+ label={`Partition #${field.partition} Offset`}
|
|
|
|
+ type="number"
|
|
|
|
+ name={`partitionsOffsets.${index}.offset` as const}
|
|
|
|
+ hookFormOptions={{
|
|
|
|
+ shouldUnregister: true,
|
|
|
|
+ required: 'Offset is required',
|
|
|
|
+ min: {
|
|
|
|
+ value: 0,
|
|
|
|
+ message: 'must be greater than or equal to 0',
|
|
|
|
+ },
|
|
|
|
+ }}
|
|
|
|
+ withError
|
|
|
|
+ />
|
|
|
|
+ ))}
|
|
|
|
+ </S.OffsetsWrapper>
|
|
|
|
+ )}
|
|
|
|
+ </FlexFieldset>
|
|
|
|
+ <div>
|
|
|
|
+ <Button
|
|
|
|
+ buttonSize="M"
|
|
|
|
+ buttonType="primary"
|
|
|
|
+ type="submit"
|
|
|
|
+ disabled={partitionsValue.length === 0}
|
|
|
|
+ >
|
|
|
|
+ Submit
|
|
|
|
+ </Button>
|
|
|
|
+ </div>
|
|
|
|
+ </StyledForm>
|
|
|
|
+ </FormProvider>
|
|
|
|
+ );
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+export default Form;
|