mirror of
				https://github.com/open-metadata/OpenMetadata.git
				synced 2025-11-03 20:19:31 +00:00 
			
		
		
		
	
							parent
							
								
									a977aa90bc
								
							
						
					
					
						commit
						8942c2b6bc
					
				@ -16,6 +16,8 @@ import {
 | 
			
		||||
  faCheck,
 | 
			
		||||
  faCheckCircle,
 | 
			
		||||
  faCheckSquare,
 | 
			
		||||
  faChevronDown,
 | 
			
		||||
  faChevronRight,
 | 
			
		||||
  faPlus,
 | 
			
		||||
  faSearch,
 | 
			
		||||
  faTimes,
 | 
			
		||||
@ -28,7 +30,16 @@ import { AuthProvider } from './authentication/auth-provider/AuthProvider';
 | 
			
		||||
import AppRouter from './router/AppRouter';
 | 
			
		||||
 | 
			
		||||
const App: FunctionComponent = () => {
 | 
			
		||||
  library.add(faTimes, faCheck, faSearch, faPlus, faCheckSquare, faCheckCircle);
 | 
			
		||||
  library.add(
 | 
			
		||||
    faTimes,
 | 
			
		||||
    faCheck,
 | 
			
		||||
    faSearch,
 | 
			
		||||
    faPlus,
 | 
			
		||||
    faCheckSquare,
 | 
			
		||||
    faCheckCircle,
 | 
			
		||||
    faChevronDown,
 | 
			
		||||
    faChevronRight
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="main-container">
 | 
			
		||||
 | 
			
		||||
@ -626,30 +626,6 @@ const DatasetDetails: React.FC<DatasetDetailsProps> = ({
 | 
			
		||||
                    onUpdate={onColumnsUpdate}
 | 
			
		||||
                  />
 | 
			
		||||
                </div>
 | 
			
		||||
 | 
			
		||||
                {threadLink ? (
 | 
			
		||||
                  <ActivityThreadPanel
 | 
			
		||||
                    createThread={createThread}
 | 
			
		||||
                    deletePostHandler={deletePostHandler}
 | 
			
		||||
                    open={Boolean(threadLink)}
 | 
			
		||||
                    postFeedHandler={postFeedHandler}
 | 
			
		||||
                    threadLink={threadLink}
 | 
			
		||||
                    onCancel={onThreadPanelClose}
 | 
			
		||||
                  />
 | 
			
		||||
                ) : null}
 | 
			
		||||
                {selectedField ? (
 | 
			
		||||
                  <RequestDescriptionModal
 | 
			
		||||
                    createThread={createThread}
 | 
			
		||||
                    defaultValue={getDefaultValue(owner)}
 | 
			
		||||
                    header="Request description"
 | 
			
		||||
                    threadLink={getEntityFeedLink(
 | 
			
		||||
                      EntityType.TABLE,
 | 
			
		||||
                      datasetFQN,
 | 
			
		||||
                      selectedField
 | 
			
		||||
                    )}
 | 
			
		||||
                    onCancel={closeRequestModal}
 | 
			
		||||
                  />
 | 
			
		||||
                ) : null}
 | 
			
		||||
              </div>
 | 
			
		||||
            )}
 | 
			
		||||
            {activeTab === 2 && (
 | 
			
		||||
@ -776,6 +752,29 @@ const DatasetDetails: React.FC<DatasetDetailsProps> = ({
 | 
			
		||||
              {getLoader()}
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
          {threadLink ? (
 | 
			
		||||
            <ActivityThreadPanel
 | 
			
		||||
              createThread={createThread}
 | 
			
		||||
              deletePostHandler={deletePostHandler}
 | 
			
		||||
              open={Boolean(threadLink)}
 | 
			
		||||
              postFeedHandler={postFeedHandler}
 | 
			
		||||
              threadLink={threadLink}
 | 
			
		||||
              onCancel={onThreadPanelClose}
 | 
			
		||||
            />
 | 
			
		||||
          ) : null}
 | 
			
		||||
          {selectedField ? (
 | 
			
		||||
            <RequestDescriptionModal
 | 
			
		||||
              createThread={createThread}
 | 
			
		||||
              defaultValue={getDefaultValue(owner)}
 | 
			
		||||
              header="Request description"
 | 
			
		||||
              threadLink={getEntityFeedLink(
 | 
			
		||||
                EntityType.TABLE,
 | 
			
		||||
                datasetFQN,
 | 
			
		||||
                selectedField
 | 
			
		||||
              )}
 | 
			
		||||
              onCancel={closeRequestModal}
 | 
			
		||||
            />
 | 
			
		||||
          ) : null}
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </PageContainer>
 | 
			
		||||
 | 
			
		||||
@ -651,6 +651,7 @@ const EntityTable = ({
 | 
			
		||||
                                        trigger="mouseenter">
 | 
			
		||||
                                        <SVGIcons
 | 
			
		||||
                                          alt="request-description"
 | 
			
		||||
                                          className="tw-mt-2.5"
 | 
			
		||||
                                          icon={Icons.REQUEST}
 | 
			
		||||
                                        />
 | 
			
		||||
                                      </PopOver>
 | 
			
		||||
 | 
			
		||||
@ -12,19 +12,21 @@
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import classNames from 'classnames';
 | 
			
		||||
import React, { FunctionComponent } from 'react';
 | 
			
		||||
import React, { CSSProperties, FunctionComponent } from 'react';
 | 
			
		||||
import './Loader.css';
 | 
			
		||||
 | 
			
		||||
type Props = {
 | 
			
		||||
  size?: 'default' | 'small';
 | 
			
		||||
  type?: 'default' | 'success' | 'error' | 'white';
 | 
			
		||||
  className?: string;
 | 
			
		||||
  style?: CSSProperties;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const Loader: FunctionComponent<Props> = ({
 | 
			
		||||
  size = 'default',
 | 
			
		||||
  type = 'default',
 | 
			
		||||
  className = '',
 | 
			
		||||
  style,
 | 
			
		||||
}: Props): JSX.Element => {
 | 
			
		||||
  let classes = 'loader';
 | 
			
		||||
  switch (size) {
 | 
			
		||||
@ -54,7 +56,11 @@ const Loader: FunctionComponent<Props> = ({
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className={classNames(classes, className)} data-testid="loader" />
 | 
			
		||||
    <div
 | 
			
		||||
      className={classNames(classes, className)}
 | 
			
		||||
      data-testid="loader"
 | 
			
		||||
      style={style}
 | 
			
		||||
    />
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -24,6 +24,7 @@ import { useAuthContext } from '../../authentication/auth-provider/AuthProvider'
 | 
			
		||||
import { deleteEntity } from '../../axiosAPIs/miscAPI';
 | 
			
		||||
import { getCategory } from '../../axiosAPIs/tagAPI';
 | 
			
		||||
import { FQN_SEPARATOR_CHAR } from '../../constants/char.constants';
 | 
			
		||||
import { TITLE_FOR_NON_ADMIN_ACTION } from '../../constants/constants';
 | 
			
		||||
import { ENTITY_DELETE_STATE } from '../../constants/entity.constants';
 | 
			
		||||
import { Operation } from '../../generated/entity/policies/accessControl/rule';
 | 
			
		||||
import { useAuth } from '../../hooks/authHooks';
 | 
			
		||||
@ -38,7 +39,7 @@ import NonAdminAction from '../common/non-admin-action/NonAdminAction';
 | 
			
		||||
import DropDownList from '../dropdown/DropDownList';
 | 
			
		||||
import Loader from '../Loader/Loader';
 | 
			
		||||
import EntityDeleteModal from '../Modals/EntityDeleteModal/EntityDeleteModal';
 | 
			
		||||
import { ManageProps } from './ManageTab.interface';
 | 
			
		||||
import { ManageProps, Status } from './ManageTab.interface';
 | 
			
		||||
 | 
			
		||||
const ManageTab: FunctionComponent<ManageProps> = ({
 | 
			
		||||
  currentTier = '',
 | 
			
		||||
@ -57,10 +58,8 @@ const ManageTab: FunctionComponent<ManageProps> = ({
 | 
			
		||||
  const { userPermissions, isAdminUser } = useAuth();
 | 
			
		||||
  const { isAuthDisabled } = useAuthContext();
 | 
			
		||||
 | 
			
		||||
  const [loading, setLoading] = useState<boolean>(false);
 | 
			
		||||
  const [status, setStatus] = useState<'initial' | 'waiting' | 'success'>(
 | 
			
		||||
    'initial'
 | 
			
		||||
  );
 | 
			
		||||
  const [statusOwner, setStatusOwner] = useState<Status>('initial');
 | 
			
		||||
  const [statusTier, setStatusTier] = useState<Status>('initial');
 | 
			
		||||
  const [activeTier, setActiveTier] = useState(currentTier);
 | 
			
		||||
  const [listVisible, setListVisible] = useState(false);
 | 
			
		||||
  const [teamJoinable, setTeamJoinable] = useState(isJoinable);
 | 
			
		||||
@ -80,6 +79,67 @@ const ManageTab: FunctionComponent<ManageProps> = ({
 | 
			
		||||
    return allowTeamOwner ? ['Teams', 'Users'] : ['Users'];
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const setInitialOwnerLoadingState = () => {
 | 
			
		||||
    setStatusOwner('initial');
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const setInitialTierLoadingState = () => {
 | 
			
		||||
    setStatusTier('initial');
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const prepareOwner = (updatedOwner: string) => {
 | 
			
		||||
    return updatedOwner !== currentUser
 | 
			
		||||
      ? {
 | 
			
		||||
          id: updatedOwner,
 | 
			
		||||
          type: listOwners.find((item) => item.value === updatedOwner)?.type as
 | 
			
		||||
            | 'user'
 | 
			
		||||
            | 'team',
 | 
			
		||||
        }
 | 
			
		||||
      : undefined;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const prepareTier = (updatedTier: string) => {
 | 
			
		||||
    return updatedTier !== currentTier ? updatedTier : undefined;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const handleOwnerSave = (updatedOwner: string, updatedTier: string) => {
 | 
			
		||||
    setStatusOwner('waiting');
 | 
			
		||||
    const newOwner: TableDetail['owner'] = prepareOwner(updatedOwner);
 | 
			
		||||
    if (hideTier) {
 | 
			
		||||
      if (newOwner || !isUndefined(teamJoinable)) {
 | 
			
		||||
        onSave(newOwner, '', Boolean(teamJoinable)).catch(() => {
 | 
			
		||||
          setInitialOwnerLoadingState();
 | 
			
		||||
        });
 | 
			
		||||
      } else {
 | 
			
		||||
        setInitialOwnerLoadingState();
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      const newTier = prepareTier(updatedTier);
 | 
			
		||||
      onSave(newOwner, newTier, Boolean(teamJoinable)).catch(() => {
 | 
			
		||||
        setInitialOwnerLoadingState();
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const handleTierSave = (updatedTier: string) => {
 | 
			
		||||
    setStatusTier('waiting');
 | 
			
		||||
    const newOwner: TableDetail['owner'] = prepareOwner(currentUser);
 | 
			
		||||
    if (hideTier) {
 | 
			
		||||
      if (newOwner || !isUndefined(teamJoinable)) {
 | 
			
		||||
        onSave(newOwner, '', Boolean(teamJoinable)).catch(() => {
 | 
			
		||||
          setInitialTierLoadingState();
 | 
			
		||||
        });
 | 
			
		||||
      } else {
 | 
			
		||||
        setInitialTierLoadingState();
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      const newTier = prepareTier(updatedTier);
 | 
			
		||||
      onSave(newOwner, newTier, Boolean(teamJoinable)).catch(() => {
 | 
			
		||||
        setInitialTierLoadingState();
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const handleOwnerSelection = (
 | 
			
		||||
    _e: React.MouseEvent<HTMLElement, MouseEvent>,
 | 
			
		||||
    value?: string
 | 
			
		||||
@ -88,6 +148,7 @@ const ManageTab: FunctionComponent<ManageProps> = ({
 | 
			
		||||
      const newOwner = listOwners.find((item) => item.value === value);
 | 
			
		||||
      if (newOwner) {
 | 
			
		||||
        setOwner(newOwner.value);
 | 
			
		||||
        handleOwnerSave(newOwner.value, activeTier);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    setListVisible(false);
 | 
			
		||||
@ -97,45 +158,6 @@ const ManageTab: FunctionComponent<ManageProps> = ({
 | 
			
		||||
    setActiveTier(cardId);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const setInitialLoadingState = () => {
 | 
			
		||||
    setStatus('initial');
 | 
			
		||||
    setLoading(false);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const handleSave = () => {
 | 
			
		||||
    setLoading(true);
 | 
			
		||||
    setStatus('waiting');
 | 
			
		||||
    // Save API call goes here...
 | 
			
		||||
    const newOwner: TableDetail['owner'] =
 | 
			
		||||
      owner !== currentUser
 | 
			
		||||
        ? {
 | 
			
		||||
            id: owner,
 | 
			
		||||
            type: listOwners.find((item) => item.value === owner)?.type as
 | 
			
		||||
              | 'user'
 | 
			
		||||
              | 'team',
 | 
			
		||||
          }
 | 
			
		||||
        : undefined;
 | 
			
		||||
    if (hideTier) {
 | 
			
		||||
      if (newOwner || !isUndefined(teamJoinable)) {
 | 
			
		||||
        onSave(newOwner, '', Boolean(teamJoinable)).catch(() => {
 | 
			
		||||
          setInitialLoadingState();
 | 
			
		||||
        });
 | 
			
		||||
      } else {
 | 
			
		||||
        setInitialLoadingState();
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      const newTier = activeTier !== currentTier ? activeTier : undefined;
 | 
			
		||||
      onSave(newOwner, newTier, Boolean(teamJoinable)).catch(() => {
 | 
			
		||||
        setInitialLoadingState();
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const handleCancel = () => {
 | 
			
		||||
    setActiveTier(currentTier);
 | 
			
		||||
    setOwner(currentUser);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const handleOnEntityDelete = () => {
 | 
			
		||||
    setEntityDeleteState((prev) => ({ ...prev, state: true }));
 | 
			
		||||
  };
 | 
			
		||||
@ -193,7 +215,7 @@ const ManageTab: FunctionComponent<ManageProps> = ({
 | 
			
		||||
 | 
			
		||||
  const getDeleteEntityWidget = () => {
 | 
			
		||||
    return allowDelete && entityId && entityName && entityType ? (
 | 
			
		||||
      <div className="tw-mt-9" data-testid="danger-zone">
 | 
			
		||||
      <div className="tw-mt-1" data-testid="danger-zone">
 | 
			
		||||
        <hr className="tw-border-main tw-mb-4" />
 | 
			
		||||
        <div className="tw-border tw-border-error tw-px-4 tw-py-2 tw-flex tw-justify-between tw-rounded tw-mt-3 tw-shadow">
 | 
			
		||||
          <div data-testid="danger-zone-text">
 | 
			
		||||
@ -204,16 +226,27 @@ const ManageTab: FunctionComponent<ManageProps> = ({
 | 
			
		||||
              {`Once you delete this ${entityType}, it would be removed permanently`}
 | 
			
		||||
            </p>
 | 
			
		||||
          </div>
 | 
			
		||||
          <Button
 | 
			
		||||
            className="tw-px-2 tw-py-1 tw-rounded tw-h-auto tw-self-center tw-shadow"
 | 
			
		||||
            data-testid="delete-button"
 | 
			
		||||
            size="custom"
 | 
			
		||||
            theme="primary"
 | 
			
		||||
            type="button"
 | 
			
		||||
            variant="outlined"
 | 
			
		||||
            onClick={handleOnEntityDelete}>
 | 
			
		||||
            Delete this {entityType}
 | 
			
		||||
          </Button>
 | 
			
		||||
          <NonAdminAction
 | 
			
		||||
            className="tw-self-center"
 | 
			
		||||
            html={
 | 
			
		||||
              <Fragment>
 | 
			
		||||
                <p>{TITLE_FOR_NON_ADMIN_ACTION}</p>
 | 
			
		||||
              </Fragment>
 | 
			
		||||
            }
 | 
			
		||||
            isOwner={isAdminUser}
 | 
			
		||||
            position="left">
 | 
			
		||||
            <Button
 | 
			
		||||
              className="tw-px-2 tw-py-1 tw-rounded tw-h-auto tw-self-center tw-shadow"
 | 
			
		||||
              data-testid="delete-button"
 | 
			
		||||
              disabled={!isAdminUser && !isAuthDisabled}
 | 
			
		||||
              size="custom"
 | 
			
		||||
              theme="primary"
 | 
			
		||||
              type="button"
 | 
			
		||||
              variant="outlined"
 | 
			
		||||
              onClick={handleOnEntityDelete}>
 | 
			
		||||
              Delete this {entityType}
 | 
			
		||||
            </Button>
 | 
			
		||||
          </NonAdminAction>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    ) : null;
 | 
			
		||||
@ -240,6 +273,9 @@ const ManageTab: FunctionComponent<ManageProps> = ({
 | 
			
		||||
              <CardListItem
 | 
			
		||||
                card={card}
 | 
			
		||||
                isActive={activeTier === card.id}
 | 
			
		||||
                isSelected={card.id === currentTier}
 | 
			
		||||
                tierStatus={statusTier}
 | 
			
		||||
                onSave={handleTierSave}
 | 
			
		||||
                onSelect={handleCardSelection}
 | 
			
		||||
              />
 | 
			
		||||
            </NonAdminAction>
 | 
			
		||||
@ -297,10 +333,8 @@ const ManageTab: FunctionComponent<ManageProps> = ({
 | 
			
		||||
          );
 | 
			
		||||
 | 
			
		||||
          setTierData(tierData);
 | 
			
		||||
          setIsLoadingTierData(false);
 | 
			
		||||
        } else {
 | 
			
		||||
          setTierData([]);
 | 
			
		||||
          setIsLoadingTierData(false);
 | 
			
		||||
        }
 | 
			
		||||
      })
 | 
			
		||||
      .catch((err: AxiosError) => {
 | 
			
		||||
@ -308,9 +342,29 @@ const ManageTab: FunctionComponent<ManageProps> = ({
 | 
			
		||||
          err,
 | 
			
		||||
          jsonData['api-error-messages']['fetch-tiers-error']
 | 
			
		||||
        );
 | 
			
		||||
      })
 | 
			
		||||
      .finally(() => {
 | 
			
		||||
        setIsLoadingTierData(false);
 | 
			
		||||
      });
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const getOwnerUpdateLoader = () => {
 | 
			
		||||
    return (
 | 
			
		||||
      <span className="tw-ml-4">
 | 
			
		||||
        {statusOwner === 'waiting' ? (
 | 
			
		||||
          <Loader
 | 
			
		||||
            className="tw-inline-block"
 | 
			
		||||
            size="small"
 | 
			
		||||
            style={{ marginBottom: '-4px' }}
 | 
			
		||||
            type="default"
 | 
			
		||||
          />
 | 
			
		||||
        ) : statusOwner === 'success' ? (
 | 
			
		||||
          <FontAwesomeIcon icon="check" />
 | 
			
		||||
        ) : null}
 | 
			
		||||
      </span>
 | 
			
		||||
    );
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (!hideTier) {
 | 
			
		||||
      getTierData();
 | 
			
		||||
@ -326,16 +380,22 @@ const ManageTab: FunctionComponent<ManageProps> = ({
 | 
			
		||||
  }, [currentUser]);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    setTimeout(() => {
 | 
			
		||||
      setLoading(false);
 | 
			
		||||
    }, 1000);
 | 
			
		||||
    if (status === 'waiting') {
 | 
			
		||||
      setStatus('success');
 | 
			
		||||
    if (statusOwner === 'waiting') {
 | 
			
		||||
      setStatusOwner('success');
 | 
			
		||||
      setTimeout(() => {
 | 
			
		||||
        setStatus('initial');
 | 
			
		||||
        setInitialOwnerLoadingState();
 | 
			
		||||
      }, 3000);
 | 
			
		||||
    }
 | 
			
		||||
  }, [currentTier, currentUser]);
 | 
			
		||||
  }, [currentUser]);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (statusTier === 'waiting') {
 | 
			
		||||
      setStatusTier('success');
 | 
			
		||||
      setTimeout(() => {
 | 
			
		||||
        setInitialTierLoadingState();
 | 
			
		||||
      }, 3000);
 | 
			
		||||
    }
 | 
			
		||||
  }, [currentTier]);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    setListOwners(getOwnerList());
 | 
			
		||||
@ -350,7 +410,10 @@ const ManageTab: FunctionComponent<ManageProps> = ({
 | 
			
		||||
      className="tw-max-w-3xl tw-mx-auto"
 | 
			
		||||
      data-testid="manage-tab"
 | 
			
		||||
      id="manageTabDetails">
 | 
			
		||||
      <div className="tw-mt-2 tw-mb-4 tw-pb-4 tw-border-b tw-border-separator ">
 | 
			
		||||
      <div
 | 
			
		||||
        className={classNames('tw-mt-2 tw-pb-4', {
 | 
			
		||||
          'tw-border-b tw-border-separator tw-mb-4': !hideTier,
 | 
			
		||||
        })}>
 | 
			
		||||
        <div>
 | 
			
		||||
          <span className="tw-mr-2">Owner:</span>
 | 
			
		||||
          <span className="tw-relative">
 | 
			
		||||
@ -410,49 +473,12 @@ const ManageTab: FunctionComponent<ManageProps> = ({
 | 
			
		||||
                onSelect={handleOwnerSelection}
 | 
			
		||||
              />
 | 
			
		||||
            )}
 | 
			
		||||
            {getOwnerUpdateLoader()}
 | 
			
		||||
          </span>
 | 
			
		||||
        </div>
 | 
			
		||||
        {getJoinableWidget()}
 | 
			
		||||
      </div>
 | 
			
		||||
      {getTierCards()}
 | 
			
		||||
      <div className="tw-mt-6 tw-text-right" data-testid="buttons">
 | 
			
		||||
        <Button
 | 
			
		||||
          size="regular"
 | 
			
		||||
          theme="primary"
 | 
			
		||||
          variant="text"
 | 
			
		||||
          onClick={handleCancel}>
 | 
			
		||||
          Discard
 | 
			
		||||
        </Button>
 | 
			
		||||
        {loading ? (
 | 
			
		||||
          <Button
 | 
			
		||||
            disabled
 | 
			
		||||
            className="tw-w-16 tw-h-10 disabled:tw-opacity-100"
 | 
			
		||||
            size="regular"
 | 
			
		||||
            theme="primary"
 | 
			
		||||
            variant="contained">
 | 
			
		||||
            <Loader size="small" type="white" />
 | 
			
		||||
          </Button>
 | 
			
		||||
        ) : status === 'success' ? (
 | 
			
		||||
          <Button
 | 
			
		||||
            disabled
 | 
			
		||||
            className="tw-w-16 tw-h-10 disabled:tw-opacity-100"
 | 
			
		||||
            size="regular"
 | 
			
		||||
            theme="primary"
 | 
			
		||||
            variant="contained">
 | 
			
		||||
            <FontAwesomeIcon icon="check" />
 | 
			
		||||
          </Button>
 | 
			
		||||
        ) : (
 | 
			
		||||
          <Button
 | 
			
		||||
            className="tw-w-16 tw-h-10"
 | 
			
		||||
            data-testid="saveManageTab"
 | 
			
		||||
            size="regular"
 | 
			
		||||
            theme="primary"
 | 
			
		||||
            variant="contained"
 | 
			
		||||
            onClick={handleSave}>
 | 
			
		||||
            Save
 | 
			
		||||
          </Button>
 | 
			
		||||
        )}
 | 
			
		||||
      </div>
 | 
			
		||||
      {getDeleteEntityWidget()}
 | 
			
		||||
      {getDeleteModal()}
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
@ -30,3 +30,5 @@ export interface ManageProps {
 | 
			
		||||
  entityType?: string;
 | 
			
		||||
  allowDelete?: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export type Status = 'initial' | 'waiting' | 'success';
 | 
			
		||||
 | 
			
		||||
@ -14,7 +14,6 @@
 | 
			
		||||
import {
 | 
			
		||||
  findAllByTestId,
 | 
			
		||||
  findByTestId,
 | 
			
		||||
  findByText,
 | 
			
		||||
  fireEvent,
 | 
			
		||||
  render,
 | 
			
		||||
} from '@testing-library/react';
 | 
			
		||||
@ -92,42 +91,6 @@ describe('Test Manage tab Component', () => {
 | 
			
		||||
    expect(card.length).toBe(3);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('there should be 2 buttons', async () => {
 | 
			
		||||
    const { container } = render(
 | 
			
		||||
      <ManageTab hasEditAccess onSave={mockFunction} />
 | 
			
		||||
    );
 | 
			
		||||
    const buttons = await findByTestId(container, 'buttons');
 | 
			
		||||
 | 
			
		||||
    expect(buttons.childElementCount).toBe(2);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('Onclick of save, onSave function also called', async () => {
 | 
			
		||||
    const { container } = render(
 | 
			
		||||
      <ManageTab hasEditAccess onSave={mockFunction} />
 | 
			
		||||
    );
 | 
			
		||||
    const card = await findAllByTestId(container, 'card');
 | 
			
		||||
 | 
			
		||||
    fireEvent.click(
 | 
			
		||||
      card[1],
 | 
			
		||||
      new MouseEvent('click', {
 | 
			
		||||
        bubbles: true,
 | 
			
		||||
        cancelable: true,
 | 
			
		||||
      })
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    const save = await findByText(container, /Save/i);
 | 
			
		||||
 | 
			
		||||
    fireEvent.click(
 | 
			
		||||
      save,
 | 
			
		||||
      new MouseEvent('click', {
 | 
			
		||||
        bubbles: true,
 | 
			
		||||
        cancelable: true,
 | 
			
		||||
      })
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    expect(mockFunction).toBeCalledTimes(1);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('Should render switch if isJoinable is present', async () => {
 | 
			
		||||
    const { container } = render(
 | 
			
		||||
      <ManageTab hasEditAccess isJoinable onSave={mockFunction} />
 | 
			
		||||
 | 
			
		||||
@ -23,7 +23,7 @@ import AddRoleModal from './AddRoleModal';
 | 
			
		||||
const mockCancel = jest.fn();
 | 
			
		||||
const mockSave = jest.fn();
 | 
			
		||||
const mockForm = jest.fn().mockReturnValue(<p data-testid="form">data</p>);
 | 
			
		||||
const mockInitionalData = {
 | 
			
		||||
const mockInitialData = {
 | 
			
		||||
  name: '',
 | 
			
		||||
  description: '',
 | 
			
		||||
};
 | 
			
		||||
@ -34,7 +34,7 @@ describe('Test AddRoleModal component', () => {
 | 
			
		||||
      <AddRoleModal
 | 
			
		||||
        form={mockForm}
 | 
			
		||||
        header="Adding new users"
 | 
			
		||||
        initialData={mockInitionalData}
 | 
			
		||||
        initialData={mockInitialData}
 | 
			
		||||
        onCancel={mockCancel}
 | 
			
		||||
        onSave={mockSave}
 | 
			
		||||
      />
 | 
			
		||||
@ -55,7 +55,7 @@ describe('Test AddRoleModal component', () => {
 | 
			
		||||
      <AddRoleModal
 | 
			
		||||
        form={mockForm}
 | 
			
		||||
        header="Adding new users"
 | 
			
		||||
        initialData={mockInitionalData}
 | 
			
		||||
        initialData={mockInitialData}
 | 
			
		||||
        onCancel={mockCancel}
 | 
			
		||||
        onSave={mockSave}
 | 
			
		||||
      />
 | 
			
		||||
@ -71,7 +71,7 @@ describe('Test AddRoleModal component', () => {
 | 
			
		||||
      <AddRoleModal
 | 
			
		||||
        form={mockForm}
 | 
			
		||||
        header="Adding new users"
 | 
			
		||||
        initialData={mockInitionalData}
 | 
			
		||||
        initialData={mockInitialData}
 | 
			
		||||
        onCancel={mockCancel}
 | 
			
		||||
        onSave={mockSave}
 | 
			
		||||
      />
 | 
			
		||||
@ -516,6 +516,7 @@ const PipelineDetails = ({
 | 
			
		||||
                                          trigger="mouseenter">
 | 
			
		||||
                                          <SVGIcons
 | 
			
		||||
                                            alt="request-description"
 | 
			
		||||
                                            className="tw-mt-2.5"
 | 
			
		||||
                                            icon={Icons.REQUEST}
 | 
			
		||||
                                          />
 | 
			
		||||
                                        </PopOver>
 | 
			
		||||
 | 
			
		||||
@ -11,6 +11,8 @@
 | 
			
		||||
 *  limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import { Status } from '../../ManageTab/ManageTab.interface';
 | 
			
		||||
 | 
			
		||||
export type CardWithListItems = {
 | 
			
		||||
  id: string;
 | 
			
		||||
  description: string;
 | 
			
		||||
@ -21,5 +23,8 @@ export type CardWithListItems = {
 | 
			
		||||
export type Props = {
 | 
			
		||||
  card: CardWithListItems;
 | 
			
		||||
  isActive: boolean;
 | 
			
		||||
  isSelected: boolean;
 | 
			
		||||
  tierStatus: Status;
 | 
			
		||||
  onSave: (updatedTier: string) => void;
 | 
			
		||||
  onSelect: (cardId: string) => void;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -14,11 +14,13 @@
 | 
			
		||||
export const cardStyle = {
 | 
			
		||||
  base: 'tw-flex tw-flex-col tw-rounded-md tw-border tw-mb-4',
 | 
			
		||||
  default: 'tw-border-main',
 | 
			
		||||
  active: 'tw-border-primary',
 | 
			
		||||
  active: 'tw-border-primary-lite',
 | 
			
		||||
  selected: 'tw-border-primary',
 | 
			
		||||
  header: {
 | 
			
		||||
    base: 'tw-flex tw-px-5 tw-py-3 tw-cursor-pointer tw-justify-between tw-items-center',
 | 
			
		||||
    default: 'tw-bg-badge',
 | 
			
		||||
    active: 'tw-bg-primary tw-rounded-t-md tw-text-white',
 | 
			
		||||
    active: 'tw-bg-primary-lite tw-rounded-t-md',
 | 
			
		||||
    selected: 'tw-bg-primary tw-rounded-t-md tw-text-white',
 | 
			
		||||
    title: 'tw-text-base tw-mb-0',
 | 
			
		||||
    description: 'tw-font-medium tw-pr-2',
 | 
			
		||||
  },
 | 
			
		||||
@ -26,6 +28,7 @@ export const cardStyle = {
 | 
			
		||||
    base: 'tw-py-5 tw-px-10',
 | 
			
		||||
    default: 'tw-hidden',
 | 
			
		||||
    active: 'tw-block',
 | 
			
		||||
    selected: 'tw-block',
 | 
			
		||||
    content: {
 | 
			
		||||
      withBorder: 'tw-py-3 tw-border-b tw-border-main',
 | 
			
		||||
      withoutBorder: 'tw-py-1',
 | 
			
		||||
 | 
			
		||||
@ -15,7 +15,8 @@ import { fireEvent, render } from '@testing-library/react';
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import CardListItem from './CardWithListItems';
 | 
			
		||||
 | 
			
		||||
const mockFunction = jest.fn();
 | 
			
		||||
const mockSelectFunction = jest.fn();
 | 
			
		||||
const mockSaveFuntion = jest.fn();
 | 
			
		||||
const mockCard = {
 | 
			
		||||
  id: 'test1',
 | 
			
		||||
  title: 'card',
 | 
			
		||||
@ -30,7 +31,14 @@ jest.mock('../../common/rich-text-editor/RichTextEditorPreviewer', () => {
 | 
			
		||||
describe('Test CardWithListing Component', () => {
 | 
			
		||||
  it('Component should render', () => {
 | 
			
		||||
    const { getByTestId } = render(
 | 
			
		||||
      <CardListItem card={mockCard} isActive={false} onSelect={mockFunction} />
 | 
			
		||||
      <CardListItem
 | 
			
		||||
        card={mockCard}
 | 
			
		||||
        isActive={false}
 | 
			
		||||
        isSelected={false}
 | 
			
		||||
        tierStatus="initial"
 | 
			
		||||
        onSave={mockSaveFuntion}
 | 
			
		||||
        onSelect={mockSelectFunction}
 | 
			
		||||
      />
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    const card = getByTestId('card-list');
 | 
			
		||||
@ -40,9 +48,16 @@ describe('Test CardWithListing Component', () => {
 | 
			
		||||
    expect(getByTestId('icon')).toBeEmptyDOMElement();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('OnClick callback function should call', () => {
 | 
			
		||||
  it('OnClick onSelect function should call', () => {
 | 
			
		||||
    const { getByTestId } = render(
 | 
			
		||||
      <CardListItem card={mockCard} isActive={false} onSelect={mockFunction} />
 | 
			
		||||
      <CardListItem
 | 
			
		||||
        card={mockCard}
 | 
			
		||||
        isActive={false}
 | 
			
		||||
        isSelected={false}
 | 
			
		||||
        tierStatus="initial"
 | 
			
		||||
        onSave={mockSaveFuntion}
 | 
			
		||||
        onSelect={mockSelectFunction}
 | 
			
		||||
      />
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    const card = getByTestId('card-list');
 | 
			
		||||
@ -54,6 +69,66 @@ describe('Test CardWithListing Component', () => {
 | 
			
		||||
      })
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    expect(mockFunction).toHaveBeenCalledTimes(1);
 | 
			
		||||
    expect(mockSelectFunction).toHaveBeenCalledTimes(1);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('OnClick onSelect function should call and Select tier button should be visible', () => {
 | 
			
		||||
    const { getByTestId } = render(
 | 
			
		||||
      <CardListItem
 | 
			
		||||
        isActive
 | 
			
		||||
        card={mockCard}
 | 
			
		||||
        isSelected={false}
 | 
			
		||||
        tierStatus="initial"
 | 
			
		||||
        onSave={mockSaveFuntion}
 | 
			
		||||
        onSelect={mockSelectFunction}
 | 
			
		||||
      />
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    const card = getByTestId('card-list');
 | 
			
		||||
    fireEvent(
 | 
			
		||||
      card,
 | 
			
		||||
      new MouseEvent('click', {
 | 
			
		||||
        bubbles: true,
 | 
			
		||||
        cancelable: true,
 | 
			
		||||
      })
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    expect(mockSelectFunction).toHaveBeenCalledTimes(1);
 | 
			
		||||
 | 
			
		||||
    const tierSelectButton = getByTestId('select-tier-buuton');
 | 
			
		||||
 | 
			
		||||
    expect(tierSelectButton).toBeInTheDocument();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('onClick of select tier button onSave function should call.', () => {
 | 
			
		||||
    const { getByTestId } = render(
 | 
			
		||||
      <CardListItem
 | 
			
		||||
        isActive
 | 
			
		||||
        card={mockCard}
 | 
			
		||||
        isSelected={false}
 | 
			
		||||
        tierStatus="initial"
 | 
			
		||||
        onSave={mockSaveFuntion}
 | 
			
		||||
        onSelect={mockSelectFunction}
 | 
			
		||||
      />
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    const card = getByTestId('card-list');
 | 
			
		||||
    fireEvent(
 | 
			
		||||
      card,
 | 
			
		||||
      new MouseEvent('click', {
 | 
			
		||||
        bubbles: true,
 | 
			
		||||
        cancelable: true,
 | 
			
		||||
      })
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    expect(mockSelectFunction).toHaveBeenCalledTimes(1);
 | 
			
		||||
 | 
			
		||||
    const tierSelectButton = getByTestId('select-tier-buuton');
 | 
			
		||||
 | 
			
		||||
    expect(tierSelectButton).toBeInTheDocument();
 | 
			
		||||
 | 
			
		||||
    fireEvent.click(tierSelectButton);
 | 
			
		||||
 | 
			
		||||
    expect(mockSaveFuntion).toHaveBeenCalledTimes(1);
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
@ -14,39 +14,90 @@
 | 
			
		||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 | 
			
		||||
import classNames from 'classnames';
 | 
			
		||||
import React, { FunctionComponent } from 'react';
 | 
			
		||||
import { Button } from '../../buttons/Button/Button';
 | 
			
		||||
import RichTextEditorPreviewer from '../../common/rich-text-editor/RichTextEditorPreviewer';
 | 
			
		||||
import Loader from '../../Loader/Loader';
 | 
			
		||||
import { Props } from './CardWithListItems.interface';
 | 
			
		||||
import { cardStyle } from './CardWithListItems.style';
 | 
			
		||||
 | 
			
		||||
const CardListItem: FunctionComponent<Props> = ({
 | 
			
		||||
  card,
 | 
			
		||||
  isActive,
 | 
			
		||||
  isSelected,
 | 
			
		||||
  onSelect,
 | 
			
		||||
  onSave,
 | 
			
		||||
  tierStatus,
 | 
			
		||||
}: Props) => {
 | 
			
		||||
  const getCardBodyStyle = () => {
 | 
			
		||||
    return isSelected
 | 
			
		||||
      ? cardStyle.selected
 | 
			
		||||
      : isActive
 | 
			
		||||
      ? cardStyle.active
 | 
			
		||||
      : cardStyle.default;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const getCardHeaderStyle = () => {
 | 
			
		||||
    return isSelected
 | 
			
		||||
      ? cardStyle.header.selected
 | 
			
		||||
      : isActive
 | 
			
		||||
      ? cardStyle.header.active
 | 
			
		||||
      : cardStyle.header.default;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const getTierSelectButton = (tier: string) => {
 | 
			
		||||
    return tierStatus === 'waiting' ? (
 | 
			
		||||
      <Loader
 | 
			
		||||
        className="tw-inline-block"
 | 
			
		||||
        size="small"
 | 
			
		||||
        style={{ marginBottom: '-4px' }}
 | 
			
		||||
        type="default"
 | 
			
		||||
      />
 | 
			
		||||
    ) : tierStatus === 'success' ? (
 | 
			
		||||
      <FontAwesomeIcon icon="check" />
 | 
			
		||||
    ) : (
 | 
			
		||||
      <Button
 | 
			
		||||
        data-testid="select-tier-buuton"
 | 
			
		||||
        size="small"
 | 
			
		||||
        theme="primary"
 | 
			
		||||
        onClick={() => onSave(tier)}>
 | 
			
		||||
        Select
 | 
			
		||||
      </Button>
 | 
			
		||||
    );
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const getCardIcon = (cardId: string) => {
 | 
			
		||||
    if (isSelected && isActive) {
 | 
			
		||||
      return <FontAwesomeIcon className="tw-text-h4" icon="check-circle" />;
 | 
			
		||||
    } else if (isSelected) {
 | 
			
		||||
      return <FontAwesomeIcon className="tw-text-h4" icon="check-circle" />;
 | 
			
		||||
    } else if (isActive) {
 | 
			
		||||
      return getTierSelectButton(cardId);
 | 
			
		||||
    } else {
 | 
			
		||||
      return null;
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div
 | 
			
		||||
      className={classNames(
 | 
			
		||||
        cardStyle.base,
 | 
			
		||||
        isActive ? cardStyle.active : cardStyle.default
 | 
			
		||||
      )}
 | 
			
		||||
      className={classNames(cardStyle.base, getCardBodyStyle())}
 | 
			
		||||
      data-testid="card-list"
 | 
			
		||||
      onClick={() => onSelect(card.id)}>
 | 
			
		||||
      <div
 | 
			
		||||
        className={classNames(
 | 
			
		||||
          cardStyle.header.base,
 | 
			
		||||
          isActive ? cardStyle.header.active : cardStyle.header.default
 | 
			
		||||
        )}>
 | 
			
		||||
        <div className="tw-flex tw-flex-col">
 | 
			
		||||
          <h4 className={cardStyle.header.title}>{card.title}</h4>
 | 
			
		||||
          <p className={cardStyle.header.description}>
 | 
			
		||||
            {card.description.replace(/\*/g, '')}
 | 
			
		||||
          </p>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div data-testid="icon">
 | 
			
		||||
          {isActive && (
 | 
			
		||||
            <FontAwesomeIcon className="tw-text-h2" icon="check-circle" />
 | 
			
		||||
          )}
 | 
			
		||||
      <div className={classNames(cardStyle.header.base, getCardHeaderStyle())}>
 | 
			
		||||
        <div className="tw-flex">
 | 
			
		||||
          <div className="tw-self-start tw-mr-2">
 | 
			
		||||
            <FontAwesomeIcon
 | 
			
		||||
              className="tw-text-xs"
 | 
			
		||||
              icon={isActive ? 'chevron-down' : 'chevron-right'}
 | 
			
		||||
            />
 | 
			
		||||
          </div>
 | 
			
		||||
          <div className="tw-flex tw-flex-col">
 | 
			
		||||
            <h4 className={cardStyle.header.title}>{card.title}</h4>
 | 
			
		||||
            <p className={cardStyle.header.description}>
 | 
			
		||||
              {card.description.replace(/\*/g, '')}
 | 
			
		||||
            </p>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div data-testid="icon">{getCardIcon(card.id)}</div>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div
 | 
			
		||||
        className={classNames(
 | 
			
		||||
 | 
			
		||||
@ -132,7 +132,11 @@ const Description = ({
 | 
			
		||||
                  position="top"
 | 
			
		||||
                  title="Request description"
 | 
			
		||||
                  trigger="mouseenter">
 | 
			
		||||
                  <SVGIcons alt="request-description" icon={Icons.REQUEST} />
 | 
			
		||||
                  <SVGIcons
 | 
			
		||||
                    alt="request-description"
 | 
			
		||||
                    className="tw-mt-2"
 | 
			
		||||
                    icon={Icons.REQUEST}
 | 
			
		||||
                  />
 | 
			
		||||
                </PopOver>
 | 
			
		||||
              </button>
 | 
			
		||||
            ) : null}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user