Browse Source

Merge branch 'master' into vlad/develop

VladSenyuta 2 years ago
parent
commit
43b496207c

+ 4 - 1
kafka-ui-api/src/main/java/com/provectus/kafka/ui/model/rbac/Permission.java

@@ -1,5 +1,6 @@
 package com.provectus.kafka.ui.model.rbac;
 
+import static com.provectus.kafka.ui.model.rbac.Resource.APPLICATIONCONFIG;
 import static com.provectus.kafka.ui.model.rbac.Resource.CLUSTERCONFIG;
 import static com.provectus.kafka.ui.model.rbac.Resource.KSQL;
 
@@ -25,6 +26,8 @@ import org.springframework.util.Assert;
 @EqualsAndHashCode
 public class Permission {
 
+  private static final List<Resource> RBAC_ACTION_EXEMPT_LIST = List.of(KSQL, CLUSTERCONFIG, APPLICATIONCONFIG);
+
   Resource resource;
   List<String> actions;
 
@@ -50,7 +53,7 @@ public class Permission {
 
   public void validate() {
     Assert.notNull(resource, "resource cannot be null");
-    if (!List.of(KSQL, CLUSTERCONFIG).contains(this.resource)) {
+    if (!RBAC_ACTION_EXEMPT_LIST.contains(this.resource)) {
       Assert.notNull(value, "permission value can't be empty for resource " + resource);
     }
   }

+ 17 - 3
kafka-ui-react-app/src/components/Schemas/Edit/Form.tsx

@@ -10,6 +10,7 @@ import {
   clusterSchemasPath,
   ClusterSubjectParam,
 } from 'lib/paths';
+import yup from 'lib/yupExtended';
 import { NewSchemaSubjectRaw } from 'redux/interfaces';
 import Editor from 'components/common/Editor/Editor';
 import Select from 'components/common/Select/Select';
@@ -28,6 +29,9 @@ import {
 import PageLoader from 'components/common/PageLoader/PageLoader';
 import { schemasApiClient } from 'lib/api';
 import { showServerError } from 'lib/errorHandling';
+import { yupResolver } from '@hookform/resolvers/yup';
+import { FormError } from 'components/common/Input/Input.styled';
+import { ErrorMessage } from '@hookform/error-message';
 
 import * as S from './Edit.styled';
 
@@ -47,8 +51,16 @@ const Form: React.FC = () => {
       : JSON.stringify(JSON.parse(schema?.schema || '{}'), null, '\t');
   }, [schema]);
 
+  const validationSchema = () =>
+    yup.object().shape({
+      newSchema:
+        schema?.schemaType === SchemaType.PROTOBUF
+          ? yup.string().required().isEnum('Schema syntax is not valid')
+          : yup.string().required().isJsonObject('Schema syntax is not valid'),
+    });
   const methods = useForm<NewSchemaSubjectRaw>({
     mode: 'onChange',
+    resolver: yupResolver(validationSchema()),
     defaultValues: {
       schemaType: schema?.schemaType,
       compatibilityLevel:
@@ -58,11 +70,10 @@ const Form: React.FC = () => {
   });
 
   const {
-    formState: { isDirty, isSubmitting, dirtyFields },
+    formState: { isDirty, isSubmitting, dirtyFields, errors },
     control,
     handleSubmit,
   } = methods;
-
   const onSubmit = async (props: NewSchemaSubjectRaw) => {
     if (!schema) return;
 
@@ -191,11 +202,14 @@ const Form: React.FC = () => {
                   )}
                 />
               </S.EditorContainer>
+              <FormError>
+                <ErrorMessage errors={errors} name="newSchema" />
+              </FormError>
               <Button
                 buttonType="primary"
                 buttonSize="M"
                 type="submit"
-                disabled={!isDirty || isSubmitting}
+                disabled={!isDirty || isSubmitting || !!errors.newSchema}
               >
                 Submit
               </Button>

+ 5 - 1
kafka-ui-react-app/src/components/Topics/Topic/Messages/Filters/Filters.tsx

@@ -235,9 +235,13 @@ const Filters: React.FC<FiltersProps> = ({
         props.seekType = SeekType.TIMESTAMP;
       }
 
+      const isSeekTypeWithSeekTo =
+        props.seekType === SeekType.TIMESTAMP ||
+        props.seekType === SeekType.OFFSET;
+
       if (
         selectedPartitions.length !== partitions.length ||
-        currentSeekType === SeekType.TIMESTAMP
+        isSeekTypeWithSeekTo
       ) {
         // not everything in the partition is selected
         props.seekTo = selectedPartitions.map(({ value }) => {

+ 32 - 1
kafka-ui-react-app/src/lib/__test__/yupExtended.spec.ts

@@ -1,5 +1,19 @@
-import { isValidJsonObject } from 'lib/yupExtended';
+import { isValidEnum, isValidJsonObject } from 'lib/yupExtended';
 
+const invalidEnum = `
+ennum SchemType {
+  AVRO = 0;
+  JSON = 1;
+  PROTOBUF = 3;
+}
+`;
+const validEnum = `
+enum SchemType {
+  AVRO = 0;
+  JSON = 1;
+  PROTOBUF = 3;
+}
+`;
 describe('yup extended', () => {
   describe('isValidJsonObject', () => {
     it('returns false for no value', () => {
@@ -21,4 +35,21 @@ describe('yup extended', () => {
       expect(isValidJsonObject('{ "foo": "bar" }')).toBeTruthy();
     });
   });
+
+  describe('isValidEnum', () => {
+    it('returns false for invalid enum', () => {
+      expect(isValidEnum(invalidEnum)).toBeFalsy();
+    });
+    it('returns false for no value', () => {
+      expect(isValidEnum()).toBeFalsy();
+    });
+    it('returns true should trim value', () => {
+      expect(
+        isValidEnum(`  enum SchemType {AVRO = 0; PROTOBUF = 3;}   `)
+      ).toBeTruthy();
+    });
+    it('returns true for valid enum', () => {
+      expect(isValidEnum(validEnum)).toBeTruthy();
+    });
+  });
 });

+ 30 - 3
kafka-ui-react-app/src/lib/yupExtended.ts

@@ -9,7 +9,8 @@ declare module 'yup' {
     TDefault = undefined,
     TFlags extends yup.Flags = ''
   > extends yup.Schema<TType, TContext, TDefault, TFlags> {
-    isJsonObject(): StringSchema<TType, TContext>;
+    isJsonObject(message?: string): StringSchema<TType, TContext>;
+    isEnum(message?: string): StringSchema<TType, TContext>;
   }
 }
 
@@ -31,15 +32,40 @@ export const isValidJsonObject = (value?: string) => {
   return false;
 };
 
-const isJsonObject = () => {
+const isJsonObject = (message?: string) => {
   return yup.string().test(
     'isJsonObject',
     // eslint-disable-next-line no-template-curly-in-string
-    '${path} is not JSON object',
+    message || '${path} is not JSON object',
     isValidJsonObject
   );
 };
 
+export const isValidEnum = (value?: string) => {
+  try {
+    if (!value) return false;
+    const trimmedValue = value.trim();
+    if (
+      trimmedValue.indexOf('enum') === 0 &&
+      trimmedValue.lastIndexOf('}') === trimmedValue.length - 1
+    ) {
+      return true;
+    }
+  } catch {
+    // do nothing
+  }
+  return false;
+};
+
+const isEnum = (message?: string) => {
+  return yup.string().test(
+    'isEnum',
+    // eslint-disable-next-line no-template-curly-in-string
+    message || '${path} is not Enum object',
+    isValidEnum
+  );
+};
+
 /**
  * due to yup rerunning all the object validiation during any render,
  * it makes sense to cache the async results
@@ -62,6 +88,7 @@ export function cacheTest(
 }
 
 yup.addMethod(yup.StringSchema, 'isJsonObject', isJsonObject);
+yup.addMethod(yup.StringSchema, 'isEnum', isEnum);
 
 export const topicFormValidationSchema = yup.object().shape({
   name: yup