Skip to content

update search evaluation experiment view and hybrid optimizer experim… #75

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 2 commits into from
Jun 9, 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
72 changes: 72 additions & 0 deletions public/components/experiment_view/document_score_table.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import {
EuiFlexGroup,
EuiTitle,
EuiPanel,
EuiFlexItem,
EuiBasicTable,
EuiButton,
EuiSpacer,
} from '@elastic/eui';
import React from 'react';

export const DocumentScoresTable: React.FC<{
queryText: string;
documentScores: Array<{ docId: string; rating: string }>;
}> = ({ queryText, documentScores }) => {
const downloadCsv = () => {
// Create CSV content
const headers = ['Document ID', 'Rating'];
const csvContent = [
headers.join(','),
...documentScores.map((item) => `${item.docId},${item.rating}`),
].join('\n');

// Create and trigger download
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
const link = document.createElement('a');
const url = URL.createObjectURL(blob);
link.setAttribute('href', url);
link.setAttribute('download', `document_scores_${queryText}_${new Date().toISOString()}.csv`);
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
};

return (
<EuiPanel>
<EuiFlexGroup justifyContent="spaceBetween" alignItems="center">
<EuiFlexItem grow={false}>
<EuiTitle size="s">
<h3>Document Scores for "{queryText}"</h3>
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButton size="s" onClick={downloadCsv} iconType="download">
Download CSV
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer size="s" />
<EuiBasicTable
items={documentScores}
columns={[
{
field: 'docId',
name: 'Document ID',
sortable: true,
},
{
field: 'rating',
name: 'Rating',
sortable: true,
},
]}
/>
</EuiPanel>
);
};
124 changes: 98 additions & 26 deletions public/components/experiment_view/evaluation_experiment_view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import {
printType,
} from '../../types/index';
import { MetricsSummaryPanel } from './metrics_summary';
import { DocumentScoresTable } from './document_score_table';

interface EvaluationExperimentViewProps extends RouteComponentProps<{ id: string }> {
http: CoreStart['http'];
Expand All @@ -58,8 +59,54 @@ export const EvaluationExperimentView: React.FC<EvaluationExperimentViewProps> =

const [tableColumns, setTableColumns] = useState<any[]>([]);

const [selectedQuery, setSelectedQuery] = useState<string | null>(null);
const [selectedQueryScores, setSelectedQueryScores] = useState<
Array<{ docId: string; rating: string }>
>([]);

const sanitizeResponse = (response) => response?.hits?.hits?.[0]?._source || undefined;

const handleQueryClick = useCallback(
(queryText: string) => {
try {
// Find the evaluation from already fetched queryEvaluations
const evaluation = queryEvaluations.find((q) => q.queryText === queryText);

if (!evaluation?.documentIds?.length) {
notifications.toasts.addWarning({
title: 'No documents',
text: 'No documents found in evaluation results',
});
return;
}

// Get ratings from the already fetched judgment set
const judgmentEntry = judgmentSet?.judgmentRatings?.find(
(entry) => entry.query === queryText
);
const judgments = judgmentEntry?.ratings || [];

// Create document scores by matching evaluation documentIds with judgments
const documentScores = evaluation.documentIds.map((docId) => {
const judgment = judgments.find((j) => j.docId === docId);
return {
docId,
rating: judgment ? judgment.rating : 'N/A',
};
});

setSelectedQuery(queryText);
setSelectedQueryScores(documentScores);
} catch (error) {
console.error('Error handling query click:', error);
notifications.toasts.addError(error, {
title: 'Error processing document scores',
});
}
},
[queryEvaluations, judgmentSet, notifications]
);

useEffect(() => {
const fetchExperiment = async () => {
try {
Expand Down Expand Up @@ -97,6 +144,7 @@ export const EvaluationExperimentView: React.FC<EvaluationExperimentViewProps> =
_id: resultIds,
},
},
size: resultIds.length,
};
const result = await http.post(ServiceEndpoints.GetSearchResults, {
body: JSON.stringify({ query1: query }),
Expand Down Expand Up @@ -164,6 +212,11 @@ export const EvaluationExperimentView: React.FC<EvaluationExperimentViewProps> =
name: 'Query',
dataType: 'string',
sortable: true,
render: (queryText: string) => (
<EuiButtonEmpty onClick={() => handleQueryClick(queryText)} size="xs">
{queryText}
</EuiButtonEmpty>
),
},
];
metricNames.forEach((metricName) => {
Expand All @@ -187,7 +240,7 @@ export const EvaluationExperimentView: React.FC<EvaluationExperimentViewProps> =
setTableColumns(columns);
setLoading(false);
}
}, [experiment, queryEvaluations]);
}, [experiment, queryEvaluations, handleQueryClick]);

const findQueries = useCallback(
async (search: any) => {
Expand Down Expand Up @@ -240,31 +293,50 @@ export const EvaluationExperimentView: React.FC<EvaluationExperimentViewProps> =

const resultsPane = (
<EuiPanel hasBorder paddingSize="l">
{error ? (
<EuiCallOut title="Error" color="danger">
<p>{error}</p>
</EuiCallOut>
) : (
<TableListView
key={`table-${queryEvaluations.length}`}
entityName="Query"
entityNamePlural="Queries"
tableColumns={tableColumns}
findItems={findQueries}
loading={loading}
pagination={{
initialPageSize: 10,
pageSizeOptions: [5, 10, 20, 50],
}}
search={{
box: {
incremental: true,
placeholder: 'Query...',
schema: true,
},
}}
/>
)}
<EuiResizableContainer>
{(EuiResizablePanel, EuiResizableButton) => (
<>
<EuiResizablePanel initialSize={50} minSize="15%">
{error ? (
<EuiCallOut title="Error" color="danger">
<p>{error}</p>
</EuiCallOut>
) : (
<TableListView
key={`table-${queryEvaluations.length}`}
entityName="Query"
entityNamePlural="Queries"
tableColumns={tableColumns}
findItems={findQueries}
loading={loading}
pagination={{
initialPageSize: 10,
pageSizeOptions: [5, 10, 20, 50],
}}
search={{
box: {
incremental: true,
placeholder: 'Query...',
schema: true,
},
}}
/>
)}
</EuiResizablePanel>

<EuiResizableButton />

<EuiResizablePanel initialSize={50} minSize="30%">
{selectedQuery && selectedQueryScores && (
<DocumentScoresTable
queryText={selectedQuery}
documentScores={selectedQueryScores}
/>
)}
</EuiResizablePanel>
</>
)}
</EuiResizableContainer>
</EuiPanel>
);

Expand Down
9 changes: 9 additions & 0 deletions public/components/experiment_view/experiment_view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { ServiceEndpoints } from '../../../common';
import { ExperimentType, toExperiment } from '../../types/index';
import { PairwiseExperimentViewWithRouter } from './pairwise_experiment_view';
import { EvaluationExperimentViewWithRouter } from './evaluation_experiment_view';
import { HybridOptimizerExperimentViewWithRouter } from './hybrid_optimizer_experiment_view';

interface ExperimentViewProps extends RouteComponentProps<{ id: string }> {
http: CoreStart['http'];
Expand Down Expand Up @@ -71,6 +72,14 @@ export const ExperimentView: React.FC<ExperimentViewProps> = ({
history={history}
/>
)}
{experiment && experiment.type === ExperimentType.HYBRID_OPTIMIZER && (
<HybridOptimizerExperimentViewWithRouter
http={http}
notifications={notifications}
inputExperiment={experiment}
history={history}
/>
)}
</EuiPageTemplate>
);
};
Expand Down
Loading
Loading