Skip to content

Drill down metrics #200

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 6, 2025
Merged
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
13 changes: 12 additions & 1 deletion packages/grafana-ui/src/components/Button/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { PopoverContent, Tooltip, TooltipPlacement } from '../Tooltip';

export type ButtonVariant = 'primary' | 'secondary' | 'destructive' | 'success';
export const allButtonVariants: ButtonVariant[] = ['primary', 'secondary', 'destructive'];
export type ButtonFill = 'solid' | 'outline' | 'text';
export type ButtonFill = 'solid' | 'outline' | 'text' | 'ghost';
export const allButtonFills: ButtonFill[] = ['solid', 'outline', 'text'];

type CommonProps = {
Expand Down Expand Up @@ -319,6 +319,17 @@ function getButtonVariantStyles(theme: GrafanaTheme2, color: ThemeRichColor, fil
};
}

if (fill === 'ghost') {
return {
background: '#5d5a5990',
color: color.text,
border: `1px solid ${borderColor}`,
transition: theme.transitions.create(['background-color', 'box-shadow', 'border-color', 'color'], {
duration: theme.transitions.duration.short,
}),
};
}

return {
background: color.main,
color: color.contrastText,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export const TimeRangeContent = (props: Props) => {
onApply: onApplyFromProps,
isReversed,
fiscalYearStartMonth,
onError,
// onError,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Remove commented code or provide justification for temporary disabling.

You commented out the onError prop destructuring, but the onError callback is still used in the commented onPaste function. This creates confusion about the intended behavior.

If this functionality is being permanently removed, clean up all related code:

-    // onError,

If it's temporary, add a comment explaining why and when it will be restored.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// onError,
🤖 Prompt for AI Agents
In
packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker/TimeRangeContent.tsx
at line 61, the onError prop destructuring is commented out but the onError
callback is still referenced in the commented onPaste function, causing
confusion. To fix this, either fully remove all onError-related code including
the onPaste function if the feature is permanently removed, or if it is
temporarily disabled, add a clear comment explaining why it is disabled and when
it will be restored.

weekStart,
} = props;
const [fromValue, toValue] = valueToState(value.raw.from, value.raw.to, timeZone);
Expand Down Expand Up @@ -112,28 +112,28 @@ export const TimeRangeContent = (props: Props) => {
}
};

const onCopy = () => {
const raw: RawTimeRange = { from: from.value, to: to.value };
navigator.clipboard.writeText(JSON.stringify(raw));
};

const onPaste = async () => {
const raw = await navigator.clipboard.readText();
let range;

try {
range = JSON.parse(raw);
} catch (error) {
if (onError) {
onError(raw);
}
return;
}

const [fromValue, toValue] = valueToState(range.from, range.to, timeZone);
setFrom(fromValue);
setTo(toValue);
};
// const onCopy = () => {
// const raw: RawTimeRange = { from: from.value, to: to.value };
// navigator.clipboard.writeText(JSON.stringify(raw));
// };

// const onPaste = async () => {
// const raw = await navigator.clipboard.readText();
// let range;

// try {
// range = JSON.parse(raw);
// } catch (error) {
// if (onError) {
// onError(raw);
// }
// return;
// }

// const [fromValue, toValue] = valueToState(range.from, range.to, timeZone);
// setFrom(fromValue);
// setTo(toValue);
// };

const fiscalYear = rangeUtil.convertRawToRange({ from: 'now/fy', to: 'now/fy' }, timeZone, fiscalYearStartMonth);
const fiscalYearMessage = t('time-picker.range-content.fiscal-year', 'Fiscal year');
Expand Down Expand Up @@ -197,7 +197,7 @@ export const TimeRangeContent = (props: Props) => {
</div>

<div className={style.buttonsContainer}>
<Button
{/* <Button
data-testid={selectors.components.TimePicker.copyTimeRange}
icon="copy"
variant="secondary"
Expand All @@ -212,13 +212,13 @@ export const TimeRangeContent = (props: Props) => {
tooltip={t('time-picker.copy-paste.tooltip-paste', 'Paste time range')}
type="button"
onClick={onPaste}
/>
/> */}
<Button
data-testid={selectors.components.TimePicker.applyTimeRange}
type="button"
onClick={onApply}
style={{
width: 193,
width: 205,
textAlign: 'center',
paddingLeft: 45,
}}
Expand Down
4 changes: 2 additions & 2 deletions packages/grafana-ui/src/components/Pagination/Pagination.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export const Pagination = ({
const pages = [...new Array(numberOfPages).keys()];

const condensePages = numberOfPages > pageLengthToCondense;
const getListItem = (page: number, fill?: 'outline') => (
const getListItem = (page: number, fill?: 'outline' | 'ghost') => (
<li key={page} className={styles.item}>
<Button size="sm" onClick={() => onNavigate(page)} fill={fill}>
{page}
Expand All @@ -44,7 +44,7 @@ export const Pagination = ({

return pages.reduce<JSX.Element[]>((pagesToRender, pageIndex) => {
const page = pageIndex + 1;
const fill: 'outline' | undefined = page === currentPage ? undefined : 'outline';
const fill: 'outline' | 'ghost' = page === currentPage ? 'ghost' : 'outline';

// The indexes at which to start and stop condensing pages
const lowerBoundIndex = pageLengthToCondense;
Expand Down
34 changes: 34 additions & 0 deletions packages/grafana-ui/src/components/Table/RowsList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ interface RowsListProps {
initialRowIndex?: number;
headerGroups: HeaderGroup[];
longestField?: Field;
onClickRow?: (row: Record<string, string | number>) => void;
}

export const RowsList = (props: RowsListProps) => {
Expand All @@ -78,6 +79,7 @@ export const RowsList = (props: RowsListProps) => {
initialRowIndex = undefined,
headerGroups,
longestField,
onClickRow,
} = props;

const [rowHighlightIndex, setRowHighlightIndex] = useState<number | undefined>(initialRowIndex);
Expand Down Expand Up @@ -303,13 +305,44 @@ export const RowsList = (props: RowsListProps) => {
}
const { key, ...rowProps } = row.getRowProps({ style, ...additionalProps });

const mapRowValues = () => {
const rowValues: Record<string, string | number> = {};
for (const [key, val] of Object.entries(row.values)) {
const k = (row.cells[Number(key)].column as unknown as { field?: { name: string | undefined } })?.field?.name;
if (k === undefined) {
continue;
}
const camelCaseKey = k
.replace(/(?:^\w|[A-Z]|\b\w)/g, (word, index) => (index === 0 ? word.toLowerCase() : word.toUpperCase()))
.replace(/\s+/g, '');
rowValues[camelCaseKey] = val;
}
return rowValues;
};

return (
<div
key={key}
{...rowProps}
className={cx(tableStyles.row, expandedRowStyle)}
onMouseEnter={() => onRowHover(index, data)}
onMouseLeave={onRowLeave}
onClick={() => {
if (onClickRow) {
onClickRow(mapRowValues());
}
}}
role={onClickRow ? 'button' : undefined}
tabIndex={onClickRow ? 0 : undefined}
onKeyDown={
onClickRow
? (e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
}
}
: undefined
}
>
{/*add the nested data to the DOM first to prevent a 1px border CSS issue on the last cell of the row*/}
{rowExpanded && (
Expand Down Expand Up @@ -343,6 +376,7 @@ export const RowsList = (props: RowsListProps) => {
);
},
[
onClickRow,
cellHeight,
data,
nestedDataField,
Expand Down
2 changes: 2 additions & 0 deletions packages/grafana-ui/src/components/Table/Table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export const Table = memo((props: Props) => {
enableSharedCrosshair = false,
initialRowIndex = undefined,
fieldConfig,
onClickRow,
} = props;

const listRef = useRef<VariableSizeList>(null);
Expand Down Expand Up @@ -334,6 +335,7 @@ export const Table = memo((props: Props) => {
enableSharedCrosshair={enableSharedCrosshair}
initialRowIndex={initialRowIndex}
longestField={longestField}
onClickRow={onClickRow}
/>
</div>
) : (
Expand Down
4 changes: 2 additions & 2 deletions packages/grafana-ui/src/components/Table/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ export function useTableStyles(theme: GrafanaTheme2, cellHeightOption: TableCell
minHeight: `${rowHeight - 1}px`,
wordBreak: textShouldWrap ? 'break-word' : undefined,
whiteSpace: textShouldWrap && overflowOnHover ? 'normal' : 'nowrap',
boxShadow: overflowOnHover ? `0 0 2px ${theme.colors.primary.main}` : undefined,
background: rowStyled ? 'inherit' : (backgroundHover ?? theme.colors.background.primary),
//boxShadow: overflowOnHover ? `0 0 2px ${theme.colors.primary.main}` : undefined,
// background: 'inherit', //: (backgroundHover ?? theme.colors.background.primary),
zIndex: 1,
'.cellActions': {
color: '#FFF',
Expand Down
1 change: 1 addition & 0 deletions packages/grafana-ui/src/components/Table/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ export interface Props {
// The index of the field value that the table will initialize scrolled to
initialRowIndex?: number;
fieldConfig?: FieldConfigSource;
onClickRow?: (row: Record<string, string | number>) => void;
}

/**
Expand Down
3 changes: 1 addition & 2 deletions public/app/core/components/Page/usePageTitle.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { useEffect } from 'react';

import { NavModel, NavModelItem } from '@grafana/data';
import { FnGlobalState } from 'app/core/reducers/fn-slice';
import { HOME_NAV_ID } from 'app/core/reducers/navModel';
import { useSelector } from 'app/types';

Expand All @@ -10,7 +9,7 @@ import { buildBreadcrumbs } from '../Breadcrumbs/utils';

export function usePageTitle(navModel?: NavModel, pageNav?: NavModelItem) {
const homeNav = useSelector((state) => state.navIndex)[HOME_NAV_ID];
const { FNDashboard, pageTitle } = useSelector<FnGlobalState>((state) => state.fnGlobalState);
const { FNDashboard, pageTitle } = useSelector((state) => state.fnGlobalState);

useEffect(() => {
const sectionNav = (navModel?.node !== navModel?.main ? navModel?.node : navModel?.main) ?? { text: 'Grafana' };
Expand Down
34 changes: 4 additions & 30 deletions public/app/core/components/TimePicker/TimePickerWithHistory.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
import { isEqual, uniqBy } from 'lodash';
import { CSSProperties, FC, useEffect, useRef } from 'react';
// eslint-disable-next-line no-restricted-imports
import { useDispatch, useSelector } from 'react-redux';
import { uniqBy } from 'lodash';
import { CSSProperties, FC } from 'react';

import { TimeRange, isDateTime, rangeUtil } from '@grafana/data';
import { TimeRangePickerProps, TimeRangePicker, useTheme2 } from '@grafana/ui';
import { FnGlobalState, updatePartialFnStates } from 'app/core/reducers/fn-slice';
import { StoreState } from 'app/types';
import { useSelector } from 'app/types';

import { LocalStorageValueProvider } from '../LocalStorageValueProvider';

Expand All @@ -24,7 +21,7 @@ interface TimePickerHistoryItem {
type LSTimePickerHistoryItem = TimePickerHistoryItem | TimeRange;

const FnText: React.FC = () => {
const { FNDashboard } = useSelector<StoreState, FnGlobalState>(({ fnGlobalState }) => fnGlobalState);
const { FNDashboard } = useSelector(({ fnGlobalState }) => fnGlobalState);
const theme = useTheme2();

const FN_TEXT_STYLE: CSSProperties = { fontWeight: 700, fontSize: 14, marginLeft: 8 };
Expand All @@ -47,32 +44,9 @@ export interface PickerProps {
}

export const Picker: FC<PickerProps> = ({ rawValues, onSaveToStore, pickerProps }) => {
const { fnGlobalTimeRange } = useSelector<StoreState, FnGlobalState>(({ fnGlobalState }) => fnGlobalState);
const dispatch = useDispatch();

const values = migrateHistory(rawValues);
const history = deserializeHistory(values);

const didMountRef = useRef(false);
useEffect(() => {
/* The condition below skips the first run of useeffect that happens when this component gets mounted */
if (didMountRef.current) {
/* If the current timerange value has changed, update fnGlobalTimeRange */
if (!isEqual(fnGlobalTimeRange?.raw, pickerProps.value.raw)) {
dispatch(
updatePartialFnStates({
fnGlobalTimeRange: pickerProps.value,
})
);
}
} else if (fnGlobalTimeRange && !isEqual(fnGlobalTimeRange.raw, pickerProps.value.raw)) {
/* If fnGlobalTimeRange exists in the initial render, set the time as that */
pickerProps.onChange(fnGlobalTimeRange);
}

didMountRef.current = true;
}, [dispatch, fnGlobalTimeRange, pickerProps]);

return (
<TimeRangePicker
{...pickerProps}
Expand Down
Loading