diff --git a/public/app/features/explore/Logs/Logs.tsx b/public/app/features/explore/Logs/Logs.tsx index 605ecd43b5e..c1864d4a30a 100644 --- a/public/app/features/explore/Logs/Logs.tsx +++ b/public/app/features/explore/Logs/Logs.tsx @@ -613,13 +613,7 @@ class UnthemedLogs extends PureComponent { ) ) : null, ]} - title={ - config.featureToggles.logsExploreTableVisualisation - ? this.state.visualisationType === 'logs' - ? 'Logs' - : 'Table' - : 'Logs' - } + title={'Logs'} actions={ <> {config.featureToggles.logsExploreTableVisualisation && ( @@ -627,16 +621,16 @@ class UnthemedLogs extends PureComponent { ) => void }) { +export function LogsColumnSearch(props: { onChange: (e: React.FormEvent) => void; value: string }) { const theme = useTheme2(); const styles = getStyles(theme); return ( - + ); } diff --git a/public/app/features/explore/Logs/LogsTableMultiSelect.tsx b/public/app/features/explore/Logs/LogsTableMultiSelect.tsx index 92e0f377280..77431c09803 100644 --- a/public/app/features/explore/Logs/LogsTableMultiSelect.tsx +++ b/public/app/features/explore/Logs/LogsTableMultiSelect.tsx @@ -13,7 +13,15 @@ function getStyles(theme: GrafanaTheme2) { overflowY: 'scroll', height: 'calc(100% - 50px)', }), + columnHeaderButton: css({ + appearance: 'none', + background: 'none', + border: 'none', + fontSize: theme.typography.pxToRem(11), + }), columnHeader: css({ + display: 'flex', + justifyContent: 'space-between', fontSize: theme.typography.h6.fontSize, background: theme.colors.background.secondary, position: 'sticky', @@ -33,6 +41,7 @@ export const LogsTableMultiSelect = (props: { toggleColumn: (columnName: string) => void; filteredColumnsWithMeta: Record | undefined; columnsWithMeta: Record; + clear: () => void; }) => { const theme = useTheme2(); const styles = getStyles(theme); @@ -41,11 +50,23 @@ export const LogsTableMultiSelect = (props: {
{/* Sidebar columns */} <> +
+ Selected fields + +
+ props.columnsWithMeta[value]?.active ?? false} + /> +
Fields
!!value} + valueFilter={(value) => !props.columnsWithMeta[value]?.active} />
diff --git a/public/app/features/explore/Logs/LogsTableNavColumn.tsx b/public/app/features/explore/Logs/LogsTableNavColumn.tsx index ec32e1caf37..5dfa2e9fe25 100644 --- a/public/app/features/explore/Logs/LogsTableNavColumn.tsx +++ b/public/app/features/explore/Logs/LogsTableNavColumn.tsx @@ -11,6 +11,10 @@ function getStyles(theme: GrafanaTheme2) { labelCount: css({ marginLeft: theme.spacing(0.5), marginRight: theme.spacing(0.5), + appearance: 'none', + background: 'none', + border: 'none', + fontSize: theme.typography.pxToRem(11), }), wrap: css({ display: 'flex', @@ -29,13 +33,11 @@ function getStyles(theme: GrafanaTheme2) { top: 0, }, '> span': { - overflow: 'scroll', - '&::-webkit-scrollbar': { - display: 'none', - }, - '&::-moz-scrollbar': { - display: 'none', - }, + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', + display: 'block', + maxWidth: '100%', }, }), columnWrapper: css({ @@ -51,70 +53,19 @@ function getStyles(theme: GrafanaTheme2) { }; } +const collator = new Intl.Collator(undefined, { sensitivity: 'base' }); function sortLabels(labels: Record) { return (a: string, b: string) => { - // First sort by active - if (labels[a].active && labels[b].active) { - // If both fields are active, sort time first - if (labels[a]?.type === 'TIME_FIELD') { - return -1; - } - if (labels[b]?.type === 'TIME_FIELD') { - return 1; - } - // And then line second - if (labels[a]?.type === 'BODY_FIELD') { - return -1; - } - // special fields are next - if (labels[b]?.type === 'BODY_FIELD') { - return 1; - } - } - - if (labels[b].active && labels[a].active) { - // Sort alphabetically - if (a < b) { - return -1; - } - if (a > b) { - return 1; - } - } + const la = labels[a]; + const lb = labels[b]; - // If just one label is active, sort it first - if (labels[b].active) { - return 1; - } - if (labels[a].active) { - return -1; + if (la != null && lb != null) { + return ( + Number(lb.type === 'TIME_FIELD') - Number(la.type === 'TIME_FIELD') || + Number(lb.type === 'BODY_FIELD') - Number(la.type === 'BODY_FIELD') || + collator.compare(a, b) + ); } - - // If both fields are special, and not selected, sort time first - if (labels[a]?.type && labels[b]?.type) { - if (labels[a]?.type === 'TIME_FIELD') { - return -1; - } - return 0; - } - - // If only one special field, stick to the top of inactive fields - if (labels[a]?.type && !labels[b]?.type) { - return -1; - } - // if the b field is special, sort it first - if (!labels[a]?.type && labels[b]?.type) { - return 1; - } - - // Finally sort by name - if (a < b) { - return -1; - } - if (a > b) { - return 1; - } - // otherwise do not sort return 0; }; @@ -122,25 +73,31 @@ function sortLabels(labels: Record) { export const LogsTableNavColumn = (props: { labels: Record; - valueFilter: (value: number) => boolean; + valueFilter: (value: string) => boolean; toggleColumn: (columnName: string) => void; }): JSX.Element => { const { labels, valueFilter, toggleColumn } = props; const theme = useTheme2(); const styles = getStyles(theme); - const labelKeys = Object.keys(labels).filter((labelName) => valueFilter(labels[labelName].percentOfLinesWithLabel)); + const labelKeys = Object.keys(labels).filter((labelName) => valueFilter(labelName)); if (labelKeys.length) { return (
{labelKeys.sort(sortLabels(labels)).map((labelName) => ( -
+
toggleColumn(labelName)} checked={labels[labelName]?.active ?? false} /> - ({labels[labelName]?.percentOfLinesWithLabel}%) +
))}
diff --git a/public/app/features/explore/Logs/LogsTableWrap.tsx b/public/app/features/explore/Logs/LogsTableWrap.tsx index 5484e51eeac..837c2deea66 100644 --- a/public/app/features/explore/Logs/LogsTableWrap.tsx +++ b/public/app/features/explore/Logs/LogsTableWrap.tsx @@ -1,5 +1,4 @@ import { css } from '@emotion/css'; -import { debounce } from 'lodash'; import React, { useCallback, useEffect, useState } from 'react'; import { @@ -51,6 +50,7 @@ export function LogsTableWrap(props: Props) { // Filtered copy of columnsWithMeta that only includes matching results const [filteredColumnsWithMeta, setFilteredColumnsWithMeta] = useState(undefined); + const [searchValue, setSearchValue] = useState(''); const height = getLogsTableHeight(); const panelStateRefId = props?.panelState?.refId; @@ -251,6 +251,14 @@ export function LogsTableWrap(props: Props) { }); } + const clearSelection = () => { + const pendingLabelState = { ...columnsWithMeta }; + Object.keys(pendingLabelState).forEach((key) => { + pendingLabelState[key].active = !!pendingLabelState[key].type; + }); + setColumnsWithMeta(pendingLabelState); + }; + // Toggle a column on or off when the user interacts with an element in the multi-select sidebar const toggleColumn = (columnName: fieldName) => { if (!columnsWithMeta || !(columnName in columnsWithMeta)) { @@ -320,14 +328,12 @@ export function LogsTableWrap(props: Props) { fuzzySearch(Object.keys(columnsWithMeta), needle, dispatcher); }; - // Debounce fuzzy search - const debouncedSearch = debounce(search, 500); - // onChange handler for search input const onSearchInputChange = (e: React.FormEvent) => { const value = e.currentTarget?.value; + setSearchValue(value); if (value) { - debouncedSearch(value); + search(value); } else { // If the search input is empty, reset the local search state. setFilteredColumnsWithMeta(undefined); @@ -376,11 +382,12 @@ export function LogsTableWrap(props: Props) {
- +