hooks#useURQL TypeScript Examples

The following examples show how to use hooks#useURQL. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: APICUDPage.tsx    From one-platform with MIT License 4 votes vote down vote up
APICUDPage = () => {
  const [wizardStep, setWizardStep] = useState(1);
  const { handleDynamicCrumbs } = useBreadcrumb();
  const userInfo = opcBase.auth?.getUserInfo();
  const navigate = useNavigate();
  const { slug } = useParams();
  const gqlClient = useURQL();
  const isUpdate = Boolean(slug);

  const [isDeleteConfirmationOpen, setIsDeleteConfirmationOpen] = useToggle();

  // gql queries
  const [createNsState, createNamespace] = useCreateNamespace();
  const [, updateANamespace] = useUpdateNamespace();
  const [deleteNamespaceState, deleteANamespace] = useDeleteANamespace();
  const { isLoading: isNamespaceLoading, data: nsData } = useGetANamespaceBySlug({ slug });

  const namespace = nsData?.getNamespaceBySlug;
  const id = namespace?.id;

  const formMethod = useForm<FormData>({
    defaultValues: FORM_DEFAULT_VALUE,
    mode: 'onBlur',
    reValidateMode: 'onBlur',
    resolver: yupResolver(
      wizardValidationSchemas[wizardStep as keyof typeof wizardValidationSchemas]
    ),
  });

  const { handleSubmit, reset } = formMethod;

  // effect for breadcrumb data
  useEffect(() => {
    if (!isNamespaceLoading && isUpdate && namespace?.name && namespace?.slug) {
      handleDynamicCrumbs({
        'api-name': { label: namespace.name, url: `/apis/${namespace.slug}` },
      });
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [isNamespaceLoading, namespace?.name, namespace?.slug, isUpdate]);

  /**
   * This effect validated whether user has edit access if not move them to explore
   * Checks user is in owner list or in createdBy
   */
  useEffect(() => {
    if (!isNamespaceLoading && isUpdate && namespace) {
      const userUuid = userInfo?.rhatUUID;
      const isApiCreatedUser = userUuid === (namespace?.createdBy as UserRoverDetails)?.rhatUUID;
      const isOwner =
        namespace?.owners.findIndex(
          (owner) => owner.group === ApiEmailGroup.USER && owner.user.rhatUUID === userUuid
        ) !== -1;
      const hasEditAccess = isApiCreatedUser || isOwner;
      if (!hasEditAccess) navigate('/apis');
    }

    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [isNamespaceLoading, namespace, isUpdate, userInfo?.rhatUUID]);

  /**
   * In update mode the form is prefilled with API config data
   */
  useEffect(() => {
    if (!isNamespaceLoading && isUpdate && namespace) {
      const owners = namespace.owners.map((owner) => ({
        group: owner.group,
        mid: owner.group === ApiEmailGroup.USER ? owner?.user?.rhatUUID : owner?.email,
        email: owner.group === ApiEmailGroup.USER ? owner?.user?.mail : owner?.email,
      }));
      reset({
        ...namespace,
        owners,
      });
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [isNamespaceLoading, namespace, isUpdate]);

  const isLastStep = wizardStep === MAX_WIZARD_STEPS;

  const formatFormData = ({ id: nsId, slug: nSlug, ...data }: FormData) => {
    return {
      ...data,
      owners: data.owners.map(({ group, mid }) => ({ group, mid })),
      schemas: data.schemas.map((schema) => ({
        ...schema,
        environments: schema.environments.map(({ slug: eSlug, ...env }) => ({
          ...env,
          headers: (env?.headers || [])
            .filter(({ id: hId, key, value }) => (hId && key) || (key && value))
            .map(({ id: hID, key, value }) => (hID ? { id: hID, key } : { key, value })),
        })),
      })),
    };
  };

  const handleCreateNamespace = async (data: FormData) => {
    const payload = formatFormData(data) as CreateNamespaceType;
    try {
      const res = await createNamespace({ payload });
      if (res.error) {
        window.OpNotification.danger({
          subject: 'Failed to create API',
          body: res.error?.message,
        });
        return;
      }
      navigate(`/apis/${res.data?.createNamespace.slug}`);
    } catch (error) {
      window.OpNotification.danger({
        subject: 'Failed to create API',
      });
    }
  };

  const handleUpdateNamespace = async (data: FormData) => {
    const payload = formatFormData(data) as CreateNamespaceType;
    delete (payload as any).createdBy;
    try {
      const res = await updateANamespace({ payload, id: id as string });
      if (res.error) {
        window.OpNotification.danger({
          subject: 'Failed to update API',
          body: res.error?.message,
        });
        return;
      }
      navigate(`/apis/${res.data?.updateNamespace.slug}`);
    } catch (error) {
      window.OpNotification.danger({
        subject: 'Failed to update API',
      });
    }
  };

  const handleSchemaValidation = async ({ envSlug, ...config }: HandleSchemaValidationArg) => {
    try {
      const res = await gqlClient
        .query<UseGetAPISchemaFileQuery, UseGetAPISchemaFileVariable>(GET_API_SCHEMA_FILE, {
          config,
          envSlug,
        })
        .toPromise();

      return res.data?.fetchAPISchema;
    } catch (error) {
      window.OpNotification.danger({
        subject: 'Failed to fetch schema',
      });
    }
    return undefined;
  };

  const handleApiDelete = async (): Promise<void> => {
    if (deleteNamespaceState.fetching) return;
    const res = await deleteANamespace({ id: id as string });
    if (res.error) {
      window.OpNotification.danger({
        subject: `Failed to delete API`,
        body: res.error?.message,
      });
    } else {
      navigate('/apis');
    }
  };

  const onFormSubmit = (data: FormData) => {
    if (wizardStep < MAX_WIZARD_STEPS) {
      setWizardStep((state) => state + 1);
      return;
    }
    if (isUpdate) {
      handleUpdateNamespace(data);
    } else {
      handleCreateNamespace(data);
    }
  };

  const onPrev = () => {
    if (wizardStep > 1) {
      setWizardStep((state) => state - 1);
    }
  };

  const onSearchOwners = async (search: string): Promise<JSX.Element[]> => {
    if (!search || search.length < 3) {
      return [
        <SelectOption key="no-result" value="Please type atleast 3 characters" isPlaceholder />,
      ];
    }

    try {
      const res = await gqlClient.query<UserSearchQuery>(GET_USERS_QUERY, { search }).toPromise();
      const options = (res.data?.searchRoverUsers || []).map((owner) => (
        <SelectOption
          key={`user:${owner.mail}-owner-${owner.rhatUUID}`}
          value={{
            ...owner,
            toString: () => owner.cn,
          }}
          description={owner.mail}
        />
      ));
      return options;
    } catch (error) {
      window.OpNotification.danger({
        subject: 'Failed to search for users',
      });
    }
    return [];
  };

  if (isUpdate && isNamespaceLoading) {
    return (
      <Bullseye>
        <Spinner size="xl" />
      </Bullseye>
    );
  }

  return (
    <PageSection
      isCenterAligned
      isWidthLimited
      style={{ backgroundColor: 'var(--pf-global--BackgroundColor--light-300)' }}
      className="pf-u-h-100 pf-u-pb-4xl"
    >
      <Form
        onSubmit={handleSubmit(onFormSubmit)}
        style={{ maxWidth: '1080px', margin: 'auto' }}
        autoComplete="off"
      >
        <FormProvider {...formMethod}>
          <Stack hasGutter>
            {/* Top Stepper */}
            <StackItem>
              <Card>
                <CardBody>
                  <ProgressStepper isCenterAligned>
                    {wizardStepDetails.map(({ title }, index) => (
                      <ProgressStep
                        variant={wizardStep <= index ? 'pending' : 'success'}
                        id={`wizard-step-icon-${index}`}
                        key={`wizard-step-icon-${index + 1}`}
                        titleId={`wizard-step-icon-${index}`}
                        aria-label={title}
                        isCurrent={wizardStep === index + 1}
                      >
                        {title}
                      </ProgressStep>
                    ))}
                  </ProgressStepper>
                </CardBody>
              </Card>
            </StackItem>
            {/* Form Steps */}
            <StackItem>
              <CSSTransition in={wizardStep === 1} timeout={200} classNames="fade-in" unmountOnExit>
                <APIBasicDetailsForm onSearchOwners={onSearchOwners} />
              </CSSTransition>
            </StackItem>
            <StackItem>
              <CSSTransition in={wizardStep === 2} timeout={200} classNames="fade-in" unmountOnExit>
                <APISchemaForm
                  handleSchemaValidation={handleSchemaValidation}
                  isUpdate={isUpdate}
                />
              </CSSTransition>
            </StackItem>
            <StackItem>
              <CSSTransition in={wizardStep === 3} timeout={200} classNames="fade-in" unmountOnExit>
                <APIReview />
              </CSSTransition>
            </StackItem>
            {/* Form Action Buttons */}
            <StackItem>
              <Card>
                <CardBody>
                  <Split hasGutter>
                    <SplitItem>
                      <Button type="submit" isLoading={createNsState.fetching}>
                        {isLastStep ? (isUpdate ? 'Update' : 'Create') : 'Next'}
                      </Button>
                    </SplitItem>
                    <SplitItem>
                      <Button variant="secondary" onClick={onPrev} isDisabled={wizardStep === 1}>
                        Back
                      </Button>
                    </SplitItem>
                    <SplitItem isFilled>
                      <Link to={isUpdate ? `/apis/${namespace?.slug}` : '/apis'}>
                        <Button variant="link">Cancel</Button>
                      </Link>
                    </SplitItem>
                    {isUpdate && (
                      <SplitItem>
                        <Button variant="link" isDanger onClick={setIsDeleteConfirmationOpen.on}>
                          Delete
                        </Button>
                      </SplitItem>
                    )}
                  </Split>
                </CardBody>
              </Card>
            </StackItem>
          </Stack>
        </FormProvider>
      </Form>
      <Modal
        variant={ModalVariant.medium}
        title={`Delete ${namespace?.name} API`}
        titleIconVariant="danger"
        isOpen={isDeleteConfirmationOpen}
        onClose={setIsDeleteConfirmationOpen.off}
        actions={[
          <Button
            key="confirm"
            variant="primary"
            onClick={handleApiDelete}
            isLoading={deleteNamespaceState.fetching}
          >
            Confirm
          </Button>,
          <Button key="cancel" variant="link" onClick={setIsDeleteConfirmationOpen.off}>
            Cancel
          </Button>,
        ]}
      >
        This action is irreversible. Are you sure you want to delete this API?
      </Modal>
    </PageSection>
  );
}