Skip to content

Commit e1a90c0

Browse files
authored
Improve log analytics and data distribution context prompt (#248)
Signed-off-by: Owen Wang <[email protected]>
1 parent 3aa4e7e commit e1a90c0

File tree

8 files changed

+261
-87
lines changed

8 files changed

+261
-87
lines changed

public/components/notebooks/components/data_distribution/data_distribution_container.tsx

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ export const DataDistributionContainer = ({
4747
paragraphState: ParagraphState<AnomalyVisualizationAnalysisOutputResult>;
4848
}) => {
4949
const {
50-
services: { embeddable, paragraphService },
50+
services: { notifications, embeddable, paragraphService },
5151
} = useOpenSearchDashboards<NoteBookServices>();
5252
const context = useContext(NotebookReactContext);
5353
const topContextValue = useObservable(
@@ -61,6 +61,7 @@ export const DataDistributionContainer = ({
6161
const { saveParagraph } = context.paragraphHooks;
6262
const [activePage, setActivePage] = useState(0);
6363
const [distributionModalExpand, setDistributionModalExpand] = useState(false);
64+
const [isUpdatingParagraph, setIsUpdatingParagraph] = useState(false);
6465
const factory = embeddable.getEmbeddableFactory<DataDistributionInput>('vega_visualization');
6566
const paragraphRegistry = paragraphService?.getParagraphRegistry(
6667
DATA_DISTRIBUTION_PARAGRAPH_TYPE
@@ -170,17 +171,30 @@ export const DataDistributionContainer = ({
170171
iconType={isSelected ? 'crossInCircleEmpty' : 'checkInCircleEmpty'}
171172
color="text"
172173
style={{ height: '16px', width: '16px' }}
174+
isDisabled={isUpdatingParagraph}
173175
onClick={async () => {
174-
const updatedFieldComparison = [...fieldComparison];
175-
updatedFieldComparison[chartIndex] = {
176-
...fieldComparison[chartIndex],
177-
excludeFromContext: !isSelected,
178-
};
179-
await saveParagraph({
180-
paragraphStateValue: ParagraphState.updateOutputResult(paragraph, {
181-
fieldComparison: updatedFieldComparison || [],
182-
}),
183-
});
176+
try {
177+
setIsUpdatingParagraph(true);
178+
179+
const updatedFieldComparison = [...fieldComparison];
180+
updatedFieldComparison[chartIndex] = {
181+
...fieldComparison[chartIndex],
182+
excludeFromContext: !isSelected,
183+
};
184+
await saveParagraph({
185+
paragraphStateValue: ParagraphState.updateOutputResult(paragraph, {
186+
fieldComparison: updatedFieldComparison || [],
187+
}),
188+
});
189+
} catch (err) {
190+
notifications.toasts.addDanger(
191+
i18n.translate('notebook.data.distribution.paragraph.error', {
192+
defaultMessage: 'Error updating paragraph',
193+
})
194+
);
195+
} finally {
196+
setIsUpdatingParagraph(false);
197+
}
184198
}}
185199
aria-label={isSelected ? 'Exclude from the results' : 'Select from the results'}
186200
/>
@@ -235,7 +249,7 @@ export const DataDistributionContainer = ({
235249
uniqueId={uniqueId}
236250
chartIndex={chartIndex}
237251
isSelected={isSelected}
238-
spec={spec}
252+
spec={spec as any}
239253
/>
240254
);
241255
})}

public/components/notebooks/components/hypothesis/reinvestigate_modal.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@ import {
1111
EuiModalHeaderTitle,
1212
EuiModalBody,
1313
EuiFormRow,
14-
EuiFieldText,
1514
EuiSpacer,
1615
EuiSwitch,
1716
EuiModalFooter,
1817
EuiButton,
1918
EuiSuperDatePicker,
19+
EuiTextArea,
2020
} from '@elastic/eui';
2121
import moment from 'moment';
2222
import dateMath from '@elastic/datemath';
@@ -84,7 +84,7 @@ export const ReinvestigateModal: React.FC<ReinvestigateModalProps> = ({
8484
</EuiModalHeader>
8585
<EuiModalBody>
8686
<EuiFormRow label="Edit initial goal">
87-
<EuiFieldText value={value} onChange={(e) => setValue(e.target.value)} required />
87+
<EuiTextArea value={value} onChange={(e) => setValue(e.target.value)} required />
8888
</EuiFormRow>
8989
{!!timeRange && (
9090
<>

public/components/notebooks/components/log_analytics/components/log_insight.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,14 @@ import { LogAnalyticsLoadingPanel } from './log_analytics_loading_panel';
2222
interface LogInsightProps {
2323
logInsights: LogPattern[];
2424
isLoadingLogInsights: boolean;
25+
disableExclude?: boolean;
2526
onExclude?: (item: LogPattern) => void;
2627
}
2728

2829
export const LogInsight: React.FC<LogInsightProps> = ({
2930
logInsights,
3031
isLoadingLogInsights,
32+
disableExclude,
3133
onExclude,
3234
}) => {
3335
const [openPopovers, setOpenPopovers] = useState<{ [key: string]: boolean }>({});
@@ -114,6 +116,7 @@ export const LogInsight: React.FC<LogInsightProps> = ({
114116
aria-label="Deselect item"
115117
onClick={() => onExclude(record)}
116118
color="subdued"
119+
isDisabled={disableExclude}
117120
/>
118121
</EuiToolTip>
119122
),

public/components/notebooks/components/log_analytics/components/log_sequence.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,15 @@ interface LogSequenceProps {
2222
baselineSequences?: LogSequenceEntry[];
2323
isLoadingLogSequence: boolean;
2424
isNotApplicable: boolean;
25+
disableExclude?: boolean;
2526
onExclude?: (item: LogSequenceEntry) => void;
2627
}
2728

2829
export const LogSequence: React.FC<LogSequenceProps> = ({
2930
exceptionalSequences,
3031
isLoadingLogSequence,
3132
isNotApplicable,
33+
disableExclude,
3234
onExclude,
3335
}) => {
3436
// Columns for sequence entries table
@@ -85,6 +87,7 @@ export const LogSequence: React.FC<LogSequenceProps> = ({
8587
aria-label="Deselect item"
8688
onClick={() => onExclude(record)}
8789
color="subdued"
90+
isDisabled={disableExclude}
8891
/>
8992
</EuiToolTip>
9093
),

public/components/notebooks/components/log_analytics/components/pattern_difference.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ interface PatternDifferenceProps {
2424
patternMapDifference: LogPattern[];
2525
isLoadingPatternMapDifference: boolean;
2626
isNotApplicable: boolean;
27+
disableExclude?: boolean;
2728
onExclude?: (item: LogPattern) => void;
2829
}
2930

@@ -78,6 +79,7 @@ export const PatternDifference: React.FC<PatternDifferenceProps> = ({
7879
patternMapDifference,
7980
isLoadingPatternMapDifference,
8081
isNotApplicable,
82+
disableExclude,
8183
onExclude,
8284
}) => {
8385
// Columns for pattern difference table
@@ -164,6 +166,7 @@ export const PatternDifference: React.FC<PatternDifferenceProps> = ({
164166
aria-label="Deselect item"
165167
onClick={() => onExclude(record)}
166168
color="subdued"
169+
isDisabled={disableExclude}
167170
/>
168171
</EuiToolTip>
169172
),

public/components/notebooks/components/log_analytics/log_pattern_container.tsx

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -104,10 +104,12 @@ export const LogPatternContainer: React.FC<LogPatternContainerProps> = ({ paragr
104104
// Save results when needed
105105
useEffect(() => {
106106
if (paragraphRef.current && resultChanged) {
107-
saveParagraph({
108-
paragraphStateValue: ParagraphState.updateOutputResult(paragraphRef.current, result),
109-
});
110-
setResultChanged(false);
107+
(async () => {
108+
await saveParagraph({
109+
paragraphStateValue: ParagraphState.updateOutputResult(paragraphRef.current!, result),
110+
});
111+
setResultChanged(false);
112+
})();
111113
}
112114
}, [result, resultChanged, saveParagraph]);
113115

@@ -159,6 +161,7 @@ export const LogPatternContainer: React.FC<LogPatternContainerProps> = ({ paragr
159161
<LogInsight
160162
logInsights={result?.logInsights || []}
161163
isLoadingLogInsights={loadingStatus.isLoadingLogInsights}
164+
disableExclude={!!resultChanged}
162165
onExclude={handleLogInsightExclude}
163166
/>
164167
<EuiSpacer size="s" />
@@ -167,6 +170,7 @@ export const LogPatternContainer: React.FC<LogPatternContainerProps> = ({ paragr
167170
patternMapDifference={result?.patternMapDifference || []}
168171
isLoadingPatternMapDifference={loadingStatus.isLoadingPatternMapDifference}
169172
isNotApplicable={!processedContext?.timeRange?.baselineFrom}
173+
disableExclude={!!resultChanged}
170174
onExclude={handlePatternDifferenceExclude}
171175
/>
172176
<EuiSpacer size="s" />
@@ -180,6 +184,7 @@ export const LogPatternContainer: React.FC<LogPatternContainerProps> = ({ paragr
180184
processedContext.indexInsight?.trace_id_field
181185
)
182186
}
187+
disableExclude={!!resultChanged}
183188
onExclude={handleLogSequenceExclude}
184189
/>
185190
</EuiPanel>

public/paragraphs/data_distribution.ts

Lines changed: 77 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,86 @@ import { getNotifications } from '../services';
1717
export const DataDistributionParagraphItem: ParagraphRegistryItem<AnomalyVisualizationAnalysisOutputResult> = {
1818
ParagraphComponent: DataDistributionContainer,
1919
getContext: async (paragraph) => {
20-
const selectedFieldComparison = paragraph?.output?.[0]?.result?.fieldComparison?.filter(
21-
(item) => !item.excludeFromContext
20+
const allFieldComparison = paragraph?.output?.[0]?.result?.fieldComparison || [];
21+
const selectedFieldComparison = allFieldComparison.filter((item) => !item.excludeFromContext);
22+
23+
const hasBaseline = selectedFieldComparison.some((f) =>
24+
f.topChanges.some((c) => c.baselinePercentage !== undefined)
2225
);
23-
return ` ## Data Distribution Analysis Results
2426

25-
### Step description
26-
This step calculate fields' distribution and find the outlines between baselineTimeRange and selectionTimeRange.
27-
These statistical deviations highlight potential areas of concern that may explain the underlying issue.
27+
const methodologyText = hasBaseline
28+
? `Compares field value distributions between baseline and selection periods:
29+
- Analyzes categorical fields (keyword, boolean, text) and numeric fields (grouped into ranges)
30+
- Calculates percentage distribution for each field value
31+
- Ranks fields by divergence score (maximum percentage point shift between periods)`
32+
: `Analyzes field value distributions in the selected time period:
33+
- Examines categorical fields (keyword, boolean, text) and numeric fields (grouped into ranges)
34+
- Calculates percentage distribution for each field value
35+
- Shows fields with highest cardinality and most significant values`;
36+
37+
const guidelinesText = hasBaseline
38+
? `**PRIMARY EVIDENCE**: Use divergence scores and distribution shifts as concrete evidence.
39+
40+
**Priority**:
41+
- [CRITICAL] divergence >30%: Severe behavioral change
42+
- [HIGH] divergence >15%: Significant change requiring investigation
43+
- [MEDIUM] divergence >5%: Notable change worth examining
44+
45+
**Investigation Strategy**:
46+
1. Start with highest divergence fields - these show the strongest anomalies
47+
2. Look for correlated changes across multiple fields (e.g., error_code + status_code + response_time)
48+
3. Examine topChanges for each field to identify which specific values shifted
49+
4. Quantify impact using baseline → selection percentages
50+
5. Cross-reference with log patterns to validate hypotheses`
51+
: `**PRIMARY EVIDENCE**: Use field distributions to understand data characteristics.
52+
53+
**Investigation Strategy**:
54+
1. Look for error-related fields (status_code, error_code, level, severity) with high error percentages
55+
2. Examine fields with concentrated distributions (>80% in single value) - may indicate systemic issues
56+
3. Check topChanges to identify dominant field values that correlate with problems
57+
4. Cross-reference field values with log patterns to identify root causes`;
58+
59+
const formatFieldData = () => {
60+
if (selectedFieldComparison.length === 0) return 'No field data available.';
61+
62+
return selectedFieldComparison
63+
.map((field, i) => {
64+
const changes = field.topChanges
65+
.map((c) => {
66+
if (c.baselinePercentage !== undefined) {
67+
return ` - "${c.value}": ${c.baselinePercentage.toFixed(
68+
1
69+
)}% → ${c.selectionPercentage.toFixed(1)}%`;
70+
}
71+
return ` - "${c.value}": ${c.selectionPercentage.toFixed(1)}%`;
72+
})
73+
.join('\n');
74+
75+
if (hasBaseline) {
76+
return `[${i + 1}] Field: ${field.field}\n Divergence: ${(
77+
field.divergence * 100
78+
).toFixed(1)}%\n Top Values:\n${changes}`;
79+
}
80+
return `[${i + 1}] Field: ${field.field}\n Top Values:\n${changes}`;
81+
})
82+
.join('\n\n');
83+
};
84+
85+
return `## Data Distribution Analysis
86+
87+
### Methodology
88+
${methodologyText}
89+
90+
### Field Data
91+
${formatFieldData()}
92+
93+
### Analysis Guidelines
94+
${guidelinesText}
2895
29-
### Step result:
30-
Anomaly detection has been performed on the data and the analysis identified anomalies in the following fields:
31-
${JSON.stringify(selectedFieldComparison)}.
96+
**Query Construction**:
97+
- Use field names and top values to build targeted queries
98+
- Filter by high-percentage values to focus on dominant behaviors
99+
- Combine multiple fields to narrow down root cause
32100
`;
33101
},
34102
runParagraph: async ({ paragraphState, notebookStateValue }) => {

0 commit comments

Comments
 (0)