Skip to content
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

fix: gracefully fail charts if columns have changed #596

Merged
merged 2 commits into from
Dec 22, 2023
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
2 changes: 0 additions & 2 deletions backend/zeno_backend/classes/chart.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,6 @@ class Chart(CamelModel):
type (ChartType): the type of the chart.
parameters (XCParameters | TableParameters | BeeswarmParameters |
RadarParameters | HeatmapParameters): the parameters of the chart.
data (str): the JSON string data of the chart.
"""

id: int
Expand All @@ -195,7 +194,6 @@ class Chart(CamelModel):
| RadarParameters
| HeatmapParameters
)
data: str | None = None


class ParametersEncoder(json.JSONEncoder):
Expand Down
8 changes: 2 additions & 6 deletions backend/zeno_backend/database/insert.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
from zeno_backend.classes.user import Author, Organization, User
from zeno_backend.database.database import db_pool
from zeno_backend.database.util import hash_api_key, resolve_metadata_type
from zeno_backend.processing.chart import calculate_chart_data


async def api_key(user: User) -> str | None:
Expand Down Expand Up @@ -709,18 +708,15 @@ async def chart(project: str, chart: Chart) -> int | None:
Returns:
int | None: the id of the newly created chart.
"""
chart_data = await calculate_chart_data(chart, project)

async with db_pool.connection() as conn:
async with conn.cursor() as cur:
await cur.execute(
"INSERT INTO charts (name, type, parameters, data, project_uuid) "
"VALUES (%s,%s,%s,%s,%s) RETURNING id;",
"INSERT INTO charts (name, type, parameters, project_uuid) "
"VALUES (%s,%s,%s,%s) RETURNING id;",
[
chart.name,
chart.type,
json.dumps(chart.parameters, cls=ParametersEncoder),
chart_data,
project,
],
)
Expand Down
30 changes: 27 additions & 3 deletions backend/zeno_backend/database/select.py
Original file line number Diff line number Diff line change
Expand Up @@ -852,7 +852,6 @@ async def charts_for_projects(project_uuids: list[str]) -> list[Chart]:
name=r[1],
type=r[2],
parameters=json.loads(r[3]),
data=json.dumps(r[4]) if r[4] is not None else None,
project_uuid=r[5],
),
chart_results,
Expand Down Expand Up @@ -1676,7 +1675,7 @@ async def chart(chart_id: int) -> Chart:
chart_id (int): the ID of the chart to be fetched.

Returns:
Chart | None: the requested chart.
Chart: the requested chart.
"""
async with db_pool.connection() as conn:
async with conn.cursor() as cur:
Expand All @@ -1698,11 +1697,36 @@ async def chart(chart_id: int) -> Chart:
name=chart_result[0][1],
type=chart_result[0][2],
parameters=json.loads(chart_result[0][3]),
data=json.dumps(chart_result[0][4]) if chart_result[0][4] is not None else None,
project_uuid=chart_result[0][5],
)


async def chart_data(chart_id: int) -> str | None:
"""Get a chart's data.

Args:
chart_id (int): ID of the chart to get data for.

Returns:
str | None: chart data.
"""
async with db_pool.connection() as conn:
async with conn.cursor() as cur:
await cur.execute(
"SELECT data FROM charts WHERE id = %s",
[chart_id],
)
chart_result = await cur.fetchall()

if len(chart_result) == 0:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Chart could not be found.",
)

return json.dumps(chart_result[0][0]) if chart_result[0][0] is not None else None


async def charts(project_uuid: str) -> list[Chart]:
"""Get a list of all charts created in the project.

Expand Down
8 changes: 1 addition & 7 deletions backend/zeno_backend/database/update.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
from zeno_backend.classes.tag import Tag
from zeno_backend.classes.user import Author, Organization, User
from zeno_backend.database.database import db_pool
from zeno_backend.processing.chart import calculate_chart_data


async def folder(folder: Folder, project: str):
Expand Down Expand Up @@ -65,26 +64,21 @@ async def chart(chart: Chart, project: str):
chart (Chart): the chart data to use for the update.
project (str): the project the user is currently working with.
"""
chart_data = await calculate_chart_data(chart, project)

async with db_pool.connection() as conn:
async with conn.cursor() as cur:
await cur.execute(
"UPDATE charts SET project_uuid = %s, name = %s, type = %s, "
"parameters = %s, data = %s, updated_at = CURRENT_TIMESTAMP "
"parameters = %s, data = NULL, updated_at = CURRENT_TIMESTAMP "
"WHERE id = %s;",
[
project,
chart.name,
chart.type,
json.dumps(chart.parameters, cls=ParametersEncoder),
chart_data,
chart.id,
],
)

return chart_data


async def chart_data(chart_id: int, data: str):
"""Add chart data to chart entry.
Expand Down
2 changes: 1 addition & 1 deletion backend/zeno_backend/processing/filtering.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ async def filter_to_sql(
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Could not find column: {f.column.name} "
"for model {f.column.model}.",
f"for model {f.column.model}.",
)
filt = (
filt
Expand Down
40 changes: 26 additions & 14 deletions backend/zeno_backend/routers/chart.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,16 +56,34 @@ async def get_chart(project_uuid: str, chart_id: int, request: Request):
HTTPException: error if the chart could not be fetched.

Returns:
ChartResponse: chart spec and data.
ChartResponse: chart spec.
"""
await util.project_access_valid(project_uuid, request)
chart = await select.chart(chart_id)
if chart.data is None:
chart_data = await calculate_chart_data(chart, project_uuid)
await update.chart_data(chart_id, chart_data)
chart.data = chart_data
return await select.chart(chart_id)


@router.get("/chart-data/{project_uuid}/{chart_id}", response_model=str, tags=["Zeno"])
async def get_chart_data(project_uuid, chart_id: int, request: Request):
"""Get a chart's data.

Args:
project_uuid (str): UUID of the project to get a chart from.
chart_id (int): id of the chart to be fetched.
request (Request): http request to get user information from.

Raises:
HTTPException: error if the chart could not be fetched.

return chart
Returns:
str: chart data.
"""
await util.project_access_valid(project_uuid, request)
data = await select.chart_data(chart_id)
if data is None:
chart = await select.chart(chart_id)
data = await calculate_chart_data(chart, project_uuid)
await update.chart_data(chart_id, data)
return data


@router.post("/chart-config/{project_uuid}", response_model=ChartConfig, tags=["zeno"])
Expand Down Expand Up @@ -112,11 +130,6 @@ async def get_charts_for_projects(project_uuids: list[str], request: Request):
for project_uuid in project_uuids:
await util.project_access_valid(project_uuid, request)
charts = await select.charts_for_projects(project_uuids)
for c in charts:
if c.data is None:
chart_data = await calculate_chart_data(c, c.project_uuid)
await update.chart_data(c.id, chart_data)
c.data = chart_data
return charts


Expand Down Expand Up @@ -165,7 +178,6 @@ async def add_chart(

@router.patch(
"/chart/{project_uuid}",
response_model=str,
tags=["zeno"],
dependencies=[Depends(util.auth)],
)
Expand All @@ -180,7 +192,7 @@ async def update_chart(project_uuid: str, chart: Chart, request: Request):
await util.project_editor(project_uuid, request)
selected_chart = await select.chart(chart.id)
if selected_chart.project_uuid == project_uuid:
return await update.chart(chart, project_uuid)
await update.chart(chart, project_uuid)
else:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
Expand Down
10 changes: 7 additions & 3 deletions frontend/src/lib/components/report/elements/ChartElement.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,21 @@
<div class="w-full">
<h3 class="text-lg font-semibold">{chart.name}</h3>
{#await zenoClient.getChartConfig(chart.projectUuid, chart.id) then chartConfig}
{#if chart.data}
{#await zenoClient.getChartData(chart.projectUuid, chart.id) then data}
<div class="text-center">
<svelte:component
this={chartMap[chart.type]}
{chart}
{chartConfig}
{width}
data={JSON.parse(chart.data)}
data={JSON.parse(data)}
height={chart.type == ChartType.RADAR ? 600 : 400}
/>
</div>
{/if}
{:catch error}
<p class="ml-4 mt-4 font-semibold text-error">
Chart data could not be loaded: {error.message}
</p>
{/await}
{/await}
</div>
2 changes: 0 additions & 2 deletions frontend/src/lib/zenoapi/models/Chart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import type { XCParameters } from './XCParameters';
* type (ChartType): the type of the chart.
* parameters (XCParameters | TableParameters | BeeswarmParameters |
* RadarParameters | HeatmapParameters): the parameters of the chart.
* data (str): the JSON string data of the chart.
*/
export type Chart = {
id: number;
Expand All @@ -33,5 +32,4 @@ export type Chart = {
| BeeswarmParameters
| RadarParameters
| HeatmapParameters;
data?: string | null;
};
72 changes: 69 additions & 3 deletions frontend/src/lib/zenoapi/services/ZenoService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ export class ZenoService {
* HTTPException: error if the chart could not be fetched.
*
* Returns:
* ChartResponse: chart spec and data.
* ChartResponse: chart spec.
* @param chartId
* @param projectUuid
* @returns Chart Successful Response
Expand All @@ -304,6 +304,72 @@ export class ZenoService {
});
}

/**
* Get Chart Data
* Get a chart's data.
*
* Args:
* project_uuid (str): UUID of the project to get a chart from.
* chart_id (int): id of the chart to be fetched.
* request (Request): http request to get user information from.
*
* Raises:
* HTTPException: error if the chart could not be fetched.
*
* Returns:
* str: chart data.
* @param projectUuid
* @param chartId
* @returns string Successful Response
* @throws ApiError
*/
public getChartData(projectUuid: any, chartId: number): CancelablePromise<string> {
return this.httpRequest.request({
method: 'GET',
url: '/chart-data/{project_uuid}/{chart_id}',
path: {
project_uuid: projectUuid,
chart_id: chartId
},
errors: {
422: `Validation Error`
}
});
}

/**
* Get Chart Data
* Get a chart's data.
*
* Args:
* project_uuid (str): UUID of the project to get a chart from.
* chart_id (int): id of the chart to be fetched.
* request (Request): http request to get user information from.
*
* Raises:
* HTTPException: error if the chart could not be fetched.
*
* Returns:
* str: chart data.
* @param projectUuid
* @param chartId
* @returns string Successful Response
* @throws ApiError
*/
public getChartData1(projectUuid: any, chartId: number): CancelablePromise<string> {
return this.httpRequest.request({
method: 'GET',
url: '/chart-data/{project_uuid}/{chart_id}',
path: {
project_uuid: projectUuid,
chart_id: chartId
},
errors: {
422: `Validation Error`
}
});
}

/**
* Get Chart Config
* Get a project's chart configuration.
Expand Down Expand Up @@ -440,10 +506,10 @@ export class ZenoService {
* request (Request): http request to get user information from.
* @param projectUuid
* @param requestBody
* @returns string Successful Response
* @returns any Successful Response
* @throws ApiError
*/
public updateChart(projectUuid: string, requestBody: Chart): CancelablePromise<string> {
public updateChart(projectUuid: string, requestBody: Chart): CancelablePromise<any> {
return this.httpRequest.request({
method: 'PATCH',
url: '/chart/{project_uuid}',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
let mounted = false;
let isChartEdit: boolean | undefined;
let chart = data.chart;
let chartData = chart.data ? JSON.parse(chart.data) : undefined;
let updatingData = false;
let chartDataRequest = zenoClient.getChartData(chart.projectUuid, chart.id);

$: updateEditUrl(isChartEdit);
$: updateChart(chart);
Expand All @@ -39,8 +39,8 @@
function updateChart(chart: Chart) {
updatingData = true;
if (mounted && $project && $project.editor && browser) {
zenoClient.updateChart($project.uuid, chart).then((d) => {
chartData = JSON.parse(d);
zenoClient.updateChart($project.uuid, chart).then(() => {
chartDataRequest = zenoClient.getChartData(chart.projectUuid, chart.id);
updatingData = false;
});
} else {
Expand All @@ -66,17 +66,21 @@
{:else}
<ViewHeader bind:isChartEdit bind:chartConfig={data.chartConfig} {chart} />
{/if}
{#if chartData}
{#await chartDataRequest then chartData}
<div class={`flex h-full w-full flex-col overflow-auto pl-2`}>
<ChartContainer chartName={chart.name} loading={updatingData}>
<svelte:component
this={chartMap[chart.type]}
{chart}
data={chartData}
data={JSON.parse(chartData)}
width={900}
chartConfig={data.chartConfig}
/>
</ChartContainer>
</div>
{/if}
{:catch error}
<p class="ml-4 mt-4 font-semibold text-error">
Chart data could not be loaded: {error.message}
</p>
{/await}
</div>
Loading