Skip to content
Draft
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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,7 @@ dist/

# external CUE dependencies
/*/cue.mod/pkg/

# ignore shared package links
.plugins-shared-link-bk.json
.plugins-shared-link-lock-bk.json
6 changes: 6 additions & 0 deletions logstable/cue.mod/module.cue
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,9 @@ language: {
source: {
kind: "git"
}
deps: {
"github.com/perses/shared/cue@v0": {
v: "v0.53.0-rc.1"
default: true
}
}
5 changes: 5 additions & 0 deletions logstable/schemas/logstable.cue
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,14 @@

package model

import (
"github.com/perses/local/cue/common"
)

kind: "LogsTable"
spec: close({
allowWrap?: bool
enableDetails?: bool
showTime?: bool
selection?: common.#selection
})
8 changes: 6 additions & 2 deletions logstable/src/LogsTable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,16 @@

import { PanelPlugin } from '@perses-dev/plugin-system';
import { LogsTableComponent } from './LogsTableComponent';
import { LogsTableOptions, LogsTableProps } from './model';
import { LogsTableSelectionsEditor } from './LogsTableSelectionsEditor';
import { LogsTableSettingsEditor } from './LogsTableSettingsEditor';
import { LogsTableOptions, LogsTableProps } from './model';

export const LogsTable: PanelPlugin<LogsTableOptions, LogsTableProps> = {
PanelComponent: LogsTableComponent,
panelOptionsEditorComponents: [{ label: 'Settings', content: LogsTableSettingsEditor }],
panelOptionsEditorComponents: [
{ label: 'Settings', content: LogsTableSettingsEditor },
{ label: 'Selections', content: LogsTableSelectionsEditor },
],
supportedQueryTypes: ['LogQuery'],
createInitialOptions: () => ({
showTime: true,
Expand Down
24 changes: 24 additions & 0 deletions logstable/src/LogsTableSelectionsEditor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Copyright The Perses Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import { SelectionOptions, SelectionOptionsEditor } from '@perses-dev/plugin-system';
import { ReactElement } from 'react';
import { LogsTableSettingsEditorProps } from './model';

export function LogsTableSelectionsEditor({ value, onChange }: LogsTableSettingsEditorProps): ReactElement {
function handleSelectionsChange(selection: SelectionOptions | undefined): void {
onChange({ ...value, selection: selection });
}

return <SelectionOptionsEditor value={value.selection} onChange={handleSelectionsChange} />;
}
2 changes: 1 addition & 1 deletion logstable/src/LogsTableSettingsEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
// limitations under the License.

import {
OptionsEditorGrid,
OptionsEditorColumn,
OptionsEditorGrid,
ThresholdsEditor,
ThresholdsEditorProps,
} from '@perses-dev/components';
Expand Down
22 changes: 21 additions & 1 deletion logstable/src/components/VirtualizedLogsList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { Box, useTheme, Popover, Button, ButtonGroup, IconButton } from '@mui/ma
import CloseIcon from 'mdi-material-ui/Close';
import { Virtuoso } from 'react-virtuoso';
import { LogEntry } from '@perses-dev/core';
import { useSelection } from '@perses-dev/components';
import { formatLogEntries, formatLogMessage } from '../utils/copyHelpers';
import { LogsTableOptions } from '../model';
import { LogRow } from './LogRow';
Expand Down Expand Up @@ -55,11 +56,30 @@ export const VirtualizedLogsList: React.FC<VirtualizedLogsListProps> = ({
}
});

// Keep ref in sync with state
const selectionEnabled = spec.selection?.enabled ?? false;
const { setSelection, clearSelection } = useSelection<LogEntry, number>();

useEffect(() => {
selectedRowsRef.current = selectedRows;
}, [selectedRows]);

// Sync local selection state with context when selection is enabled
useEffect(() => {
if (!selectionEnabled) return;

if (selectedRows.size === 0) {
clearSelection();
} else {
const selectionItems = Array.from(selectedRows)
.map((index) => {
const log = logs[index];
return log ? { id: index, item: log } : null;
})
.filter((entry): entry is { id: number; item: LogEntry } => entry !== null);
setSelection(selectionItems);
}
}, [selectedRows, logs, selectionEnabled, setSelection, clearSelection]);

const handleDismissHints = useCallback(() => {
setIsHintsDismissed(true);
try {
Expand Down
5 changes: 4 additions & 1 deletion logstable/src/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
// limitations under the License.

import { LogData, ThresholdOptions } from '@perses-dev/core';
import { PanelProps, LegendSpecOptions } from '@perses-dev/plugin-system';
import { PanelProps, LegendSpecOptions, SelectionOptions, OptionsEditorProps } from '@perses-dev/plugin-system';

export type LogsTableProps = PanelProps<LogsTableOptions, LogsQueryData>;

Expand All @@ -27,4 +27,7 @@ export interface LogsTableOptions {
enableDetails?: boolean;
showTime?: boolean;
showAll?: boolean;
selection?: SelectionOptions;
}

export type LogsTableSettingsEditorProps = OptionsEditorProps<LogsTableOptions>;
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,17 @@ import { LabelFilter } from '../../utils/types';
import { getProfileData } from './get-profile-data';
import { PyroscopeProfileQueryEditor } from './PyroscopeProfileQueryEditor';

export const PyroscopeProfileQuery = {
export const PyroscopeProfileQuery: {
getProfileData: typeof getProfileData;
OptionsEditorComponent: typeof PyroscopeProfileQueryEditor;
createInitialOptions: () => {
maxNodes: number;
datasource?: string;
service: string;
profileType: string;
filters: LabelFilter[];
};
} = {
getProfileData,
OptionsEditorComponent: PyroscopeProfileQueryEditor,
createInitialOptions: (): {
Expand Down
10 changes: 6 additions & 4 deletions table/schemas/table.cue
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,12 @@ spec: close({
defaultColumnHeight?: "auto" | number
defaultColumnHidden?: bool
pagination?: bool
enableFiltering?: bool
columnSettings?: [...#columnSettings]
cellSettings?: [...#cellSettings]
transforms?: [...common.#transform]
enableFiltering?: bool
selection?: common.#selection
actions?: common.#actions
columnSettings?: [...#columnSettings]
cellSettings?: [...#cellSettings]
transforms?: [...common.#transform]
})

#columnSettings: {
Expand Down
10 changes: 7 additions & 3 deletions table/src/Table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,18 @@
// limitations under the License.

import { PanelPlugin } from '@perses-dev/plugin-system';
import { createInitialTableOptions, TableOptions } from './models';
import {
getTablePanelQueryOptions,
TableCellsEditor,
TableColumnsEditor,
TablePanel,
TableProps,
TableColumnsEditor,
TableSettingsEditor,
TableCellsEditor,
TableTransformsEditor,
} from './components';
import { TableSelectionsEditor } from './components/TableSelectionsEditor';
import { createInitialTableOptions, TableOptions } from './models';
import { TableItemActionsEditor } from './components/TableItemActionsEditor';

/**
* The core TimeSeriesTable panel plugin for Perses.
Expand All @@ -35,6 +37,8 @@ export const Table: PanelPlugin<TableOptions, TableProps> = {
{ label: 'Column Settings', content: TableColumnsEditor },
{ label: 'Cell Settings', content: TableCellsEditor },
{ label: 'Transforms', content: TableTransformsEditor },
{ label: 'Selections', content: TableSelectionsEditor },
{ label: 'Item Actions', content: TableItemActionsEditor },
],
createInitialOptions: createInitialTableOptions,
};
24 changes: 24 additions & 0 deletions table/src/components/TableItemActionsEditor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Copyright The Perses Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import { ActionsOptions, ItemActionsEditor } from '@perses-dev/plugin-system';
import { ReactElement } from 'react';
import { TableSettingsEditorProps } from '../models';

export function TableItemActionsEditor({ value, onChange }: TableSettingsEditorProps): ReactElement {
function handleActionsChange(actions: ActionsOptions | undefined): void {
onChange({ ...value, actions: actions });
}

return <ItemActionsEditor value={value.actions} onChange={handleActionsChange} />;
}
67 changes: 61 additions & 6 deletions table/src/components/TablePanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,21 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import { Box, Theme, Typography, useTheme } from '@mui/material';
import { Table, TableCellConfigs, TableColumnConfig, useSelection } from '@perses-dev/components';
import { formatValue, Labels, QueryDataType, TimeSeries, TimeSeriesData, transformData } from '@perses-dev/core';
import { useSelectionItemActions } from '@perses-dev/dashboards';
import {
ActionsOptions,
PanelData,
PanelProps,
replaceVariablesInString,
useAllVariableValues,
VariableStateMap,
} from '@perses-dev/plugin-system';
import { Table, TableCellConfigs, TableColumnConfig } from '@perses-dev/components';
import { ReactElement, useEffect, useMemo, useState } from 'react';
import { formatValue, Labels, QueryDataType, TimeSeries, TimeSeriesData, transformData } from '@perses-dev/core';
import { PaginationState, SortingState, ColumnFiltersState } from '@tanstack/react-table';
import { useTheme, Theme, Typography, Box } from '@mui/material';
import { ColumnSettings, TableOptions, evaluateConditionalFormatting } from '../models';
import { ColumnFiltersState, PaginationState, RowSelectionState, SortingState } from '@tanstack/react-table';
import { ReactElement, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { ColumnSettings, evaluateConditionalFormatting, TableOptions } from '../models';
import { EmbeddedPanel } from './EmbeddedPanel';

function generateCellContentConfig(
Expand Down Expand Up @@ -211,6 +213,50 @@ export function TablePanel({ contentDimensions, spec, queryResults }: TableProps
const theme = useTheme();
const allVariables = useAllVariableValues();

const selectionEnabled = spec.selection?.enabled ?? false;
const { selectionMap, setSelection, clearSelection } = useSelection<Record<string, unknown>, string>();

const itemActionsConfig = spec.actions ? (spec.actions as ActionsOptions) : undefined;
const itemActionsListConfig =
itemActionsConfig?.enabled && itemActionsConfig.displayWithItem ? itemActionsConfig.actionsList : [];

const { getItemActionButtons, confirmDialog, actionButtons } = useSelectionItemActions({
actions: itemActionsListConfig,
variableState: allVariables,
});

const filteredDataRef = useRef<Array<Record<string, unknown>>>([]);

// Convert selectionMap to TanStack's RowSelectionState format
const rowSelection = useMemo((): RowSelectionState => {
const result: RowSelectionState = {};
selectionMap.forEach((_, id) => {
result[id as string] = true;
});
return result;
}, [selectionMap]);

const handleRowSelectionChange = useCallback(
(newRowSelection: RowSelectionState) => {
const newSelection: Array<{ id: string; item: Record<string, unknown> }> = [];
for (const [id, isSelected] of Object.entries(newRowSelection)) {
if (isSelected) {
const index = parseInt(id, 10);
if (filteredDataRef.current[index] !== undefined) {
newSelection.push({ id, item: filteredDataRef.current[index] });
}
}
}

if (newSelection.length === 0) {
clearSelection();
} else {
setSelection(newSelection);
}
},
[setSelection, clearSelection]
);

// TODO: handle other query types
const queryMode = getTablePanelQueryOptions(spec).mode;
const rawData: Array<Record<string, unknown>> = useMemo(() => {
Expand Down Expand Up @@ -452,6 +498,9 @@ export function TablePanel({ contentDimensions, spec, queryResults }: TableProps
return filtered;
}, [data, columnFilters, spec.enableFiltering]);

// Keep ref in sync with filtered data for use in selection handler
filteredDataRef.current = filteredData;

const [pagination, setPagination] = useState<PaginationState | undefined>(
spec.pagination ? { pageIndex: 0, pageSize: 10 } : undefined
);
Expand Down Expand Up @@ -486,6 +535,7 @@ export function TablePanel({ contentDimensions, spec, queryResults }: TableProps

return (
<>
{confirmDialog}
{spec.enableFiltering && (
<div
style={{
Expand Down Expand Up @@ -592,6 +642,11 @@ export function TablePanel({ contentDimensions, spec, queryResults }: TableProps
onSortingChange={setSorting}
pagination={pagination}
onPaginationChange={setPagination}
checkboxSelection={selectionEnabled}
rowSelection={rowSelection}
onRowSelectionChange={handleRowSelectionChange}
getItemActions={({ id, data }) => getItemActionButtons({ id, data: data as Record<string, unknown> })}
hasItemActions={actionButtons && actionButtons.length > 0}
/>
</>
);
Expand Down
24 changes: 24 additions & 0 deletions table/src/components/TableSelectionsEditor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Copyright The Perses Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import { SelectionOptions, SelectionOptionsEditor } from '@perses-dev/plugin-system';
import { ReactElement } from 'react';
import { TableSettingsEditorProps } from '../models';

export function TableSelectionsEditor({ value, onChange }: TableSettingsEditorProps): ReactElement {
function handleSelectionsChange(selection: SelectionOptions | undefined): void {
onChange({ ...value, selection: selection });
}

return <SelectionOptionsEditor value={value.selection} onChange={handleSelectionsChange} />;
}
2 changes: 2 additions & 0 deletions table/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ export * from './ColumnsEditor';
export * from './EmbeddedPanel';
export * from './TableCellsEditor';
export * from './TableColumnsEditor';
export * from './TableItemActionsEditor';
export * from './TablePanel';
export * from './TableSelectionsEditor';
export * from './TableSettingsEditor';
export * from './TableTransformsEditor';
Loading
Loading