diff --git a/package-lock.json b/package-lock.json
index c37a4e45ab..63cb765d74 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -5,9 +5,9 @@
"requires": true,
"dependencies": {
"@apollo/client": {
- "version": "3.5.5",
- "resolved": "https://registry.npmjs.org/@apollo/client/-/client-3.5.5.tgz",
- "integrity": "sha512-EiQstc8VjeqosS2h21bwY9fhL3MCRRmACtRrRh2KYpp9vkDyx5pUfMnN3swgiBVYw1twdXg9jHmyZa1gZlvlog==",
+ "version": "3.5.6",
+ "resolved": "https://registry.npmjs.org/@apollo/client/-/client-3.5.6.tgz",
+ "integrity": "sha512-XHoouuEJ4L37mtfftcHHO1caCRrKKAofAwqRoq28UQIPMJk+e7n3X9OtRRNXKk/9tmhNkwelSary+EilfPwI7A==",
"requires": {
"@graphql-typed-document-node/core": "^3.0.0",
"@wry/context": "^0.6.0",
@@ -18,7 +18,7 @@
"optimism": "^0.16.1",
"prop-types": "^15.7.2",
"symbol-observable": "^4.0.0",
- "ts-invariant": "^0.9.0",
+ "ts-invariant": "^0.9.4",
"tslib": "^2.3.0",
"zen-observable-ts": "^1.2.0"
}
diff --git a/package.json b/package.json
index a16ce726ef..7a594f68e5 100644
--- a/package.json
+++ b/package.json
@@ -39,7 +39,7 @@
"snyk-test": "snyk test -prune-repeated-subdependencies"
},
"dependencies": {
- "@apollo/client": "^3.5.5",
+ "@apollo/client": "^3.5.6",
"@graphql-tools/schema": "7.1.5",
"@material-ui/core": "^4.11.4",
"@material-ui/icons": "^4.11.2",
@@ -138,4 +138,4 @@
"not op_mini all"
],
"snyk": true
-}
\ No newline at end of file
+}
diff --git a/package/kedro_viz/api/graphql.py b/package/kedro_viz/api/graphql.py
index 12131e056c..80029bd126 100644
--- a/package/kedro_viz/api/graphql.py
+++ b/package/kedro_viz/api/graphql.py
@@ -27,6 +27,7 @@ class JSONObject(dict):
https://github.com/python/mypy/issues/2477
"""
+
else:
JSONObject = strawberry.scalar(
NewType("JSONObject", dict),
@@ -46,7 +47,11 @@ def format_run(run_id: str, run_blob: Dict) -> Run:
"""
session = data_access_manager.db_session
git_data = run_blob.get("git")
- user_details = session.query(UserRunDetailsModel).filter(UserRunDetailsModel.run_id == run_id).scalar()
+ user_details = (
+ session.query(UserRunDetailsModel)
+ .filter(UserRunDetailsModel.run_id == run_id)
+ .scalar()
+ )
run = Run(
id=ID(run_id),
author="",
@@ -95,7 +100,9 @@ def get_all_runs() -> List[Run]:
return runs
-def format_run_tracking_data(tracking_data: Dict, show_diff: Optional[bool] = False) -> JSONObject:
+def format_run_tracking_data(
+ tracking_data: Dict, show_diff: Optional[bool] = False
+) -> JSONObject:
"""Convert tracking data in the front-end format.
Args:
@@ -140,7 +147,9 @@ def format_run_tracking_data(tracking_data: Dict, show_diff: Optional[bool] = Fa
for run_id, run_tracking_data in tracking_data.items():
for tracking_name, data in run_tracking_data.items():
- formatted_tracking_data[tracking_name].append({"runId": run_id, "value": data})
+ formatted_tracking_data[tracking_name].append(
+ {"runId": run_id, "value": data}
+ )
if not show_diff:
for tracking_key, run_tracking_data in list(formatted_tracking_data.items()):
if len(run_tracking_data) != len(tracking_data):
@@ -149,7 +158,9 @@ def format_run_tracking_data(tracking_data: Dict, show_diff: Optional[bool] = Fa
return JSONObject(formatted_tracking_data)
-def get_run_tracking_data(run_ids: List[ID], show_diff: Optional[bool] = False) -> List[TrackingDataset]:
+def get_run_tracking_data(
+ run_ids: List[ID], show_diff: Optional[bool] = False
+) -> List[TrackingDataset]:
# pylint: disable=protected-access,import-outside-toplevel
"""Get all tracking data for a list of runs. Tracking data contains the data from the
tracking MetricsDataSet and JSONDataSet instances that have been logged
@@ -179,7 +190,9 @@ def get_run_tracking_data(run_ids: List[ID], show_diff: Optional[bool] = False)
run_id = ID(run_id)
file_path = dataset._get_versioned_path(str(run_id))
if Path(file_path).is_file():
- with dataset._fs.open(file_path, **dataset._fs_open_args_load) as fs_file:
+ with dataset._fs.open(
+ file_path, **dataset._fs_open_args_load
+ ) as fs_file:
json_data = json.load(fs_file)
all_runs[run_id] = json_data
else:
@@ -238,7 +251,9 @@ def run_metadata(self, run_ids: List[ID]) -> List[Run]:
return get_runs(run_ids)
@strawberry.field
- def run_tracking_data(self, run_ids: List[ID], show_diff: Optional[bool] = False) -> List[TrackingDataset]:
+ def run_tracking_data(
+ self, run_ids: List[ID], show_diff: Optional[bool] = False
+ ) -> List[TrackingDataset]:
"""Query to get data for specific runs from the session store"""
return get_run_tracking_data(run_ids, show_diff)
@@ -261,18 +276,20 @@ class RunInput:
class UpdateRunDetailsSuccess:
"""Response type for sucessful update of runs"""
- run_details: JSONObject
+ run: Run
@strawberry.type
class UpdateRunDetailsFailure:
"""Response type for failed update of runs"""
- run_id: ID
+ id: ID
error_message: str
-Response = strawberry.union("UpdateRunDetailsResponse", (UpdateRunDetailsSuccess, UpdateRunDetailsFailure))
+Response = strawberry.union(
+ "UpdateRunDetailsResponse", (UpdateRunDetailsSuccess, UpdateRunDetailsFailure)
+)
@strawberry.type
@@ -284,34 +301,50 @@ def update_run_details(self, run_id: ID, run_input: RunInput) -> Response:
"""Updates run details based on run inputs provided by user"""
runs = get_runs([run_id])
if not runs:
- return UpdateRunDetailsFailure(run_id=run_id, error_message=f"Given run_id: {run_id} doesn't exist")
+ return UpdateRunDetailsFailure(
+ id=run_id, error_message=f"Given run_id: {run_id} doesn't exist"
+ )
existing_run = runs[0]
- updated_user_run_details = {
- "run_id": run_id,
- "bookmark": run_input.bookmark if run_input.bookmark is not None else existing_run.bookmark,
- "notes": run_input.notes if run_input.notes is not None else existing_run.notes,
- }
-
+ new_run = existing_run
# if user doesn't provide a new title, use the old title.
if run_input.title is None:
- updated_user_run_details["title"] = existing_run.title
+ new_run.title = existing_run.title
# if user provides an empty title, we assume they want to revert to the old timestamp title
elif run_input.title.strip() == "":
- updated_user_run_details["title"] = existing_run.timestamp
+ new_run.title = existing_run.timestamp
else:
- updated_user_run_details["title"] = run_input.title
+ new_run.title = run_input.title
+
+ new_run.bookmark = (
+ run_input.bookmark
+ if run_input.bookmark is not None
+ else existing_run.bookmark
+ )
+
+ new_run.notes = (
+ run_input.notes if run_input.notes is not None else existing_run.notes
+ )
+
+ updated_user_run_details = {
+ "run_id": run_id,
+ "title": new_run.title,
+ "bookmark": new_run.bookmark,
+ "notes": new_run.notes,
+ }
session = data_access_manager.db_session
- user_run_details = session.query(UserRunDetailsModel).filter(UserRunDetailsModel.run_id == run_id).first()
+ user_run_details = (
+ session.query(UserRunDetailsModel)
+ .filter(UserRunDetailsModel.run_id == run_id)
+ .first()
+ )
if not user_run_details:
- session.add(UserRunDetailsModel(**updated_user_run_details)) # type: ignore
+ session.add(UserRunDetailsModel(**updated_user_run_details)) # type: ignore
else:
for key, value in updated_user_run_details.items():
setattr(user_run_details, key, value)
session.commit()
- return UpdateRunDetailsSuccess(
- run_details=JSONObject(updated_user_run_details)
- )
+ return UpdateRunDetailsSuccess(new_run)
schema = strawberry.Schema(query=Query, mutation=Mutation)
diff --git a/package/kedro_viz/integrations/kedro/sqlite_store.py b/package/kedro_viz/integrations/kedro/sqlite_store.py
index a54744839d..1a39cbaa7d 100644
--- a/package/kedro_viz/integrations/kedro/sqlite_store.py
+++ b/package/kedro_viz/integrations/kedro/sqlite_store.py
@@ -51,7 +51,7 @@ def to_json(self) -> str:
branch = git.Repo(search_parent_directories=True).active_branch
value["branch"] = branch.name
- except ImportError as exc: # pragma: no cover
+ except ImportError as exc: # pragma: no cover
logger.warning("%s:%s", exc.__class__.__name__, exc.msg)
if _is_json_serializable(value):
diff --git a/package/tests/test_api/test_graphql.py b/package/tests/test_api/test_graphql.py
index 73032d10ad..dd2ffbcd57 100644
--- a/package/tests/test_api/test_graphql.py
+++ b/package/tests/test_api/test_graphql.py
@@ -534,23 +534,43 @@ def test_graphql_runs_tracking_data_endpoint(
class TestGraphQLMutation:
- @pytest.mark.parametrize("bookmark,notes,title", [
- (False, "new notes", "new title", ),
- (True, "new notes", "new title"),
- (True, "", ""),
- ])
+ @pytest.mark.parametrize(
+ "bookmark,notes,title",
+ [
+ (
+ False,
+ "new notes",
+ "new title",
+ ),
+ (True, "new notes", "new title"),
+ (True, "", ""),
+ ],
+ )
def test_update_user_details_success(
- self, bookmark, notes, title, client, save_version, example_runs, example_db_dataset, mocker
+ self,
+ bookmark,
+ notes,
+ title,
+ client,
+ save_version,
+ example_runs,
+ example_db_dataset,
+ mocker,
):
query = f"""
mutation updateRun {{
updateRunDetails(runId: "{save_version}", runInput: {{bookmark: {str(bookmark).lower()}, notes: "{notes}", title: "{title}"}}) {{
__typename
... on UpdateRunDetailsSuccess {{
- runDetails
+ run {{
+ id
+ title
+ bookmark
+ notes
+ }}
}}
... on UpdateRunDetailsFailure {{
- runId
+ id
errorMessage
}}
}}
@@ -563,13 +583,13 @@ def test_update_user_details_success(
) as mock_session:
mock_session.return_value = example_db_dataset
mocker.patch("kedro_viz.api.graphql.get_runs").return_value = example_runs
- response = client.post("/graphql", json={"query": query})
+ response = client.post("/graphql", json={"query": query})
assert response.json() == {
"data": {
"updateRunDetails": {
"__typename": "UpdateRunDetailsSuccess",
- "runDetails": {
- "run_id": save_version,
+ "run": {
+ "id": save_version,
"bookmark": bookmark,
"title": title if title != "" else save_version,
"notes": notes,
@@ -586,10 +606,15 @@ def test_update_user_details_only_bookmark(
updateRunDetails(runId: "{save_version}", runInput: {{bookmark: true}}) {{
__typename
... on UpdateRunDetailsSuccess {{
- runDetails
+ run {{
+ id
+ title
+ bookmark
+ notes
+ }}
}}
... on UpdateRunDetailsFailure {{
- runId
+ id
errorMessage
}}
}}
@@ -602,13 +627,13 @@ def test_update_user_details_only_bookmark(
) as mock_session:
mock_session.return_value = example_db_dataset
mocker.patch("kedro_viz.api.graphql.get_runs").return_value = example_runs
- response = client.post("/graphql", json={"query": query})
+ response = client.post("/graphql", json={"query": query})
assert response.json() == {
"data": {
"updateRunDetails": {
"__typename": "UpdateRunDetailsSuccess",
- "runDetails": {
- "run_id": save_version,
+ "run": {
+ "id": save_version,
"bookmark": True,
"title": example_runs[0].title,
"notes": example_runs[0].notes,
@@ -617,16 +642,23 @@ def test_update_user_details_only_bookmark(
}
}
- def test_update_user_details_should_add_when_it_does_not_exist(self, save_version, client, example_runs, mocker):
+ def test_update_user_details_should_add_when_it_does_not_exist(
+ self, save_version, client, example_runs, mocker
+ ):
query = f"""
mutation updateRun {{
updateRunDetails(runId: "{save_version}", runInput: {{bookmark: true}}) {{
__typename
... on UpdateRunDetailsSuccess {{
- runDetails
+ run {{
+ id
+ title
+ bookmark
+ notes
+ }}
}}
... on UpdateRunDetailsFailure {{
- runId
+ id
errorMessage
}}
}}
@@ -637,15 +669,17 @@ def test_update_user_details_should_add_when_it_does_not_exist(self, save_versio
"kedro_viz.data_access.DataAccessManager.db_session",
new_callable=PropertyMock,
) as mock_session:
- mock_session.return_value.query.return_value.filter.return_value.first.return_value = None
+ mock_session.return_value.query.return_value.filter.return_value.first.return_value = (
+ None
+ )
mocker.patch("kedro_viz.api.graphql.get_runs").return_value = example_runs
- response = client.post("/graphql", json={"query": query})
+ response = client.post("/graphql", json={"query": query})
assert response.json() == {
"data": {
"updateRunDetails": {
"__typename": "UpdateRunDetailsSuccess",
- "runDetails": {
- "run_id": save_version,
+ "run": {
+ "id": save_version,
"bookmark": True,
"title": example_runs[0].title,
"notes": example_runs[0].notes,
@@ -673,10 +707,15 @@ def test_update_user_details_fail(self, client, example_db_dataset, mocker):
{
__typename
... on UpdateRunDetailsSuccess {
- runDetails
+ run {
+ id
+ title
+ notes
+ bookmark
+ }
}
... on UpdateRunDetailsFailure {
- runId
+ id
errorMessage
} }
}"""
@@ -686,7 +725,7 @@ def test_update_user_details_fail(self, client, example_db_dataset, mocker):
"data": {
"updateRunDetails": {
"__typename": "UpdateRunDetailsFailure",
- "runId": "2021-11-02T12.24.24.329Z",
+ "id": "2021-11-02T12.24.24.329Z",
"errorMessage": "Given run_id: 2021-11-02T12.24.24.329Z doesn't exist",
}
}
diff --git a/src/apollo/config.js b/src/apollo/config.js
index c3c6927302..90a94c4e32 100644
--- a/src/apollo/config.js
+++ b/src/apollo/config.js
@@ -11,10 +11,12 @@ export const client = new ApolloClient({
connectToDevTools: true,
link,
cache: new InMemoryCache(),
- resolvers: {},
defaultOptions: {
query: {
errorPolicy: 'all',
},
+ mutate: {
+ errorPolicy: 'all',
+ },
},
});
diff --git a/src/apollo/mutations.js b/src/apollo/mutations.js
new file mode 100644
index 0000000000..c25e4d145c
--- /dev/null
+++ b/src/apollo/mutations.js
@@ -0,0 +1,20 @@
+import gql from 'graphql-tag';
+
+export const UPDATE_RUN_DETAILS = gql`
+ mutation updateRunDetails($runId: ID!, $runInput: RunInput!) {
+ updateRunDetails(runId: $runId, runInput: $runInput) {
+ ... on UpdateRunDetailsFailure {
+ errorMessage
+ id
+ }
+ ... on UpdateRunDetailsSuccess {
+ run {
+ bookmark
+ id
+ notes
+ title
+ }
+ }
+ }
+ }
+`;
diff --git a/src/apollo/queries.js b/src/apollo/queries.js
index 6527eab7b3..a103bce3c3 100644
--- a/src/apollo/queries.js
+++ b/src/apollo/queries.js
@@ -21,6 +21,7 @@ export const GET_RUN_METADATA = gql`
author
gitBranch
gitSha
+ id
notes
runCommand
timestamp
diff --git a/src/apollo/schema.graphql b/src/apollo/schema.graphql
index 9a32bd8b4f..9397a7c916 100644
--- a/src/apollo/schema.graphql
+++ b/src/apollo/schema.graphql
@@ -36,12 +36,12 @@ type TrackingDataset {
}
type UpdateRunDetailsFailure {
- runId: ID!
+ id: ID!
errorMessage: String!
}
union UpdateRunDetailsResponse = UpdateRunDetailsSuccess | UpdateRunDetailsFailure
type UpdateRunDetailsSuccess {
- runDetails: JSONObject!
+ run: Run!
}
diff --git a/src/apollo/schema.js b/src/apollo/schema.js
index c82f71f952..0eb7e3e31d 100644
--- a/src/apollo/schema.js
+++ b/src/apollo/schema.js
@@ -51,7 +51,7 @@ const typeDefs = gql`
}
type UpdateRunDetailsFailure {
- runId: ID!
+ id: ID!
errorMessage: String!
}
@@ -60,7 +60,7 @@ const typeDefs = gql`
| UpdateRunDetailsFailure
type UpdateUserDetailsSuccess {
- userDetails: JSONObject!
+ run: Run!
}
`;
diff --git a/src/components/experiment-tracking/details/index.js b/src/components/experiment-tracking/details/index.js
index 7276d269be..d68143dd33 100644
--- a/src/components/experiment-tracking/details/index.js
+++ b/src/components/experiment-tracking/details/index.js
@@ -1,8 +1,9 @@
-import React from 'react';
+import React, { useEffect, useState } from 'react';
import { useApolloQuery } from '../../../apollo/utils';
import classnames from 'classnames';
import RunMetadata from '../run-metadata';
import RunDataset from '../run-dataset';
+import RunDetailsModal from '../run-details-modal';
import {
GET_RUN_METADATA,
GET_RUN_TRACKING_DATA,
@@ -11,12 +12,17 @@ import {
import './details.css';
const Details = ({
+ enableComparisonView,
enableShowChanges,
pinnedRun,
selectedRuns,
setPinnedRun,
+ setShowRunDetailsModal,
+ showRunDetailsModal,
sidebarVisible,
+ theme,
}) => {
+ const [runMetadataToEdit, setRunMetadataToEdit] = useState(null);
const { data: { runMetadata } = [], error } = useApolloQuery(
GET_RUN_METADATA,
{
@@ -31,14 +37,29 @@ const Details = ({
variables: { runIds: selectedRuns, showDiff: false },
});
+ useEffect(() => {
+ if (runMetadata && !enableComparisonView) {
+ const metadata = runMetadata.find((run) => run.id === selectedRuns[0]);
+
+ setRunMetadataToEdit(metadata);
+ }
+ }, [enableComparisonView, runMetadata, selectedRuns]);
+
+ const isSingleRun = runMetadata?.length === 1 ? true : false;
+
if (error || trackingError) {
return null;
}
- const isSingleRun = runMetadata && runMetadata.length === 1 ? true : false;
-
return (
<>
+
Couldn't update run details. Please try again later.
+onTitleOrNoteClick(run.id)} style={toggleNotes[i] ? { display: 'block' } : null} > - {sanitiseEmptyValue(run.notes)} + {run.notes !== '' ? run.notes : '- Add notes here'}
{run.notes.length > 100 ? (