diff --git a/.github/workflows/javascript-lint-and-tests.yml b/.github/workflows/javascript-lint-and-tests.yml index ca880da94e..89d4a4a5bc 100644 --- a/.github/workflows/javascript-lint-and-tests.yml +++ b/.github/workflows/javascript-lint-and-tests.yml @@ -43,7 +43,7 @@ jobs: run: |- sudo sed -i 's/archive.ubuntu.com/us-east-1.ec2.archive.ubuntu.com/g' /etc/apt/sources.list sudo apt-get update - sudo apt-get install libgtk2.0-0 libgtk-3-0 libgbm-dev libnotify-dev libgconf-2-4 libnss3 libxss1 libasound2 libxtst6 xauth xvfb + sudo apt-get install libgtk2.0-0t64 libgtk-3-0t64 libgbm-dev libnotify-dev libnss3 libxss1 libasound2t64 libxtst6 xauth xvfb - name: Test lib transpilation run: npm run lib diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 02489d36dd..b7945eb330 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -38,8 +38,5 @@ jobs: - name: Run security scan run: make security-scan - - name: Verify GraphQL schema is up to date - run: make schema-check - - name: Run Python formatters and linters run: make format-check lint-check diff --git a/cypress/support/commands.js b/cypress/support/commands.js index d57261b556..de8f12d13f 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -1,32 +1,6 @@ // Add any reusable custom commands here import { join } from 'path'; -/** - * Custom command for intercepting network requests using fixtures for GraphQL - * @param {String} operationName - * @returns {Object} The mock/fixtured json response - */ -Cypress.Commands.add('__interceptGql__', (operationName, mutationFor) => { - // Assign an alias to the intercept based on the graphql request (mutation or query). - const interceptAlias = mutationFor ? mutationFor : operationName; - - cy.intercept('POST', '/graphql', (req) => { - const requestBody = req.body; - - // check for the operation name match in the graphql request body - if (requestBody?.operationName === operationName) { - // Assign a fixture path based on the graphql request (mutation or query). - const fixturePath = mutationFor - ? `graphql/${mutationFor}.json` - : `graphql/${operationName}.json`; - - // Stub the server response (request will never reach the origin server, instead the response is - // served from the fixture) - req.reply({ fixture: fixturePath }); - } - }).as(interceptAlias); -}); - /** * Custom command for intercepting network requests for REST * @param {String} url diff --git a/package/kedro_viz/api/apps.py b/package/kedro_viz/api/apps.py index 1f57a26b79..4628b94ef9 100644 --- a/package/kedro_viz/api/apps.py +++ b/package/kedro_viz/api/apps.py @@ -18,7 +18,6 @@ from kedro_viz.api.rest.responses.utils import EnhancedORJSONResponse from kedro_viz.integrations.kedro import telemetry as kedro_telemetry -from .graphql.router import router as graphql_router from .rest.router import router as rest_router _HTML_DIR = Path(__file__).parent.parent.absolute() / "html" @@ -63,7 +62,6 @@ def create_api_app_from_project( """ app = _create_base_api_app() app.include_router(rest_router) - app.include_router(graphql_router) # Check for html directory existence. if Path(_HTML_DIR).is_dir(): diff --git a/package/kedro_viz/api/graphql/__init__.py b/package/kedro_viz/api/graphql/__init__.py deleted file mode 100644 index a7f4533f8a..0000000000 --- a/package/kedro_viz/api/graphql/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""`kedro_viz.api.graphql` defines the GraphQL API.""" diff --git a/package/kedro_viz/api/graphql/router.py b/package/kedro_viz/api/graphql/router.py deleted file mode 100644 index 803a5b7527..0000000000 --- a/package/kedro_viz/api/graphql/router.py +++ /dev/null @@ -1,18 +0,0 @@ -"""`kedro_viz.api.graphql.router` defines GraphQL routes.""" - -# mypy: ignore-errors -from fastapi import APIRouter -from strawberry.asgi import GraphQL - -from .schema import schema - -router = APIRouter() - -# graphiql=False can be removed if you wish to use the graphiql playground locally -graphql_app: GraphQL = GraphQL(schema, graphiql=False) -router.add_route("/graphql", graphql_app) -router.add_websocket_route("/graphql", graphql_app) - -# {subpath:path} is to handle urls with subpath e.g. demo.kedro.org/web -router.add_route("/{subpath:path}/graphql", graphql_app) -router.add_websocket_route("/{subpath:path}/graphql", graphql_app) diff --git a/package/kedro_viz/api/graphql/schema.py b/package/kedro_viz/api/graphql/schema.py deleted file mode 100644 index f2dc246fc0..0000000000 --- a/package/kedro_viz/api/graphql/schema.py +++ /dev/null @@ -1,39 +0,0 @@ -"""`kedro_viz.api.graphql.schema` defines the GraphQL schema: queries and mutations.""" - -from __future__ import annotations - -import logging - -import strawberry -from graphql.validation import NoSchemaIntrospectionCustomRule -from packaging.version import parse -from strawberry.extensions import AddValidationRules -from strawberry.tools import merge_types - -from kedro_viz import __version__ -from kedro_viz.integrations.pypi import get_latest_version, is_running_outdated_version - -from .types import Version - -logger = logging.getLogger(__name__) - - -@strawberry.type -class VersionQuery: - @strawberry.field(description="Get the installed and latest Kedro-Viz versions") - def version(self) -> Version: - installed_version = parse(__version__) - latest_version = get_latest_version() - return Version( - installed=str(installed_version), - is_outdated=is_running_outdated_version(installed_version, latest_version), - latest=str(latest_version) or "", - ) - - -schema = strawberry.Schema( - query=merge_types("Query", (VersionQuery,)), - extensions=[ - AddValidationRules([NoSchemaIntrospectionCustomRule]), - ], -) diff --git a/package/kedro_viz/api/graphql/types.py b/package/kedro_viz/api/graphql/types.py deleted file mode 100644 index 56fef78ff6..0000000000 --- a/package/kedro_viz/api/graphql/types.py +++ /dev/null @@ -1,12 +0,0 @@ -"""`kedro_viz.api.graphql.types` defines strawberry types.""" - -from __future__ import annotations - -import strawberry - - -@strawberry.type(description="Installed and latest Kedro-Viz versions") -class Version: - installed: str - is_outdated: bool - latest: str diff --git a/package/kedro_viz/api/rest/responses/version.py b/package/kedro_viz/api/rest/responses/version.py new file mode 100755 index 0000000000..612ac3f5b1 --- /dev/null +++ b/package/kedro_viz/api/rest/responses/version.py @@ -0,0 +1,43 @@ +"""`kedro_viz.api.rest.responses.version` contains response classes +and utility functions for the `/version` REST endpoint""" + +from pydantic import ConfigDict + +from kedro_viz import __version__ +from kedro_viz.api.rest.responses.base import BaseAPIResponse +from kedro_viz.integrations.pypi import get_latest_version, is_running_outdated_version + + +class VersionAPIResponse(BaseAPIResponse): + """ + VersionAPIResponse is a subclass of BaseAPIResponse that represents the response structure for version API. + + Attributes: + installed (str): The installed version of the Kedro Viz package. + is_outdated (bool): Whether the installed version is outdated. + latest (str): The latest available version of the Kedro Viz package. + """ + + installed: str + is_outdated: bool + latest: str + model_config = ConfigDict( + json_schema_extra={ + "installed": __version__, + "is_outdated": False, + "latest": "0.0.0", + } + ) + + +def get_version_response(): + """API response for `/api/version`.""" + installed_version = str(__version__) + latest_version = str(get_latest_version()) + is_outdated = is_running_outdated_version(installed_version, latest_version) + + return VersionAPIResponse( + installed=installed_version, + is_outdated=is_outdated, + latest=latest_version, + ) diff --git a/package/kedro_viz/api/rest/router.py b/package/kedro_viz/api/rest/router.py index 2a743239fb..c1de2bce9c 100644 --- a/package/kedro_viz/api/rest/router.py +++ b/package/kedro_viz/api/rest/router.py @@ -19,6 +19,10 @@ GraphAPIResponse, get_pipeline_response, ) +from kedro_viz.api.rest.responses.version import ( + VersionAPIResponse, + get_version_response, +) logger = logging.getLogger(__name__) @@ -50,6 +54,14 @@ async def get_single_pipeline_data(registered_pipeline_id: str): return get_pipeline_response(registered_pipeline_id) +@router.get( + "/version", + response_model=VersionAPIResponse, +) +async def get_version(): + return get_version_response() + + @router.post("/deploy") async def deploy_kedro_viz(input_values: DeployerConfiguration): from kedro_viz.integrations.deployment.deployer_factory import DeployerFactory diff --git a/package/tests/test_api/test_graphql/test_queries.py b/package/tests/test_api/test_graphql/test_queries.py deleted file mode 100644 index 1bb0dea7df..0000000000 --- a/package/tests/test_api/test_graphql/test_queries.py +++ /dev/null @@ -1,25 +0,0 @@ -import pytest -from packaging.version import parse - -from kedro_viz import __version__ - - -class TestQueryVersion: - def test_graphql_version_endpoint(self, client, mocker): - mocker.patch( - "kedro_viz.api.graphql.schema.get_latest_version", - return_value=parse("1.0.0"), - ) - response = client.post( - "/graphql", - json={"query": "{version {installed isOutdated latest}}"}, - ) - assert response.json() == { - "data": { - "version": { - "installed": __version__, - "isOutdated": False, - "latest": "1.0.0", - } - } - } diff --git a/package/tests/test_api/test_rest/test_router.py b/package/tests/test_api/test_rest/test_router.py index 523043d96d..4e45182610 100644 --- a/package/tests/test_api/test_rest/test_router.py +++ b/package/tests/test_api/test_rest/test_router.py @@ -90,3 +90,17 @@ def test_metadata( mock_get_metadata_response.assert_called_once() assert response.status_code == expected_status_code assert response.json() == expected_response + + +def test_version(client): + response = client.get("/api/version") + assert response.status_code == 200 + + json_response = response.json() + assert "installed" in json_response + assert "is_outdated" in json_response + assert "latest" in json_response + + assert isinstance(json_response["installed"], str) + assert isinstance(json_response["is_outdated"], bool) + assert isinstance(json_response["latest"], str) diff --git a/src/apollo/config.js b/src/apollo/config.js deleted file mode 100644 index 9d95d36c4e..0000000000 --- a/src/apollo/config.js +++ /dev/null @@ -1,77 +0,0 @@ -import fetch from 'cross-fetch'; -import { - ApolloClient, - InMemoryCache, - HttpLink, - ApolloLink, - Observable, -} from '@apollo/client'; -import { onError } from '@apollo/client/link/error'; -import loadJsonData from '../store/load-data'; - -const apiBaseUrl = `/graphql`; - -// HTTP link for GraphQL API calls -const httpLink = new HttpLink({ - uri: apiBaseUrl, - fetch, -}); - -// Error handling link -const errorLink = onError( - ({ graphQLErrors, networkError, operation, forward, response }) => { - if (networkError) { - console.error(`[Network error]: ${networkError}`); - - // Try reading from static file if network error occurs - return new Observable((observer) => { - (async () => { - const { operationName, variables } = operation; - let staticFilePath = `${apiBaseUrl}/${operationName}.json`; - - if (variables?.runIds) { - staticFilePath = `${apiBaseUrl}/${operationName}/${variables.runIds}.json`; - } - - try { - const staticData = await loadJsonData(staticFilePath, null); - if (staticData) { - observer.next({ data: staticData }); - observer.complete(); - } else { - observer.error(networkError); - } - } catch (error) { - observer.error(networkError); - } - })(); - }); - } - - if (graphQLErrors) { - graphQLErrors.forEach(({ message, locations, path }) => - console.error( - `[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}` - ) - ); - } - } -); - -// Combine the links -const link = ApolloLink.from([errorLink, httpLink]); - -// Create the Apollo Client -export const client = new ApolloClient({ - connectToDevTools: true, - link, - cache: new InMemoryCache(), - defaultOptions: { - query: { - errorPolicy: 'all', - }, - mutate: { - errorPolicy: 'all', - }, - }, -}); diff --git a/src/apollo/queries.js b/src/apollo/queries.js deleted file mode 100644 index 138d8a3a49..0000000000 --- a/src/apollo/queries.js +++ /dev/null @@ -1,12 +0,0 @@ -import gql from 'graphql-tag'; - -/** query for obtaining installed and latest Kedro-Viz versions */ -export const GET_VERSIONS = gql` - query getVersion { - version { - installed - isOutdated - latest - } - } -`; diff --git a/src/apollo/schema.graphql b/src/apollo/schema.graphql deleted file mode 100644 index 3aa405737e..0000000000 --- a/src/apollo/schema.graphql +++ /dev/null @@ -1,11 +0,0 @@ -type Query { - """Get the installed and latest Kedro-Viz versions""" - version: Version! -} - -"""Installed and latest Kedro-Viz versions""" -type Version { - installed: String! - isOutdated: Boolean! - latest: String! -} diff --git a/src/apollo/utils.js b/src/apollo/utils.js deleted file mode 100644 index 5236db08bc..0000000000 --- a/src/apollo/utils.js +++ /dev/null @@ -1,20 +0,0 @@ -import { useEffect, useState } from 'react'; -import { useQuery } from '@apollo/client'; - -/** - * @param {Apollo query Object} query - * @param {Apollo query options Object} options - * @returns The data from the query, including error and loading states - */ -export const useApolloQuery = (query, options) => { - const [data, setData] = useState(undefined); - const { data: queryData, error, loading } = useQuery(query, options); - - useEffect(() => { - if (queryData !== undefined) { - setData(queryData); - } - }, [queryData]); - - return { data, error, loading }; -}; diff --git a/src/components/provider/provider.js b/src/components/provider/provider.js deleted file mode 100644 index b4de899803..0000000000 --- a/src/components/provider/provider.js +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react'; -import { ApolloProvider } from '@apollo/client'; -import { client } from '../../apollo/config'; - -export const GraphQLProvider = ({ children }) => { - return ( - - <>{children} - - ); -}; diff --git a/src/components/update-reminder/update-reminder.js b/src/components/update-reminder/update-reminder.js index 951632a061..5aa665232a 100644 --- a/src/components/update-reminder/update-reminder.js +++ b/src/components/update-reminder/update-reminder.js @@ -16,10 +16,10 @@ function replaceBackticksWithCodeBlocks(text) { return text.replace(regex, '$1'); } -const UpdateReminder = ({ isOutdated, versions, visibleMetaSidebar }) => { +const UpdateReminder = ({ isOutdated, version, visibleMetaSidebar }) => { const [dismissed, setDismissed] = useState(false); const [expand, setExpand] = useState(false); - const { latest, installed } = versions; + const { latest, installed } = version; const command = 'pip install -U kedro-viz'; diff --git a/src/components/update-reminder/update-reminder.test.js b/src/components/update-reminder/update-reminder.test.js index f723262999..9fd2e966a0 100644 --- a/src/components/update-reminder/update-reminder.test.js +++ b/src/components/update-reminder/update-reminder.test.js @@ -21,7 +21,7 @@ describe('Update Reminder', () => { const wrapper = setup.mount( ); expect(wrapper.find('.update-reminder-unexpanded').length).toBe(1); @@ -31,7 +31,7 @@ describe('Update Reminder', () => { const wrapper = setup.mount( ); const container = wrapper.find('.update-reminder-unexpanded'); @@ -44,7 +44,7 @@ describe('Update Reminder', () => { const wrapper = setup.mount( ); const container = wrapper.find('.update-reminder-unexpanded'); @@ -56,7 +56,7 @@ describe('Update Reminder', () => { const wrapper = setup.mount( ); wrapper.find('.buttons-container').find('button').at(1).simulate('click'); @@ -69,7 +69,7 @@ describe('Update Reminder', () => { const wrapper = setup.mount( ); expect( @@ -81,7 +81,7 @@ describe('Update Reminder', () => { const wrapper = setup.mount( ); const container = wrapper.find('.update-reminder-unexpanded'); @@ -96,7 +96,7 @@ describe('Update Reminder', () => { const wrapper = setup.mount( ); const container = wrapper.find('.update-reminder-unexpanded'); @@ -110,7 +110,7 @@ describe('Update Reminder', () => { const wrapper = setup.mount( ); const container = wrapper.find('.update-reminder-version-tag--up-to-date'); diff --git a/src/components/wrapper/wrapper.js b/src/components/wrapper/wrapper.js index f37d7927f5..62cc6b0817 100644 --- a/src/components/wrapper/wrapper.js +++ b/src/components/wrapper/wrapper.js @@ -3,11 +3,7 @@ import { connect } from 'react-redux'; import { BrowserRouter as Router, Switch, Route } from 'react-router-dom'; import classnames from 'classnames'; import { isRunningLocally } from '../../utils'; -import { useApolloQuery } from '../../apollo/utils'; -import { client } from '../../apollo/config'; -import { GraphQLProvider } from '../provider/provider'; -import { GET_VERSIONS } from '../../apollo/queries'; - +import { getVersion } from '../../utils'; import FeatureHints from '../feature-hints'; import GlobalToolbar from '../global-toolbar'; import FlowChartWrapper from '../flowchart-wrapper'; @@ -21,19 +17,28 @@ import './wrapper.scss'; * Main app container. Handles showing/hiding the sidebar nav, and theme classes. */ export const Wrapper = ({ displayGlobalNavigation, theme }) => { - const { data: versionData } = useApolloQuery(GET_VERSIONS, { - client, - skip: !displayGlobalNavigation || !isRunningLocally(), - }); const [isOutdated, setIsOutdated] = useState(false); const [latestVersion, setLatestVersion] = useState(null); + const [version, setVersion] = useState(null); useEffect(() => { - if (versionData) { - setIsOutdated(versionData.version.isOutdated); - setLatestVersion(versionData.version.latest); + async function checkKedroVizVersion() { + try { + const request = await getVersion(); + const response = await request.json(); + + if (request.ok) { + setIsOutdated(response.is_outdated); + setLatestVersion(response.latest); + setVersion(response); + } + } catch (error) { + console.error('Error fetching Kedro-Viz version:', error); + } } - }, [versionData]); + + checkKedroVizVersion(); + }, []); return (
{

Kedro-Viz

{displayGlobalNavigation ? ( - + <> {isRunningLocally() ? : null} - {versionData && ( - + {version && ( + )} @@ -64,7 +66,7 @@ export const Wrapper = ({ displayGlobalNavigation, theme }) => { - + ) : ( )} diff --git a/src/utils/index.js b/src/utils/index.js index 2d6a261148..2d60b9dbe5 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -248,6 +248,16 @@ export async function deployViz(inputValues) { return request; } +export async function getVersion() { + const request = await fetch(`${pathRoot}/version`, { + headers: { + 'Content-Type': 'application/json', + Accept: 'application/json', + }, + }); + return request; +} + const nodeTypeMapObj = { nodes: 'task', task: 'nodes',