Add additional help to quick search and an advanced search documentation page (#1757)

Co-authored-by: Sebastian Sdorra <sebastian.sdorra@cloudogu.com>
This commit is contained in:
Konstantin Schaper
2021-08-09 12:07:28 +02:00
committed by GitHub
parent f2249cea73
commit ddd2fc1055
43 changed files with 1494 additions and 94 deletions

View File

@@ -21,7 +21,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
import React, { FC, KeyboardEvent as ReactKeyboardEvent, MouseEvent, useCallback, useState, useEffect } from "react";
import React, { FC, KeyboardEvent as ReactKeyboardEvent, MouseEvent, useCallback, useEffect, useState } from "react";
import { Hit, Links, ValueHitField } from "@scm-manager/ui-types";
import styled from "styled-components";
import { BackendError, useSearch } from "@scm-manager/ui-api";
@@ -32,10 +32,13 @@ import {
Button,
ErrorNotification,
HitProps,
LinkStyleButton,
Notification,
RepositoryAvatar,
useStringHitFieldValue,
} from "@scm-manager/ui-components";
import SyntaxHelp from "../search/SyntaxHelp";
import SyntaxModal from "../search/SyntaxModal";
const Field = styled.div`
margin-bottom: 0 !important;
@@ -58,6 +61,7 @@ const namespaceAndName = (hit: Hit) => {
type HitsProps = {
hits: Hit[];
index: number;
showHelp: () => void;
gotoDetailSearch: () => void;
clear: () => void;
};
@@ -68,26 +72,28 @@ type GotoProps = {
gotoDetailSearch: () => void;
};
const EmptyHits: FC<GotoProps> = ({ gotoDetailSearch }) => {
const EmptyHits: FC = () => {
const [t] = useTranslation("commons");
return (
<QuickSearchNotification>
<Notification type="info">{t("search.quickSearch.noResults")}</Notification>
<MoreResults gotoDetailSearch={gotoDetailSearch} />
</QuickSearchNotification>
<Notification className="m-4" type="info">
{t("search.quickSearch.noResults")}
</Notification>
);
};
type ErrorProps = {
error: Error;
showHelp: () => void;
};
const ParseErrorNotification: FC = () => {
const ParseErrorNotification: FC<ErrorProps> = ({ showHelp }) => {
const [t] = useTranslation("commons");
// TODO add link to query syntax page/modal
return (
<QuickSearchNotification>
<Notification type="warning">{t("search.quickSearch.parseError")}</Notification>
<Notification type="warning">
<p>{t("search.quickSearch.parseError")}</p>
<LinkStyleButton onClick={showHelp}>{t("search.quickSearch.parseErrorHelp")}</LinkStyleButton>
</Notification>
</QuickSearchNotification>
);
};
@@ -96,10 +102,10 @@ const isBackendError = (error: Error | BackendError): error is BackendError => {
return (error as BackendError).errorCode !== undefined;
};
const SearchErrorNotification: FC<ErrorProps> = ({ error }) => {
const SearchErrorNotification: FC<ErrorProps> = ({ error, showHelp }) => {
// 5VScek8Xp1 is the id of sonia.scm.search.QueryParseException
if (isBackendError(error) && error.errorCode === "5VScek8Xp1") {
return <ParseErrorNotification />;
return <ParseErrorNotification error={error} showHelp={showHelp} />;
}
return (
<QuickSearchNotification>
@@ -113,6 +119,9 @@ const ResultHeading = styled.h3`
margin: 0 0.5rem;
padding: 0.375rem 0.5rem;
font-weight: bold;
display: flex;
align-items: center;
justify-content: space-between;
`;
const DropdownMenu = styled.div`
@@ -153,17 +162,13 @@ const MoreResults: FC<GotoProps> = ({ gotoDetailSearch }) => {
);
};
const Hits: FC<HitsProps> = ({ hits, index, clear, gotoDetailSearch }) => {
const HitsList: FC<HitsProps> = ({ hits, index, clear, gotoDetailSearch }) => {
const id = useCallback(namespaceAndName, [hits]);
const [t] = useTranslation("commons");
if (hits.length === 0) {
return <EmptyHits gotoDetailSearch={gotoDetailSearch} />;
return <EmptyHits />;
}
return (
<div aria-expanded="true" role="listbox" className="dropdown-content">
<ResultHeading className="dropdown-item">{t("search.quickSearch.resultHeading")}</ResultHeading>
<>
{hits.map((hit, idx) => (
<div key={id(hit)} onMouseDown={(e) => e.preventDefault()} onClick={clear}>
<Link
@@ -175,12 +180,29 @@ const Hits: FC<HitsProps> = ({ hits, index, clear, gotoDetailSearch }) => {
role="option"
data-omnisearch="true"
>
<AvatarSection hit={hit} /> {id(hit)}
<AvatarSection hit={hit} />
{id(hit)}
</Link>
</div>
))}
<MoreResults gotoDetailSearch={gotoDetailSearch} />
</div>
</>
);
};
const Hits: FC<HitsProps> = ({ showHelp, gotoDetailSearch, ...rest }) => {
const [t] = useTranslation("commons");
return (
<>
<div aria-expanded="true" role="listbox" className="dropdown-content">
<ResultHeading className="dropdown-item">
<span>{t("search.quickSearch.resultHeading")}</span>
<SyntaxHelp onClick={showHelp} />
</ResultHeading>
<HitsList showHelp={showHelp} gotoDetailSearch={gotoDetailSearch} {...rest} />
<MoreResults gotoDetailSearch={gotoDetailSearch} />
</div>
</>
);
};
@@ -305,11 +327,12 @@ const OmniSearch: FC = () => {
const debouncedQuery = useDebounce(query, 250);
const { data, isLoading, error } = useSearch(debouncedQuery, { type: "repository", pageSize: 5 });
const { showResults, hideResults, ...handlers } = useShowResultsOnFocus();
const [showHelp, setShowHelp] = useState(false);
const history = useHistory();
const clearQuery = () => {
setQuery("");
};
const openHelp = () => setShowHelp(true);
const closeHelp = () => setShowHelp(false);
const clearQuery = () => setQuery("");
const gotoDetailSearch = () => {
history.push(`/search/repository/?q=${query}`);
@@ -320,6 +343,7 @@ const OmniSearch: FC = () => {
return (
<Field className="navbar-item field">
{showHelp ? <SyntaxModal close={closeHelp} /> : null}
<div
className={classNames("control", "has-icons-right", {
"is-loading": isLoading,
@@ -346,9 +370,15 @@ const OmniSearch: FC = () => {
)}
</div>
<DropdownMenu className="dropdown-menu" onMouseDown={(e) => e.preventDefault()}>
{error ? <SearchErrorNotification error={error} /> : null}
{error ? <SearchErrorNotification error={error} showHelp={openHelp} /> : null}
{!error && data ? (
<Hits gotoDetailSearch={gotoDetailSearch} clear={clearQuery} index={index} hits={data._embedded.hits} />
<Hits
showHelp={openHelp}
gotoDetailSearch={gotoDetailSearch}
clear={clearQuery}
index={index}
hits={data._embedded.hits}
/>
) : null}
</DropdownMenu>
</div>