diff --git a/RELEASE.md b/RELEASE.md
index 8da48896b5..81f3a63be9 100644
--- a/RELEASE.md
+++ b/RELEASE.md
@@ -7,6 +7,11 @@ Please follow the established format:
-->
# Next release
+## Major features and improvements
+- Display published URLs. (#1907)
+
+## Bug fixes and other changes
+
# Release 9.1.0
## Major features and improvements
diff --git a/cypress/support/commands.js b/cypress/support/commands.js
index 27e7878878..a0a38b75ac 100644
--- a/cypress/support/commands.js
+++ b/cypress/support/commands.js
@@ -199,3 +199,39 @@ Cypress.Commands.add('__comparisonMode__', () => {
cy.get(':nth-child(3) > .runs-list-card__checked').click();
cy.wait('@compareThreeRuns').its('response.statusCode').should('eq', 200);
});
+
+/**
+ * Custom command to fillout and submit the hosting shareable URL form
+ */
+Cypress.Commands.add(
+ '__setupAndSubmitShareableUrlForm__',
+ (bucketName, endpointName, primaryButtonNodeText) => {
+ // Intercept the network request to mock with a fixture
+ cy.__interceptRest__(
+ '/api/deploy',
+ 'POST',
+ '/mock/deploySuccessResponse.json'
+ ).as('publishRequest');
+
+ // Reload the page to ensure a fresh state
+ cy.reload();
+
+ // Open the deploy modal
+ cy.get('.pipeline-menu-button--deploy').click();
+
+ // Select the first hosting platform from the dropdown
+ cy.get('.shareable-url-modal [data-test=kedro-pipeline-selector]').click();
+ cy.get('.shareable-url-modal .dropdown__options section div').eq(1).click();
+
+ // Fill in the form
+ cy.get('.shareable-url-modal [data-test="bucket_name"]').type(bucketName);
+ cy.get('.shareable-url-modal [data-test="endpoint_name"]').type(
+ endpointName
+ );
+
+ // Submit the form
+ cy.get('.shareable-url-modal__button-wrapper button')
+ .contains(primaryButtonNodeText)
+ .click();
+ }
+);
diff --git a/cypress/tests/ui/flowchart/shareable-urls.cy.js b/cypress/tests/ui/flowchart/shareable-urls.cy.js
index 68b7185742..76926073db 100644
--- a/cypress/tests/ui/flowchart/shareable-urls.cy.js
+++ b/cypress/tests/ui/flowchart/shareable-urls.cy.js
@@ -1,5 +1,10 @@
-describe('Shareable URLs', () => {
- it('verifies that users can open the Deploy Kedro-Viz modal. #TC-52', () => {
+describe('Shareable URLs with empty localStorage', () => {
+ beforeEach(() => {
+ // Clears localStorage before each test
+ cy.clearLocalStorage();
+ });
+
+ it('verifies that users can open the Deploy Kedro-Viz modal if the localStorage is empty. #TC-52', () => {
// Intercept the network request to mock with a fixture
cy.__interceptRest__(
'/api/package-compatibilities',
@@ -10,7 +15,6 @@ describe('Shareable URLs', () => {
// Action
cy.reload();
cy.get('.pipeline-menu-button--deploy').click({ force: true });
- cy.get('[data-test="disclaimerButton"]').click({ force: true });
// Assert after action
cy.get('.shareable-url-modal .modal__wrapper').contains(
@@ -39,7 +43,6 @@ describe('Shareable URLs', () => {
it('verifies that shareable url modal closes on close button click #TC-54', () => {
// Action
cy.get('.pipeline-menu-button--deploy').click();
- cy.get('[data-test="disclaimerButton"]').click();
cy.get('.shareable-url-modal__button-wrapper button')
.contains('Cancel')
.click();
@@ -56,7 +59,6 @@ describe('Shareable URLs', () => {
// Action
cy.get('.pipeline-menu-button--deploy').click();
- cy.get('[data-test="disclaimerButton"]').click();
cy.get('.shareable-url-modal [data-test=kedro-pipeline-selector]').click();
// Assert after action
@@ -72,13 +74,14 @@ describe('Shareable URLs', () => {
// Action
cy.get('.pipeline-menu-button--deploy').click();
- cy.get('[data-test="disclaimerButton"]').click();
// Assert after action
cy.get(
'.shareable-url-modal [data-test=kedro-pipeline-selector] .dropdown__label span'
).contains(selectedPlatform);
- cy.get('.shareable-url-modal input').should('have.value', '');
+ cy.get(
+ '.shareable-url-modal .shareable-url-modal__input-wrapper input'
+ ).should('have.value', '');
cy.get('.shareable-url-modal__button-wrapper button')
.contains(primaryButtonNodeText)
.should('be.disabled');
@@ -89,14 +92,15 @@ describe('Shareable URLs', () => {
// Action
cy.get('.pipeline-menu-button--deploy').click();
- cy.get('[data-test="disclaimerButton"]').click();
cy.get('.shareable-url-modal [data-test=kedro-pipeline-selector]').click();
cy.get('.shareable-url-modal .dropdown__options section div')
.first()
.click();
// Assert after action
- cy.get('.shareable-url-modal input').should('have.value', '');
+ cy.get(
+ '.shareable-url-modal .shareable-url-modal__input-wrapper input'
+ ).should('have.value', '');
cy.get('.shareable-url-modal__button-wrapper button')
.contains(primaryButtonNodeText)
.should('be.disabled');
@@ -109,7 +113,6 @@ describe('Shareable URLs', () => {
// Action
cy.get('.pipeline-menu-button--deploy').click();
- cy.get('[data-test="disclaimerButton"]').click();
cy.get('.shareable-url-modal [data-test=kedro-pipeline-selector]').click();
cy.get('.shareable-url-modal .dropdown__options section div')
.first()
@@ -131,7 +134,6 @@ describe('Shareable URLs', () => {
const primaryButtonNodeText = 'Publish';
// Action
cy.get('.pipeline-menu-button--deploy').click();
- cy.get('[data-test="disclaimerButton"]').click();
cy.get('.shareable-url-modal [data-test=kedro-pipeline-selector]').click();
cy.get('.shareable-url-modal .dropdown__options section div')
.first()
@@ -165,7 +167,6 @@ describe('Shareable URLs', () => {
// Action
cy.reload();
cy.get('.pipeline-menu-button--deploy').click();
- cy.get('[data-test="disclaimerButton"]').click();
cy.get('.shareable-url-modal [data-test=kedro-pipeline-selector]').click();
cy.get('.shareable-url-modal .dropdown__options section div')
.first()
@@ -181,9 +182,7 @@ describe('Shareable URLs', () => {
// Wait for the POST request to complete and check the mocked response
cy.wait('@publishRequest').then((interception) => {
// Assert after action
- cy.get('.shareable-url-modal__result-url').contains(
- interception.response.body.url
- );
+ cy.get('.url-box__result-url').contains(interception.response.body.url);
});
});
@@ -191,8 +190,6 @@ describe('Shareable URLs', () => {
const bucketName = 'myBucketName';
const endpointName = 'http://www.example.com';
const primaryButtonNodeText = 'Publish';
- const primaryButtonNodeTextVariant = 'Publish';
- const secondaryButtonNodeText = 'Link Settings';
// Intercept the network request to mock with a fixture
cy.__interceptRest__(
@@ -204,7 +201,6 @@ describe('Shareable URLs', () => {
// Action
cy.reload();
cy.get('.pipeline-menu-button--deploy').click();
- cy.get('[data-test="disclaimerButton"]').click();
cy.get('.shareable-url-modal [data-test=kedro-pipeline-selector]').click();
cy.get('.shareable-url-modal .dropdown__options section div')
.first()
@@ -217,23 +213,91 @@ describe('Shareable URLs', () => {
.contains(primaryButtonNodeText)
.click();
- // Wait for the POST request to complete
- cy.wait('@publishRequest');
-
- // Action
- cy.get('.shareable-url-modal__button-wrapper button')
- .contains(secondaryButtonNodeText)
- .click();
- cy.get('.shareable-url-modal__button-wrapper button')
- .contains(primaryButtonNodeTextVariant)
- .click();
-
// Wait for the POST request to complete and check the mocked response
cy.wait('@publishRequest').then((interception) => {
// Assert after action
- cy.get('.shareable-url-modal__result-url').contains(
- interception.response.body.url
+ cy.get('.url-box__result-url').contains(interception.response.body.url);
+ });
+ });
+});
+
+describe('Shareable URLs with valid localStorage', () => {
+ const bucketName = 'myBucketName';
+ const endpointName = 'http://www.example.com';
+ const secondBucketName = 'mySecondBucketName';
+ const secondEndpointName = 'http://www.exampleNumber2.com';
+
+ it('verifies that users can open the Published Content Kedro-Viz modal with valid URL after published it succesfully. #TC-XX', () => {
+ cy.__setupAndSubmitShareableUrlForm__(bucketName, endpointName, 'Publish');
+
+ // Wait for the POST request to complete
+ cy.wait('@publishRequest').then(() => {
+ // Close the modal once it publishes succesfully
+ cy.get('body').click(0, 0);
+
+ // Open the deploy modal again
+ cy.get('.pipeline-menu-button--deploy').click();
+ cy.get('.shareable-url-modal .modal__wrapper').contains(
+ `Publish and Share Kedro-Viz`
);
+ cy.get('.url-box__result-url').contains(endpointName);
+ });
+ });
+
+ it('verifies that after published to more than one platform, users can open the Published Content Kedro-Viz modal to select on different option. #TC-XX1', () => {
+ const fillFormAndSubmit = (bucketName, endpointName) => {
+ cy.get('.shareable-url-modal [data-test="bucket_name"]').clear();
+ cy.get('.shareable-url-modal [data-test="bucket_name"]').type(bucketName);
+ cy.get('.shareable-url-modal [data-test="endpoint_name"]').clear();
+ cy.get('.shareable-url-modal [data-test="endpoint_name"]').type(
+ endpointName
+ );
+ cy.get('.shareable-url-modal__button-wrapper button')
+ .contains('Publish')
+ .click();
+ };
+
+ const selectHostingPlatform = (index) => {
+ cy.get(
+ '.shareable-url-modal [data-test=kedro-pipeline-selector]'
+ ).click();
+ cy.get('.shareable-url-modal .dropdown__options section div')
+ .eq(index)
+ .click();
+ };
+
+ cy.__setupAndSubmitShareableUrlForm__(bucketName, endpointName, 'Publish');
+
+ // Wait for the POST request to complete
+ cy.wait('@publishRequest').then(() => {
+ // Close the modal once it publishes successfully
+ cy.get('body').click(0, 0);
+ // Open the deploy modal again
+ cy.get('.pipeline-menu-button--deploy').click();
+ cy.get('.shareable-url-modal__published-action button').click();
+
+ // Select the second hosting platform from the dropdown
+ selectHostingPlatform(2);
+
+ // Fill in the form with second option
+ fillFormAndSubmit(secondBucketName, secondEndpointName);
+
+ // Close the modal once it publishes successfully
+ cy.get('body').click(0, 0);
+
+ cy.get('.pipeline-menu-button--deploy').click();
+
+ cy.get(
+ '.shareable-url-modal__published-dropdown-wrapper [data-test=kedro-pipeline-selector]'
+ ).click();
+
+ cy.get(
+ '.shareable-url-modal__published-dropdown-wrapper .dropdown__options section div'
+ )
+ .eq(1)
+ .click();
+
+ cy.get('.url-box__result-url').contains(secondEndpointName);
});
});
});
diff --git a/src/components/icons/info.js b/src/components/icons/info.js
new file mode 100644
index 0000000000..3c822d98a9
--- /dev/null
+++ b/src/components/icons/info.js
@@ -0,0 +1,9 @@
+import React from 'react';
+
+const InfoIcon = ({ className }) => (
+
+
+
+);
+
+export default InfoIcon;
diff --git a/src/components/shareable-url-modal/compatibility-error-view/compatibility-error-view.js b/src/components/shareable-url-modal/compatibility-error-view/compatibility-error-view.js
new file mode 100644
index 0000000000..d728f9787f
--- /dev/null
+++ b/src/components/shareable-url-modal/compatibility-error-view/compatibility-error-view.js
@@ -0,0 +1,19 @@
+import React from 'react';
+import Button from '../../ui/button';
+
+const CompatibilityErrorView = ({ onClick }) => (
+
+);
+
+export default CompatibilityErrorView;
diff --git a/src/components/shareable-url-modal/error-view/error-view.js b/src/components/shareable-url-modal/error-view/error-view.js
new file mode 100644
index 0000000000..cf36677301
--- /dev/null
+++ b/src/components/shareable-url-modal/error-view/error-view.js
@@ -0,0 +1,13 @@
+import React from 'react';
+import Button from '../../ui/button';
+
+const ErrorView = ({ onClick, responseError }) => (
+
+
Error message: {responseError}
+
+ Go back
+
+
+);
+
+export default ErrorView;
diff --git a/src/components/shareable-url-modal/loading-view/loading-view.js b/src/components/shareable-url-modal/loading-view/loading-view.js
new file mode 100644
index 0000000000..967366ae7e
--- /dev/null
+++ b/src/components/shareable-url-modal/loading-view/loading-view.js
@@ -0,0 +1,10 @@
+import React from 'react';
+import LoadingIcon from '../../icons/loading';
+
+const LoadingView = ({ isLoading }) => (
+
+
+
+);
+
+export default LoadingView;
diff --git a/src/components/shareable-url-modal/main-view/main-view.js b/src/components/shareable-url-modal/main-view/main-view.js
new file mode 100644
index 0000000000..c0c9fadfe2
--- /dev/null
+++ b/src/components/shareable-url-modal/main-view/main-view.js
@@ -0,0 +1,190 @@
+/* eslint-disable camelcase */
+import React from 'react';
+import classnames from 'classnames';
+import Dropdown from '../../ui/dropdown';
+import Button from '../../ui/button';
+import IconButton from '../../ui/icon-button';
+import InfoIcon from '../../icons/info';
+import Input from '../../ui/input';
+import MenuOption from '../../ui/menu-option';
+import Toggle from '../../ui/toggle';
+import {
+ hostingPlatforms,
+ KEDRO_VIZ_PUBLISH_AWS_DOCS_URL,
+ KEDRO_VIZ_PUBLISH_AZURE_DOCS_URL,
+ KEDRO_VIZ_PUBLISH_GCP_DOCS_URL,
+ KEDRO_VIZ_PUBLISH_DOCS_URL,
+} from '../../../config';
+
+const renderTextContent = (isPreviewEnabled, setIsPreviewEnabled) => {
+ return (
+
+
+ Publish and Share Kedro-Viz
+
+
+ Prerequisites{' '}
+
+
+ Deploying and hosting Kedro-Viz requires access keys or user
+ credentials, depending on the chosen service provider. To use this
+ feature, please add your access keys or credentials as environment
+ variables in your project. More information can be found in the{' '}
+
+ documentation
+
+ .
+
+
+ Disclaimer{' '}
+
+
+ Disclaimer Kedro-Viz contains preview data for multiple datasets. You
+ can enable or disable all previews when publishing Kedro-Viz.
+
+
+ All dataset previews
+ setIsPreviewEnabled((prev) => !prev)}
+ />
+
+
+ );
+};
+
+const MainView = ({
+ handleModalClose,
+ handleSubmit,
+ inputValues,
+ isFormDirty,
+ onPlatformChange,
+ onBuckNameChange,
+ onEndpointChange,
+ setIsPreviewEnabled,
+ isPreviewEnabled,
+ visible,
+}) => {
+ const { platform, bucket_name, endpoint } = inputValues || {};
+
+ return (
+ <>
+
+ {renderTextContent(isPreviewEnabled, setIsPreviewEnabled)}
+
+
+ Please enter the required information below.
+
+
+
+ Hosting platform
+
+
+ {Object.entries(hostingPlatforms).map(([value, label]) => (
+
+ ))}
+
+
+
+
+
+
+ Endpoint URL
+
+
+ The endpoint URL is the link to where your Kedro-Viz will be
+ hosted. For information on obtaining the endpoint URL,
+ please refer to the documentation for{' '}
+
+ AWS
+
+ ,{' '}
+
+ Azure
+
+ ,{' '}
+
+ GCP
+
+
+ }
+ icon={InfoIcon}
+ />
+
+
+
+
+
+
+
+ Cancel
+
+ value)}
+ size="small"
+ onClick={handleSubmit}
+ >
+ Publish
+
+
+ >
+ );
+};
+
+export default MainView;
diff --git a/src/components/shareable-url-modal/published-view/published-view.js b/src/components/shareable-url-modal/published-view/published-view.js
new file mode 100644
index 0000000000..dc4d93f13e
--- /dev/null
+++ b/src/components/shareable-url-modal/published-view/published-view.js
@@ -0,0 +1,98 @@
+import React from 'react';
+import classnames from 'classnames';
+import UrlBox from '../url-box/url-box';
+import Button from '../../ui/button';
+import Dropdown from '../../ui/dropdown';
+import MenuOption from '../../ui/menu-option';
+
+import { getFilteredPlatforms, handleResponseUrl } from '../utils';
+
+const PublishedView = ({
+ hostingPlatformLocalStorageVal,
+ hostingPlatforms,
+ onChange,
+ onCopyClick,
+ onRepublishClick,
+ platform,
+ showCopied,
+}) => {
+ const platformsKeysFromLocalStorage = Object.keys(
+ hostingPlatformLocalStorageVal
+ );
+ const platformsValFromLocalStorage = Object.values(
+ hostingPlatformLocalStorageVal
+ );
+
+ const url = platform
+ ? hostingPlatformLocalStorageVal[platform]['endpoint']
+ : platformsValFromLocalStorage[0]['endpoint'];
+
+ const filteredPlatforms = getFilteredPlatforms(
+ hostingPlatforms,
+ platformsKeysFromLocalStorage
+ );
+
+ const href = handleResponseUrl(
+ url,
+ platform || platformsValFromLocalStorage[0]['platform']
+ );
+
+ return (
+ <>
+
+
+ Publish and Share Kedro-Viz
+
+ {platformsKeysFromLocalStorage.length === 1 ? (
+
+ ) : (
+
+
+ {Object.entries(filteredPlatforms).map(([value, label]) => (
+
+ ))}
+
+
+
+ )}
+
+
+
+ Republish Kedro-Viz to push new updates,
+
+ or publish and host Kedro-Viz with a new link.
+
+
+ Republish
+
+
+ >
+ );
+};
+
+export default PublishedView;
diff --git a/src/components/shareable-url-modal/shareable-url-modal.js b/src/components/shareable-url-modal/shareable-url-modal.js
index 9d58bee238..f65cdd07af 100644
--- a/src/components/shareable-url-modal/shareable-url-modal.js
+++ b/src/components/shareable-url-modal/shareable-url-modal.js
@@ -4,40 +4,26 @@ import { connect } from 'react-redux';
import classnames from 'classnames';
import { toggleShareableUrlModal } from '../../actions';
import { fetchPackageCompatibilities } from '../../utils';
+import { saveLocalStorage, loadLocalStorage } from '../../store/helpers';
import {
- hostingPlatform,
+ hostingPlatforms,
inputKeyToStateKeyMap,
- KEDRO_VIZ_PUBLISH_DOCS_URL,
- KEDRO_VIZ_PREVIEW_DATASETS_DOCS_URL,
- KEDRO_VIZ_PUBLISH_AWS_DOCS_URL,
- KEDRO_VIZ_PUBLISH_AZURE_DOCS_URL,
- KEDRO_VIZ_PUBLISH_GCP_DOCS_URL,
+ localStorageShareableUrl,
PACKAGE_FSSPEC,
+ shareableUrlMessages,
} from '../../config';
-
-import Button from '../ui/button';
-import CopyIcon from '../icons/copy';
-import Dropdown from '../ui/dropdown';
-import IconButton from '../ui/icon-button';
-import Input from '../ui/input';
-import LoadingIcon from '../icons/loading';
import Modal from '../ui/modal';
-import MenuOption from '../ui/menu-option';
-import Tooltip from '../ui/tooltip';
-import './shareable-url-modal.scss';
+import PublishedView from './published-view/published-view';
+import CompatibilityErrorView from './compatibility-error-view/compatibility-error-view';
+import MainView from './main-view/main-view';
+import LoadingView from './loading-view/loading-view';
+import ErrorView from './error-view/error-view';
+import SuccessView from './success-view/success-view';
+import { getDeploymentStateByType, handleResponseUrl } from './utils';
+import { deployViz } from '../../utils';
-const modalMessages = (status, info = '') => {
- const messages = {
- failure: 'Something went wrong. Please try again later.',
- loading: 'Shooting your files through space. Sit tight...',
- success:
- 'The current version of Kedro-Viz has been published and hosted via the link below.',
- incompatible: `Publishing Kedro-Viz is only supported with fsspec>=2023.9.0. You are currently on version ${info}.\n\nPlease upgrade fsspec to a supported version and ensure you're using Kedro 0.18.2 or above.`,
- };
-
- return messages[status];
-};
+import './shareable-url-modal.scss';
const ShareableUrlModal = ({ onToggleModal, visible }) => {
const [deploymentState, setDeploymentState] = useState('default');
@@ -52,8 +38,12 @@ const ShareableUrlModal = ({ onToggleModal, visible }) => {
const [responseError, setResponseError] = useState(null);
const [showCopied, setShowCopied] = useState(false);
const [compatibilityData, setCompatibilityData] = useState({});
- const [canUseShareableUrls, setCanUseShareableUrls] = useState(true);
- const [isDisclaimerViewed, setIsDisclaimerViewed] = useState(false);
+ const [isCompatible, setIsCompatible] = useState(true);
+ const [showPublishedView, setShowPublishedView] = useState(false);
+ const [hostingPlatformLocalStorageVal, setHostingPlatformLocalStorageVal] =
+ useState(loadLocalStorage(localStorageShareableUrl) || {});
+ const [publishedPlatformKey, setPublishedPlatformKey] = useState(undefined);
+ const [isPreviewEnabled, setIsPreviewEnabled] = useState(true);
useEffect(() => {
async function fetchPackageCompatibility() {
@@ -66,7 +56,7 @@ const ShareableUrlModal = ({ onToggleModal, visible }) => {
(pckg) => pckg.package_name === PACKAGE_FSSPEC
);
setCompatibilityData(fsspecPackage);
- setCanUseShareableUrls(fsspecPackage?.is_compatible || false);
+ setIsCompatible(fsspecPackage?.is_compatible || false);
// User's fsspec package version isn't compatible, so set
// the necessary state to reflect that in the UI.
@@ -82,6 +72,38 @@ const ShareableUrlModal = ({ onToggleModal, visible }) => {
fetchPackageCompatibility();
}, []);
+ const setStateForPublishedView = () => {
+ if (Object.keys(hostingPlatformLocalStorageVal).length > 0) {
+ setDeploymentState('published');
+ setShowPublishedView(true);
+ // set the publishedPlatformKey as the first one from localStorage by default
+ setPublishedPlatformKey(Object.keys(hostingPlatformLocalStorageVal)[0]);
+ }
+ };
+
+ const setStateForMainViewWithPublishedContent = () => {
+ if (Object.keys(hostingPlatformLocalStorageVal).length > 0) {
+ setShowPublishedView(false);
+ setDeploymentState('default');
+
+ const populatedContent =
+ hostingPlatformLocalStorageVal[publishedPlatformKey];
+
+ setInputValues(populatedContent);
+
+ setIsFormDirty({
+ hasBucketName: true,
+ hasPlatform: true,
+ hasEndpoint: true,
+ });
+ }
+ };
+
+ useEffect(() => {
+ setStateForPublishedView();
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+
const onChange = (key, value) => {
setIsFormDirty((prevState) => ({
...prevState,
@@ -94,23 +116,68 @@ const ShareableUrlModal = ({ onToggleModal, visible }) => {
);
};
+ const updateFormWithLocalStorageData = (platformKey) => {
+ // if the selected platform is stored in localStorage, populate the form with the stored data
+ if (hostingPlatformLocalStorageVal[platformKey]) {
+ const populatedContent = hostingPlatformLocalStorageVal[platformKey];
+
+ setInputValues(populatedContent);
+ setIsFormDirty({
+ hasBucketName: true,
+ hasPlatform: true,
+ hasEndpoint: true,
+ });
+ } else {
+ // if not, only set the platform and reset the rest
+ const emptyContent = {
+ platform: platformKey,
+ bucket_name: '',
+ endpoint: '',
+ };
+ setInputValues(emptyContent);
+ setIsFormDirty({
+ hasBucketName: false,
+ hasPlatform: true,
+ hasEndpoint: false,
+ });
+ }
+ };
+
+ const updateLocalStorageState = () => {
+ const selectedHostingPlatformVal = {};
+ if (hostingPlatforms.hasOwnProperty(inputValues.platform)) {
+ selectedHostingPlatformVal[inputValues.platform] = { ...inputValues };
+ }
+ saveLocalStorage(localStorageShareableUrl, selectedHostingPlatformVal);
+
+ // filtering out the pairs where the key is in selectedHostingPlatformVal
+ const localStorageExcludingSelectedPlatform = Object.fromEntries(
+ Object.entries(hostingPlatformLocalStorageVal).filter(
+ ([key]) => !(key in selectedHostingPlatformVal)
+ )
+ );
+
+ // set the new state with selectedHostingPlatformVal as the first value and localStorageExcludingSelectedPlatform
+ const newState = {
+ ...selectedHostingPlatformVal,
+ ...localStorageExcludingSelectedPlatform,
+ };
+ setHostingPlatformLocalStorageVal(newState);
+ };
+
const handleSubmit = async () => {
setDeploymentState('loading');
setIsLoading(true);
+ setShowPublishedView(false);
try {
- const request = await fetch('/api/deploy', {
- headers: {
- 'Content-Type': 'application/json',
- },
- method: 'POST',
- body: JSON.stringify(inputValues),
- });
+ const request = await deployViz(inputValues);
const response = await request.json();
if (request.ok) {
setResponseUrl(response.url);
setDeploymentState('success');
+ updateLocalStorageState();
} else {
setResponseUrl(null);
setResponseError(response.message || 'Error occurred!');
@@ -125,8 +192,8 @@ const ShareableUrlModal = ({ onToggleModal, visible }) => {
}
};
- const onCopyClick = () => {
- window.navigator.clipboard.writeText(responseUrl);
+ const onCopyClick = (url) => {
+ window.navigator.clipboard.writeText(url);
setShowCopied(true);
setTimeout(() => {
@@ -137,13 +204,20 @@ const ShareableUrlModal = ({ onToggleModal, visible }) => {
const handleModalClose = () => {
onToggleModal(false);
if (deploymentState !== 'incompatible') {
- setDeploymentState('default');
+ // reset the state to default as long as the user's fsspec package version is compatible
+ // and there are nothing stored in localStorage
+ if (Object.keys(hostingPlatformLocalStorageVal).length === 0) {
+ setDeploymentState('default');
+ }
+
+ // if there are items stored in localStorage, display the published view
+ setStateForPublishedView();
}
+
setResponseError(null);
setIsLoading(false);
setResponseUrl(null);
setInputValues({});
- setIsDisclaimerViewed(false);
setIsFormDirty({
hasBucketName: false,
hasPlatform: false,
@@ -151,333 +225,85 @@ const ShareableUrlModal = ({ onToggleModal, visible }) => {
});
};
- const getDeploymentStateByType = (type) => {
- if (deploymentState === 'default') {
- return null;
- }
-
- if (type === 'title') {
- return deploymentState === 'success'
- ? 'Kedro-Viz Published and Hosted'
- : 'Publish and Share Kedro-Viz';
- }
-
- return modalMessages(deploymentState, compatibilityData.package_version);
- };
-
- const handleResponseUrl = () => {
- // If the URL does not start with http:// or https://, append http:// to avoid relative path issue for GCP platform.
- if (!/^https?:\/\//.test(responseUrl) && inputValues.platform === 'gcp') {
- const url = 'http://' + responseUrl;
- return url;
- }
- return responseUrl;
- };
-
- const clearDisclaimerMessage = () => setIsDisclaimerViewed(true);
-
- const renderCompatibilityMessage = () => {
- return !canUseShareableUrls ? (
-
- ) : null;
- };
-
- const renderSuccessContent = () => {
- return responseUrl ? (
- <>
-
-
- {
- setDeploymentState('default');
- setIsLoading(false);
- setResponseUrl(null);
- }}
- size="small"
- >
- Link Settings
-
- handleModalClose()}
- size="small"
- >
- Close
-
-
- >
- ) : null;
- };
-
- const renderErrorContent = () => {
- return responseError ? (
-
-
Error message: {responseError}
-
{
- setDeploymentState('default');
- setIsLoading(false);
- setResponseUrl(null);
- setResponseError(null);
- }}
- size="small"
- >
- Go back
-
-
- ) : null;
- };
-
- const renderDisclaimerContent = () => {
- return (
-
-
- Disclaimer: Please note that Kedro-Viz contains preview data for
- multiple datasets. If you wish to disable the preview when publishing
- Kedro-Viz, please refer to{' '}
-
- the documentation
- {' '}
- on how to do so.
-
-
- handleModalClose()}
- size="small"
- >
- Cancel
-
-
- Continue
-
-
-
- );
- };
-
- const renderTextContent = () => {
- return (
-
-
- Publish and Share Kedro-Viz
-
-
- Prerequisite: Deploying and hosting Kedro-Viz requires access keys or
- user credentials, depending on the chosen cloud provider. To use this
- feature, please add your access keys or credentials as environment
- variables in your Kedro project. More information can be found in{' '}
-
- docs
-
- .
-
-
- Enter the required information and a hosted link will be generated.
-
-
- For more information on obtaining the Endpoint URL, refer to{' '}
-
- AWS
-
- ,{' '}
-
- Azure
- {' '}
- and{' '}
-
- GCP
- {' '}
- docs.
-
-
- );
- };
-
- const renderLoadingContent = () => {
- return isLoading ? (
-
-
-
- ) : null;
- };
-
- const renderMainContent = () => {
- return !isLoading &&
- !responseUrl &&
- canUseShareableUrls &&
- !responseError ? (
- <>
-
- {renderTextContent()}
-
-
-
- Hosting platform
-
-
{
- onChange('platform', selectedPlatform.value);
- }}
- width={null}
- >
- {Object.entries(hostingPlatform).map(([value, label]) => (
-
- ))}
-
-
-
-
- Bucket Name
-
-
onChange('bucket_name', value)}
- placeholder="Enter name"
- resetValueTrigger={visible}
- size="small"
- type="input"
- dataTest={'bucket_name'}
- />
-
-
-
- Endpoint Link
-
-
onChange('endpoint', value)}
- placeholder="Enter url"
- resetValueTrigger={visible}
- size="small"
- type="input"
- dataTest={'endpoint_name'}
- />
-
-
-
-
- handleModalClose()}
- size="small"
- >
- Cancel
-
- value)}
- size="small"
- onClick={handleSubmit}
- >
- Publish
-
-
- >
- ) : null;
- };
-
- const { platform, bucket_name, endpoint } = inputValues || {};
+ const { platform } = inputValues || {};
return (
- {renderCompatibilityMessage()}
- {!isDisclaimerViewed && canUseShareableUrls ? (
- renderDisclaimerContent()
+ {!isCompatible ? (
+
+ ) : showPublishedView ? (
+ {
+ onChange('platform', selectedPlatform.value);
+ setPublishedPlatformKey(selectedPlatform.value);
+ }}
+ onCopyClick={onCopyClick}
+ onRepublishClick={setStateForMainViewWithPublishedContent}
+ platform={platform}
+ showCopied={showCopied}
+ />
) : (
<>
- {renderMainContent()}
- {renderLoadingContent()}
- {renderErrorContent()}
- {renderSuccessContent()}
+ {!isLoading && !responseUrl && !responseError && (
+ {
+ updateFormWithLocalStorageData(selectedPlatform.value);
+ }}
+ onBuckNameChange={(value) => onChange('bucket_name', value)}
+ onEndpointChange={(value) => onChange('endpoint', value)}
+ setIsPreviewEnabled={setIsPreviewEnabled}
+ isPreviewEnabled={isPreviewEnabled}
+ visible={visible}
+ />
+ )}
+ {isLoading && }
+ {responseError && (
+ {
+ setDeploymentState('default');
+ setIsLoading(false);
+ setResponseUrl(null);
+ setResponseError(null);
+ }}
+ responseError={responseError}
+ />
+ )}
+ {responseUrl && (
+
+ )}
>
)}
diff --git a/src/components/shareable-url-modal/shareable-url-modal.scss b/src/components/shareable-url-modal/shareable-url-modal.scss
index a03602afcc..337250382e 100644
--- a/src/components/shareable-url-modal/shareable-url-modal.scss
+++ b/src/components/shareable-url-modal/shareable-url-modal.scss
@@ -3,25 +3,33 @@
.kui-theme--light {
--color-description-text: #{variables.$black-300};
--color-deployed-link: #{variables.$black-900};
+ --color-preview-data-text: #{variables.$black-900};
--color-modal-bg-1: #{variables.$grey-100};
--color-modal-bg-2: #{variables.$grey-0};
--color-footer-border: #{variables.$grey-300};
--color-text-doc-link: #{variables.$black-800};
- --color-form-label: #{variables.$grey-700};
+ --color-form-label: #{variables.$black-200};
--input-bg: #{variables.$white-100};
--input-shadow: #{variables.$white-800};
+ --color-btn-bg: #{variables.$white-500};
+ --dropdown-bg: #{variables.$white-0};
+ --dropdown-bg-hovered: #{variables.$white-200};
}
.kui-theme--dark {
--color-description-text: #{variables.$white-900};
--color-deployed-link: #{variables.$white-0};
+ --color-preview-data-text: #{variables.$white-0};
--color-modal-bg-1: #{variables.$slate-300};
--color-modal-bg-2: #{variables.$slate-100};
--color-footer-border: #{variables.$black-600};
--color-text-doc-link: #{variables.$white-600};
- --color-form-label: #{variables.$grey-500};
+ --color-form-label: #{variables.$black-0};
--input-bg: #{variables.$slate-700};
--input-shadow: #{variables.$slate-900};
+ --color-btn-bg: #{variables.$black-600};
+ --dropdown-bg: #{variables.$slate-600};
+ --dropdown-bg-hovered: #{variables.$slate-400};
}
.shareable-url-modal {
@@ -36,7 +44,7 @@
}
.modal__title {
- margin-bottom: 24px;
+ margin: 0 0 24px;
}
.modal__description {
@@ -71,7 +79,17 @@
display: flex;
}
+ &__form-wrapper-title {
+ font-size: 14px;
+ font-style: normal;
+ font-weight: 400;
+ line-height: 20px;
+ margin-bottom: 32px;
+ margin-top: 0;
+ }
+
&__content-title {
+ color: var(--color-deployed-link);
font-size: 20px;
font-style: normal;
font-weight: 400;
@@ -80,6 +98,7 @@
}
&__content-description {
+ color: var(--color-description-text);
font-size: 14px;
font-style: normal;
font-weight: 400;
@@ -91,29 +110,58 @@
}
}
+ &__content-description-title {
+ color: var(--color-deployed-link);
+ font-size: 14px;
+ font-style: normal;
+ font-weight: 400;
+ line-height: 20px;
+ margin-top: 0;
+ }
+
+ &__content-description-title--disclaimer::before {
+ background: var(--color-divider);
+ content: '';
+ display: block;
+ height: 1px;
+ margin: 24px 0;
+ }
+
+ &__content-preview-dataset {
+ align-items: center;
+ color: var(--color-preview-data-text);
+ display: flex;
+ font-size: 14px;
+ font-style: normal;
+ font-weight: 400;
+ justify-content: space-between;
+ line-height: 20px;
+ margin-top: 12px;
+ }
+
+ &__content-toggle {
+ margin: 0;
+ }
+
&__content-note {
- margin-top: 18px;
color: var(--color-form-label);
+ margin-top: 18px;
a {
color: var(--color-form-label);
}
}
- &__paregraph-divider {
- margin-bottom: 30px;
- }
-
&__content-wrapper {
- padding: 48px;
background: var(--color-modal-bg-2);
box-shadow: 0 0 16px 0 rgb(0 0 0 / 10%);
+ padding: 48px;
}
&__form-wrapper {
- padding: 48px;
background: var(--color-modal-bg-1);
box-shadow: inset 0 0 16px 0 rgb(0 0 0 / 10%);
+ padding: 48px;
}
&__input-wrapper {
@@ -137,6 +185,11 @@
text-decoration: none;
border: 1px solid variables.$blue-300;
}
+
+ &::placeholder {
+ color: #{variables.$black-500};
+ opacity: 1;
+ }
}
}
@@ -148,6 +201,56 @@
color: var(--color-form-label);
}
+ &__endpoint-url-wrapper {
+ align-items: center;
+ display: flex;
+ justify-content: space-between;
+
+ .pipeline-icon--container {
+ list-style-type: none;
+ }
+
+ .shareable-url-modal__input-label {
+ margin-bottom: 0;
+ }
+
+ .shareable-url-modal__input-label-text {
+ color: inherit;
+ }
+
+ .shareable-url-modal__information-icon {
+ height: 40px;
+
+ .pipeline-icon {
+ right: 0;
+ fill: #{variables.$black-0};
+ }
+
+ .pipeline-toolbar__label__visible {
+ max-width: 180px;
+ text-align: left;
+ white-space: inherit;
+ width: 180px;
+
+ &::after {
+ top: 5%;
+ }
+ }
+
+ .pipeline-toolbar__label-right {
+ margin-left: 2em;
+
+ .shareable-url-modal__information-text {
+ font-size: 12px;
+ font-style: normal;
+ font-weight: 400;
+ line-height: 16px;
+ margin: 0;
+ }
+ }
+ }
+ }
+
&__button-wrapper {
align-items: baseline;
display: flex;
@@ -180,21 +283,6 @@
width: 100%;
}
- &__result {
- margin-bottom: 48px;
- width: 100%;
-
- .toolbox {
- margin: 0 4px 0 20px;
- }
-
- .pipeline-icon {
- position: relative;
- top: unset;
- right: unset;
- }
- }
-
&__error {
display: flex;
flex-direction: column;
@@ -210,7 +298,7 @@
}
&__url-wrapper {
- background: rgb(255 255 255 / 4%);
+ background-color: #{variables.$slate-700};
display: flex;
height: 50px;
justify-content: space-between;
@@ -273,6 +361,99 @@
.menu-option__content {
font-size: 14px;
}
+
+ .dropdown__placeholder {
+ color: #{variables.$black-500};
+ }
+}
+
+.shareable-url-modal__published-wrapper {
+ .modal__wrapper {
+ padding: 0;
+ width: 707px;
+ }
+}
+
+.shareable-url-modal__published {
+ background-color: var(--color-modal-bg-1);
+ padding: 48px;
+ width: 100%;
+
+ .shareable-url-modal__content-title {
+ padding-bottom: 32px;
+ }
+}
+
+.shareable-url-modal__published-dropdown-wrapper {
+ display: flex;
+ flex-direction: row;
+
+ .dropdown__label {
+ height: 100%;
+ width: 209px;
+ padding-left: 20px;
+ }
+}
+
+// override the dropdown styles for both mainView and publishedView
+.shareable-url-modal__input-wrapper,
+.shareable-url-modal__published-dropdown-wrapper {
+ .menu-option {
+ background-color: var(--dropdown-bg);
+ padding-left: 20px;
+
+ &:hover {
+ background-color: var(--dropdown-bg-hovered);
+ }
+ }
+
+ .dropdown__options {
+ box-shadow: none;
+ }
+}
+
+.shareable-url-modal__published-action {
+ align-items: baseline;
+ background: var(--color-modal-bg-1);
+ border-top: 1px solid var(--color-footer-border);
+ display: flex;
+ justify-content: space-between;
+ padding: 48px 48px 56px;
+ width: 100%;
+
+ .button__btn--secondary {
+ background-color: var(--color-btn-bg);
+ }
+
+ .shareable-url-modal__published-action-text {
+ font-size: 16px;
+ font-weight: 400;
+ line-height: 24px;
+ width: 424px;
+ }
+}
+
+.shareable-url-modal__success-wrapper {
+ .modal__content {
+ background-color: var(--color-modal-bg-1);
+ }
+
+ .modal__wrapper {
+ width: 642px;
+ padding: 48px;
+ }
+
+ .modal__description {
+ margin-top: 0;
+ }
+
+ .shareable-url-modal__result {
+ width: 100%;
+
+ .url-box__result-url-wrapper {
+ width: 440px;
+ }
+ }
}
.shareable-url-timestamp {
diff --git a/src/components/shareable-url-modal/shareable-url-modal.test.js b/src/components/shareable-url-modal/shareable-url-modal.test.js
index aaa85a2905..44cb4ab0a5 100644
--- a/src/components/shareable-url-modal/shareable-url-modal.test.js
+++ b/src/components/shareable-url-modal/shareable-url-modal.test.js
@@ -5,7 +5,6 @@ import { setup } from '../../utils/state.mock';
describe('ShareableUrlModal', () => {
it('renders without crashing', () => {
const wrapper = setup.mount( );
- wrapper.find('[data-test="disclaimerButton"]').simulate('click');
- expect(wrapper.find('.shareable-url-modal__input-wrapper').length).toBe(3);
+ expect(wrapper.exists()).toBe(true);
});
});
diff --git a/src/components/shareable-url-modal/success-view/success-view.js b/src/components/shareable-url-modal/success-view/success-view.js
new file mode 100644
index 0000000000..e2217d2999
--- /dev/null
+++ b/src/components/shareable-url-modal/success-view/success-view.js
@@ -0,0 +1,22 @@
+import React from 'react';
+import UrlBox from '../url-box/url-box';
+
+const SuccessView = ({
+ handleResponseUrl,
+ onClick,
+ responseUrl,
+ showCopied,
+}) => {
+ return responseUrl ? (
+
+
+
+ ) : null;
+};
+
+export default SuccessView;
diff --git a/src/components/shareable-url-modal/url-box/url-box.js b/src/components/shareable-url-modal/url-box/url-box.js
new file mode 100644
index 0000000000..7c473aa8ee
--- /dev/null
+++ b/src/components/shareable-url-modal/url-box/url-box.js
@@ -0,0 +1,49 @@
+import React from 'react';
+import classnames from 'classnames';
+import Tooltip from '../../ui/tooltip';
+import Button from '../../ui/button';
+
+import './url-box.scss';
+
+const UrlBox = ({ className, url, onCopyClick, href, showCopiedText }) => (
+
+
+ {window.navigator.clipboard && (
+
+ onCopyClick(url)}
+ size="small"
+ dataHeapEvent={`clicked.run_command`}
+ >
+ Copy link
+
+
+
+ )}
+
+);
+
+export default UrlBox;
diff --git a/src/components/shareable-url-modal/url-box/url-box.scss b/src/components/shareable-url-modal/url-box/url-box.scss
new file mode 100644
index 0000000000..b16547f433
--- /dev/null
+++ b/src/components/shareable-url-modal/url-box/url-box.scss
@@ -0,0 +1,81 @@
+@use '../../../styles/variables' as variables;
+
+.kui-theme--light {
+ --color-link: #{variables.$black-900};
+ --color-divider: #{variables.$white-900};
+ --color-bg: #{variables.$white-100};
+ --color-bg-copied: #{variables.$black-900};
+ --color-text-copied: #{variables.$white-0};
+}
+
+.kui-theme--dark {
+ --color-link: #{variables.$white-0};
+ --color-divider: #{variables.$black-500};
+ --color-bg: #{variables.$slate-700};
+ --color-bg-copied: #{variables.$white-0};
+ --color-text-copied: #{variables.$black-900};
+}
+
+.url-box__wrapper {
+ background-color: var(--color-bg);
+ display: flex;
+ height: 50px;
+ justify-content: space-between;
+ text-overflow: ellipsis;
+ width: 100%;
+}
+
+.url-box__wrapper--half-width {
+ border-left: 1px solid var(--color-divider);
+
+ .url-box__result-url-wrapper {
+ width: 300px;
+ }
+}
+
+.url-box__result-url-wrapper {
+ align-items: center;
+ display: flex;
+ max-width: 510px;
+ width: 100%;
+}
+
+.url-box__result-url {
+ align-items: center;
+ color: var(--color-link);
+ cursor: pointer;
+ display: block;
+ font-family: inherit;
+ font-size: 16px;
+ line-height: 32px;
+ overflow: hidden;
+ padding-left: 16px;
+ text-decoration: underline;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ width: 90%;
+}
+
+.url-box___button {
+ align-items: center;
+ display: flex;
+ position: relative;
+
+ .button__btn {
+ width: 100px;
+ padding: 13px 0;
+ }
+
+ .button__btn--secondary::after {
+ display: none;
+ }
+}
+
+.url-box__button-copied {
+ border: none;
+
+ .button__btn {
+ background-color: var(--color-bg-copied);
+ color: var(--color-text-copied);
+ }
+}
diff --git a/src/components/shareable-url-modal/utils.js b/src/components/shareable-url-modal/utils.js
new file mode 100644
index 0000000000..6e675d870a
--- /dev/null
+++ b/src/components/shareable-url-modal/utils.js
@@ -0,0 +1,50 @@
+export const getFilteredPlatforms = (hostingPlatforms, platformsKeys) => {
+ const filteredPlatforms = {};
+ platformsKeys.forEach((key) => {
+ if (hostingPlatforms.hasOwnProperty(key)) {
+ filteredPlatforms[key] = hostingPlatforms[key];
+ }
+ });
+
+ return filteredPlatforms;
+};
+
+/**
+ * Gets the deployment state message based on the type requested.
+ *
+ * @param {string} type - The type of message to return ('title' or 'message').
+ * @param {string} deploymentState - The current deployment state.
+ * @param {Object} compatibilityData - Data containing the package version.
+ * @param {Function} modalMessages - Function to get modal messages based on deployment state and package version.
+ * @returns {string|null} - The message based on the deployment state and type, or null for default/published states.
+ */
+export const getDeploymentStateByType = (
+ type,
+ deploymentState,
+ compatibilityData,
+ modalMessages
+) => {
+ // This is because the default and published view has its own style
+ if (deploymentState === 'default' || deploymentState === 'published') {
+ return null;
+ }
+
+ if (type === 'title') {
+ return deploymentState === 'success'
+ ? 'Kedro-Viz successfully hosted and published'
+ : 'Publish and Share Kedro-Viz';
+ }
+
+ if (type === 'message') {
+ return modalMessages(deploymentState, compatibilityData.package_version);
+ }
+};
+
+export const handleResponseUrl = (responseUrl, platform) => {
+ // If the URL does not start with http:// or https://, append http:// to avoid relative path issue for GCP platform.
+ if (!/^https?:\/\//.test(responseUrl) && platform === 'gcp') {
+ const url = 'http://' + responseUrl;
+ return url;
+ }
+ return responseUrl;
+};
diff --git a/src/components/ui/icon-button/icon-button.js b/src/components/ui/icon-button/icon-button.js
index 20bf1def6a..9c2d32b55d 100644
--- a/src/components/ui/icon-button/icon-button.js
+++ b/src/components/ui/icon-button/icon-button.js
@@ -104,7 +104,7 @@ IconButton.propTypes = {
dataHeapEvent: PropTypes.string,
disabled: PropTypes.bool,
icon: PropTypes.func,
- labelText: PropTypes.string,
+ labelText: PropTypes.oneOfType([PropTypes.string, PropTypes.node]), // it takes a string or a JSX element
onClick: PropTypes.func,
visible: PropTypes.bool,
};
diff --git a/src/components/ui/modal/modal.js b/src/components/ui/modal/modal.js
index 0572f2882a..6eda0b9539 100644
--- a/src/components/ui/modal/modal.js
+++ b/src/components/ui/modal/modal.js
@@ -45,8 +45,8 @@ const Modal = ({
})}
>
- {title &&
{title}
}
- {message &&
{message}
}
+ {title &&
{title} }
+ {message &&
{message}
}
{children}
diff --git a/src/config.js b/src/config.js
index 9de91e6263..d68b1f9655 100644
--- a/src/config.js
+++ b/src/config.js
@@ -4,6 +4,7 @@ export const localStorageName = 'KedroViz';
export const localStorageFlowchartLink = 'KedroViz-link-to-flowchart';
export const localStorageMetricsSelect = 'KedroViz-metrics-chart-select';
export const localStorageRunsMetadata = 'KedroViz-runs-metadata';
+export const localStorageShareableUrl = 'KedroViz-shareable-url';
export const linkToFlowchartInitialVal = {
fromURL: null,
@@ -149,12 +150,24 @@ export const datasetStatLabels = ['rows', 'columns', 'file_size'];
export const statsRowLen = 33;
-export const hostingPlatform = {
+export const hostingPlatforms = {
aws: 'Amazon Web Services',
gcp: 'Google Cloud',
azure: 'Microsoft Azure',
};
+export const shareableUrlMessages = (status, info = '') => {
+ const messages = {
+ failure: 'Something went wrong. Please try again later.',
+ loading: 'Shooting your files through space. Sit tight...',
+ success:
+ 'The deployment has been successful and Kedro-Viz is hosted via the link below..',
+ incompatible: `Publishing Kedro-Viz is only supported with fsspec>=2023.9.0. You are currently on version ${info}.\n\nPlease upgrade fsspec to a supported version and ensure you're using Kedro 0.18.2 or above.`,
+ };
+
+ return messages[status];
+};
+
export const inputKeyToStateKeyMap = {
// eslint-disable-next-line camelcase
bucket_name: 'hasBucketName',
diff --git a/src/utils/index.js b/src/utils/index.js
index c84615c78f..72fe34af97 100644
--- a/src/utils/index.js
+++ b/src/utils/index.js
@@ -227,6 +227,18 @@ export async function fetchPackageCompatibilities() {
return request;
}
+export async function deployViz(inputValues) {
+ const request = await fetch('/api/deploy', {
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ method: 'POST',
+ body: JSON.stringify(inputValues),
+ });
+
+ return request;
+}
+
const nodeTypeMapObj = {
nodes: 'task',
task: 'nodes',