2025-04-01 10:38:02 +09:00

155 lines
5.4 KiB
TypeScript

import { css } from '@emotion/css';
import { capitalize } from 'lodash';
import { useMemo, useState } from 'react';
import { CoreApp, GrafanaTheme2, getNextRefId } from '@grafana/data';
import { reportInteraction } from '@grafana/runtime';
import { DataQuery } from '@grafana/schema';
import { Button, Collapse, Modal, useStyles2 } from '@grafana/ui';
import { LokiQuery } from '../../types';
import { lokiQueryModeller } from '../LokiQueryModeller';
import { operationDefinitions } from '../operations';
import { buildVisualQueryFromString } from '../parsing';
import { LokiOperationId, LokiQueryPattern, LokiQueryPatternType, LokiVisualQueryOperationCategory } from '../types';
import { QueryPattern } from './QueryPattern';
type Props = {
isOpen: boolean;
query: LokiQuery;
queries: DataQuery[] | undefined;
app?: CoreApp;
onClose: () => void;
onChange: (query: LokiQuery) => void;
onAddQuery?: (query: LokiQuery) => void;
};
const keepOperationCategories: string[] = [
LokiVisualQueryOperationCategory.Formats,
LokiVisualQueryOperationCategory.LineFilters,
LokiVisualQueryOperationCategory.LabelFilters,
];
const excludeOperationIds: string[] = [LokiOperationId.Unwrap];
const keepOperations = operationDefinitions
.filter(
(operation) =>
operation.category &&
keepOperationCategories.includes(operation.category) &&
!excludeOperationIds.includes(operation.id)
)
.map((operation) => operation.id);
export const QueryPatternsModal = (props: Props) => {
const { isOpen, onClose, onChange, onAddQuery, query, queries, app } = props;
const [openTabs, setOpenTabs] = useState<string[]>([]);
const [selectedPatternName, setSelectedPatternName] = useState<string | null>(null);
const styles = useStyles2(getStyles);
const hasNewQueryOption = !!onAddQuery;
const hasPreviousQuery = useMemo(
() => buildVisualQueryFromString(query.expr).query.operations.length > 0,
[query.expr]
);
const onPatternSelect = (pattern: LokiQueryPattern, selectAsNewQuery = false) => {
const visualQuery = buildVisualQueryFromString(selectAsNewQuery ? '' : query.expr);
reportInteraction('grafana_loki_query_patterns_selected', {
version: 'v2',
app: app ?? '',
editorMode: query.editorMode,
selectedPattern: pattern.name,
preSelectedOperationsCount: visualQuery.query.operations.length,
preSelectedLabelsCount: visualQuery.query.labels.length,
createNewQuery: hasNewQueryOption && selectAsNewQuery,
});
// Filter operations in the original query except those we configured to keep
visualQuery.query.operations = visualQuery.query.operations.filter((op) => keepOperations.includes(op.id));
// Filter operations in the pattern that are present in the original query
const patternOperations = pattern.operations.filter(
(patternOp) => visualQuery.query.operations.findIndex((op) => op.id === patternOp.id) < 0
);
visualQuery.query.operations = [...visualQuery.query.operations, ...patternOperations];
if (hasNewQueryOption && selectAsNewQuery) {
onAddQuery({
...query,
refId: getNextRefId(queries ?? [query]),
expr: lokiQueryModeller.renderQuery(visualQuery.query),
});
} else {
onChange({
...query,
expr: lokiQueryModeller.renderQuery(visualQuery.query),
});
}
setSelectedPatternName(null);
onClose();
};
return (
<Modal isOpen={isOpen} title="Kick start your query" onDismiss={onClose} className={styles.modal}>
<div className={styles.spacing}>
Kick start your query by selecting one of these queries. You can then continue to complete your query.
</div>
{Object.values(LokiQueryPatternType).map((patternType) => {
return (
<Collapse
key={patternType}
label={`${capitalize(patternType)} query starters`}
isOpen={openTabs.includes(patternType)}
collapsible={true}
onToggle={() =>
setOpenTabs((tabs) =>
// close tab if it's already open, otherwise open it
tabs.includes(patternType) ? tabs.filter((t) => t !== patternType) : [...tabs, patternType]
)
}
>
<div className={styles.cardsContainer}>
{lokiQueryModeller
.getQueryPatterns()
.filter((pattern) => pattern.type === patternType)
.map((pattern) => (
<QueryPattern
key={pattern.name}
pattern={pattern}
hasNewQueryOption={hasNewQueryOption}
hasPreviousQuery={hasPreviousQuery}
onPatternSelect={onPatternSelect}
selectedPatternName={selectedPatternName}
setSelectedPatternName={setSelectedPatternName}
/>
))}
</div>
</Collapse>
);
})}
<Button variant="secondary" onClick={onClose}>
Close
</Button>
</Modal>
);
};
const getStyles = (theme: GrafanaTheme2) => {
return {
cardsContainer: css({
display: 'flex',
flexDirection: 'row',
flexWrap: 'wrap',
justifyContent: 'space-between',
}),
spacing: css({
marginBottom: theme.spacing(1),
}),
modal: css({
width: '85vw',
[theme.breakpoints.down('md')]: {
width: '100%',
},
}),
};
};