mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-10-17 11:43:54 +00:00
Tour feature (#716)
* setup tour page and implemented react-tutorial * completd tour-feature implementation
This commit is contained in:
parent
be7437c820
commit
ec51fadf19
@ -17600,6 +17600,17 @@
|
||||
"prop-types": "^15.6.2"
|
||||
}
|
||||
},
|
||||
"react-tutorial": {
|
||||
"version": "https://github.com/deuex-solutions/react-tutorial/tarball/master",
|
||||
"integrity": "sha512-QFHGepStGMR15zt3lRTqZB3NT6AmpBEHkxM6hi3fW6R86Jgu5y2ZYVY0fu93KDsAou/1saTUtkSJjs0rzwDKRA==",
|
||||
"requires": {
|
||||
"classnames": "^2.2.6",
|
||||
"lodash": "^4.17.15",
|
||||
"prop-types": "^15.7.2",
|
||||
"scroll-smooth": "^1.1.0",
|
||||
"scrollparent": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"reactjs-localstorage": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/reactjs-localstorage/-/reactjs-localstorage-1.0.1.tgz",
|
||||
@ -18594,6 +18605,16 @@
|
||||
"compute-scroll-into-view": "^1.0.17"
|
||||
}
|
||||
},
|
||||
"scroll-smooth": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/scroll-smooth/-/scroll-smooth-1.1.0.tgz",
|
||||
"integrity": "sha512-68OUOXKN/ykM/Dbp4Lhza3O9QQUuW/c01WTsZzDOUyVgb1I5QjT/awOHCCbuYTSV1QnExUQ9w+KcxmVxlXIiAg=="
|
||||
},
|
||||
"scrollparent": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/scrollparent/-/scrollparent-2.0.1.tgz",
|
||||
"integrity": "sha1-cV1bnMV3YPsivczDvvtb/gaxoxc="
|
||||
},
|
||||
"select-hose": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz",
|
||||
|
@ -80,7 +80,8 @@
|
||||
"stream-http": "^3.2.0",
|
||||
"styled-components": "^5.2.3",
|
||||
"tailwindcss": "^2.1.4",
|
||||
"to-arraybuffer": "^1.0.1"
|
||||
"to-arraybuffer": "^1.0.1",
|
||||
"react-tutorial": "https://github.com/deuex-solutions/react-tutorial/tarball/master"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "NODE_ENV=development BABEL_ENV=development webpack serve --config ./webpack.config.dev.js --env development",
|
||||
|
@ -36,6 +36,8 @@ class AppState {
|
||||
inPageSearchText = '';
|
||||
explorePageTab = 'tables';
|
||||
|
||||
isTourOpen = false;
|
||||
|
||||
constructor() {
|
||||
makeAutoObservable(this);
|
||||
}
|
||||
|
@ -126,6 +126,7 @@ export const FirstTimeUserModal: FunctionComponent<Props> = ({
|
||||
) : (
|
||||
<Button
|
||||
className="tw-text-primary-active"
|
||||
id="next"
|
||||
size="regular"
|
||||
theme="primary"
|
||||
variant="text"
|
||||
|
@ -47,6 +47,7 @@ import SVGIcons, { Icons } from '../../utils/SvgUtils';
|
||||
import DropDown from '../dropdown/DropDown';
|
||||
import { WhatsNewModal } from '../Modals/WhatsNewModal';
|
||||
import { COOKIE_VERSION } from '../Modals/WhatsNewModal/whatsNewData';
|
||||
import Tour from '../tour/Tour';
|
||||
import { ReactComponent as IconDefaultUserProfile } from './../../assets/svg/ic-default-profile.svg';
|
||||
import SearchOptions from './SearchOptions';
|
||||
import Suggestions from './Suggestions';
|
||||
@ -69,6 +70,7 @@ const Appbar: React.FC = (): JSX.Element => {
|
||||
});
|
||||
|
||||
const [version, setVersion] = useState<string>('');
|
||||
|
||||
const navStyle = (value: boolean) => {
|
||||
if (value) return { color: activeLink };
|
||||
|
||||
@ -167,7 +169,7 @@ const Appbar: React.FC = (): JSX.Element => {
|
||||
<div className="tw-h-14 tw-py-2 tw-px-5 tw-border-b-2 tw-border-separator">
|
||||
<div className="tw-flex tw-items-center tw-flex-row tw-justify-between tw-flex-nowrap">
|
||||
<div className="tw-flex tw-items-center tw-flex-row tw-justify-between tw-flex-nowrap tw-mr-auto">
|
||||
<NavLink to="/">
|
||||
<NavLink id="openmetadata_logo" to="/">
|
||||
<SVGIcons
|
||||
alt="OpenMetadata Logo"
|
||||
icon={Icons.LOGO_SMALL}
|
||||
@ -180,6 +182,7 @@ const Appbar: React.FC = (): JSX.Element => {
|
||||
<span className="fa fa-search tw-absolute tw-block tw-z-10 tw-w-9 tw-h-8 tw-leading-8 tw-text-center tw-pointer-events-none tw-text-gray-400" />
|
||||
<input
|
||||
className="tw-relative search-grey tw-rounded tw-border tw-border-main tw-bg-body-main focus:tw-outline-none tw-pl-8 tw-py-1"
|
||||
id="searchBox"
|
||||
type="text"
|
||||
value={searchValue || ''}
|
||||
onChange={(e) => {
|
||||
@ -224,6 +227,7 @@ const Appbar: React.FC = (): JSX.Element => {
|
||||
<NavLink
|
||||
className="tw-nav focus:tw-no-underline"
|
||||
data-testid="appbar-item"
|
||||
id="explore"
|
||||
style={navStyle(location.pathname.startsWith('/explore'))}
|
||||
to={{
|
||||
pathname: '/explore',
|
||||
@ -249,6 +253,20 @@ const Appbar: React.FC = (): JSX.Element => {
|
||||
/>
|
||||
<span>What's new</span>
|
||||
</button>
|
||||
<NavLink
|
||||
className="tw-nav focus:tw-no-underline hover:tw-underline"
|
||||
style={navStyle(location.pathname.startsWith('/explore'))}
|
||||
to={{
|
||||
pathname: '/tour',
|
||||
}}>
|
||||
<SVGIcons
|
||||
alt="Doc icon"
|
||||
className="tw-align-middle tw--mt-0.5 tw-mr-1"
|
||||
icon={Icons.WHATS_NEW}
|
||||
width="16"
|
||||
/>
|
||||
<span>Tour</span>
|
||||
</NavLink>
|
||||
<div>
|
||||
<DropDown
|
||||
dropDownList={supportLinks}
|
||||
@ -319,6 +337,7 @@ const Appbar: React.FC = (): JSX.Element => {
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
<Tour />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -149,6 +149,7 @@ const Suggestions = ({ searchText, isOpen, setIsOpen }: SuggestionProp) => {
|
||||
<Link
|
||||
className="tw-block tw-px-4 tw-py-2 tw-text-sm"
|
||||
data-testid="data-name"
|
||||
id={fqdn.replace(/\./g, '')}
|
||||
to={getEntityLink(index, fqdn)}
|
||||
onClick={() => setIsOpen(false)}>
|
||||
{name}
|
||||
@ -265,7 +266,7 @@ const Suggestions = ({ searchText, isOpen, setIsOpen }: SuggestionProp) => {
|
||||
aria-labelledby="menu-button"
|
||||
aria-orientation="vertical"
|
||||
className="tw-origin-top-right tw-absolute tw-z-10
|
||||
tw-w-60 tw-mt-1 tw-rounded-md tw-shadow-lg
|
||||
tw-w-60 tw-mt-1 tw-rounded-md tw-shadow-lg
|
||||
tw-bg-white tw-ring-1 tw-ring-black tw-ring-opacity-5 focus:tw-outline-none"
|
||||
role="menu">
|
||||
{getEntitiesSuggestions()}
|
||||
|
@ -25,7 +25,9 @@ const TabsPane = ({ activeTab, setActiveTab, tabs }: Props) => {
|
||||
|
||||
return (
|
||||
<div className="tw-bg-transparent tw--mx-4">
|
||||
<nav className="tw-flex tw-flex-row tw-gh-tabs-container tw-px-4">
|
||||
<nav
|
||||
className="tw-flex tw-flex-row tw-gh-tabs-container tw-px-4"
|
||||
id="tabs">
|
||||
{tabs.map((tab) =>
|
||||
tab.isProtected ? (
|
||||
<NonAdminAction
|
||||
|
@ -383,7 +383,7 @@ const EntityTable = ({
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="tw-table-responsive">
|
||||
<div className="tw-table-responsive" id="schemaTable">
|
||||
<table className="tw-w-full" {...getTableProps()}>
|
||||
<thead>
|
||||
{/* eslint-disable-next-line */}
|
||||
|
@ -0,0 +1,106 @@
|
||||
import { AxiosResponse } from 'axios';
|
||||
import { observer } from 'mobx-react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import ReactTutorial from 'react-tutorial';
|
||||
import { searchData } from '../../axiosAPIs/miscAPI';
|
||||
import { PAGE_SIZE } from '../../constants/constants';
|
||||
import { SearchIndex } from '../../enums/search.enum';
|
||||
import { useTour } from '../../hooks/useTour';
|
||||
|
||||
type Steps = {
|
||||
content: string;
|
||||
actionType: string;
|
||||
position: string;
|
||||
selector: string;
|
||||
userTypeText?: string;
|
||||
waitTimer?: number;
|
||||
};
|
||||
|
||||
const getSteps = (value: string) => {
|
||||
return [
|
||||
{
|
||||
content: 'Click on the next.',
|
||||
actionType: 'click',
|
||||
position: 'bottom',
|
||||
selector: '#next',
|
||||
},
|
||||
{
|
||||
content: 'Click on the next.',
|
||||
actionType: 'click',
|
||||
position: 'bottom',
|
||||
selector: '#next',
|
||||
},
|
||||
{
|
||||
content: 'Click on Explore OpenMetadata.',
|
||||
actionType: 'click',
|
||||
position: 'bottom',
|
||||
selector: '#take-tour',
|
||||
},
|
||||
{
|
||||
content: 'Click on explore.',
|
||||
actionType: 'click',
|
||||
position: 'bottom',
|
||||
selector: '#explore',
|
||||
},
|
||||
{
|
||||
content: `Type "${value}" in search box.`,
|
||||
actionType: 'typing',
|
||||
userTypeText: value,
|
||||
position: 'bottom',
|
||||
selector: '#searchBox',
|
||||
},
|
||||
{
|
||||
content: 'Click on the table.',
|
||||
actionType: 'click',
|
||||
position: 'bottom',
|
||||
selector: '#bigqueryshopifydim_address',
|
||||
},
|
||||
{
|
||||
content:
|
||||
'Understand the schema of the table and add description, Claim ownership. Add tags etc..',
|
||||
position: 'bottom',
|
||||
selector: '#tabs',
|
||||
actionType: 'wait',
|
||||
waitTimer: 10000,
|
||||
},
|
||||
{
|
||||
content: 'Click here to explore more',
|
||||
actionType: 'click',
|
||||
position: 'bottom',
|
||||
selector: '#openmetadata_logo',
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
const Tour = () => {
|
||||
const { isTourOpen, handleIsTourOpen } = useTour();
|
||||
const [steps, setSteps] = useState<Steps[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
searchData('', 1, PAGE_SIZE, '', '', '', SearchIndex.TABLE).then(
|
||||
(res: AxiosResponse) => {
|
||||
const table = res.data.hits.hits[0];
|
||||
setSteps(getSteps(table._source.table_name));
|
||||
}
|
||||
);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isTourOpen ? (
|
||||
<ReactTutorial
|
||||
disableKeyboardNavigation
|
||||
showNumber
|
||||
maskColor="#302E36"
|
||||
playTour={isTourOpen}
|
||||
showButtons={false}
|
||||
showNavigation={false}
|
||||
steps={steps}
|
||||
onRequestClose={() => handleIsTourOpen(false)}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default observer(Tour);
|
@ -100,6 +100,7 @@ export const ROUTES = {
|
||||
CALLBACK: '/callback',
|
||||
NOT_FOUND: '/404',
|
||||
MY_DATA: '/my-data',
|
||||
TOUR: '/tour',
|
||||
REPORTS: '/reports',
|
||||
EXPLORE: '/explore',
|
||||
EXPLORE_WITH_SEARCH: `/explore/${PLACEHOLDER_ROUTE_TAB}/${PLACEHOLDER_ROUTE_SEARCHQUERY}`,
|
||||
|
@ -0,0 +1,19 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import AppState from '../AppState';
|
||||
|
||||
export const useTour = () => {
|
||||
const [isTourOpen, setIsTourOpen] = useState<boolean>();
|
||||
|
||||
useEffect(() => {
|
||||
setIsTourOpen(AppState.isTourOpen);
|
||||
}, [AppState.isTourOpen]);
|
||||
|
||||
const handleIsTourOpen = (value: boolean) => {
|
||||
AppState.isTourOpen = value;
|
||||
};
|
||||
|
||||
return {
|
||||
isTourOpen,
|
||||
handleIsTourOpen,
|
||||
};
|
||||
};
|
@ -0,0 +1,31 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { FirstTimeUserModal } from '../../components/Modals/FirstTimeUserModal/FirstTimeUserModal';
|
||||
import { useTour } from '../../hooks/useTour';
|
||||
import MyDataPage from '../my-data';
|
||||
|
||||
const TourPage = () => {
|
||||
const [showFirstTimeUserModal, setShowFirstTimeUserModal] = useState(true);
|
||||
const { handleIsTourOpen } = useTour();
|
||||
|
||||
useEffect(() => {
|
||||
handleIsTourOpen(true);
|
||||
}, []);
|
||||
|
||||
const handleFirstTimeUser = () => {
|
||||
setShowFirstTimeUserModal(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<MyDataPage />
|
||||
{showFirstTimeUserModal && (
|
||||
<FirstTimeUserModal
|
||||
onCancel={() => setShowFirstTimeUserModal(true)}
|
||||
onSave={handleFirstTimeUser}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TourPage;
|
@ -29,3 +29,4 @@ declare module 'react-slick';
|
||||
declare module 'slick-carousel';
|
||||
declare module 'react-table';
|
||||
declare module 'recharts';
|
||||
declare module 'react-tutorial';
|
||||
|
@ -39,12 +39,14 @@ import SwaggerPage from '../pages/swagger';
|
||||
import TagsPage from '../pages/tags';
|
||||
import TeamsPage from '../pages/teams';
|
||||
import MyTopicDetailPage from '../pages/topic-details';
|
||||
import TourPage from '../pages/tour-page';
|
||||
import UsersPage from '../pages/users';
|
||||
import WorkflowsPage from '../pages/workflows';
|
||||
const AuthenticatedAppRouter: FunctionComponent = () => {
|
||||
return (
|
||||
<Switch>
|
||||
<Route exact component={MyDataPage} path={ROUTES.MY_DATA} />
|
||||
<Route exact component={TourPage} path={ROUTES.TOUR} />
|
||||
<Route exact component={ReportsPage} path={ROUTES.REPORTS} />
|
||||
<Route exact component={ExplorePage} path={ROUTES.EXPLORE} />
|
||||
<Route component={ExplorePage} path={ROUTES.EXPLORE_WITH_SEARCH} />
|
||||
|
@ -31,7 +31,7 @@ module.exports = {
|
||||
mode: 'development',
|
||||
|
||||
// Input configuration
|
||||
entry: path.join(__dirname, 'src/index.js'),
|
||||
entry: ['@babel/polyfill', path.join(__dirname, 'src/index.js')],
|
||||
|
||||
// Output configuration
|
||||
output: {
|
||||
|
Loading…
x
Reference in New Issue
Block a user