[Workplace Search] Polish Workplace Search Sources & Groups UI (#85071)

* Add spacer to sources page title

* Add space to source list description

* Remove sidebar content from headers

* Polish inner source overview content

* Polish source content loading state and view

* Hide sources header / remove spacers

* Formatting fix

* Fix lint issues

* Add align right to schema table

* Remove rendom EmptyPrompt

WTF

* Make SourceIcon take a variable size

* Add back SourceInfoCard with update design

Co-authored-by: scottybollinger <scotty.bollinger@elastic.co>
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
John Barrier Wilson 2020-12-08 15:30:41 -06:00 committed by GitHub
parent 516d886c3e
commit 30611f431a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 117 additions and 206 deletions

View file

@ -11,7 +11,6 @@
align-items: center;
min-height: 200px;
border-radius: 4px;
background-color: #FAFBFD;
.componentLoaderText {
margin-left: 10px;

View file

@ -8,7 +8,7 @@ import React from 'react';
import { camelCase } from 'lodash';
import { EuiIcon } from '@elastic/eui';
import { EuiIcon, IconSize } from '@elastic/eui';
import './source_icon.scss';
@ -21,6 +21,7 @@ interface SourceIconProps {
className?: string;
wrapped?: boolean;
fullBleed?: boolean;
size?: IconSize;
}
export const SourceIcon: React.FC<SourceIconProps> = ({
@ -29,13 +30,14 @@ export const SourceIcon: React.FC<SourceIconProps> = ({
className,
wrapped,
fullBleed = false,
size = 'xxl',
}) => {
const icon = (
<EuiIcon
type={fullBleed ? imagesFull[camelCase(serviceType)] : images[camelCase(serviceType)]}
title={name}
className={className}
size="xxl"
size={size}
/>
);
return wrapped ? (

View file

@ -44,6 +44,7 @@ export const ViewContentHeader: React.FC<ViewContentHeaderProps> = ({
<EuiFlexGroup alignItems={alignItems} justifyContent="spaceBetween">
<EuiFlexItem>
<EuiTitle size={titleSize}>{titleElement}</EuiTitle>
<EuiSpacer size="s" />
{description && (
<EuiText grow={false} color="subdued">
<p>{description}</p>

View file

@ -12,11 +12,10 @@ import { useHistory } from 'react-router-dom';
import { AppLogic } from '../../../../app_logic';
import { Loading } from '../../../../../../applications/shared/loading';
import { ViewContentHeader } from '../../../../components/shared/view_content_header';
import { CUSTOM_SERVICE_TYPE } from '../../../../constants';
import { staticSourceData } from '../../source_data';
import { SourceLogic } from '../../source_logic';
import { SourceDataItem, FeatureIds } from '../../../../types';
import { SourceDataItem } from '../../../../types';
import { SOURCE_ADDED_PATH, getSourcesPath } from '../../../../routes';
import { AddSourceHeader } from './add_source_header';
@ -90,7 +89,6 @@ export const AddSource: React.FC<AddSourceProps> = ({
}, []);
const isCustom = serviceType === CUSTOM_SERVICE_TYPE;
const isRemote = features?.platinumPrivateContext.includes(FeatureIds.Remote);
const getFirstStep = () => {
if (isCustom) return Steps.ConfigureCustomStep;
@ -121,61 +119,10 @@ export const AddSource: React.FC<AddSourceProps> = ({
history.push(`${getSourcesPath(SOURCE_ADDED_PATH, isOrganization)}/?name=${sourceName}`);
};
const pageTitle = () => {
if (currentStep === Steps.ConnectInstanceStep || currentStep === Steps.ConfigureOauthStep) {
return 'Connect';
}
if (currentStep === Steps.ReAuthenticateStep) {
return 'Re-authenticate';
}
if (currentStep === Steps.ConfigureCustomStep || currentStep === Steps.SaveCustomStep) {
return 'Create a';
}
return 'Configure';
};
const CREATE_CUSTOM_SOURCE_SIDEBAR_BLURB =
'Custom API Sources provide a set of feature-rich endpoints for indexing data from any content repository.';
const CONFIGURE_ORGANIZATION_SOURCE_SIDEBAR_BLURB =
'Follow the configuration flow to add a new content source to Workplace Search. First, create an OAuth application in the content source. After that, connect as many instances of the content source that you need.';
const CONFIGURE_PRIVATE_SOURCE_SIDEBAR_BLURB =
'Follow the configuration flow to add a new private content source to Workplace Search. Private content sources are added by each person via their own personal dashboards. Their data stays safe and visible only to them.';
const CONNECT_ORGANIZATION_SOURCE_SIDEBAR_BLURB = `Upon successfully connecting ${name}, source content will be synced to your organization and will be made available and searchable.`;
const CONNECT_PRIVATE_REMOTE_SOURCE_SIDEBAR_BLURB = (
<>
{name} is a <strong>remote source</strong>, which means that each time you search, we reach
out to the content source and get matching results directly from {name}&apos;s servers.
</>
);
const CONNECT_PRIVATE_STANDARD_SOURCE_SIDEBAR_BLURB = (
<>
{name} is a <strong>standard source</strong> for which content is synchronized on a regular
basis, in a relevant and secure way.
</>
);
const CONNECT_PRIVATE_SOURCE_SIDEBAR_BLURB = isRemote
? CONNECT_PRIVATE_REMOTE_SOURCE_SIDEBAR_BLURB
: CONNECT_PRIVATE_STANDARD_SOURCE_SIDEBAR_BLURB;
const CONFIGURE_SOURCE_SIDEBAR_BLURB = accountContextOnly
? CONFIGURE_PRIVATE_SOURCE_SIDEBAR_BLURB
: CONFIGURE_ORGANIZATION_SOURCE_SIDEBAR_BLURB;
const CONFIG_SIDEBAR_BLURB = isCustom
? CREATE_CUSTOM_SOURCE_SIDEBAR_BLURB
: CONFIGURE_SOURCE_SIDEBAR_BLURB;
const CONNECT_SIDEBAR_BLURB = isOrganization
? CONNECT_ORGANIZATION_SOURCE_SIDEBAR_BLURB
: CONNECT_PRIVATE_SOURCE_SIDEBAR_BLURB;
const PAGE_DESCRIPTION =
currentStep === Steps.ConnectInstanceStep ? CONNECT_SIDEBAR_BLURB : CONFIG_SIDEBAR_BLURB;
const header = <AddSourceHeader name={name} serviceType={serviceType} categories={categories} />;
return (
<>
<ViewContentHeader title={pageTitle()} description={PAGE_DESCRIPTION} />
{currentStep === Steps.ConfigIntroStep && (
<ConfigurationIntro name={name} advanceStep={goToSaveConfig} header={header} />
)}

View file

@ -31,7 +31,7 @@ import { AvailableSourcesList } from './available_sources_list';
import { ConfiguredSourcesList } from './configured_sources_list';
const NEW_SOURCE_DESCRIPTION =
'When configuring and connecting a source, you are creating distinct entities with searchable content synchronized from the content platform itself. A source can be added using one of the available source connectors or via Custom API Sources, for additional flexibility.';
'When configuring and connecting a source, you are creating distinct entities with searchable content synchronized from the content platform itself. A source can be added using one of the available source connectors or via Custom API Sources, for additional flexibility. ';
const ORG_SOURCE_DESCRIPTION =
'Shared content sources are available to your entire organization or can be assigned to specific user groups.';
const PRIVATE_SOURCE_DESCRIPTION =
@ -99,7 +99,7 @@ export const AddSourceList: React.FC = () => {
<ViewContentHeader title={PAGE_TITLE} description={PAGE_DESCRIPTION} />
{showConfiguredSourcesList || isOrganization ? (
<ContentSection>
<EuiSpacer />
<EuiSpacer size="m" />
<EuiFormRow>
<EuiFieldSearch
data-test-subj="FilterSourcesInput"

View file

@ -248,11 +248,9 @@ export const Overview: React.FC = () => {
};
return !groups.length ? null : (
<EuiPanel>
<EuiText size="s">
<h6>
<EuiTextColor color="subdued">Group Access</EuiTextColor>
</h6>
<>
<EuiText>
<h4>Group Access</h4>
</EuiText>
<EuiSpacer size="s" />
<EuiFlexGroup direction="column" gutterSize="s">
@ -275,35 +273,36 @@ export const Overview: React.FC = () => {
</EuiFlexItem>
))}
</EuiFlexGroup>
</EuiPanel>
</>
);
};
const detailsSummary = (
<EuiPanel>
<EuiText size="s">
<h6>
<EuiTextColor color="subdued">Configuration</EuiTextColor>
</h6>
<>
<EuiSpacer size="l" />
<EuiText>
<h4>Configuration</h4>
</EuiText>
<EuiSpacer />
<EuiText size="s">
{details.map((detail, index) => (
<EuiFlexGroup
wrap
gutterSize="s"
alignItems="center"
justifyContent="spaceBetween"
key={index}
>
<EuiFlexItem grow={false}>
<strong>{detail.title}</strong>
</EuiFlexItem>
<EuiFlexItem grow={false}>{detail.description}</EuiFlexItem>
</EuiFlexGroup>
))}
</EuiText>
</EuiPanel>
<EuiSpacer size="s" />
<EuiPanel>
<EuiText size="s">
{details.map((detail, index) => (
<EuiFlexGroup
wrap
gutterSize="s"
alignItems="center"
justifyContent="spaceBetween"
key={index}
>
<EuiFlexItem grow={false}>
<strong>{detail.title}</strong>
</EuiFlexItem>
<EuiFlexItem grow={false}>{detail.description}</EuiFlexItem>
</EuiFlexGroup>
))}
</EuiText>
</EuiPanel>
</>
);
const documentPermissions = (
@ -472,10 +471,9 @@ export const Overview: React.FC = () => {
return (
<>
<ViewContentHeader title="Source overview" />
<EuiSpacer size="m" />
<EuiFlexGroup gutterSize="xl" alignItems="flexStart">
<EuiFlexItem>
<EuiFlexGroup gutterSize="xl" direction="column">
<EuiFlexGroup gutterSize="s" direction="column">
<EuiFlexItem>
<DocumentSummary />
</EuiFlexItem>
@ -525,7 +523,6 @@ export const Overview: React.FC = () => {
)}
</EuiFlexGroup>
</EuiFlexItem>
<EuiEmptyPrompt />
</EuiFlexGroup>
</>
);

View file

@ -34,10 +34,12 @@ export const SchemaFieldsTable: React.FC = () => {
const { filteredSchemaFields, filterValue } = useValues(SchemaLogic);
return Object.keys(filteredSchemaFields).length > 0 ? (
<EuiTable>
<EuiTable tableLayout="auto">
<EuiTableHeader>
<EuiTableHeaderCell>{SCHEMA_ERRORS_TABLE_FIELD_NAME_HEADER}</EuiTableHeaderCell>
<EuiTableHeaderCell>{SCHEMA_ERRORS_TABLE_DATA_TYPE_HEADER}</EuiTableHeaderCell>
<EuiTableHeaderCell align="right">
{SCHEMA_ERRORS_TABLE_DATA_TYPE_HEADER}
</EuiTableHeaderCell>
</EuiTableHeader>
<EuiTableBody>
{Object.keys(filteredSchemaFields).map((fieldName) => (
@ -49,7 +51,7 @@ export const SchemaFieldsTable: React.FC = () => {
</EuiFlexItem>
</EuiFlexGroup>
</EuiTableRowCell>
<EuiTableRowCell>
<EuiTableRowCell align="right">
<SchemaExistingField
disabled={fieldName === 'id'}
key={fieldName}

View file

@ -95,24 +95,20 @@ export const SourceContent: React.FC = () => {
const emptyState = (
<EuiPanel className="euiPanel--inset">
<EuiSpacer size="xxl" />
<EuiPanel className="euiPanel--inset">
<EuiEmptyPrompt
title={<h2>{emptyMessage}</h2>}
iconType="documents"
body={
isCustomSource ? (
<p>
Learn more about adding content in our{' '}
<EuiLink target="_blank" href={CUSTOM_SOURCE_DOCS_URL}>
documentation
</EuiLink>
</p>
) : null
}
/>
</EuiPanel>
<EuiSpacer size="l" />
<EuiEmptyPrompt
title={<h2>{emptyMessage}</h2>}
iconType="documents"
body={
isCustomSource ? (
<p>
Learn more about adding content in our{' '}
<EuiLink target="_blank" href={CUSTOM_SOURCE_DOCS_URL}>
documentation
</EuiLink>
</p>
) : null
}
/>
</EuiPanel>
);
@ -185,7 +181,6 @@ export const SourceContent: React.FC = () => {
return (
<>
<ViewContentHeader title="Source content" />
<EuiSpacer size="l" />
<EuiFlexGroup gutterSize="s">
<EuiFlexItem grow={false}>
<EuiFieldSearch

View file

@ -7,13 +7,13 @@
import React from 'react';
import {
EuiDescriptionList,
EuiDescriptionListDescription,
EuiDescriptionListTitle,
EuiBadge,
EuiFlexGroup,
EuiFlexItem,
EuiHealth,
EuiSpacer,
EuiText,
EuiTitle,
} from '@elastic/eui';
import { SourceIcon } from '../../../components/shared/source_icon';
@ -31,77 +31,56 @@ export const SourceInfoCard: React.FC<SourceInfoCardProps> = ({
dateCreated,
isFederatedSource,
}) => (
<EuiFlexGroup gutterSize="none" justifyContent="spaceBetween">
<EuiFlexGroup gutterSize="none" justifyContent="spaceBetween" alignItems="center">
<EuiFlexItem>
<EuiDescriptionList textStyle="reverse" className="content-source-meta">
<EuiDescriptionListTitle>
<span className="content-source-meta__title">Connector</span>
</EuiDescriptionListTitle>
<EuiDescriptionListDescription>
<EuiFlexGroup
gutterSize="xs"
alignItems="center"
className="content-source-meta__content"
>
<EuiFlexItem grow={false}>
<SourceIcon
className="content-source-meta__icon"
serviceType={sourceType}
name={sourceType}
/>
</EuiFlexItem>
<EuiFlexItem>
<span title={sourceName} className="eui-textTruncate">
{sourceName}
</span>
</EuiFlexItem>
</EuiFlexGroup>
</EuiDescriptionListDescription>
</EuiDescriptionList>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiSpacer className="euiSpacer--vertical" />
</EuiFlexItem>
<EuiFlexItem grow={isFederatedSource}>
<EuiDescriptionList textStyle="reverse" className="content-source-meta">
<EuiDescriptionListTitle>
<span className="content-source-meta__title">Created</span>
</EuiDescriptionListTitle>
<EuiDescriptionListDescription>
<EuiFlexGroup
gutterSize="xs"
alignItems="center"
className="content-source-meta__content"
>
<EuiFlexItem>{dateCreated}</EuiFlexItem>
</EuiFlexGroup>
</EuiDescriptionListDescription>
</EuiDescriptionList>
</EuiFlexItem>
{isFederatedSource && (
<>
<EuiFlexItem grow={false}>
<EuiSpacer className="euiSpacer--vertical" />
<EuiFlexGroup gutterSize="none" justifyContent="flexStart" alignItems="center">
<EuiFlexItem grow={null}>
<SourceIcon
className="content-source-meta__icon"
serviceType={sourceType}
name={sourceType}
fullBleed
size="l"
/>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiDescriptionList textStyle="reverse" className="content-source-meta">
<EuiDescriptionListTitle>
<span className="content-source-meta__title">Status</span>
</EuiDescriptionListTitle>
<EuiDescriptionListDescription>
<EuiFlexGroup
gutterSize="xs"
alignItems="center"
className="content-source-meta__content"
>
<EuiFlexItem>
<EuiHealth color="success">Ready to search</EuiHealth>
</EuiFlexItem>
</EuiFlexGroup>
</EuiDescriptionListDescription>
</EuiDescriptionList>
<EuiFlexItem>
<EuiTitle size="s">
<h5 style={{ paddingLeft: 8 }}>{sourceName}</h5>
</EuiTitle>
</EuiFlexItem>
</>
)}
</EuiFlexGroup>
{isFederatedSource && (
<EuiFlexGroup gutterSize="none" justifyContent="flexStart">
<EuiFlexItem grow={null}>
<EuiSpacer size="xs" />
<EuiBadge iconType="online" iconSide="left">
Remote Source
</EuiBadge>
</EuiFlexItem>
</EuiFlexGroup>
)}
</EuiFlexItem>
<EuiFlexItem>
<EuiText textAlign="right" size="s">
<strong>Created: </strong>
{dateCreated}
</EuiText>
{isFederatedSource && (
<EuiFlexGroup gutterSize="none" justifyContent="flexEnd" alignItems="center">
<EuiFlexItem grow={null}>
<EuiText textAlign="right" size="s">
<strong>Status: </strong>
</EuiText>
</EuiFlexItem>
<EuiFlexItem grow={null}>
<EuiText textAlign="right" size="s">
<EuiHealth color="success">Ready to search</EuiHealth>
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
)}
</EuiFlexItem>
</EuiFlexGroup>
);

View file

@ -20,7 +20,6 @@ import {
EuiFlexGroup,
EuiFlexItem,
EuiFormRow,
EuiSpacer,
} from '@elastic/eui';
import { SOURCES_PATH, getSourcesPath } from '../../../routes';
@ -108,7 +107,6 @@ export const SourceSettings: React.FC = () => {
return (
<>
<ViewContentHeader title="Source settings" />
<EuiSpacer />
<ContentSection
title="Content source name"
description="Customize the name of this content source."

View file

@ -22,9 +22,6 @@ import { SourcesLogic } from './sources_logic';
import { SourcesView } from './sources_view';
const ORG_LINK_TITLE = 'Add an organization content source';
const ORG_PAGE_TITLE = 'Manage organization content sources';
const ORG_PAGE_DESCRIPTION =
'Organization sources are available to the entire organization and can be shared to specific user groups. By default, newly created organization sources are added to the Default group.';
const ORG_HEADER_TITLE = 'Organization sources';
const ORG_HEADER_DESCRIPTION =
'Organization sources are available to the entire organization and can be assigned to specific user groups.';
@ -50,8 +47,6 @@ export const OrganizationSources: React.FC = () => {
return (
<SourcesView>
{/* TODO: Figure out with design how to make this look better w/o 2 ViewContentHeaders */}
<ViewContentHeader title={ORG_PAGE_TITLE} description={ORG_PAGE_DESCRIPTION} />
<ViewContentHeader
title={headerTitle}
action={

View file

@ -11,7 +11,7 @@ import { useActions, useValues } from 'kea';
import moment from 'moment';
import { Route, Switch, useHistory, useParams } from 'react-router-dom';
import { EuiButton, EuiCallOut, EuiSpacer } from '@elastic/eui';
import { EuiButton, EuiCallOut, EuiHorizontalRule, EuiSpacer } from '@elastic/eui';
import { SetWorkplaceSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome';
import { SendWorkplaceSearchTelemetry as SendTelemetry } from '../../../shared/telemetry';
@ -69,18 +69,15 @@ export const SourceRouter: React.FC = () => {
const isCustomSource = serviceType === CUSTOM_SERVICE_TYPE;
const pageHeader = (
<div>
<span className="eui-textOverflowWrap" title={name}>
{name}
<SourceInfoCard
sourceName={serviceName}
sourceType={serviceType}
dateCreated={moment(createdAt).format('MMMM D, YYYY')}
isFederatedSource={isFederatedSource}
/>
</span>
</div>
<>
<SourceInfoCard
sourceName={serviceName}
sourceType={serviceType}
dateCreated={moment(createdAt).format('MMMM D, YYYY')}
isFederatedSource={isFederatedSource}
/>
<EuiHorizontalRule />
</>
);
const callout = (
@ -101,7 +98,6 @@ export const SourceRouter: React.FC = () => {
return (
<>
{!supportedByLicense && callout}
{/* TODO: Figure out with design how to make this look better */}
{pageHeader}
<Switch>
<Route exact path={sourcePath(SOURCE_DETAILS_PATH, sourceId, isOrganization)}>