mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-10-05 13:56:41 +00:00
Enhanced Tour feature (#2082)
* Enhanced Tour feature * Fix: #2182 - Tour steps getting skipped * Fix: #2180 - Stat links deactivated on browser back from Tour route * Addressing comments * Addressing comments * Adding license
This commit is contained in:
parent
ad362a31fd
commit
b29fdd3ccc
@ -12,7 +12,7 @@
|
|||||||
"directory": "openmetadata-ui/src/main/resources/ui"
|
"directory": "openmetadata-ui/src/main/resources/ui"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@deuex-solutions/react-tour": "^1.0.0",
|
"@deuex-solutions/react-tour": "^1.1.1",
|
||||||
"@deuex-solutions/redoc": "2.0.0-rc.31",
|
"@deuex-solutions/redoc": "2.0.0-rc.31",
|
||||||
"autoprefixer": "^9.8.6",
|
"autoprefixer": "^9.8.6",
|
||||||
"axios": "^0.21.1",
|
"axios": "^0.21.1",
|
||||||
|
@ -451,7 +451,7 @@ const DatasetDetails: React.FC<DatasetDetailsProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{activeTab === 2 && (
|
{activeTab === 2 && (
|
||||||
<div>
|
<div id="sampleDataDetails">
|
||||||
<SampleDataTable sampleData={getSampleDataWithType()} />
|
<SampleDataTable sampleData={getSampleDataWithType()} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@ -127,9 +127,7 @@ const MyAssetStats: FunctionComponent<Props> = ({
|
|||||||
className="tw-font-medium tw-pl-2"
|
className="tw-font-medium tw-pl-2"
|
||||||
data-testid={data.dataTestId}
|
data-testid={data.dataTestId}
|
||||||
to={data.link}>
|
to={data.link}>
|
||||||
<button
|
<button className="tw-text-grey-body hover:tw-text-primary-hover hover:tw-underline">
|
||||||
className="tw-text-grey-body hover:tw-text-primary-hover hover:tw-underline"
|
|
||||||
disabled={AppState.isTourOpen}>
|
|
||||||
{data.data}
|
{data.data}
|
||||||
</button>
|
</button>
|
||||||
</Link>
|
</Link>
|
||||||
|
@ -17,47 +17,34 @@ import { isEmpty } from 'lodash';
|
|||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
import { Match } from 'Models';
|
import { Match } from 'Models';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import {
|
import { useHistory, useLocation, useRouteMatch } from 'react-router-dom';
|
||||||
Link,
|
|
||||||
NavLink,
|
|
||||||
useHistory,
|
|
||||||
useLocation,
|
|
||||||
useRouteMatch,
|
|
||||||
} from 'react-router-dom';
|
|
||||||
import appState from '../../AppState';
|
import appState from '../../AppState';
|
||||||
import { getVersion } from '../../axiosAPIs/miscAPI';
|
import { getVersion } from '../../axiosAPIs/miscAPI';
|
||||||
import {
|
import {
|
||||||
getExplorePathWithSearch,
|
getExplorePathWithSearch,
|
||||||
navLinkSettings,
|
navLinkSettings,
|
||||||
ROUTES,
|
ROUTES,
|
||||||
TOUR_SEARCH_TERM,
|
|
||||||
} from '../../constants/constants';
|
} from '../../constants/constants';
|
||||||
import { urlGitbookDocs, urlJoinSlack } from '../../constants/url.const';
|
import { urlGitbookDocs, urlJoinSlack } from '../../constants/url.const';
|
||||||
import { CurrentTourPageType } from '../../enums/tour.enum';
|
|
||||||
import { useAuth } from '../../hooks/authHooks';
|
import { useAuth } from '../../hooks/authHooks';
|
||||||
import { userSignOut } from '../../utils/AuthUtils';
|
import { userSignOut } from '../../utils/AuthUtils';
|
||||||
import { addToRecentSearched } from '../../utils/CommonUtils';
|
import { addToRecentSearched } from '../../utils/CommonUtils';
|
||||||
import {
|
|
||||||
inPageSearchOptions,
|
|
||||||
isInPageSearchAllowed,
|
|
||||||
} from '../../utils/RouterUtils';
|
|
||||||
import { activeLink, normalLink } from '../../utils/styleconstant';
|
|
||||||
import SVGIcons, { Icons } from '../../utils/SvgUtils';
|
import SVGIcons, { Icons } from '../../utils/SvgUtils';
|
||||||
import PopOver from '../common/popover/PopOver';
|
|
||||||
import DropDown from '../dropdown/DropDown';
|
|
||||||
import { WhatsNewModal } from '../Modals/WhatsNewModal';
|
|
||||||
import { COOKIE_VERSION } from '../Modals/WhatsNewModal/whatsNewData';
|
import { COOKIE_VERSION } from '../Modals/WhatsNewModal/whatsNewData';
|
||||||
import { ReactComponent as IconDefaultUserProfile } from './../../assets/svg/ic-default-profile.svg';
|
import NavBar from '../nav-bar/NavBar';
|
||||||
import SearchOptions from './SearchOptions';
|
|
||||||
import Suggestions from './Suggestions';
|
|
||||||
|
|
||||||
const cookieStorage = new CookieStorage();
|
const cookieStorage = new CookieStorage();
|
||||||
|
|
||||||
const Appbar: React.FC = (): JSX.Element => {
|
const Appbar: React.FC = (): JSX.Element => {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const { isAuthenticatedRoute, isSignedIn, isFirstTimeUser, isAuthDisabled } =
|
const {
|
||||||
useAuth(location.pathname);
|
isAuthenticatedRoute,
|
||||||
|
isSignedIn,
|
||||||
|
isFirstTimeUser,
|
||||||
|
isAuthDisabled,
|
||||||
|
isTourRoute,
|
||||||
|
} = useAuth(location.pathname);
|
||||||
const match: Match | null = useRouteMatch({
|
const match: Match | null = useRouteMatch({
|
||||||
path: ROUTES.EXPLORE_WITH_SEARCH,
|
path: ROUTES.EXPLORE_WITH_SEARCH,
|
||||||
});
|
});
|
||||||
@ -65,18 +52,15 @@ const Appbar: React.FC = (): JSX.Element => {
|
|||||||
const [searchValue, setSearchValue] = useState(searchQuery);
|
const [searchValue, setSearchValue] = useState(searchQuery);
|
||||||
const [isOpen, setIsOpen] = useState<boolean>(true);
|
const [isOpen, setIsOpen] = useState<boolean>(true);
|
||||||
const [isFeatureModalOpen, setIsFeatureModalOpen] = useState<boolean>(false);
|
const [isFeatureModalOpen, setIsFeatureModalOpen] = useState<boolean>(false);
|
||||||
const [searchIcon, setSearchIcon] = useState<string>('icon-searchv1');
|
|
||||||
|
|
||||||
const [version, setVersion] = useState<string>('');
|
const [version, setVersion] = useState<string>('');
|
||||||
|
|
||||||
const navStyle = (value: boolean) => {
|
const handleFeatureModal = (value: boolean) => {
|
||||||
if (value) return { color: activeLink };
|
setIsFeatureModalOpen(value);
|
||||||
|
|
||||||
return { color: normalLink };
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const openModal = () => {
|
const handleSearchChange = (value: string) => {
|
||||||
setIsFeatureModalOpen(true);
|
setSearchValue(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
const supportLinks = [
|
const supportLinks = [
|
||||||
@ -156,6 +140,40 @@ const Appbar: React.FC = (): JSX.Element => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const profileDropdown = [
|
||||||
|
{
|
||||||
|
name: getUserDisplayName(),
|
||||||
|
to: '',
|
||||||
|
disabled: false,
|
||||||
|
icon: <></>,
|
||||||
|
isText: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Logout',
|
||||||
|
to: '#/action-1',
|
||||||
|
disabled: false,
|
||||||
|
method: userSignOut,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||||
|
const target = e.target as HTMLInputElement;
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
setIsOpen(false);
|
||||||
|
|
||||||
|
addToRecentSearched(target.value);
|
||||||
|
history.push(
|
||||||
|
getExplorePathWithSearch(
|
||||||
|
target.value,
|
||||||
|
// this is for if user is searching from another page
|
||||||
|
location.pathname.startsWith(ROUTES.EXPLORE)
|
||||||
|
? appState.explorePageTab
|
||||||
|
: 'tables'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setSearchValue(searchQuery);
|
setSearchValue(searchQuery);
|
||||||
}, [searchQuery]);
|
}, [searchQuery]);
|
||||||
@ -183,217 +201,20 @@ const Appbar: React.FC = (): JSX.Element => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{isAuthenticatedRoute && isSignedIn ? (
|
{isAuthenticatedRoute && isSignedIn && !isTourRoute ? (
|
||||||
<div className="tw-h-16 tw-py-3 tw-border-b-2 tw-border-separator">
|
<NavBar
|
||||||
<div className="tw-flex tw-items-center tw-flex-row tw-justify-between tw-flex-nowrap tw-px-6 centered-layout">
|
handleFeatureModal={handleFeatureModal}
|
||||||
<div className="tw-flex tw-items-center tw-flex-row tw-justify-between tw-flex-nowrap">
|
handleKeyDown={handleKeyDown}
|
||||||
<NavLink id="openmetadata_logo" to="/">
|
handleSearchBoxOpen={setIsOpen}
|
||||||
<SVGIcons
|
handleSearchChange={handleSearchChange}
|
||||||
alt="OpenMetadata Logo"
|
isFeatureModalOpen={isFeatureModalOpen}
|
||||||
icon={Icons.LOGO}
|
isSearchBoxOpen={isOpen}
|
||||||
width="90"
|
pathname={location.pathname}
|
||||||
/>
|
profileDropdown={profileDropdown}
|
||||||
</NavLink>
|
searchValue={searchValue || ''}
|
||||||
<div className="tw-ml-5">
|
settingDropdown={navLinkSettings}
|
||||||
<NavLink
|
supportDropdown={supportLinks}
|
||||||
className="tw-nav focus:tw-no-underline"
|
/>
|
||||||
data-testid="appbar-item"
|
|
||||||
id="explore"
|
|
||||||
style={navStyle(location.pathname.startsWith('/explore'))}
|
|
||||||
to={{
|
|
||||||
pathname: '/explore/tables',
|
|
||||||
}}>
|
|
||||||
Explore
|
|
||||||
</NavLink>
|
|
||||||
<DropDown
|
|
||||||
dropDownList={navLinkSettings}
|
|
||||||
label="Settings"
|
|
||||||
type="link"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="tw-flex-none tw-relative tw-justify-items-center tw-ml-auto"
|
|
||||||
data-testid="appbar-item">
|
|
||||||
<SVGIcons
|
|
||||||
alt="icon-search"
|
|
||||||
className="tw-absolute tw-block tw-z-10 tw-w-4 tw-h-4 tw-right-2.5 tw-top-2.5 tw-leading-8 tw-text-center tw-pointer-events-none"
|
|
||||||
icon={searchIcon}
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
autoComplete="off"
|
|
||||||
className="tw-relative search-grey tw-rounded tw-border tw-border-main focus:tw-outline-none tw-pl-2 tw-pt-2 tw-pb-1.5 tw-form-inputs"
|
|
||||||
data-testid="searchBox"
|
|
||||||
id="searchBox"
|
|
||||||
placeholder="Search for Table, Topics, Dashboards and Pipeline"
|
|
||||||
type="text"
|
|
||||||
value={searchValue || ''}
|
|
||||||
onBlur={() => setSearchIcon('icon-searchv1')}
|
|
||||||
onChange={(e) => {
|
|
||||||
setSearchValue(e.target.value);
|
|
||||||
}}
|
|
||||||
onFocus={() => setSearchIcon('icon-searchv1color')}
|
|
||||||
onKeyDown={(e: React.KeyboardEvent<HTMLInputElement>) => {
|
|
||||||
const target = e.target as HTMLInputElement;
|
|
||||||
if (e.key === 'Enter') {
|
|
||||||
setIsOpen(false);
|
|
||||||
// below code is for tour feature
|
|
||||||
if (location.pathname.includes(ROUTES.TOUR)) {
|
|
||||||
if (searchValue === TOUR_SEARCH_TERM) {
|
|
||||||
appState.currentTourPage =
|
|
||||||
CurrentTourPageType.EXPLORE_PAGE;
|
|
||||||
setSearchValue('');
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
addToRecentSearched(target.value);
|
|
||||||
history.push(
|
|
||||||
getExplorePathWithSearch(
|
|
||||||
target.value,
|
|
||||||
// this is for if user is searching from another page
|
|
||||||
location.pathname.startsWith(ROUTES.EXPLORE)
|
|
||||||
? appState.explorePageTab
|
|
||||||
: 'tables'
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{!location.pathname.includes(ROUTES.TOUR) &&
|
|
||||||
searchValue &&
|
|
||||||
(isInPageSearchAllowed(location.pathname) ? (
|
|
||||||
<SearchOptions
|
|
||||||
isOpen={isOpen}
|
|
||||||
options={inPageSearchOptions(location.pathname)}
|
|
||||||
searchText={searchValue}
|
|
||||||
selectOption={(text) => {
|
|
||||||
appState.inPageSearchText = text;
|
|
||||||
}}
|
|
||||||
setIsOpen={setIsOpen}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<Suggestions
|
|
||||||
isOpen={isOpen}
|
|
||||||
searchText={searchValue}
|
|
||||||
setIsOpen={setIsOpen}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<div className="tw-flex tw-ml-auto tw-pl-36">
|
|
||||||
<button
|
|
||||||
className="tw-nav focus:tw-no-underline hover:tw-underline"
|
|
||||||
data-testid="whatsnew-modal"
|
|
||||||
onClick={openModal}>
|
|
||||||
<PopOver
|
|
||||||
position="bottom"
|
|
||||||
title="What's new?"
|
|
||||||
trigger="mouseenter">
|
|
||||||
<SVGIcons
|
|
||||||
alt="Doc icon"
|
|
||||||
className="tw-align-middle tw-mr-1"
|
|
||||||
icon={Icons.WHATS_NEW}
|
|
||||||
width="20"
|
|
||||||
/>
|
|
||||||
</PopOver>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
className="tw-nav focus:tw-no-underline hover:tw-underline"
|
|
||||||
data-testid="tour">
|
|
||||||
<PopOver
|
|
||||||
position="bottom"
|
|
||||||
title="Take a tour"
|
|
||||||
trigger="mouseenter">
|
|
||||||
<Link to={ROUTES.TOUR}>
|
|
||||||
<SVGIcons
|
|
||||||
alt="tour icon"
|
|
||||||
className="tw-align-middle tw-mr-0.5"
|
|
||||||
icon={Icons.TOUR}
|
|
||||||
width="20"
|
|
||||||
/>
|
|
||||||
</Link>
|
|
||||||
</PopOver>
|
|
||||||
</button>
|
|
||||||
<div>
|
|
||||||
<DropDown
|
|
||||||
dropDownList={supportLinks}
|
|
||||||
icon={
|
|
||||||
<PopOver
|
|
||||||
position="bottom"
|
|
||||||
title="Help"
|
|
||||||
trigger="mouseenter">
|
|
||||||
<SVGIcons
|
|
||||||
alt="Doc icon"
|
|
||||||
className="tw-align-middle tw-mt-0.5 tw-mr-1"
|
|
||||||
icon={Icons.HELP_CIRCLE}
|
|
||||||
width="20"
|
|
||||||
/>
|
|
||||||
</PopOver>
|
|
||||||
}
|
|
||||||
isDropDownIconVisible={false}
|
|
||||||
isLableVisible={false}
|
|
||||||
label="Need Help"
|
|
||||||
type="link"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div data-testid="dropdown-profile">
|
|
||||||
<DropDown
|
|
||||||
dropDownList={[
|
|
||||||
{
|
|
||||||
name: getUserDisplayName(),
|
|
||||||
to: '',
|
|
||||||
disabled: false,
|
|
||||||
icon: <></>,
|
|
||||||
isText: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Logout',
|
|
||||||
to: '#/action-1',
|
|
||||||
disabled: false,
|
|
||||||
method: userSignOut,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
icon={
|
|
||||||
<>
|
|
||||||
<PopOver
|
|
||||||
position="bottom"
|
|
||||||
title="Profile"
|
|
||||||
trigger="mouseenter">
|
|
||||||
{appState?.userDetails?.profile?.images?.image512 ? (
|
|
||||||
<div className="profile-image tw--mr-2">
|
|
||||||
<img
|
|
||||||
alt="user"
|
|
||||||
src={appState.userDetails.profile.images.image512}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<IconDefaultUserProfile
|
|
||||||
className="tw--mr-2"
|
|
||||||
style={{
|
|
||||||
height: '22px',
|
|
||||||
width: '22px',
|
|
||||||
borderRadius: '50%',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</PopOver>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
isDropDownIconVisible={false}
|
|
||||||
type="link"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{isFeatureModalOpen && (
|
|
||||||
<WhatsNewModal
|
|
||||||
header="What’s new!"
|
|
||||||
onCancel={() => setIsFeatureModalOpen(false)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
) : null}
|
) : null}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { isNil, lowerCase } from 'lodash';
|
import { camelCase, isNil } from 'lodash';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { TITLE_FOR_NON_OWNER_ACTION } from '../../../constants/constants';
|
import { TITLE_FOR_NON_OWNER_ACTION } from '../../../constants/constants';
|
||||||
import { getCountBadge } from '../../../utils/CommonUtils';
|
import { getCountBadge } from '../../../utils/CommonUtils';
|
||||||
@ -58,7 +58,7 @@ const TabsPane = ({ activeTab, setActiveTab, tabs, className = '' }: Props) => {
|
|||||||
<button
|
<button
|
||||||
className={getTabClasses(tab.position, activeTab)}
|
className={getTabClasses(tab.position, activeTab)}
|
||||||
data-testid="tab"
|
data-testid="tab"
|
||||||
id={lowerCase(tab.name)}
|
id={camelCase(tab.name)}
|
||||||
onClick={() => setActiveTab?.(tab.position)}>
|
onClick={() => setActiveTab?.(tab.position)}>
|
||||||
<SVGIcons
|
<SVGIcons
|
||||||
alt={tab.icon.alt}
|
alt={tab.icon.alt}
|
||||||
@ -77,7 +77,7 @@ const TabsPane = ({ activeTab, setActiveTab, tabs, className = '' }: Props) => {
|
|||||||
<button
|
<button
|
||||||
className={getTabClasses(tab.position, activeTab)}
|
className={getTabClasses(tab.position, activeTab)}
|
||||||
data-testid="tab"
|
data-testid="tab"
|
||||||
id={lowerCase(tab.name)}
|
id={camelCase(tab.name)}
|
||||||
key={tab.position}
|
key={tab.position}
|
||||||
onClick={() => setActiveTab?.(tab.position)}>
|
onClick={() => setActiveTab?.(tab.position)}>
|
||||||
<SVGIcons
|
<SVGIcons
|
||||||
|
@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021 Collate
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { DropDownListItem } from '../dropdown/types';
|
||||||
|
|
||||||
|
export interface NavBarProps {
|
||||||
|
settingDropdown: DropDownListItem[];
|
||||||
|
supportDropdown: DropDownListItem[];
|
||||||
|
profileDropdown: DropDownListItem[];
|
||||||
|
searchValue: string;
|
||||||
|
isTourRoute?: boolean;
|
||||||
|
isFeatureModalOpen: boolean;
|
||||||
|
pathname: string;
|
||||||
|
isSearchBoxOpen: boolean;
|
||||||
|
handleSearchBoxOpen: (value: boolean) => void;
|
||||||
|
handleFeatureModal: (value: boolean) => void;
|
||||||
|
handleSearchChange: (value: string) => void;
|
||||||
|
handleKeyDown: (e: React.KeyboardEvent<HTMLInputElement>) => void;
|
||||||
|
}
|
@ -0,0 +1,221 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021 Collate
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { Link, NavLink } from 'react-router-dom';
|
||||||
|
import AppState from '../../AppState';
|
||||||
|
import { ROUTES } from '../../constants/constants';
|
||||||
|
import {
|
||||||
|
inPageSearchOptions,
|
||||||
|
isInPageSearchAllowed,
|
||||||
|
} from '../../utils/RouterUtils';
|
||||||
|
import { activeLink, normalLink } from '../../utils/styleconstant';
|
||||||
|
import SVGIcons, { Icons } from '../../utils/SvgUtils';
|
||||||
|
import SearchOptions from '../app-bar/SearchOptions';
|
||||||
|
import Suggestions from '../app-bar/Suggestions';
|
||||||
|
import PopOver from '../common/popover/PopOver';
|
||||||
|
import DropDown from '../dropdown/DropDown';
|
||||||
|
import { WhatsNewModal } from '../Modals/WhatsNewModal';
|
||||||
|
import { ReactComponent as IconDefaultUserProfile } from './../../assets/svg/ic-default-profile.svg';
|
||||||
|
import { NavBarProps } from './NavBar.interface';
|
||||||
|
|
||||||
|
const NavBar = ({
|
||||||
|
settingDropdown,
|
||||||
|
supportDropdown,
|
||||||
|
profileDropdown,
|
||||||
|
searchValue,
|
||||||
|
isFeatureModalOpen,
|
||||||
|
isTourRoute = false,
|
||||||
|
pathname,
|
||||||
|
isSearchBoxOpen,
|
||||||
|
handleSearchBoxOpen,
|
||||||
|
handleFeatureModal,
|
||||||
|
handleSearchChange,
|
||||||
|
handleKeyDown,
|
||||||
|
}: NavBarProps) => {
|
||||||
|
const [searchIcon, setSearchIcon] = useState<string>('icon-searchv1');
|
||||||
|
const navStyle = (value: boolean) => {
|
||||||
|
if (value) return { color: activeLink };
|
||||||
|
|
||||||
|
return { color: normalLink };
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="tw-h-16 tw-py-3 tw-border-b-2 tw-border-separator">
|
||||||
|
<div className="tw-flex tw-items-center tw-flex-row tw-justify-between tw-flex-nowrap tw-px-6 centered-layout">
|
||||||
|
<div className="tw-flex tw-items-center tw-flex-row tw-justify-between tw-flex-nowrap">
|
||||||
|
<NavLink id="openmetadata_logo" to="/">
|
||||||
|
<SVGIcons alt="OpenMetadata Logo" icon={Icons.LOGO} width="90" />
|
||||||
|
</NavLink>
|
||||||
|
<div className="tw-ml-5">
|
||||||
|
<NavLink
|
||||||
|
className="tw-nav focus:tw-no-underline"
|
||||||
|
data-testid="appbar-item"
|
||||||
|
id="explore"
|
||||||
|
style={navStyle(pathname.startsWith('/explore'))}
|
||||||
|
to={{
|
||||||
|
pathname: '/explore/tables',
|
||||||
|
}}>
|
||||||
|
Explore
|
||||||
|
</NavLink>
|
||||||
|
<DropDown
|
||||||
|
dropDownList={settingDropdown}
|
||||||
|
label="Settings"
|
||||||
|
type="link"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="tw-flex-none tw-relative tw-justify-items-center tw-ml-auto"
|
||||||
|
data-testid="appbar-item">
|
||||||
|
<SVGIcons
|
||||||
|
alt="icon-search"
|
||||||
|
className="tw-absolute tw-block tw-z-10 tw-w-4 tw-h-4 tw-right-2.5 tw-top-2.5 tw-leading-8 tw-text-center tw-pointer-events-none"
|
||||||
|
icon={searchIcon}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
autoComplete="off"
|
||||||
|
className="tw-relative search-grey tw-rounded tw-border tw-border-main focus:tw-outline-none tw-pl-2 tw-pt-2 tw-pb-1.5 tw-form-inputs"
|
||||||
|
data-testid="searchBox"
|
||||||
|
id="searchBox"
|
||||||
|
placeholder="Search for Table, Topics, Dashboards and Pipeline"
|
||||||
|
type="text"
|
||||||
|
value={searchValue}
|
||||||
|
onBlur={() => setSearchIcon('icon-searchv1')}
|
||||||
|
onChange={(e) => {
|
||||||
|
handleSearchChange(e.target.value);
|
||||||
|
}}
|
||||||
|
onFocus={() => setSearchIcon('icon-searchv1color')}
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
|
/>
|
||||||
|
{!isTourRoute &&
|
||||||
|
searchValue &&
|
||||||
|
(isInPageSearchAllowed(pathname) ? (
|
||||||
|
<SearchOptions
|
||||||
|
isOpen={isSearchBoxOpen}
|
||||||
|
options={inPageSearchOptions(pathname)}
|
||||||
|
searchText={searchValue}
|
||||||
|
selectOption={(text) => {
|
||||||
|
AppState.inPageSearchText = text;
|
||||||
|
}}
|
||||||
|
setIsOpen={handleSearchBoxOpen}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Suggestions
|
||||||
|
isOpen={isSearchBoxOpen}
|
||||||
|
searchText={searchValue}
|
||||||
|
setIsOpen={handleSearchBoxOpen}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="tw-flex tw-ml-auto tw-pl-36">
|
||||||
|
<button
|
||||||
|
className="tw-nav focus:tw-no-underline hover:tw-underline"
|
||||||
|
data-testid="whatsnew-modal"
|
||||||
|
onClick={() => handleFeatureModal(true)}>
|
||||||
|
<PopOver
|
||||||
|
position="bottom"
|
||||||
|
title="What's new?"
|
||||||
|
trigger="mouseenter">
|
||||||
|
<SVGIcons
|
||||||
|
alt="Doc icon"
|
||||||
|
className="tw-align-middle tw-mr-1"
|
||||||
|
icon={Icons.WHATS_NEW}
|
||||||
|
width="20"
|
||||||
|
/>
|
||||||
|
</PopOver>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="tw-nav focus:tw-no-underline hover:tw-underline"
|
||||||
|
data-testid="tour">
|
||||||
|
<PopOver
|
||||||
|
position="bottom"
|
||||||
|
title="Take a tour"
|
||||||
|
trigger="mouseenter">
|
||||||
|
<Link to={ROUTES.TOUR}>
|
||||||
|
<SVGIcons
|
||||||
|
alt="tour icon"
|
||||||
|
className="tw-align-middle tw-mr-0.5"
|
||||||
|
icon={Icons.TOUR}
|
||||||
|
width="20"
|
||||||
|
/>
|
||||||
|
</Link>
|
||||||
|
</PopOver>
|
||||||
|
</button>
|
||||||
|
<div>
|
||||||
|
<DropDown
|
||||||
|
dropDownList={supportDropdown}
|
||||||
|
icon={
|
||||||
|
<PopOver position="bottom" title="Help" trigger="mouseenter">
|
||||||
|
<SVGIcons
|
||||||
|
alt="Doc icon"
|
||||||
|
className="tw-align-middle tw-mt-0.5 tw-mr-1"
|
||||||
|
icon={Icons.HELP_CIRCLE}
|
||||||
|
width="20"
|
||||||
|
/>
|
||||||
|
</PopOver>
|
||||||
|
}
|
||||||
|
isDropDownIconVisible={false}
|
||||||
|
isLableVisible={false}
|
||||||
|
label="Need Help"
|
||||||
|
type="link"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div data-testid="dropdown-profile">
|
||||||
|
<DropDown
|
||||||
|
dropDownList={profileDropdown}
|
||||||
|
icon={
|
||||||
|
<>
|
||||||
|
<PopOver
|
||||||
|
position="bottom"
|
||||||
|
title="Profile"
|
||||||
|
trigger="mouseenter">
|
||||||
|
{AppState?.userDetails?.profile?.images?.image512 ? (
|
||||||
|
<div className="profile-image tw--mr-2">
|
||||||
|
<img
|
||||||
|
alt="user"
|
||||||
|
src={AppState.userDetails.profile.images.image512}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<IconDefaultUserProfile
|
||||||
|
className="tw--mr-2"
|
||||||
|
style={{
|
||||||
|
height: '22px',
|
||||||
|
width: '22px',
|
||||||
|
borderRadius: '50%',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</PopOver>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
isDropDownIconVisible={false}
|
||||||
|
type="link"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{isFeatureModalOpen && (
|
||||||
|
<WhatsNewModal
|
||||||
|
header="What’s new!"
|
||||||
|
onCancel={() => handleFeatureModal(false)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NavBar;
|
@ -13,210 +13,14 @@
|
|||||||
|
|
||||||
import ReactTutorial from '@deuex-solutions/react-tour';
|
import ReactTutorial from '@deuex-solutions/react-tour';
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
|
import { TourSteps } from 'Models';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
import AppState from '../../AppState';
|
|
||||||
import { TOUR_SEARCH_TERM } from '../../constants/constants';
|
|
||||||
import { CurrentTourPageType } from '../../enums/tour.enum';
|
|
||||||
import { useTour } from '../../hooks/useTour';
|
import { useTour } from '../../hooks/useTour';
|
||||||
import TourEndModal from '../Modals/TourEndModal/TourEndModal';
|
import TourEndModal from '../Modals/TourEndModal/TourEndModal';
|
||||||
|
|
||||||
type Steps = {
|
const Tour = ({ steps }: { steps: TourSteps[] }) => {
|
||||||
content?: string | React.ReactNode;
|
|
||||||
actionType?: string;
|
|
||||||
position?: string | number[];
|
|
||||||
selector?: string;
|
|
||||||
userTypeText?: string;
|
|
||||||
waitTimer?: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getSteps = (value: string) => {
|
|
||||||
const modifiedValue = value.substr(0, 6);
|
|
||||||
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
content: () => (
|
|
||||||
<p>
|
|
||||||
Discover all your data assets in a single place with{' '}
|
|
||||||
<strong>OpenMetadata</strong>, a centralized metadata store.
|
|
||||||
Collaborate with your team and get a holistic picture of the data in
|
|
||||||
your organization.
|
|
||||||
</p>
|
|
||||||
),
|
|
||||||
position: [5, 360],
|
|
||||||
stepInteraction: false,
|
|
||||||
selector: '#assetStatsCount',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
content: () => (
|
|
||||||
<p>
|
|
||||||
<strong>Activity Feeds</strong> help you understand how the data is
|
|
||||||
changing in your organization.
|
|
||||||
</p>
|
|
||||||
),
|
|
||||||
position: [540, 540],
|
|
||||||
selector: '#feedData',
|
|
||||||
stepInteraction: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
content: () => (
|
|
||||||
<p>
|
|
||||||
Look up for matching data assets by "name",
|
|
||||||
"description", "column name", and so on from the{' '}
|
|
||||||
<strong>search</strong> box.
|
|
||||||
</p>
|
|
||||||
),
|
|
||||||
position: [535, 70],
|
|
||||||
selector: '#searchBox',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
content: () => (
|
|
||||||
<p>
|
|
||||||
In the search box, type <strong>"{modifiedValue}"</strong>.
|
|
||||||
Hit <strong>Enter.</strong>
|
|
||||||
</p>
|
|
||||||
),
|
|
||||||
actionType: 'enter',
|
|
||||||
userTypeText: modifiedValue,
|
|
||||||
position: [535, 70],
|
|
||||||
selector: '#searchBox',
|
|
||||||
beforeNext: () => {
|
|
||||||
AppState.currentTourPage = CurrentTourPageType.EXPLORE_PAGE;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
beforePrev: () => {
|
|
||||||
AppState.currentTourPage = CurrentTourPageType.MY_DATA_PAGE;
|
|
||||||
},
|
|
||||||
content: () => (
|
|
||||||
<p>
|
|
||||||
From the <strong>"Explore"</strong> page, get to know the
|
|
||||||
details on the table entity, tier, usage, and database information
|
|
||||||
</p>
|
|
||||||
),
|
|
||||||
selector: '#tabledatacard0',
|
|
||||||
stepInteraction: false,
|
|
||||||
position: [550, 310],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
content: () => (
|
|
||||||
<p>
|
|
||||||
Click on the <strong>title of the asset</strong> to view more details.
|
|
||||||
</p>
|
|
||||||
),
|
|
||||||
actionType: 'click',
|
|
||||||
selector: '#tabledatacard0Title',
|
|
||||||
position: [210, 190],
|
|
||||||
beforeNext: () => {
|
|
||||||
AppState.currentTourPage = CurrentTourPageType.DATASET_PAGE;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
beforePrev: () => {
|
|
||||||
AppState.currentTourPage = CurrentTourPageType.EXPLORE_PAGE;
|
|
||||||
},
|
|
||||||
content: () => (
|
|
||||||
<p>
|
|
||||||
{' '}
|
|
||||||
Get to know the table <strong>schema.</strong> Add a description.
|
|
||||||
</p>
|
|
||||||
),
|
|
||||||
stepInteraction: false,
|
|
||||||
position: [540, 65],
|
|
||||||
arrowPosition: 'bottom',
|
|
||||||
selector: '#schemaDetails',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
beforePrev: () => {
|
|
||||||
AppState.activeTabforTourDatasetPage = 1;
|
|
||||||
},
|
|
||||||
beforeNext: () => {
|
|
||||||
AppState.activeTabforTourDatasetPage = 2;
|
|
||||||
},
|
|
||||||
actionType: 'click',
|
|
||||||
content: () => (
|
|
||||||
<p>
|
|
||||||
Click on the <strong>"Profiler"</strong> tab.
|
|
||||||
</p>
|
|
||||||
),
|
|
||||||
position: [20, 240],
|
|
||||||
selector: '#profiler',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
content: () => (
|
|
||||||
<p>
|
|
||||||
Discover assets with the <strong>Data Profiler</strong>. Get to know
|
|
||||||
the table usage stats, check for null values and duplicates, and
|
|
||||||
understand the column data distributions.
|
|
||||||
</p>
|
|
||||||
),
|
|
||||||
arrowPosition: 'bottom',
|
|
||||||
stepInteraction: false,
|
|
||||||
position: [530, 20],
|
|
||||||
selector: '#profilerDetails',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
beforePrev: () => {
|
|
||||||
AppState.activeTabforTourDatasetPage = 2;
|
|
||||||
},
|
|
||||||
beforeNext: () => {
|
|
||||||
AppState.activeTabforTourDatasetPage = 3;
|
|
||||||
},
|
|
||||||
actionType: 'click',
|
|
||||||
content: () => (
|
|
||||||
<p>
|
|
||||||
Click on the <strong>"Lineage"</strong> tab
|
|
||||||
</p>
|
|
||||||
),
|
|
||||||
position: [140, 240],
|
|
||||||
selector: '#lineage',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
content: () => (
|
|
||||||
<p>
|
|
||||||
With <strong>Lineage</strong>, trace the path of data across tables,
|
|
||||||
pipelines, & dashboards.
|
|
||||||
</p>
|
|
||||||
),
|
|
||||||
position: [530, 45],
|
|
||||||
stepInteraction: false,
|
|
||||||
arrowPosition: 'bottom',
|
|
||||||
selector: '#lineageDetails',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
beforeNext: () => {
|
|
||||||
AppState.activeTabforTourDatasetPage = 5;
|
|
||||||
},
|
|
||||||
actionType: 'click',
|
|
||||||
content: () => (
|
|
||||||
<p>
|
|
||||||
Click on the <strong>"Manage"</strong> tab
|
|
||||||
</p>
|
|
||||||
),
|
|
||||||
position: [260, 240],
|
|
||||||
selector: '#manage',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
beforePrev: () => {
|
|
||||||
AppState.activeTabforTourDatasetPage = 3;
|
|
||||||
},
|
|
||||||
content: () => (
|
|
||||||
<p>
|
|
||||||
From <strong>"Manage"</strong>, you can claim ownership, and
|
|
||||||
set the tiers.
|
|
||||||
</p>
|
|
||||||
),
|
|
||||||
position: [560, 60],
|
|
||||||
arrowPosition: 'bottom',
|
|
||||||
stepInteraction: false,
|
|
||||||
selector: '#manageTabDetails',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
const Tour = () => {
|
|
||||||
const { isTourOpen, handleIsTourOpen } = useTour();
|
const { isTourOpen, handleIsTourOpen } = useTour();
|
||||||
const [steps] = useState<Steps[]>(getSteps(TOUR_SEARCH_TERM));
|
|
||||||
const [showTourEndModal, setShowTourEndModal] = useState(false);
|
const [showTourEndModal, setShowTourEndModal] = useState(false);
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
|
||||||
@ -230,6 +34,7 @@ const Tour = () => {
|
|||||||
<ReactTutorial
|
<ReactTutorial
|
||||||
disableDotsNavigation
|
disableDotsNavigation
|
||||||
disableKeyboardNavigation
|
disableKeyboardNavigation
|
||||||
|
showCloseButton
|
||||||
showNumber
|
showNumber
|
||||||
accentColor="#7147E8"
|
accentColor="#7147E8"
|
||||||
inViewThreshold={200}
|
inViewThreshold={200}
|
||||||
@ -251,9 +56,10 @@ const Tour = () => {
|
|||||||
}
|
}
|
||||||
maskColor="#302E36"
|
maskColor="#302E36"
|
||||||
playTour={isTourOpen}
|
playTour={isTourOpen}
|
||||||
stepWaitTimer={100}
|
stepWaitTimer={300}
|
||||||
steps={steps}
|
steps={steps}
|
||||||
onRequestClose={() => handleIsTourOpen(false)}
|
onRequestClose={() => handleIsTourOpen(false)}
|
||||||
|
onRequestSkip={handleModalSubmit}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
|
@ -1,3 +1,16 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021 Collate
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
|
||||||
export const mockFeedData = [
|
export const mockFeedData = [
|
||||||
|
@ -21,6 +21,7 @@ export const useAuth = (pathname = '') => {
|
|||||||
pathname !== ROUTES.SIGNUP &&
|
pathname !== ROUTES.SIGNUP &&
|
||||||
pathname !== ROUTES.SIGNIN &&
|
pathname !== ROUTES.SIGNIN &&
|
||||||
pathname !== ROUTES.CALLBACK;
|
pathname !== ROUTES.CALLBACK;
|
||||||
|
const isTourRoute = pathname === ROUTES.TOUR;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isSigningIn: authProvider.signingIn,
|
isSigningIn: authProvider.signingIn,
|
||||||
@ -31,9 +32,10 @@ export const useAuth = (pathname = '') => {
|
|||||||
!authProvider.signingIn &&
|
!authProvider.signingIn &&
|
||||||
isEmpty(userDetails) &&
|
isEmpty(userDetails) &&
|
||||||
isEmpty(newUser),
|
isEmpty(newUser),
|
||||||
isAuthenticatedRoute: isAuthenticatedRoute,
|
isAuthenticatedRoute,
|
||||||
isAuthDisabled: authDisabled,
|
isAuthDisabled: authDisabled,
|
||||||
isAdminUser: userDetails?.isAdmin,
|
isAdminUser: userDetails?.isAdmin,
|
||||||
isFirstTimeUser: !isEmpty(userDetails) && !isEmpty(newUser),
|
isFirstTimeUser: !isEmpty(userDetails) && !isEmpty(newUser),
|
||||||
|
isTourRoute,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -423,6 +423,15 @@ declare module 'Models' {
|
|||||||
showLabel?: boolean;
|
showLabel?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TourSteps = {
|
||||||
|
content?: string | React.ReactNode;
|
||||||
|
actionType?: string;
|
||||||
|
position?: string | number[];
|
||||||
|
selector?: string;
|
||||||
|
userTypeText?: string;
|
||||||
|
waitTimer?: number;
|
||||||
|
};
|
||||||
|
|
||||||
export interface FormErrorData {
|
export interface FormErrorData {
|
||||||
[key: string]: string | undefined;
|
[key: string]: string | undefined;
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,20 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021 Collate
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
import { LeafNodes, SearchResponse } from 'Models';
|
import { LeafNodes, SearchResponse } from 'Models';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { useLocation } from 'react-router-dom';
|
||||||
import AppState from '../../AppState';
|
import AppState from '../../AppState';
|
||||||
import DatasetDetails from '../../components/DatasetDetails/DatasetDetails.component';
|
import DatasetDetails from '../../components/DatasetDetails/DatasetDetails.component';
|
||||||
import { DatasetOwner } from '../../components/DatasetDetails/DatasetDetails.interface';
|
import { DatasetOwner } from '../../components/DatasetDetails/DatasetDetails.interface';
|
||||||
@ -8,7 +22,9 @@ import Explore from '../../components/Explore/Explore.component';
|
|||||||
import { ExploreSearchData } from '../../components/Explore/explore.interface';
|
import { ExploreSearchData } from '../../components/Explore/explore.interface';
|
||||||
import MyData from '../../components/MyData/MyData.component';
|
import MyData from '../../components/MyData/MyData.component';
|
||||||
import { MyDataProps } from '../../components/MyData/MyData.interface';
|
import { MyDataProps } from '../../components/MyData/MyData.interface';
|
||||||
|
import NavBar from '../../components/nav-bar/NavBar';
|
||||||
import Tour from '../../components/tour/Tour';
|
import Tour from '../../components/tour/Tour';
|
||||||
|
import { ROUTES, TOUR_SEARCH_TERM } from '../../constants/constants';
|
||||||
import {
|
import {
|
||||||
mockDatasetData,
|
mockDatasetData,
|
||||||
mockFeedData,
|
mockFeedData,
|
||||||
@ -22,6 +38,7 @@ import {
|
|||||||
} from '../../generated/entity/data/table';
|
} from '../../generated/entity/data/table';
|
||||||
import { TagLabel } from '../../generated/type/tagLabel';
|
import { TagLabel } from '../../generated/type/tagLabel';
|
||||||
import { useTour } from '../../hooks/useTour';
|
import { useTour } from '../../hooks/useTour';
|
||||||
|
import { getSteps } from '../../utils/TourUtils';
|
||||||
|
|
||||||
const mockData = {
|
const mockData = {
|
||||||
data: { hits: { hits: [] } },
|
data: { hits: { hits: [] } },
|
||||||
@ -36,6 +53,7 @@ const exploreCount = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const TourPage = () => {
|
const TourPage = () => {
|
||||||
|
const location = useLocation();
|
||||||
const { handleIsTourOpen } = useTour();
|
const { handleIsTourOpen } = useTour();
|
||||||
const [currentPage, setCurrentPage] = useState<CurrentTourPageType>(
|
const [currentPage, setCurrentPage] = useState<CurrentTourPageType>(
|
||||||
AppState.currentTourPage
|
AppState.currentTourPage
|
||||||
@ -47,11 +65,29 @@ const TourPage = () => {
|
|||||||
AppState.activeTabforTourDatasetPage
|
AppState.activeTabforTourDatasetPage
|
||||||
);
|
);
|
||||||
const [explorePageCounts, setExplorePageCounts] = useState(exploreCount);
|
const [explorePageCounts, setExplorePageCounts] = useState(exploreCount);
|
||||||
|
const [searchValue, setSearchValue] = useState('');
|
||||||
|
|
||||||
const handleCountChange = () => {
|
const handleCountChange = () => {
|
||||||
setExplorePageCounts(exploreCount);
|
setExplorePageCounts(exploreCount);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const clearSearchTerm = () => {
|
||||||
|
setSearchValue('');
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
if (location.pathname.includes(ROUTES.TOUR)) {
|
||||||
|
if (searchValue === TOUR_SEARCH_TERM) {
|
||||||
|
AppState.currentTourPage = CurrentTourPageType.EXPLORE_PAGE;
|
||||||
|
clearSearchTerm();
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
handleIsTourOpen(true);
|
handleIsTourOpen(true);
|
||||||
AppState.currentTourPage = CurrentTourPageType.MY_DATA_PAGE;
|
AppState.currentTourPage = CurrentTourPageType.MY_DATA_PAGE;
|
||||||
@ -164,7 +200,21 @@ const TourPage = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Tour />
|
<NavBar
|
||||||
|
isTourRoute
|
||||||
|
handleFeatureModal={handleCountChange}
|
||||||
|
handleKeyDown={handleKeyDown}
|
||||||
|
handleSearchBoxOpen={handleCountChange}
|
||||||
|
handleSearchChange={(value) => setSearchValue(value)}
|
||||||
|
isFeatureModalOpen={false}
|
||||||
|
isSearchBoxOpen={false}
|
||||||
|
pathname={location.pathname}
|
||||||
|
profileDropdown={[]}
|
||||||
|
searchValue={searchValue}
|
||||||
|
settingDropdown={[]}
|
||||||
|
supportDropdown={[]}
|
||||||
|
/>
|
||||||
|
<Tour steps={getSteps(TOUR_SEARCH_TERM, clearSearchTerm)} />
|
||||||
{getCurrentPage(currentPage)}
|
{getCurrentPage(currentPage)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
232
openmetadata-ui/src/main/resources/ui/src/utils/TourUtils.tsx
Normal file
232
openmetadata-ui/src/main/resources/ui/src/utils/TourUtils.tsx
Normal file
@ -0,0 +1,232 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021 Collate
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import AppState from '../AppState';
|
||||||
|
import { CurrentTourPageType } from '../enums/tour.enum';
|
||||||
|
|
||||||
|
export const getSteps = (value: string, clearSearchTerm: () => void) => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
content: () => (
|
||||||
|
<p>
|
||||||
|
Discover all your data assets in a single place with{' '}
|
||||||
|
<strong>OpenMetadata</strong>, a centralized metadata store.
|
||||||
|
Collaborate with your team and get a holistic picture of the data in
|
||||||
|
your organization.
|
||||||
|
</p>
|
||||||
|
),
|
||||||
|
position: [5, 360],
|
||||||
|
stepInteraction: false,
|
||||||
|
selector: '#assetStatsCount',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
content: () => (
|
||||||
|
<p>
|
||||||
|
<strong>Activity Feeds</strong> help you understand how the data is
|
||||||
|
changing in your organization.
|
||||||
|
</p>
|
||||||
|
),
|
||||||
|
position: [540, 540],
|
||||||
|
selector: '#feedData',
|
||||||
|
stepInteraction: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
content: () => (
|
||||||
|
<p>
|
||||||
|
Search for matching data assets by "name",
|
||||||
|
"description", "column name", and so on from the{' '}
|
||||||
|
<strong>Search</strong> box.
|
||||||
|
</p>
|
||||||
|
),
|
||||||
|
position: [535, 70],
|
||||||
|
selector: '#searchBox',
|
||||||
|
stepInteraction: false,
|
||||||
|
beforeNext: clearSearchTerm,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
beforePrev: clearSearchTerm,
|
||||||
|
content: () => (
|
||||||
|
<p>
|
||||||
|
In the search box, type <strong>"{value}"</strong>. Hit{' '}
|
||||||
|
<strong>Enter.</strong>
|
||||||
|
</p>
|
||||||
|
),
|
||||||
|
actionType: 'enter',
|
||||||
|
userTypeText: value,
|
||||||
|
position: [535, 70],
|
||||||
|
selector: '#searchBox',
|
||||||
|
beforeNext: () => {
|
||||||
|
clearSearchTerm();
|
||||||
|
AppState.currentTourPage = CurrentTourPageType.EXPLORE_PAGE;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
beforePrev: () => {
|
||||||
|
AppState.currentTourPage = CurrentTourPageType.MY_DATA_PAGE;
|
||||||
|
},
|
||||||
|
content: () => (
|
||||||
|
<p>
|
||||||
|
From the <strong>"Explore"</strong> page, view a summary of
|
||||||
|
each asset, including: title, description, owner, tier (importance),
|
||||||
|
usage, and location.
|
||||||
|
</p>
|
||||||
|
),
|
||||||
|
selector: '#tabledatacard0',
|
||||||
|
stepInteraction: false,
|
||||||
|
position: [550, 310],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
content: () => (
|
||||||
|
<p>
|
||||||
|
Click on the <strong>title of the asset</strong> to view more details.
|
||||||
|
</p>
|
||||||
|
),
|
||||||
|
actionType: 'click',
|
||||||
|
selector: '#tabledatacard0Title',
|
||||||
|
position: [210, 190],
|
||||||
|
beforeNext: () => {
|
||||||
|
AppState.currentTourPage = CurrentTourPageType.DATASET_PAGE;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
beforePrev: () => {
|
||||||
|
AppState.currentTourPage = CurrentTourPageType.EXPLORE_PAGE;
|
||||||
|
},
|
||||||
|
content: () => (
|
||||||
|
<p>
|
||||||
|
{' '}
|
||||||
|
Get to know the table <strong>Schema</strong>, including column names
|
||||||
|
and data types as well as column descriptions and tags. You can even
|
||||||
|
view metadata for complex types such as structs.
|
||||||
|
</p>
|
||||||
|
),
|
||||||
|
stepInteraction: false,
|
||||||
|
position: [540, 23],
|
||||||
|
arrowPosition: 'bottom',
|
||||||
|
selector: '#schemaDetails',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
beforePrev: () => {
|
||||||
|
AppState.activeTabforTourDatasetPage = 1;
|
||||||
|
},
|
||||||
|
actionType: 'click',
|
||||||
|
content: () => (
|
||||||
|
<p>
|
||||||
|
Click on the <strong>"Sample Data"</strong> tab.
|
||||||
|
</p>
|
||||||
|
),
|
||||||
|
position: [70, 240],
|
||||||
|
selector: '#sampleData',
|
||||||
|
beforeNext: () => {
|
||||||
|
AppState.activeTabforTourDatasetPage = 2;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
arrowPosition: 'bottom',
|
||||||
|
content: () => (
|
||||||
|
<p>
|
||||||
|
Take a look at the <strong>Sample Data</strong> to get a feel for what
|
||||||
|
the table contains and how you might use it.
|
||||||
|
</p>
|
||||||
|
),
|
||||||
|
position: [530, 60],
|
||||||
|
selector: '#sampleDataDetails',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
beforePrev: () => {
|
||||||
|
AppState.activeTabforTourDatasetPage = 2;
|
||||||
|
},
|
||||||
|
beforeNext: () => {
|
||||||
|
AppState.activeTabforTourDatasetPage = 3;
|
||||||
|
},
|
||||||
|
actionType: 'click',
|
||||||
|
content: () => (
|
||||||
|
<p>
|
||||||
|
Click on the <strong>"Profiler"</strong> tab.
|
||||||
|
</p>
|
||||||
|
),
|
||||||
|
position: [200, 240],
|
||||||
|
selector: '#profiler',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
content: () => (
|
||||||
|
<p>
|
||||||
|
Discover assets with the <strong>Data Profiler</strong>. Get to know
|
||||||
|
the table usage stats, check for null values and duplicates, and
|
||||||
|
understand the column data distributions.
|
||||||
|
</p>
|
||||||
|
),
|
||||||
|
arrowPosition: 'bottom',
|
||||||
|
stepInteraction: false,
|
||||||
|
position: [530, 20],
|
||||||
|
selector: '#profilerDetails',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
beforePrev: () => {
|
||||||
|
AppState.activeTabforTourDatasetPage = 3;
|
||||||
|
},
|
||||||
|
beforeNext: () => {
|
||||||
|
AppState.activeTabforTourDatasetPage = 4;
|
||||||
|
},
|
||||||
|
actionType: 'click',
|
||||||
|
content: () => (
|
||||||
|
<p>
|
||||||
|
Click on the <strong>"Lineage"</strong> tab
|
||||||
|
</p>
|
||||||
|
),
|
||||||
|
position: [320, 240],
|
||||||
|
selector: '#lineage',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
content: () => (
|
||||||
|
<p>
|
||||||
|
With <strong>Lineage</strong>, trace the path of data across tables,
|
||||||
|
pipelines, & dashboards.
|
||||||
|
</p>
|
||||||
|
),
|
||||||
|
position: [530, 45],
|
||||||
|
stepInteraction: false,
|
||||||
|
arrowPosition: 'bottom',
|
||||||
|
selector: '#lineageDetails',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
beforeNext: () => {
|
||||||
|
AppState.activeTabforTourDatasetPage = 6;
|
||||||
|
},
|
||||||
|
actionType: 'click',
|
||||||
|
content: () => (
|
||||||
|
<p>
|
||||||
|
Click on the <strong>"Manage"</strong> tab
|
||||||
|
</p>
|
||||||
|
),
|
||||||
|
position: [440, 240],
|
||||||
|
selector: '#manage',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
beforePrev: () => {
|
||||||
|
AppState.activeTabforTourDatasetPage = 4;
|
||||||
|
},
|
||||||
|
content: () => (
|
||||||
|
<p>
|
||||||
|
From <strong>"Manage"</strong>, you can claim ownership, and
|
||||||
|
set the tier.
|
||||||
|
</p>
|
||||||
|
),
|
||||||
|
position: [560, 60],
|
||||||
|
arrowPosition: 'bottom',
|
||||||
|
stepInteraction: false,
|
||||||
|
selector: '#manageTabDetails',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
@ -996,10 +996,10 @@
|
|||||||
exec-sh "^0.3.2"
|
exec-sh "^0.3.2"
|
||||||
minimist "^1.2.0"
|
minimist "^1.2.0"
|
||||||
|
|
||||||
"@deuex-solutions/react-tour@^1.0.0":
|
"@deuex-solutions/react-tour@^1.1.1":
|
||||||
version "1.0.0"
|
version "1.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/@deuex-solutions/react-tour/-/react-tour-1.0.0.tgz#5714dd38cf3f89fc781cc7f53965b08400e68a13"
|
resolved "https://registry.yarnpkg.com/@deuex-solutions/react-tour/-/react-tour-1.1.1.tgz#ad96bf0bcd651916a6e44d5e9c3bdc60e5fac036"
|
||||||
integrity sha512-oSprYz81nejf3ZqgZAy6dgyuyEEujJvGs5b+YB2Pnfui9i0vyOEseTtIoamYV+eRTjBsPhwv2wUa7RkdcJUcSQ==
|
integrity sha512-BIw1zlGprOEtCoGexdyQl97bvBdeZb9ZPcB6e90fV+x2s9h+YDeZwEC+qisTQodLkBwovSRO990K2Quc6p39Aw==
|
||||||
dependencies:
|
dependencies:
|
||||||
classnames "^2.2.6"
|
classnames "^2.2.6"
|
||||||
lodash "^4.17.15"
|
lodash "^4.17.15"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user