Skip to content

Commit

Permalink
Fixed auto complete pr coments (#4072)
Browse files Browse the repository at this point in the history
  • Loading branch information
Ankit-Keshari-Vituity committed Feb 9, 2022
1 parent 8002fd8 commit a0a2e02
Show file tree
Hide file tree
Showing 7 changed files with 160 additions and 50 deletions.
@@ -1,4 +1,4 @@
import React from 'react';
import React, { useEffect } from 'react';
import queryString from 'query-string';
import { useLocation } from 'react-router-dom';

Expand All @@ -10,26 +10,36 @@ import MDEditor from '@uiw/react-md-editor';
import TabToolbar from '../../components/styled/TabToolbar';
import { AddLinkModal } from '../../components/styled/AddLinkModal';
import { EmptyTab } from '../../components/styled/EmptyTab';

import { DescriptionEditor } from './components/DescriptionEditor';
import { LinkList } from './components/LinkList';

import { useEntityData, useRefetch, useRouteToTab } from '../../EntityContext';
import { EDITED_DESCRIPTIONS_CACHE_NAME } from '../../utils';

const DocumentationContainer = styled.div`
margin: 0 auto;
padding: 40px 0;
max-width: 550px;
max-width: calc(100% - 10px);
margin: 0 32px;
`;

export const DocumentationTab = () => {
const { entityData } = useEntityData();
const { urn, entityData } = useEntityData();
const refetch = useRefetch();
const description = entityData?.editableProperties?.description || entityData?.properties?.description || '';
const links = entityData?.institutionalMemory?.elements || [];
const localStorageDictionary = localStorage.getItem(EDITED_DESCRIPTIONS_CACHE_NAME);

const routeToTab = useRouteToTab();
const isEditing = queryString.parse(useLocation().search, { parseBooleans: true }).editing;

useEffect(() => {
const editedDescriptions = (localStorageDictionary && JSON.parse(localStorageDictionary)) || {};
if (editedDescriptions.hasOwnProperty(urn)) {
routeToTab({ tabName: 'Documentation', tabParams: { editing: true } });
}
}, [urn, routeToTab, localStorageDictionary]);

return isEditing ? (
<>
<DescriptionEditor onComplete={() => routeToTab({ tabName: 'Documentation' })} />
Expand Down
@@ -1,4 +1,4 @@
import React, { useState } from 'react';
import React, { useState, useEffect } from 'react';
import { message, Button } from 'antd';
import { CheckOutlined } from '@ant-design/icons';

Expand All @@ -10,15 +10,24 @@ import TabToolbar from '../../../components/styled/TabToolbar';
import { GenericEntityUpdate } from '../../../types';
import { useEntityData, useEntityUpdate, useRefetch } from '../../../EntityContext';
import { useUpdateDescriptionMutation } from '../../../../../../graphql/mutations.generated';
import { DiscardDescriptionModal } from './DiscardDescriptionModal';
import { EDITED_DESCRIPTIONS_CACHE_NAME } from '../../../utils';

export const DescriptionEditor = ({ onComplete }: { onComplete?: () => void }) => {
const { urn, entityType, entityData } = useEntityData();
const refetch = useRefetch();
const updateEntity = useEntityUpdate<GenericEntityUpdate>();
const [updateDescriptionMutation] = useUpdateDescriptionMutation();

const description = entityData?.editableProperties?.description || entityData?.properties?.description || '';
const localStorageDictionary = localStorage.getItem(EDITED_DESCRIPTIONS_CACHE_NAME);
const editedDescriptions = (localStorageDictionary && JSON.parse(localStorageDictionary)) || {};
const description = editedDescriptions.hasOwnProperty(urn)
? editedDescriptions[urn]
: entityData?.editableProperties?.description || entityData?.properties?.description || '';

const [updatedDescription, setUpdatedDescription] = useState(description);
const [isDescriptionUpdated, setIsDescriptionUpdated] = useState(editedDescriptions.hasOwnProperty(urn));
const [cancelModalVisible, setCancelModalVisible] = useState(false);

const updateDescriptionLegacy = () => {
return updateEntity?.({
Expand Down Expand Up @@ -55,6 +64,13 @@ export const DescriptionEditor = ({ onComplete }: { onComplete?: () => void }) =
entityUrn: urn,
});
message.success({ content: 'Description Updated', duration: 2 });
// Updating the localStorage after save
delete editedDescriptions[urn];
if (Object.keys(editedDescriptions).length === 0) {
localStorage.removeItem(EDITED_DESCRIPTIONS_CACHE_NAME);
} else {
localStorage.setItem(EDITED_DESCRIPTIONS_CACHE_NAME, JSON.stringify(editedDescriptions));
}
if (onComplete) onComplete();
} catch (e: unknown) {
message.destroy();
Expand All @@ -65,22 +81,74 @@ export const DescriptionEditor = ({ onComplete }: { onComplete?: () => void }) =
refetch?.();
};

// Function to handle all changes in Editor
const handleEditorChange = (editedDescription: string) => {
setUpdatedDescription(editedDescription);
if (editedDescription === description) {
setIsDescriptionUpdated(false);
} else {
setIsDescriptionUpdated(true);
}
};

// Updating the localStorage when the user has paused for 5 sec
useEffect(() => {
let delayDebounceFn: ReturnType<typeof setTimeout>;
const editedDescriptionsLocal = (localStorageDictionary && JSON.parse(localStorageDictionary)) || {};

if (isDescriptionUpdated) {
delayDebounceFn = setTimeout(() => {
editedDescriptionsLocal[urn] = updatedDescription;
localStorage.setItem(EDITED_DESCRIPTIONS_CACHE_NAME, JSON.stringify(editedDescriptionsLocal));
}, 5000);
}
return () => clearTimeout(delayDebounceFn);
}, [urn, isDescriptionUpdated, updatedDescription, localStorageDictionary]);

// Handling the Discard Modal
const showModal = () => {
if (isDescriptionUpdated) {
setCancelModalVisible(true);
} else if (onComplete) onComplete();
};

function onCancel() {
setCancelModalVisible(false);
}

const onDiscard = () => {
delete editedDescriptions[urn];
if (Object.keys(editedDescriptions).length === 0) {
localStorage.removeItem(EDITED_DESCRIPTIONS_CACHE_NAME);
} else {
localStorage.setItem(EDITED_DESCRIPTIONS_CACHE_NAME, JSON.stringify(editedDescriptions));
}
if (onComplete) onComplete();
};

return entityData ? (
<>
<TabToolbar>
<Button type="text" onClick={onComplete}>
Cancel
<Button type="text" onClick={showModal}>
Back
</Button>
<Button onClick={handleSaveDescription}>
<Button onClick={handleSaveDescription} disabled={!isDescriptionUpdated}>
<CheckOutlined /> Save
</Button>
</TabToolbar>
<StyledMDEditor
value={description}
onChange={(v) => setUpdatedDescription(v || '')}
onChange={(v) => handleEditorChange(v || '')}
preview="live"
visiableDragbar={false}
/>
{cancelModalVisible && (
<DiscardDescriptionModal
cancelModalVisible={cancelModalVisible}
onDiscard={onDiscard}
onCancel={onCancel}
/>
)}
</>
) : null;
};
@@ -0,0 +1,29 @@
import React from 'react';
import { Modal, Button } from 'antd';

type Props = {
cancelModalVisible?: boolean;
onDiscard?: () => void;
onCancel?: () => void;
};

export const DiscardDescriptionModal = ({ cancelModalVisible, onDiscard, onCancel }: Props) => {
return (
<>
<Modal
title="Discard Changes"
visible={cancelModalVisible}
destroyOnClose
onCancel={onCancel}
footer={[
<Button type="text" onClick={onCancel}>
Cancel
</Button>,
<Button onClick={onDiscard}>Discard</Button>,
]}
>
<p>Changes will not be saved. Do you want to proceed?</p>
</Modal>
</>
);
};
2 changes: 2 additions & 0 deletions datahub-web-react/src/app/entity/shared/utils.ts
Expand Up @@ -57,3 +57,5 @@ export const singularizeCollectionName = (collectionName: string): string => {

return collectionName;
};

export const EDITED_DESCRIPTIONS_CACHE_NAME = 'editedDescriptions';
72 changes: 36 additions & 36 deletions datahub-web-react/src/app/shared/tags/AddTagTermModal.tsx
Expand Up @@ -2,14 +2,8 @@ import React, { useState } from 'react';
import { message, Button, Modal, Select, Typography } from 'antd';
import styled from 'styled-components';

import { useGetAutoCompleteMultipleResultsLazyQuery } from '../../../graphql/search.generated';
import {
GlobalTags,
EntityType,
AutoCompleteResultForEntity,
GlossaryTerms,
SubResourceType,
} from '../../../types.generated';
import { useGetSearchResultsLazyQuery } from '../../../graphql/search.generated';
import { GlobalTags, EntityType, GlossaryTerms, SubResourceType, SearchResult } from '../../../types.generated';
import CreateTagModal from './CreateTagModal';
import { useEntityRegistry } from '../../useEntityRegistry';
import { IconStyleType } from '../../entity/Entity';
Expand Down Expand Up @@ -74,49 +68,56 @@ export default function AddTagTermModal({
entitySubresource,
type = EntityType.Tag,
}: AddTagModalProps) {
const [getAutoCompleteResults, { loading, data: suggestionsData }] = useGetAutoCompleteMultipleResultsLazyQuery({
fetchPolicy: 'no-cache',
});
const [inputValue, setInputValue] = useState('');
const [selectedValue, setSelectedValue] = useState('');
const [showCreateModal, setShowCreateModal] = useState(false);
const [disableAdd, setDisableAdd] = useState(false);
const entityRegistry = useEntityRegistry();
const [addTagMutation] = useAddTagMutation();
const [addTermMutation] = useAddTermMutation();
const [tagSearch, { data: tagSearchData }] = useGetSearchResultsLazyQuery();
const tagSearchResults = tagSearchData?.search?.searchResults || [];

const autoComplete = (query: string) => {
if (query && query !== '') {
getAutoCompleteResults({
const handleSearch = (text: string) => {
if (text.length > 0) {
tagSearch({
variables: {
input: {
types: [type],
query,
limit: 25,
type: EntityType.Tag,
query: text,
start: 0,
count: 10,
},
},
});
}
};

const options =
suggestionsData?.autoCompleteForMultiple?.suggestions.flatMap((entity: AutoCompleteResultForEntity) =>
entity.suggestions.map((suggestion: string) =>
renderItem(suggestion, entityRegistry.getIcon(entity.type, 14, IconStyleType.TAB_VIEW), entity.type),
),
) || [];
const renderSearchResult = (result: SearchResult) => {
const displayName = entityRegistry.getDisplayName(result.entity.type, result.entity);
const item = renderItem(
displayName,
entityRegistry.getIcon(result.entity.type, 14, IconStyleType.ACCENT),
result.entity.type,
);
return (
<Select.Option value={`${item.value}${NAME_TYPE_SEPARATOR}${item.type}`} key={item.value}>
{item.label}
</Select.Option>
);
};

const inputExistsInAutocomplete = options.some((option) => option.value.toLowerCase() === inputValue.toLowerCase());
const tagSearchOptions = tagSearchResults.map((result) => {
return renderSearchResult(result);
});

const autocompleteOptions =
options.map((option) => (
<Select.Option value={`${option.value}${NAME_TYPE_SEPARATOR}${option.type}`} key={option.value}>
{option.label}
</Select.Option>
)) || [];
const inputExistsInTagSearch = tagSearchResults.some((result: SearchResult) => {
const displayName = entityRegistry.getDisplayName(result.entity.type, result.entity);
return displayName.toLowerCase() === inputValue.toLowerCase();
});

if (!inputExistsInAutocomplete && inputValue.length > 0 && !loading && type === EntityType.Tag) {
autocompleteOptions.push(
if (!inputExistsInTagSearch && inputValue.length > 0 && type === EntityType.Tag) {
tagSearchOptions.push(
<Select.Option value={CREATE_TAG_VALUE} key={CREATE_TAG_VALUE}>
<Typography.Link> Create {inputValue}</Typography.Link>
</Select.Option>,
Expand Down Expand Up @@ -250,20 +251,19 @@ export default function AddTagTermModal({
allowClear
autoFocus
showSearch
placeholder={`Find a ${entityRegistry.getEntityName(type)?.toLowerCase()}`}
placeholder={`Search for ${entityRegistry.getEntityName(type)?.toLowerCase()}...`}
defaultActiveFirstOption={false}
showArrow={false}
filterOption={false}
onSearch={(value: string) => {
autoComplete(value.trim());
handleSearch(value.trim());
setInputValue(value.trim());
}}
onSelect={(selected) =>
selected === CREATE_TAG_VALUE ? setShowCreateModal(true) : setSelectedValue(String(selected))
}
notFoundContent={loading ? 'loading' : 'type to search'}
>
{autocompleteOptions}
{tagSearchOptions}
</TagSelect>
</Modal>
);
Expand Down
1 change: 1 addition & 0 deletions datahub-web-react/src/app/shared/tags/CreateTagModal.tsx
Expand Up @@ -75,6 +75,7 @@ export default function CreateTagModal({
<Modal
title={`Create ${tagName}`}
visible={visible}
onCancel={onClose}
footer={
<>
<Button onClick={onBack} type="text">
Expand Down
Expand Up @@ -111,10 +111,10 @@ describe('TagTermGroup', () => {
</MockedProvider>,
);
expect(queryByText('Add Tag')).toBeInTheDocument();
expect(queryByText('Find a tag')).not.toBeInTheDocument();
expect(queryByText('Search for tag...')).not.toBeInTheDocument();
const AddTagButton = getByText('Add Tag');
fireEvent.click(AddTagButton);
expect(queryByText('Find a tag')).toBeInTheDocument();
expect(queryByText('Search for tag...')).toBeInTheDocument();
});

it('renders create term', () => {
Expand All @@ -133,10 +133,10 @@ describe('TagTermGroup', () => {
</MockedProvider>,
);
expect(queryByText('Add Term')).toBeInTheDocument();
expect(queryByText('Find a glossary term')).not.toBeInTheDocument();
expect(queryByText('Search for glossary term...')).not.toBeInTheDocument();
const AddTagButton = getByText('Add Term');
fireEvent.click(AddTagButton);
expect(queryByText('Find a glossary term')).toBeInTheDocument();
expect(queryByText('Search for glossary term...')).toBeInTheDocument();
});

it('renders terms', () => {
Expand Down

0 comments on commit a0a2e02

Please sign in to comment.