feat(React Browse): Adding Browse Logic and misc refactorings (#2060)

Co-authored-by: John Joyce <jjoyce0510@gmail.com>
Co-authored-by: Gabe Lyons <gabe@Gabes-MacBook-Pro.local>
This commit is contained in:
John Joyce 2021-01-25 13:29:23 -08:00 committed by GitHub
parent 50cec65f57
commit bee6ba43f8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 458 additions and 98 deletions

View File

@ -27,9 +27,15 @@ module.exports = {
'require-await': 'warn',
'import/prefer-default-export': 'off', // TODO: remove this lint rule
'react/jsx-props-no-spreading': 'off',
'@typescript-eslint/no-unused-vars': ['error', {
varsIgnorePattern: '^_',
argsIgnorePattern: '^_' }],
'no-plusplus': 'off',
'react/require-default-props': 'off',
'@typescript-eslint/no-unused-vars': [
'error',
{
varsIgnorePattern: '^_',
argsIgnorePattern: '^_',
},
],
},
settings: {
react: {

View File

@ -6,7 +6,8 @@ schema {
}
type Query {
dataset(urn: String): Dataset
dataset(urn: String!): Dataset
user(urn: String!): CorpUser
search(input: SearchInput!): SearchResults
autoComplete(input: AutoCompleteInput!): AutoCompleteResults
browse(input: BrowseInput!): BrowseResults
@ -303,7 +304,7 @@ input BrowseInput {
# Browse Output
type BrowseResults {
entities: [BrowseResultEntity]!
entities: [BrowseResultEntity!]!
start: Int!
count: Int!
total: Int!

View File

@ -2,6 +2,7 @@ import { GetDatasetDocument } from './graphql/dataset.generated';
import { GetBrowsePathsDocument, GetBrowseResultsDocument } from './graphql/browse.generated';
import { GetAutoCompleteResultsDocument, GetSearchResultsDocument } from './graphql/search.generated';
import { LoginDocument } from './graphql/auth.generated';
import { GetUserDocument } from './graphql/user.generated';
const user1 = {
username: 'sdas',
@ -163,6 +164,21 @@ export const mocks = [
},
},
},
{
request: {
query: GetUserDocument,
variables: {
urn: 'urn:li:corpuser:1',
},
},
result: {
data: {
dataset: {
...user1,
},
},
},
},
{
request: {
query: GetBrowsePathsDocument,
@ -179,6 +195,74 @@ export const mocks = [
},
},
},
{
request: {
query: GetBrowseResultsDocument,
variables: {
input: {
type: 'DATASET',
path: [],
start: 0,
count: 20,
filters: null,
},
},
},
result: {
data: {
browse: {
entities: [],
start: 0,
count: 0,
total: 0,
metadata: {
path: [],
groups: [
{
name: 'prod',
count: 1,
},
],
totalNumEntities: 1,
},
},
},
},
},
{
request: {
query: GetBrowseResultsDocument,
variables: {
input: {
type: 'DATASET',
path: ['prod'],
start: 0,
count: 20,
filters: null,
},
},
},
result: {
data: {
browse: {
entities: [],
start: 0,
count: 0,
total: 0,
metadata: {
path: ['prod'],
groups: [
{
name: 'hdfs',
count: 1,
},
],
totalNumEntities: 1,
},
},
},
},
},
{
request: {
query: GetBrowseResultsDocument,
@ -187,7 +271,8 @@ export const mocks = [
type: 'DATASET',
path: ['prod', 'hdfs'],
start: 0,
count: 10,
count: 20,
filters: null,
},
},
},
@ -196,8 +281,8 @@ export const mocks = [
browse: {
entities: [
{
name: 'The Great Test Dataset',
urn: 'urn:li:dataset:1',
name: 'Test Dataset',
},
],
start: 0,
@ -232,6 +317,26 @@ export const mocks = [
},
},
},
{
request: {
query: GetAutoCompleteResultsDocument,
variables: {
input: {
type: 'USER',
query: 'j',
field: 'ldap',
},
},
},
result: {
data: {
autoComplete: {
query: 'j',
suggestions: ['jjoyce'],
},
},
},
},
{
request: {
query: GetSearchResultsDocument,

View File

@ -1,7 +1,8 @@
import React from 'react';
import { Switch, Route, RouteProps, Redirect } from 'react-router-dom';
import { useReactiveVar } from '@apollo/client';
import { BrowsePage } from './browse/BrowsePage';
import { BrowseTypesPage } from './browse/BrowseTypesPage';
import { BrowseResultsPage } from './browse/BrowseResultsPage';
import { DatasetPage } from './entity/dataset/DatasetPage';
import { UserPage } from './entity/user/UserPage';
import { SearchPage } from './search/SearchPage';
@ -27,10 +28,10 @@ const ProtectedRoute = ({
*/
export const Routes = (): JSX.Element => {
const isLoggedIn = useReactiveVar(isLoggedInVar);
return (
<div>
<Switch>
<ProtectedRoute isLoggedIn={isLoggedIn} exact path="/" render={() => <BrowseTypesPage />} />
<Route path={PageRoutes.LOG_IN} component={LogIn} />
<ProtectedRoute
isLoggedIn={isLoggedIn}
@ -38,7 +39,17 @@ export const Routes = (): JSX.Element => {
render={() => <DatasetPage />}
/>
<ProtectedRoute isLoggedIn={isLoggedIn} path={PageRoutes.SEARCH} render={() => <SearchPage />} />
<ProtectedRoute isLoggedIn={isLoggedIn} path={PageRoutes.BROWSE} render={() => BrowsePage} />
<ProtectedRoute
isLoggedIn={isLoggedIn}
exact
path={PageRoutes.BROWSE_TYPES}
render={() => <BrowseTypesPage />}
/>
<ProtectedRoute
isLoggedIn={isLoggedIn}
path={PageRoutes.BROWSE_RESULTS}
render={() => <BrowseResultsPage />}
/>
<ProtectedRoute isLoggedIn={isLoggedIn} path={PageRoutes.USERS} render={() => <UserPage />} />
<Route component={NoPageFound} />
</Switch>

View File

@ -32,6 +32,7 @@ export const LogIn: React.VFC<LogInProps> = () => {
})
.then(() => {
Cookies.set('PLAY_SESSION', 'DUMMY_VALUE');
Cookies.set('IS_LOGGED_IN', 'true');
isLoggedInVar(true);
})
.catch((e: ApolloError) => {

View File

@ -6,7 +6,7 @@ export const checkAuthStatus = (): boolean => {
// TODO: perhaps there's a more robust way to detect this?
// e.g. what happens if the PLAY_SESSION cookie is stuck but the session is
// invalid or expired?
return !!Cookies.get('PLAY_SESSION');
return !!Cookies.get('IS_LOGGED_IN');
};
export const isLoggedInVar = makeVar(checkAuthStatus());

View File

@ -12,7 +12,7 @@ interface Props {
}
/**
* A page that includes a search header & entity browse path view
* A entity-details page that includes a search header & entity browse path view
*/
export const BrowsableEntityPage = ({ urn: _urn, type: _type, children: _children }: Props) => {
const { data } = useGetBrowsePathsQuery({ variables: { input: { urn: _urn, type: _type } } });

View File

@ -1,9 +0,0 @@
import * as React from 'react';
/**
* Needs Implemented!
*/
export const BrowsePage: React.FC = () => {
// TODO: /browse?path=<entity-type>/sub/list
return <div>Needs Implemented!</div>;
};

View File

@ -1,8 +1,8 @@
import * as React from 'react';
import { Link } from 'react-router-dom';
import { Breadcrumb, Row } from 'antd';
import { EntityType, toCollectionName, toPathName } from '../shared/EntityTypeUtil';
import { PageRoutes } from '../../conf/Global';
import { EntityType, toCollectionName, toPathName } from '../shared/EntityTypeUtil';
interface Props {
type: EntityType;
@ -17,13 +17,11 @@ export const BrowsePath = ({ type, path }: Props) => {
return parts.join('/');
};
const baseBrowsePath = `${PageRoutes.BROWSE}?type=${toPathName(type)}`;
const baseBrowsePath = `${PageRoutes.BROWSE}/${toPathName(type)}`;
const pathCrumbs = path.map((part, index) => (
<Breadcrumb.Item>
<Link to={`${baseBrowsePath}&path=${encodeURIComponent(createPartialPath(path.slice(0, index + 1)))}`}>
{part}
</Link>
<Link to={`${baseBrowsePath}/${createPartialPath(path.slice(0, index + 1))}`}>{part}</Link>
</Breadcrumb.Item>
));
@ -31,7 +29,7 @@ export const BrowsePath = ({ type, path }: Props) => {
<Row style={{ backgroundColor: 'white', padding: '10px 100px', borderBottom: '1px solid #dcdcdc' }}>
<Breadcrumb style={{ fontSize: '16px' }}>
<Breadcrumb.Item>
<Link to={`${baseBrowsePath}`}>{toCollectionName(type)}</Link>
<Link to={baseBrowsePath}>{toCollectionName(type)}</Link>
</Breadcrumb.Item>
{pathCrumbs}
</Breadcrumb>

View File

@ -0,0 +1,22 @@
import React from 'react';
import { Card } from 'antd';
import { Link } from 'react-router-dom';
type Props = {
url: string;
name: string;
count?: number | undefined;
};
export default function BrowseResultCard({ url, count, name }: Props) {
return (
<Link to={url}>
<Card hoverable>
<div style={{ display: 'flex', width: '100%' }}>
<div style={{ fontSize: '12px', fontWeight: 'bold', color: '#0073b1' }}>{name}</div>
{count && <div style={{ marginLeft: 'auto' }}>{count}</div>}
</div>
</Card>
</Link>
);
}

View File

@ -0,0 +1,61 @@
import { Col, Pagination, Row } from 'antd';
import { Content } from 'antd/lib/layout/layout';
import React from 'react';
import { BrowseResultEntity, BrowseResultGroup } from '../../types.generated';
import BrowseResultCard from './BrowseResultCard';
import { browseEntityResultToUrl } from './util/entityToUrl';
interface Props {
title: string;
rootPath: string;
pageStart: number;
pageSize: number;
totalResults: number;
groups: Array<BrowseResultGroup>;
entities: Array<BrowseResultEntity>;
onChangePage: (page: number) => void;
}
/**
* Display browse groups + entities.
*/
export const BrowseResults = ({
title,
rootPath,
pageStart,
pageSize,
totalResults,
entities,
groups,
onChangePage,
}: Props) => {
return (
<div>
<Content style={{ backgroundColor: 'white', padding: '25px 100px' }}>
<h1 className="ant-typography">{title}</h1>
<Row gutter={[4, 8]}>
{groups.map((group) => (
<Col span={24}>
<BrowseResultCard name={group.name} count={group.count} url={`${rootPath}/${group.name}`} />
</Col>
))}
{entities.map((entity) => (
<Col span={24}>
<BrowseResultCard name={entity.name} url={browseEntityResultToUrl(entity)} />
</Col>
))}
<Col span={24}>
<Pagination
style={{ width: '100%', display: 'flex', justifyContent: 'center', paddingTop: 16 }}
current={pageStart}
pageSize={pageSize}
total={totalResults / pageSize}
showLessItems
onChange={onChangePage}
/>
</Col>
</Row>
</Content>
</div>
);
};

View File

@ -0,0 +1,74 @@
import * as React from 'react';
import { Redirect, useHistory, useLocation, useParams } from 'react-router';
import * as QueryString from 'query-string';
import { Affix } from 'antd';
import { fromPathName, toCollectionName } from '../shared/EntityTypeUtil';
import { BrowseCfg } from '../../conf';
import { BrowseResults } from './BrowseResults';
import { SearchablePage } from '../search/SearchablePage';
import { useGetBrowseResultsQuery } from '../../graphql/browse.generated';
import { BrowsePath } from './BrowsePath';
import { PageRoutes } from '../../conf/Global';
const { RESULTS_PER_PAGE } = BrowseCfg;
type BrowseResultsPageParams = {
type: string;
};
export const BrowseResultsPage = () => {
const location = useLocation();
const history = useHistory();
const { type } = useParams<BrowseResultsPageParams>();
const rootPath = location.pathname;
const params = QueryString.parse(location.search);
const entityType = fromPathName(type);
const path = rootPath.split('/').slice(3);
const page = Number(params.page) || 1;
const { data, loading, error } = useGetBrowseResultsQuery({
variables: {
input: {
type: entityType,
path,
start: (page - 1) * RESULTS_PER_PAGE,
count: RESULTS_PER_PAGE,
filters: null,
},
},
});
const onChangePage = (newPage: number) => {
history.push({
pathname: rootPath,
search: `&page=${newPage}`,
});
};
if (page < 0 || page === undefined || Number.isNaN(page)) {
return <Redirect to={`${PageRoutes.BROWSE}`} />;
}
return (
<SearchablePage>
<Affix offsetTop={64}>
<BrowsePath type={entityType} path={path} />
</Affix>
{error && <p>Error fetching browse results!</p>}
{loading && <p>Loading browse results...</p>}
{data && data.browse && (
<BrowseResults
rootPath={rootPath}
title={path.length > 0 ? path[path.length - 1] : toCollectionName(entityType)}
pageSize={RESULTS_PER_PAGE}
pageStart={page * RESULTS_PER_PAGE}
groups={data.browse.metadata.groups}
entities={data.browse.entities}
totalResults={data.browse.total}
onChangePage={onChangePage}
/>
)}
</SearchablePage>
);
};

View File

@ -0,0 +1,42 @@
import * as React from 'react';
import 'antd/dist/antd.css';
import { Link } from 'react-router-dom';
import { Col, Row, Card } from 'antd';
import { Content } from 'antd/lib/layout/layout';
import { BrowseCfg } from '../../conf';
import { SearchablePage } from '../search/SearchablePage';
import { toCollectionName, toPathName } from '../shared/EntityTypeUtil';
import { PageRoutes } from '../../conf/Global';
import '../../App.css';
const { BROWSABLE_ENTITY_TYPES } = BrowseCfg;
export const BrowseTypesPage = () => {
return (
<SearchablePage>
<Content style={{ backgroundColor: 'white', padding: '25px 100px' }}>
<h1 className="ant-typography">Browse</h1>
<Row gutter={[16, 16]}>
{BROWSABLE_ENTITY_TYPES.map((entityType) => (
<Col xs={24} sm={24} md={8}>
<Link to={`${PageRoutes.BROWSE}/${toPathName(entityType)}`}>
<Card
style={{
padding: '30px 0px',
display: 'flex',
justifyContent: 'center',
}}
hoverable
>
<div style={{ fontSize: '18px', fontWeight: 'bold', color: '#0073b1' }}>
{toCollectionName(entityType)}
</div>
</Card>
</Link>
</Col>
))}
</Row>
</Content>
</SearchablePage>
);
};

View File

@ -0,0 +1,8 @@
// NOTE: this file is a temporary solution. Soon, entity classes will provide the urls via their asBrowseResult method.
import { PageRoutes } from '../../../conf/Global';
import { BrowseResultEntity } from '../../../types.generated';
export function browseEntityResultToUrl(browseResultEntity: BrowseResultEntity) {
return `${PageRoutes.DATASETS}/${browseResultEntity.urn}`;
}

View File

@ -22,6 +22,8 @@ interface RouteParams {
urn: string;
}
const EMPTY_OWNER_ARR: never[] = [];
/**
* Responsible for display the Dataset Page
*/
@ -71,7 +73,7 @@ export const DatasetPage: React.VFC = () => {
path: TabType.Ownership.toLowerCase(),
content: (
<OwnershipView
owners={(ownership && ownership.owners) || []}
initialOwners={(ownership && ownership.owners) || EMPTY_OWNER_ARR}
lastModifiedAt={ownership && ownership.lastModified}
/>
),

View File

@ -1,15 +1,16 @@
import { AutoComplete, Avatar, Button, Col, Row, Select, Table } from 'antd';
import React, { useState } from 'react';
import { Link } from 'react-router-dom';
import { Owner, OwnershipType } from '../../../types.generated';
import { EntityType, Owner, OwnershipType } from '../../../types.generated';
import defaultAvatar from '../../../images/default_avatar.png';
import { PageRoutes } from '../../../conf/Global';
import { useGetAutoCompleteResultsLazyQuery } from '../../../graphql/search.generated';
const OWNER_SEARCH_PLACEHOLDER = 'Enter an LDAP...';
const NUMBER_OWNERS_REQUIRED = 2;
interface Props {
owners: Array<Owner>;
initialOwners: Array<Owner>;
lastModifiedAt: number;
}
@ -18,8 +19,11 @@ interface Props {
*
* TODO: Add mutations to change ownership on explicit save.
*/
export const Ownership: React.FC<Props> = ({ owners, lastModifiedAt }: Props): JSX.Element => {
console.log(lastModifiedAt);
export const Ownership: React.FC<Props> = ({
initialOwners: _initialOwners,
lastModifiedAt: _lastModifiedAt,
}: Props): JSX.Element => {
console.log(_lastModifiedAt);
const getOwnerTableData = (ownerArr: Array<Owner>) => {
const rows = ownerArr.map((owner) => ({
@ -33,45 +37,55 @@ export const Ownership: React.FC<Props> = ({ owners, lastModifiedAt }: Props): J
return rows;
};
const [ownerTableData, setOwnerTableData] = useState(getOwnerTableData(owners));
const [owners, setOwners] = useState(_initialOwners);
const [showAddOwner, setShowAddOwner] = useState(false);
const [ownerSuggestions, setOwnerSuggestions] = useState(new Array<string>());
const [getOwnerAutoCompleteResults, { data: searchOwnerSuggestionsData }] = useGetAutoCompleteResultsLazyQuery();
const onShowAddAnOwner = () => {
setShowAddOwner(true);
};
const onDeleteOwner = (urn: string) => {
const newOwnerTableData = ownerTableData.filter((ownerRow) => !(ownerRow.urn === urn));
setOwnerTableData(newOwnerTableData);
const newOwners = owners.filter((owner: Owner) => !(owner.owner.urn === urn));
setOwners(newOwners);
};
const onOwnerQueryChange = (_: string) => {
// TODO: Fetch real suggestions!
setOwnerSuggestions(['jjoyce']);
const onOwnerQueryChange = (query: string) => {
getOwnerAutoCompleteResults({
variables: {
input: {
type: EntityType.User,
query,
field: 'ldap',
},
},
});
};
const onSelectOwner = (ldap: string) => {
// TODO: Remove sample suggestions.
const newOwnerTableData = [
...ownerTableData,
// TODO: Remove this sample code.
const newOwners = [
...owners,
{
urn: 'urn:li:corpuser:ldap',
ldap,
fullName: 'John Joyce',
type: 'USER',
role: OwnershipType.Delegate,
pictureLink: null,
},
owner: {
urn: `urn:li:corpuser:${ldap}`,
username: ldap,
info: {
fullName: 'John Joyce',
},
editableInfo: {
pictureLink: null,
},
},
type: OwnershipType.Delegate,
} as Owner,
];
setOwnerTableData(newOwnerTableData);
setOwners(newOwners);
};
const onOwnershipTypeChange = (urn: string, type: string) => {
const newOwnerTableData = ownerTableData.map((ownerRow) =>
ownerRow.urn === urn ? { ...ownerRow, type } : ownerRow,
);
setOwnerTableData(newOwnerTableData);
const onOwnershipTypeChange = (urn: string, type: OwnershipType) => {
const newOwners = owners.map((owner: Owner) => (owner.owner.urn === urn ? { ...owner, type } : owner));
setOwners(newOwners);
};
const ownerTableColumns = [
@ -137,7 +151,7 @@ export const Ownership: React.FC<Props> = ({ owners, lastModifiedAt }: Props): J
</Row>
<Row>
<Col span={24}>
<Table pagination={false} columns={ownerTableColumns} dataSource={ownerTableData} />
<Table pagination={false} columns={ownerTableColumns} dataSource={getOwnerTableData(owners)} />
</Col>
</Row>
<Row>
@ -149,7 +163,14 @@ export const Ownership: React.FC<Props> = ({ owners, lastModifiedAt }: Props): J
)}
{showAddOwner && (
<AutoComplete
options={ownerSuggestions.map((result: string) => ({ value: result }))}
options={
(searchOwnerSuggestionsData &&
searchOwnerSuggestionsData.autoComplete &&
searchOwnerSuggestionsData.autoComplete.suggestions.map((result: string) => ({
value: result,
}))) ||
[]
}
style={{
width: 150,
}}

View File

@ -7,7 +7,7 @@ import { SearchablePage } from './SearchablePage';
import { fromCollectionName, fromPathName, toCollectionName, toPathName } from '../shared/EntityTypeUtil';
import { useGetSearchResultsQuery } from '../../graphql/search.generated';
import { SearchResults } from './SearchResults';
import { PlatformNativeType } from '../../types.generated';
import { EntityType, PlatformNativeType } from '../../types.generated';
import { SearchFilters } from './SearchFilters';
import { SearchCfg } from '../../conf';
import { PageRoutes } from '../../conf/Global';
@ -24,21 +24,19 @@ export const SearchPage = () => {
const location = useLocation();
const params = QueryString.parse(location.search);
const typeParam = params.type ? fromPathName(params.type as string) : SEARCHABLE_ENTITY_TYPES[0];
const queryParam = params.query ? (params.query as string) : '';
const pageParam = params.page && Number(params.page as string) > 0 ? Number(params.page as string) : 1;
const filtersParam = location.state
? ((location.state as any).filters as Array<{ field: string; value: string }>)
: [];
const type = params.type ? fromPathName(params.type as string) : SEARCHABLE_ENTITY_TYPES[0];
const query = params.query ? (params.query as string) : '';
const page = params.page && Number(params.page as string) > 0 ? Number(params.page as string) : 1;
const filters = location.state ? ((location.state as any).filters as Array<{ field: string; value: string }>) : [];
const { loading, error, data } = useGetSearchResultsQuery({
variables: {
input: {
type: typeParam,
query: queryParam,
start: (pageParam - 1) * RESULTS_PER_PAGE,
type,
query,
start: (page - 1) * RESULTS_PER_PAGE,
count: RESULTS_PER_PAGE,
filters: filtersParam,
filters,
},
},
});
@ -47,30 +45,30 @@ export const SearchPage = () => {
const entityType = fromCollectionName(newType);
history.push({
pathname: PageRoutes.SEARCH,
search: `?type=${toPathName(entityType)}&query=${queryParam}&page=1`,
search: `?type=${toPathName(entityType)}&query=${query}&page=1`,
});
};
const onFilterSelect = (selected: boolean, field: string, value: string) => {
const newFilters = selected
? [...filtersParam, { field, value }]
: filtersParam.filter((filter) => filter.field !== field || filter.value !== value);
? [...filters, { field, value }]
: filters.filter((filter) => filter.field !== field || filter.value !== value);
history.push({
pathname: PageRoutes.SEARCH,
search: `?type=${toPathName(typeParam)}&query=${queryParam}&page=1`,
search: `?type=${toPathName(type)}&query=${query}&page=1`,
state: {
filters: newFilters,
},
});
};
const onResultsPageChange = (page: number) => {
const onResultsPageChange = (newPage: number) => {
return history.push({
pathname: PageRoutes.SEARCH,
search: `?type=${toPathName(typeParam)}&query=${queryParam}&page=${page}`,
search: `?type=${toPathName(type)}&query=${query}&page=${newPage}`,
state: {
filters: [...filtersParam],
filters: [...filters],
},
});
};
@ -78,9 +76,6 @@ export const SearchPage = () => {
const navigateToDataset = (urn: string) => {
return history.push({
pathname: `${PageRoutes.DATASETS}/${urn}`,
state: {
filters: [...filtersParam],
},
});
};
@ -126,32 +121,29 @@ export const SearchPage = () => {
};
};
/* eslint-disable no-underscore-dangle */
const toSearchResults = (elements: { __typename: string }[]) => {
return elements.map((element) => {
switch (element.__typename) {
case 'Dataset':
return toDatasetSearchResult(element as any);
default:
throw new Error('Non-dataset search type currently not supported!');
}
});
const toSearchResults = (elements: any) => {
switch (type) {
case EntityType.Dataset:
return elements.map((element: any) => toDatasetSearchResult(element));
default:
throw new Error(`Search for entity of type ${type} currently not supported!`);
}
};
const searchResults = (data && data?.search && toSearchResults(data.search.elements)) || [];
return (
<SearchablePage initialQuery={queryParam} initialType={typeParam}>
<SearchablePage initialQuery={query} initialType={type}>
<Layout.Content style={{ backgroundColor: 'white' }}>
<Affix offsetTop={64}>
<Tabs
tabBarStyle={{ backgroundColor: 'white', padding: '0px 165px', marginBottom: '0px' }}
defaultActiveKey={toCollectionName(typeParam)}
defaultActiveKey={toCollectionName(type)}
size="large"
onChange={(newPath) => onSearchTypeChange(newPath)}
>
{SEARCHABLE_ENTITY_TYPES.map((type) => (
<Tabs.TabPane tab={toCollectionName(type)} key={toCollectionName(type)} />
{SEARCHABLE_ENTITY_TYPES.map((t) => (
<Tabs.TabPane tab={toCollectionName(t)} key={toCollectionName(t)} />
))}
</Tabs>
</Affix>
@ -159,7 +151,7 @@ export const SearchPage = () => {
<Col style={{ margin: '24px 0px 0px 0px', padding: '0px 15px' }} span={6}>
<SearchFilters
facets={(data && data?.search && data.search.facets) || []}
selectedFilters={filtersParam}
selectedFilters={filters}
onFilterSelect={onFilterSelect}
/>
</Col>
@ -168,7 +160,7 @@ export const SearchPage = () => {
{error && !data && <p>Search error!</p>}
{data && data?.search && (
<SearchResults
typeName={toCollectionName(typeParam)}
typeName={toCollectionName(type)}
results={searchResults}
pageStart={data.search?.start}
pageSize={data.search?.count}

View File

@ -22,7 +22,7 @@ export const SearchResults = ({
pageSize: _pageSize,
totalResults: _totalResults,
results: _results,
onChangePage,
onChangePage: _onChangePage,
}: Props) => {
return (
<Card
@ -52,7 +52,7 @@ export const SearchResults = ({
pageSize={_pageSize}
total={_totalResults / _pageSize}
showLessItems
onChange={onChangePage}
onChange={_onChangePage}
/>
</Card>
);

View File

@ -4,3 +4,8 @@ import { EntityType } from '../components/shared/EntityTypeUtil';
Browsable Entity Types
*/
export const BROWSABLE_ENTITY_TYPES = [EntityType.Dataset];
/*
Number of results shown per browse results page
*/
export const RESULTS_PER_PAGE = 20;

View File

@ -11,7 +11,8 @@ export const LOGO_IMAGE = DataHubLogo;
export enum PageRoutes {
LOG_IN = '/login',
SEARCH = '/search',
BROWSE = '/browse',
BROWSE_TYPES = '/browse',
BROWSE_RESULTS = '/browse/:type',
DATASETS = '/datasets',
USERS = '/users',
}

View File

@ -7,6 +7,7 @@ export const SEARCHABLE_ENTITY_TYPES = [EntityType.Dataset, EntityType.User];
/*
Default AutoComplete field by entity. Required if autocomplete is desired on a SEARCHABLE_ENTITY_TYPE.
Note that we need to consider this further, because in some cases fields may differ: eg. ldap vs name search for Users.
*/
export const getAutoCompleteFieldName = (type: EntityType) => {
switch (type) {

View File

@ -3,5 +3,6 @@ import * as Search from './Search';
import * as Browse from './Browse';
// TODO: A way to populate configs without code changes?
// TODO: Introduce Theme configs
// TOOD: Entity-oriented configurations?
// TODO: Theme configs
export { Global as GlobalCfg, Search as SearchCfg, Browse as BrowseCfg };

View File

@ -0,0 +1,17 @@
query getUser($urn: String!) {
user(urn: $urn) {
urn
username
info {
active
displayName
title
firstName
lastName
fullName
}
editableInfo {
pictureLink
}
}
}