Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ type Props = {

const browserTimezone = getBrowserTimezone();

const TimezonesList: FC<Props>= ({ onChange }) => {
const TimezonesList: FC<Props> = ({ onChange }) => {
const { isMobile } = useDeviceDetect();
const { defaultTimezone } = useTimeState();

Expand Down Expand Up @@ -74,30 +74,30 @@ const TimezonesList: FC<Props>= ({ onChange }) => {
return (
<div
className={classNames({
"vm-timezones-list": true,
"vm-timezones-list_mobile": isMobile,
})}
"vm-list": true,
"vm-timezones-list": true,
"vm-timezones-list_mobile": isMobile,
})}
>
<div className="vm-timezones-list-header">
<div className="vm-timezones-list-header__search">
<TextField
autofocus
label="Search"
value={search}
onChange={handleChangeSearch}
/>
</div>
{pinnedTimezones.map((t, i) => t && (
</div>
Comment thread
Loori-R marked this conversation as resolved.
{pinnedTimezones.map((t, i) => t && (
<div
key={`${i}_${t.region}`}
className="vm-timezones-item vm-timezones-list-group-options__item"
className="vm-list-item vm-timezones-item vm-timezones-list-group-options__item"
onClick={createHandlerSetTimezone(t)}
>
<div className="vm-timezones-item__title">{t.title}{t.isInvalid && <WarningTimezone/>}</div>
<div className="vm-timezones-item__utc">{t.utc}</div>
</div>
))}
</div>
))}
{timezonesGroups.map(t => (
<div
className="vm-timezones-list-group"
Expand All @@ -110,18 +110,18 @@ const TimezonesList: FC<Props>= ({ onChange }) => {
<div className="vm-timezones-list-group-options">
{searchTimezones[t] && searchTimezones[t].map(item => (
<div
className="vm-timezones-item vm-timezones-list-group-options__item"
className="vm-list-item vm-timezones-item vm-timezones-list-group-options__item"
onClick={createHandlerSetTimezone(item)}
key={item.search}
>
<div className="vm-timezones-item__title">{item.region}</div>
<div className="vm-timezones-item__utc">{item.utc}</div>
</div>
))}
))}
</div>
</Accordion>
</div>
))}
))}
</div>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
}

&-list {
padding-top: 0;
max-height: 300px;
background-color: $color-background-block;
border-radius: $border-radius-medium;
Expand All @@ -72,10 +73,9 @@
top: 0;
background-color: $color-background-block;
z-index: 2;
border-bottom: $border-divider;

&__search {
padding: $padding-small;
padding: $padding-small $padding-small calc($padding-small / 2);
}
}

Expand All @@ -91,14 +91,15 @@
font-weight: bold;
color: $color-text-secondary;
padding: $padding-small $padding-global;
font-size: $font-size-small;
}

&-options {
display: grid;
align-items: flex-start;

&__item {
padding: $padding-small $padding-global;
padding: calc($padding-small / 2) $padding-global;
transition: background-color 200ms ease;

&:hover {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { useQueryDispatch, useQueryState } from "../../../state/query/QueryState
import Button from "../../Main/Button/Button";
import { AutocompleteIcon } from "../../Main/Icons";
import useDeviceDetect from "../../../hooks/useDeviceDetect";
import "./style.scss";

const AutocompleteToggle: FC = () => {
const { isMobile } = useDeviceDetect();
Expand All @@ -20,7 +21,13 @@ const AutocompleteToggle: FC = () => {
onClick={onChangeAutocomplete}
startIcon={<AutocompleteIcon/>}
>
{!isMobile && "Autocomplete: "}{autocomplete ? "On" : "Off"}
{!isMobile && "Autocomplete: "}
<span className="vm-autocomplete-status">
<span className="vm-autocomplete-status__placeholder">Off</span>
<span className="vm-autocomplete-status__value">
{autocomplete ? "On" : "Off"}
</span>
</span>
</Button>
);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,169 +1,58 @@
import { FC, useCallback, useEffect, useMemo, useState } from "preact/compat";
import Autocomplete, { AutocompleteOptions } from "../../../Main/Autocomplete/Autocomplete";
import { FC } from "preact/compat";
import Autocomplete from "../../../Main/Autocomplete/Autocomplete";
import { AUTOCOMPLETE_LIMITS } from "../../../../constants/queryAutocomplete";
import { QueryEditorAutocompleteProps } from "../QueryEditor";
import { getContextData, splitLogicalParts } from "./parser";
import { ContextType, LogicalPart } from "./types";
import { useFetchLogsQLOptions } from "./useFetchLogsQLOptions";
import { pipeList } from "./pipes";
import { useExtraFilters } from "../../../ExtraFilters/hooks/useExtraFilters";
import { useLogsQLContext } from "./hooks/useLogsQLContext";
import { useLogsQLFetchOptions } from "./hooks/useLogsQLFetchOptions";
import { useLogsQLOptions } from "./hooks/useLogsQLOptions";
import { useLogsQLSelect } from "./hooks/useLogsQLSelect";
import { DEFAULT_QUERY } from "../../../../pages/QueryPage/hooks/useQueryController";

const LogsQueryEditorAutocomplete: FC<QueryEditorAutocompleteProps> = ({
value,
anchorEl,
caretPosition,
hasHelperText,
onSelect,
onFoundOptions,
isOpen
}) => {
const [offsetPos, setOffsetPos] = useState({ top: 0, left: 0 });

const fullValue = useMemo(() => {
if (caretPosition[0] !== caretPosition[1]) return { valueBeforeCursor: value, valueAfterCursor: "" };
const valueBeforeCursor = value.substring(0, caretPosition[0]);
const valueAfterCursor = value.substring(caretPosition[1]);
return { valueBeforeCursor, valueAfterCursor };
}, [value, caretPosition]);

const logicalParts = useMemo(() => {
return splitLogicalParts(value);
}, [value]);

const contextData = useMemo(() => {
if (caretPosition[0] !== caretPosition[1]) return;
const part = logicalParts.find(p => caretPosition[0] >= p.position[0] && caretPosition[0] <= p.position[1]);
if (!part) return;
const cursorStartPosition = caretPosition[0] - part.position[0];
const prevPart = logicalParts.find(p => p.id === part.id - 1);
const queryBeforeIncompleteFilter = prevPart ? value.substring(0, prevPart.position[1] + 1) : undefined;
return {
...part,
queryBeforeIncompleteFilter,
query: value,
...getContextData(part, cursorStartPosition)
};
}, [logicalParts, caretPosition, value]);

const { extraParams } = useExtraFilters();
const { fieldNames, fieldValues, loading } = useFetchLogsQLOptions(contextData, extraParams);

const options = useMemo(() => {
switch (contextData?.contextType) {
case ContextType.FilterName:
case ContextType.FilterUnknown:
return fieldNames;
case ContextType.FilterValue:
return fieldValues;
case ContextType.PipeName:
return pipeList;
case ContextType.FilterOrPipeName:
return [...fieldNames, ...pipeList];
default:
return [];
}
}, [contextData, fieldNames, fieldValues]);

const getUpdatedValue = (insertValue: string, logicalParts: LogicalPart[], id?: number) => {
return logicalParts.reduce((acc, part) => {
const value = part.id === id ? insertValue : part.value;
return `${acc}${part.separator}${value}`;
}, "").trim();
};

const getModifyInsert = (insert: string, contextType: ContextType, value = "", insertType?: string) => {
let modifiedInsert = insert;

if (insertType === ContextType.FilterName) {
modifiedInsert += ":";
} else if (contextType === ContextType.FilterValue) {
const insertWithQuotes = value.startsWith("_stream:") ? modifiedInsert : `${JSON.stringify(modifiedInsert)}`;
modifiedInsert = `${contextData?.filterName || ""}${contextData?.operator || ":"}${insertWithQuotes}`;
}

const indentStart = value.match(/^[ \t]+/)?.[0] ?? "";
const indentEnd = value.match(/[ \t]+$/)?.[0] ?? "";

return `${indentStart}${modifiedInsert}${indentEnd}`;
};

const handleSelect = useCallback((insert: string, item: AutocompleteOptions) => {
const {
id,
contextType = ContextType.FilterUnknown,
value = "",
position = [0, 0]
} = contextData || {};

const insertValue = getModifyInsert(insert, contextType, value, item.type);
const newValue = getUpdatedValue(insertValue, logicalParts, id);

const updatedPosition = (position[0] || 1) + insertValue.trim().length;

onSelect(newValue, updatedPosition);
}, [contextData, logicalParts]);


useEffect(() => {
if (!anchorEl.current) {
setOffsetPos({ top: 0, left: 0 });
return;
}

const element = anchorEl.current.querySelector("textarea") || anchorEl.current;
const style = window.getComputedStyle(element);
const fontSize = `${style.getPropertyValue("font-size")}`;
const fontFamily = `${style.getPropertyValue("font-family")}`;
const lineHeight = parseInt(`${style.getPropertyValue("line-height")}`);

const span = document.createElement("div");
span.style.font = `${fontSize} ${fontFamily}`;
span.style.padding = style.getPropertyValue("padding");
span.style.lineHeight = `${lineHeight}px`;
span.style.width = `${element.offsetWidth}px`;
span.style.maxWidth = `${element.offsetWidth}px`;
span.style.whiteSpace = style.getPropertyValue("white-space");
span.style.overflowWrap = style.getPropertyValue("overflow-wrap");

const marker = document.createElement("span");
span.appendChild(document.createTextNode(fullValue.valueBeforeCursor || ""));
span.appendChild(marker);
span.appendChild(document.createTextNode(fullValue.valueAfterCursor || ""));
document.body.appendChild(span);

const spanRect = span.getBoundingClientRect();
const markerRect = marker.getBoundingClientRect();
const { logicalParts, contextData, } = useLogsQLContext({ value, caretPosition });

const leftOffset = markerRect.left - spanRect.left;
const topOffset = markerRect.bottom - spanRect.bottom - (hasHelperText ? lineHeight : 0);
setOffsetPos({ top: topOffset, left: leftOffset });
const { fieldNames, fieldValues, loading } = useLogsQLFetchOptions({ contextData, extraParams });

span.remove();
marker.remove();
}, [anchorEl, caretPosition, hasHelperText, fullValue]);
const options = useLogsQLOptions({
value,
contextData,
fieldNames,
fieldValues,
showHistory: true,
showExamples: value === DEFAULT_QUERY || !value.trim(),
});

if (!isOpen) {
return;
}
const handleSelect = useLogsQLSelect({
contextData,
logicalParts,
onSelect,
});

return (
<>
<Autocomplete
loading={loading}
disabledFullScreen
value={contextData?.valueContext || ""}
options={options}
anchor={anchorEl}
minLength={0}
offset={offsetPos}
onSelect={handleSelect}
onFoundOptions={onFoundOptions}
maxDisplayResults={{
limit: AUTOCOMPLETE_LIMITS.displayResults,
message: "Please, specify the query more precisely."
}}
/>
</>
<Autocomplete
value={value === DEFAULT_QUERY ? "" : contextData?.valueContext || ""}
options={options}
loading={loading}
disabledFullScreen
showKeyboardHints
anchor={anchorEl}
fullWidth
minLength={0}
offset={{ top: 0, left: 0 }}
onSelect={handleSelect}
maxDisplayResults={{
limit: AUTOCOMPLETE_LIMITS.displayResults,
message: "Please, specify the query more precisely.",
}}
/>
);
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { describe, expect, it } from "vitest";
import { hasBalancedQuotes, splitLogicalParts } from "./parser";
import { LogicalPart, LogicalPartType } from "./types";
import { LogicalPart, LogicalPartType } from "../types";

function getValue(p: LogicalPart): string {
return p.value;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { ContextData, ContextType, LogicalPart, LogicalPartPosition, LogicalPartType } from "./types";
import { pipeList } from "./pipes";
import { ContextData, ContextType, LogicalPart, LogicalPartPosition, LogicalPartType } from "../types";
import { pipeOptions } from "./pipes";

const BUILDER_OPERATORS = ["AND", "OR", "NOT"];
const PIPE_NAMES = pipeList.map(p => p.value);
const PIPE_NAMES = pipeOptions.map(p => p.value);

const QUOTE_CHARS = ["'", "\"", "`"];
const OPENING_BRACKETS = ["(", "[", "{"];
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { ContextType } from "../types";
import { SuggestFunctionIcon } from "../../../../Main/Icons";
import { pipes } from "../../../../../generated/logsql.pipes";
import { AutocompleteOptions } from "../../../../Main/Autocomplete/Autocomplete";

export const pipeOptions: AutocompleteOptions[] = pipes.map(item => ({
...item,
type: ContextType.PipeName,
group: "Autocomplete",
meta: "pipe",
icon: <SuggestFunctionIcon/>,
}));
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { expect } from "vitest";
import { generateQuery } from "./utils";
import { ContextType } from "./types";
import { ContextType } from "../types";

describe("utils", () => {
describe("_time", () => {
Expand Down Expand Up @@ -128,4 +128,4 @@ describe("utils", () => {
})).toStrictEqual("repo.name:Victori*");
});
});
});
});
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ContextData } from "./types";
import { ContextData } from "../types";

const getStreamFieldQuery = (valueContext: string) => {
if (valueContext.includes("=")) {
Expand Down
Loading
Loading