Skip to content

Commit

Permalink
feat: Add a loader to the MatchersInput component
Browse files Browse the repository at this point in the history
  • Loading branch information
yomete committed Jul 11, 2022
1 parent 5a7103f commit a2afe58
Show file tree
Hide file tree
Showing 2 changed files with 117 additions and 74 deletions.
189 changes: 116 additions & 73 deletions ui/packages/shared/components/src/MatchersInput/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import {Query} from '@parca/parser';
import {LabelsResponse, QueryServiceClient, ValuesResponse} from '@parca/client';
import {usePopper} from 'react-popper';
import cx from 'classnames';

import Spinner from '../Spinner';
import {useGrpcMetadata} from '../GrpcMetadataContext';

interface MatchersInputProps {
Expand All @@ -22,6 +24,11 @@ export interface ILabelValuesResult {
error?: Error;
}

interface UseLabelNames {
result: ILabelNamesResult;
loading: boolean;
}

interface Matchers {
key: string;
matcherType: string;
Expand All @@ -34,6 +41,14 @@ enum Labels {
literal = 'literal',
}

const LoadingSpinner = () => {
return (
<div className="pt-2 pb-4">
<Spinner />
</div>
);
};

// eslint-disable-next-line no-useless-escape
const labelNameValueRe = /(^([a-z])\w+)(=|!=|=~|!~)(\")[a-zA-Z0-9_.-:]*(\")$/g;

Expand All @@ -42,19 +57,26 @@ const addQuoteMarks = (labelValue: string) => {
return `\"${labelValue}\"`;
};

export const useLabelNames = (client: QueryServiceClient): ILabelNamesResult => {
export const useLabelNames = (client: QueryServiceClient): UseLabelNames => {
const [loading, setLoading] = useState(true);
const [result, setResult] = useState<ILabelNamesResult>({});
const metadata = useGrpcMetadata();

useEffect(() => {
const call = client.labels({match: []}, {meta: metadata});

setLoading(true);
call.response
.then(response => setResult({response: response}))
.catch(error => setResult({error: error}));
.then(response => {
setResult({response: response});
setLoading(false);
})
.catch(error => {
setResult({error: error});
setLoading(false);
});
}, [client, metadata]);

return result;
return {result, loading};
};

class Suggestion {
Expand Down Expand Up @@ -97,19 +119,28 @@ const MatchersInput = ({
const [lastCompleted, setLastCompleted] = useState<Suggestion>(new Suggestion('', '', ''));
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
const [labelValuesResponse, setLabelValuesResponse] = useState<string[] | null>(null);
const [labelValuesLoading, setLabelValuesLoading] = useState(false);
const {styles, attributes} = usePopper(divInputRef, popperElement, {
placement: 'bottom-start',
});
const metadata = useGrpcMetadata();

const {response: labelNamesResponse, error: labelNamesError} = useLabelNames(queryClient);
const {loading: labelNamesLoading, result} = useLabelNames(queryClient);
const {response: labelNamesResponse, error: labelNamesError} = result;

const getLabelNameValues = (labelName: string) => {
const call = queryClient.values({labelName: labelName, match: []}, {meta: metadata});

setLabelValuesLoading(true);
call.response
.then(response => setLabelValuesResponse(response.labelValues))
.catch(() => setLabelValuesResponse(null));
.then(response => {
setLabelValuesResponse(response.labelValues);
setLabelValuesLoading(false);
})
.catch(() => {
setLabelValuesResponse(null);
setLabelValuesLoading(false);
});
};

const labelNames =
Expand Down Expand Up @@ -467,73 +498,85 @@ const MatchersInput = ({
/>
</div>

{suggestionsLength > 0 && (
<div
ref={setPopperElement}
style={{...styles.popper, marginLeft: 0}}
{...attributes.popper}
className="z-50"
<div
ref={setPopperElement}
style={{...styles.popper, marginLeft: 0}}
{...attributes.popper}
className="z-50"
>
<Transition
show={focusedInput && showSuggest}
as={Fragment}
leave="transition ease-in duration-100"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<Transition
show={focusedInput && showSuggest}
as={Fragment}
leave="transition ease-in duration-100"
leaveFrom="opacity-100"
leaveTo="opacity-0"
<div
style={{width: divInputRef?.offsetWidth}}
className="absolute z-10 mt-1 bg-gray-50 dark:bg-gray-900 shadow-lg rounded-md text-base ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none sm:text-sm"
>
<div
style={{width: divInputRef?.offsetWidth}}
className="absolute z-10 mt-1 bg-gray-50 dark:bg-gray-900 shadow-lg rounded-md text-base ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none sm:text-sm"
>
{suggestionSections.labelNames.map((l, i) => (
<div
key={i}
className={cx(
highlightedSuggestionIndex === i && 'text-white bg-indigo-600',
'cursor-default select-none relative py-2 pl-3 pr-9'
)}
onMouseOver={() => setHighlightedSuggestionIndex(i)}
onClick={() => applySuggestion(i)}
onMouseOut={() => resetHighlight()}
>
{l.value}
</div>
))}
{suggestionSections.literals.map((l, i) => (
<div
key={i}
className={cx(
highlightedSuggestionIndex === i + suggestionSections.labelNames.length &&
'text-white bg-indigo-600',
'cursor-default select-none relative py-2 pl-3 pr-9'
)}
onMouseOver={() =>
setHighlightedSuggestionIndex(i + suggestionSections.labelNames.length)
}
onClick={() => applySuggestion(i + suggestionSections.labelNames.length)}
onMouseOut={() => resetHighlight()}
>
{l.value}
</div>
))}
{suggestionSections.labelValues.map((l, i) => (
<div
key={i}
className={cx(
highlightedSuggestionIndex === i && 'text-white bg-indigo-600',
'cursor-default select-none relative py-2 pl-3 pr-9'
)}
onMouseOver={() => setHighlightedSuggestionIndex(i)}
onClick={() => applySuggestion(i)}
onMouseOut={() => resetHighlight()}
>
{l.value}
</div>
))}
</div>
</Transition>
</div>
)}
{labelNamesLoading ? (
<LoadingSpinner />
) : (
<>
{suggestionSections.labelNames.map((l, i) => (
<div
key={i}
className={cx(
highlightedSuggestionIndex === i && 'text-white bg-indigo-600',
'cursor-default select-none relative py-2 pl-3 pr-9'
)}
onMouseOver={() => setHighlightedSuggestionIndex(i)}
onClick={() => applySuggestion(i)}
onMouseOut={() => resetHighlight()}
>
{l.value}
</div>
))}
</>
)}

{suggestionSections.literals.map((l, i) => (
<div
key={i}
className={cx(
highlightedSuggestionIndex === i + suggestionSections.labelNames.length &&
'text-white bg-indigo-600',
'cursor-default select-none relative py-2 pl-3 pr-9'
)}
onMouseOver={() =>
setHighlightedSuggestionIndex(i + suggestionSections.labelNames.length)
}
onClick={() => applySuggestion(i + suggestionSections.labelNames.length)}
onMouseOut={() => resetHighlight()}
>
{l.value}
</div>
))}

{labelValuesLoading && suggestionSections.labelValues.length > 0 ? (
<LoadingSpinner />
) : (
<>
{suggestionSections.labelValues.map((l, i) => (
<div
key={i}
className={cx(
highlightedSuggestionIndex === i && 'text-white bg-indigo-600',
'cursor-default select-none relative py-2 pl-3 pr-9'
)}
onMouseOver={() => setHighlightedSuggestionIndex(i)}
onClick={() => applySuggestion(i)}
onMouseOut={() => resetHighlight()}
>
{l.value}
</div>
))}
</>
)}
</div>
</Transition>
</div>
</>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import MetricsGraph from '../MetricsGraph';
import {ProfileSelection, SingleProfileSelection} from '@parca/profile';
import {QueryServiceClient, QueryRangeResponse, Label, Timestamp} from '@parca/client';
import {RpcError} from '@protobuf-ts/runtime-rpc';
import {DateTimeRange, Spinner, useGrpcMetadata} from '../';
import {DateTimeRange, useGrpcMetadata} from '../';
import {Query} from '@parca/parser';
import {useParcaTheme} from '../ParcaThemeContext';

Expand Down

0 comments on commit a2afe58

Please sign in to comment.