diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index c611c20d7..8daf93de9 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -12,14 +12,14 @@ on: - devmain jobs: - extraxt_branch: + extract_branch: runs-on: ubuntu-latest outputs: - currentTag: ${{ steps.extract_branch.outputs.extract_branch }} + branch: ${{ steps.extract_branch.outputs.branch }} steps: - name: Extract branch name shell: bash - run: echo "##[set-output name=branch;]$(echo ${GITHUB_REF#refs/heads/})" + run: echo "::set-output name=branch::$(echo ${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}})" id: extract_branch unit_tests_with_coverage: @@ -66,7 +66,7 @@ jobs: release: name: Generate Release if: ${{ (github.event_name != 'pull_request') && (needs.extract_branch.outputs.branch == 'master') }} - needs: [unit_tests_with_coverage, lint_project, build_docker_image, extraxt_branch] + needs: [unit_tests_with_coverage, lint_project, build_docker_image, extract_branch] runs-on: ubuntu-latest steps: - name: Checkout @@ -107,17 +107,32 @@ jobs: # a tag, update our target run: | CURRENT_TAG=$(git tag --sort=-refname --points-at ${{ github.ref }} | head -n 1) + echo $CURRENT_TAG echo "::set-output name=currentTag::$CURRENT_TAG" push_docker_image: - name: Build & Push Flyteconsole Image + name: Push to Github Registry needs: [check_for_tag] - uses: flyteorg/flytetools/.github/workflows/publish.yml@master - with: - version: ${{ needs.check_for_tag.outputs.currentTag }} - dockerfile: Dockerfile - push: true - repository: ${{ github.repository }} - secrets: - FLYTE_BOT_PAT: ${{ secrets.FLYTE_BOT_PAT }} - FLYTE_BOT_USERNAME: ${{ secrets.FLYTE_BOT_USERNAME }} + runs-on: ubuntu-latest + if: ${{ needs.check_for_tag.outputs.currentTag != '' }} + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: '0' + - name: Autobump version + env: + TAG: ${{ needs.check_for_tag.outputs.currentTag }} + run: | + VERSION=${TAG:1} make update_npmversion + - name: Push Docker Image to Github Registry + uses: whoan/docker-build-with-cache-action@v5 + with: + username: "${{ secrets.FLYTE_BOT_USERNAME }}" + password: "${{ secrets.FLYTE_BOT_PAT }}" + image_name: flyteorg/flyteconsole + image_tag: latest,${{ github.sha }},${{ needs.check_for_tag.outputs.currentTag }} + push_git_tag: true + push_image_and_stages: true + dockerfile: Dockerfile + registry: ghcr.io + build_extra_args: "--compress=true" diff --git a/.github/workflows/upgrade_automtion.yml b/.github/workflows/upgrade_automation.yml similarity index 89% rename from .github/workflows/upgrade_automtion.yml rename to .github/workflows/upgrade_automation.yml index d52bb075f..763fd73b0 100644 --- a/.github/workflows/upgrade_automtion.yml +++ b/.github/workflows/upgrade_automation.yml @@ -10,10 +10,11 @@ on: options: - boilerplate - flyteidl + - flyteconsole jobs: trigger-upgrade: name: ${{ github.event.inputs.component }} Upgrade - if: ${{ github.event.inputs.component == "boilerplate" }} + if: ${{ github.event.inputs.component == 'boilerplate' }} uses: flyteorg/flytetools/.github/workflows/flyte_automation.yml@master with: component: ${{ github.event.inputs.component }} @@ -23,7 +24,7 @@ jobs: upgrade_flyteidl: name: Upgrade Flyteidl runs-on: ubuntu-latest - if: ${{ github.event.inputs.component == "boilerplate" }} + if: ${{ github.event.inputs.component == 'flyteidl' }} steps: - uses: actions/checkout@v2 with: @@ -47,7 +48,7 @@ jobs: signoff: true branch: flyte-bot-update-flyteidl delete-branch: true - title: 'Update Flyteidl version' + title: "Update Flyteidl version" body: | Update Flyteidl version - Auto-generated by [flyte-bot] @@ -57,3 +58,4 @@ jobs: owners maintainers draft: false + diff --git a/.releaserc.json b/.releaserc.json index 89c98cecf..bd07673b9 100644 --- a/.releaserc.json +++ b/.releaserc.json @@ -9,13 +9,6 @@ } ], ["@semantic-release/npm", { "npmPublish": false }], - "@semantic-release/github", - [ - "@semantic-release/git", - { - "assets": ["package.json", "CHANGELOG.md"], - "message": "chore(release): Release ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}" - } - ] + "@semantic-release/github" ] } diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 000000000..2b570e2c7 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,16 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "launch generator", + "type": "node", + "request": "launch", + "skipFiles": ["/**"], + "program": "${workspaceFolder}/script/generator/src/index.js", + "console": "integratedTerminal" + } + ] +} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 34d40bfeb..e795a4ec7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -22,6 +22,30 @@ a single module, you can specify that one specifically (ex. `localStorage.debug = 'flyte:adminEntity'` to only see decoded Flyte Admin API requests). +## Generate new package + +To add a new package use a script + +```bash +yarn generate:package +``` + +After new package is generated, you will need to update some values to be able to use it with other packages. +For example in case if package plan to be used in `console` app + +Ensure to add proper webpack alias path resolutions into: +* ./storybook/main.js - as `'@flyteconsole/flyte-api': path.resolve(__dirname, '../packages/plugins/flyte-api/src’),` +* packages/zapp/console/webpack.common.config.ts to alias section - as `'@flyteconsole/flyte-api': path.resolve(__dirname, '../packages/plugins/flyte-api/src’),` + +To add child package usage to other package, in parent package -> +* Add `{ "path": “../../${type}/${package-name}" }` to tsconfig.json +* Add `{ "path": “../../${type}/${package-name}/tsconfig.build.json" }` to tsconfig.build.json (if exists) +- Then you can import your changes as `import { getLoginUrl } from '@flyteconsole/flyte-api’;` + +> If you see `yarn lint` package not defined issues update `.\eslintrc.js` by adding your package to + 'import/core-modules': ['@clients/locale', '@clients/primitives', '@clients/theme'], + + ## Storybook This project has support for [Storybook](https://storybook.js.org/). @@ -115,9 +139,10 @@ brew install asdf brew install vips ``` -- Add Yarn plugin to asdf, to manage yarn versions +- Add Yarn and NodeJs plugins to asdf, to manage versions ```bash +asdf plugin add nodejs https://github.com/asdf-vm/asdf-nodejs.git asdf plugin-add yarn https://github.com/twuni/asdf-yarn.git brew install gpg ``` diff --git a/Makefile b/Makefile index ecf6bb4de..41bccb94c 100644 --- a/Makefile +++ b/Makefile @@ -42,3 +42,10 @@ test_unit_codecov: .PHONY: generate_ssl generate_ssl: ./script/generate_ssl.sh + +PLACEHOLDER_NPM := \"version\": \"0.0.0-develop\" + +.PHONY: update_npmversion +update_npmversion: + grep "$(PLACEHOLDER_NPM)" "packages/zapp/console/package.json" + sed -i "s/$(PLACEHOLDER_NPM)/\"version\": \"${VERSION}\"/g" "packages/zapp/console/package.json" \ No newline at end of file diff --git a/README.md b/README.md index 0986faa67..f24a757b2 100644 --- a/README.md +++ b/README.md @@ -54,10 +54,28 @@ For help with installing dependencies look into 3. Start the server (uses localhost:3000) - `yarn start` + `bash yarn start ` 4. Explore your local copy at `http://localhost:3000` +### Note: Python errors with OSX + +Recently OSX (12.3) removed python 2.7 from default installation and this can cause build errors for some users depending on it's setup. In this repository you can experience `env: python: No such file or directory` error from gyp ([node-gyp](https://github.com/nodejs/node-gyp)). +The easiest way to fix it: + +- Install the XCode Command Line Tools standalone by running `xcode-select --install` in the terminal + +OR + +```bash + brew install python # install python with brew + which python # check if python path is properly defined + # if path not defined + where python3 + # Take the version and location from above and run this command (replacing `/usr/bin/python3` with the location of your python instalation). This will symlink python to python3 + ln -s /usr/bin/python3 /usr/local/bin/python +``` + ### Environment Variables - `ADMIN_API_URL` (default: [window.location.origin](https://developer.mozilla.org/en-US/docs/Web/API/Window/location>)) @@ -80,11 +98,15 @@ For help with installing dependencies look into - `BASE_URL` (default: `undefined`) - This allows running the console at a prefix on the target host. This is + This setting allows running the console at a prefix on the target host. This is necessary when hosting the API and console on the same domain (with prefixes of `/api/v1` and `/console` for example). For local development, this is usually not needed, so the default behavior is to run without a prefix. +- `FLYTE_NAVIGATION` (default: `undefined`) + UI related. Allows you to change colors of the navigation bar and add links + to other internal pages or external sites. **[More info](packages/zapp/console/src/components/Navigation/Readme.md)** + ### Running from docker image as localhost To run flyteconsole directly from your docker image as localhost you must set a @@ -116,6 +138,10 @@ and start the NodeJS server on the default port (3000). All requests to the Node will be stalled until the bundles have finished. The application will be accessible at http://localhost:3000 (if using the default port). +### 🎱 Using items in your own application + +- Authorize your app to call flyte admin api. **[More info](packages/plugins/flyte-api/README.md)** + ## 🛠 Development For continious development we are using: diff --git a/boilerplate/update.cfg b/boilerplate/update.cfg index 6ed62933a..45ff77d2e 100644 --- a/boilerplate/update.cfg +++ b/boilerplate/update.cfg @@ -1,4 +1,3 @@ flyte/docker_build flyte/pull_request_template -flyte/welcome_bot flyte/code_of_conduct diff --git a/package.json b/package.json index 36114967e..04ae8c5c4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "flyteconsole", - "version": "1.1.0-rc1", + "version": "1.3.5", "private": true, "description": "The web UI for the Flyte platform", "repository": { @@ -12,7 +12,8 @@ "packages/basics/**", "packages/composites/**", "packages/plugins/**", - "packages/zapp/**" + "packages/zapp/**", + "script/generator/**" ], "scripts": { "clean": "yarn workspace @flyteconsole/client-app clean", @@ -20,6 +21,7 @@ "start:prod": "yarn workspace @flyteconsole/client-app start:prod", "build:prod": "yarn workspace @flyteconsole/client-app build:prod", "build:storybook": "build-storybook", + "generate:package": "yarn workspace @flyteconsole/generator start", "lint": "eslint . --ext .js,.jsx,.ts,.tsx", "storybook": "start-storybook -p 6006", "test": "NODE_ENV=test jest", @@ -45,7 +47,9 @@ "git add" ] }, - "dependencies": {}, + "dependencies": { + "@flyteorg/flyteidl": "1.1.16" + }, "devDependencies": { "@storybook/addon-actions": "^6.4.19", "@storybook/addon-essentials": "^6.4.19", @@ -58,9 +62,9 @@ "@testing-library/jest-dom": "^5.5.0", "@testing-library/react": "^10.0.3", "@testing-library/react-hooks": "^7.0.2", - "ts-jest": "^26.3.0", "jest": "^26.0.0", - "react-hot-loader": "^4.1.2" + "react-hot-loader": "^4.1.2", + "ts-jest": "^26.3.0" }, "resolutions": { "@babel/cli": "~7.16.0", diff --git a/packages/composites/ui-atoms/src/Icons/MapCacheIcon/index.tsx b/packages/composites/ui-atoms/src/Icons/MapCacheIcon/index.tsx new file mode 100644 index 000000000..a99e85533 --- /dev/null +++ b/packages/composites/ui-atoms/src/Icons/MapCacheIcon/index.tsx @@ -0,0 +1,24 @@ +import * as React from 'react'; +import SvgIcon, { SvgIconProps } from '@material-ui/core/SvgIcon'; + +export const MapCacheIcon = React.forwardRef((props, ref) => { + return ( + + + + + + + + + + + + ); +}); diff --git a/packages/composites/ui-atoms/src/Icons/MuiLaunchPlanIcon/index.tsx b/packages/composites/ui-atoms/src/Icons/MuiLaunchPlanIcon/index.tsx new file mode 100644 index 000000000..cd9f459fc --- /dev/null +++ b/packages/composites/ui-atoms/src/Icons/MuiLaunchPlanIcon/index.tsx @@ -0,0 +1,39 @@ +import { makeStyles } from '@material-ui/core/styles'; +import { SvgIconProps, Theme } from '@material-ui/core'; +import classnames from 'classnames'; +import * as React from 'react'; + +const useStyles = makeStyles((theme: Theme) => ({ + svg: { + marginTop: 0, + marginRight: theme.spacing(2), + display: 'inline-block', + fontSize: '1.5rem', + transition: 'fill 200ms cubic-bezier(0.4, 0, 0.2, 1) 0ms', + flexShrink: 0, + userSelect: 'none', + color: '#666666', + }, +})); + +export const MuiLaunchPlanIcon = (props: SvgIconProps): JSX.Element => { + const { fill, className, width = '1em', height = '1em', fontSize } = props; + const styles = useStyles(); + return ( + + + + ); +}; diff --git a/packages/composites/ui-atoms/src/Icons/index.tsx b/packages/composites/ui-atoms/src/Icons/index.tsx index 0cbe71ead..1f591b50b 100644 --- a/packages/composites/ui-atoms/src/Icons/index.tsx +++ b/packages/composites/ui-atoms/src/Icons/index.tsx @@ -1,3 +1,5 @@ export { FlyteLogo } from './FlyteLogo'; export { InfoIcon } from './InfoIcon'; export { RerunIcon } from './RerunIcon'; +export { MapCacheIcon } from './MapCacheIcon'; +export { MuiLaunchPlanIcon } from './MuiLaunchPlanIcon'; diff --git a/packages/zapp/console/env.js b/packages/zapp/console/env.js index a8a9224db..ec741db9d 100644 --- a/packages/zapp/console/env.js +++ b/packages/zapp/console/env.js @@ -39,6 +39,8 @@ const STATUS_URL = process.env.STATUS_URL; const ENABLE_GA = process.env.ENABLE_GA || false; const GA_TRACKING_ID = process.env.GA_TRACKING_ID || 'G-0QW4DJWJ20'; +const FLYTE_NAVIGATION = process.env.FLYTE_NAVIGATION || ''; + module.exports = { ADMIN_API_URL, ADMIN_API_USE_SSL, @@ -58,5 +60,6 @@ module.exports = { GA_TRACKING_ID, NODE_ENV, STATUS_URL, + FLYTE_NAVIGATION, }, }; diff --git a/packages/zapp/console/package.json b/packages/zapp/console/package.json index 5348f4968..d4451e70b 100644 --- a/packages/zapp/console/package.json +++ b/packages/zapp/console/package.json @@ -1,6 +1,6 @@ { "name": "@flyteconsole/client-app", - "version": "1.1.0-rc1", + "version": "0.0.0-develop", "description": "The web UI for the Flyte platform", "repository": { "type": "git", @@ -11,7 +11,7 @@ "clean": "rm -rf dist", "build": "TS_NODE_COMPILER_OPTIONS='{\"module\": \"commonjs\"}' webpack --config webpack.dev.config.ts --mode=development", "build:prod": "TS_NODE_COMPILER_OPTIONS='{\"module\": \"commonjs\"}' NODE_ENV=production webpack --config webpack.prod.config.ts --mode=production --progress", - "start": "webpack serve --open --config webpack.dev.config.ts --hot", + "start": "webpack serve --config webpack.dev.config.ts", "start:prod": "NODE_ENV=production node -r dotenv/config index.js", "lint": "eslint . --ext .js,.jsx,.ts,.tsx", "format": "prettier --ignore-path .eslintignore --write \"**/*.+(js|jsx|ts|tsx|json)\"", @@ -30,7 +30,6 @@ "dependencies": { "@rjsf/core": "^3.2.1", "@rjsf/material-ui": "^3.2.1", - "cache": "^2.1.0", "chalk": "^2.0.1", "chart.js": "^3.6.2", "chartjs-plugin-datalabels": "^2.0.0", @@ -45,7 +44,7 @@ "lodash": "^4.17.21", "morgan": "^1.8.2", "react-chartjs-2": "^4.0.0", - "react-flow-renderer": "10.1.1", + "react-flow-renderer": "10.3.8", "react-ga4": "^1.4.1", "react-json-view": "^1.21.3", "react-transition-group": "^2.3.1", @@ -58,7 +57,7 @@ "@commitlint/cli": "^8.3.5", "@commitlint/config-conventional": "^8.3.4", "@date-io/moment": "1.3.9", - "@flyteorg/flyteidl": "1.1.0", + "@flyteorg/flyteidl": "1.1.4", "@material-ui/core": "^4.0.0", "@material-ui/icons": "^4.0.0", "@material-ui/pickers": "^3.2.2", @@ -86,7 +85,7 @@ "@types/pure-render-decorator": "^0.2.27", "@types/react": "^16.9.34", "@types/react-dom": "^16.9.7", - "@types/react-router-dom": "^4.3.2", + "@types/react-router-dom": "^5.3.3", "@types/react-virtualized": "^9.21.4", "@types/serve-static": "^1.7.31", "@types/shallowequal": "^0.2.3", @@ -104,6 +103,7 @@ "compression-webpack-plugin": "^9.2.0", "contrast": "^1.0.1", "copy-to-clipboard": "^3.0.8", + "copy-webpack-plugin": "^11.0.0", "cronstrue": "^1.31.0", "d3-dag": "^0.3.4", "d3-shape": "^1.2.2", @@ -116,8 +116,6 @@ "eslint-plugin-jsx-a11y": "^6.5.1", "eslint-plugin-react": "^7.29.4", "eslint-plugin-react-hooks": "^4.3.0", - "favicons": "^6.2.0", - "favicons-webpack-plugin": "^5.0.2", "fork-ts-checker-webpack-plugin": "^7.2.1", "html-webpack-plugin": "^5.5.0", "husky": "^4.2.5", @@ -126,13 +124,13 @@ "lint-staged": "^7.0.4", "lossless-json": "^1.0.3", "memoize-one": "^5.0.0", - "moment": "^2.29.2", + "moment": "^2.29.4", "moment-timezone": "^0.5.28", "msw": "^0.24.1", "notistack": "^1.0.10", "object-hash": "^1.3.1", "prettier": "^2.5.1", - "protobufjs": "~6.8.0", + "protobufjs": "~6.11.3", "query-string": "^6.5.0", "react": "^16.13.1", "react-dom": "^16.13.1", @@ -141,11 +139,11 @@ "react-loading-skeleton": "^1.1.2", "react-query": "^3.3.0", "react-query-devtools": "^3.0.0-beta", - "react-router": "^5.0.1", - "react-router-dom": "^5.0.1", + "react-router": "^5.2.0", + "react-router-dom": "^5.2.0", "react-virtualized": "^9.21.1", "resolve-url-loader": "^5.0.0", - "semantic-release": "^17.2.3", + "semantic-release": "^19.0.3", "shallowequal": "^1.1.0", "snakecase-keys": "^5.4.2", "source-map-loader": "^3.0.1", diff --git a/packages/zapp/console/src/assets/favicon.ico b/packages/zapp/console/src/assets/favicon.ico new file mode 100644 index 000000000..f447ccf9d Binary files /dev/null and b/packages/zapp/console/src/assets/favicon.ico differ diff --git a/packages/zapp/console/src/assets/favicon.png b/packages/zapp/console/src/assets/favicon.png deleted file mode 100644 index 49cdbbbc3..000000000 Binary files a/packages/zapp/console/src/assets/favicon.png and /dev/null differ diff --git a/packages/zapp/console/src/assets/favicon.svg b/packages/zapp/console/src/assets/favicon.svg new file mode 100644 index 000000000..d71fa52dd --- /dev/null +++ b/packages/zapp/console/src/assets/favicon.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/packages/zapp/console/src/assets/index.html b/packages/zapp/console/src/assets/index.html index cd907741a..074cdef27 100644 --- a/packages/zapp/console/src/assets/index.html +++ b/packages/zapp/console/src/assets/index.html @@ -8,6 +8,28 @@ href="https://fonts.googleapis.com/css?family=Lato:300,400,700|Open+Sans:400,700" rel="stylesheet" /> + + + + + + +
diff --git a/packages/zapp/console/src/assets/public/apple-touch-icon.png b/packages/zapp/console/src/assets/public/apple-touch-icon.png new file mode 100644 index 000000000..a1e361d84 Binary files /dev/null and b/packages/zapp/console/src/assets/public/apple-touch-icon.png differ diff --git a/packages/zapp/console/src/assets/public/icon-192.png b/packages/zapp/console/src/assets/public/icon-192.png new file mode 100644 index 000000000..7f9fb5a32 Binary files /dev/null and b/packages/zapp/console/src/assets/public/icon-192.png differ diff --git a/packages/zapp/console/src/assets/public/icon-512.png b/packages/zapp/console/src/assets/public/icon-512.png new file mode 100644 index 000000000..2e3953d16 Binary files /dev/null and b/packages/zapp/console/src/assets/public/icon-512.png differ diff --git a/packages/zapp/console/src/assets/public/manifest.webmanifest b/packages/zapp/console/src/assets/public/manifest.webmanifest new file mode 100644 index 000000000..947c6581d --- /dev/null +++ b/packages/zapp/console/src/assets/public/manifest.webmanifest @@ -0,0 +1,7 @@ +{ + "name": "My website", + "icons": [ + { "src": "/icon-192.png", "type": "image/png", "sizes": "192x192" }, + { "src": "/icon-512.png", "type": "image/png", "sizes": "512x512" } + ] +} diff --git a/packages/zapp/console/src/common/formatters.test.ts b/packages/zapp/console/src/common/formatters.test.ts new file mode 100644 index 000000000..8d428e060 --- /dev/null +++ b/packages/zapp/console/src/common/formatters.test.ts @@ -0,0 +1,40 @@ +import { millisecondsToHMS } from 'common/formatters'; +import { unknownValueString, zeroSecondsString } from './constants'; + +describe('millisecondsToHMS', () => { + it('should display unknown if the value is negative', () => { + expect(millisecondsToHMS(-1)).toEqual(unknownValueString); + }); + + it('should display 0s if the value is 0', () => { + expect(millisecondsToHMS(0)).toEqual(zeroSecondsString); + }); + + it('should display in `ms` format if the value is < 1000', () => { + expect(millisecondsToHMS(999)).toEqual('999ms'); + }); + + it('should display in `h` format if the value is >= 86400000 (24h)', () => { + expect(millisecondsToHMS(86400001)).toEqual('24h'); + }); + + it('should display in `h m` format if the value is < 86400000 (24h)', () => { + expect(millisecondsToHMS(86340000)).toEqual('23h 59m'); + }); + + it('should display in `h m s` format if the value is < 86400000 (24h)', () => { + expect(millisecondsToHMS(86399999)).toEqual('23h 59m 59s'); + }); + + it('should display in `m s` format if the value is < 3600000 (1h)', () => { + expect(millisecondsToHMS(3599999)).toEqual('59m 59s'); + }); + + it('should display in `m` format if the value is < 3600000 (1h) and seconds < 1', () => { + expect(millisecondsToHMS(3540000)).toEqual('59m'); + }); + + it('should display in `s` format if the value is < 60000 (1m)', () => { + expect(millisecondsToHMS(59999)).toEqual('59s'); + }); +}); diff --git a/packages/zapp/console/src/common/formatters.ts b/packages/zapp/console/src/common/formatters.ts index 51046118c..25624fb1c 100644 --- a/packages/zapp/console/src/common/formatters.ts +++ b/packages/zapp/console/src/common/formatters.ts @@ -75,7 +75,8 @@ export function millisecondsToHMS(valueMS: number): string { } if (duration.seconds() >= 1) { - parts.push(`${duration.seconds()}s`); + // there may be a bug in Momemt.js that shows float number of seconds, so rounding it down to make sure it will be int + parts.push(`${Math.floor(duration.seconds())}s`); } return parts.length ? parts.join(' ') : unknownValueString; diff --git a/packages/zapp/console/src/common/utils.ts b/packages/zapp/console/src/common/utils.ts index 0f5973571..138d9f17e 100644 --- a/packages/zapp/console/src/common/utils.ts +++ b/packages/zapp/console/src/common/utils.ts @@ -1,5 +1,8 @@ import { Protobuf } from 'flyteidl'; import * as Long from 'long'; +import { WorkflowExecutionPhase } from 'models/Execution/enums'; +import { WorkflowExecutionIdentifier } from 'models/Execution/types'; +import { Routes } from 'routes/routes'; /** Determines if a given date string or object is a valid, usable date. This will detect * JS Date objects which have been initialized with invalid values as well as strings which @@ -101,3 +104,22 @@ export function toBoolean(value?: string): boolean { export function stringifyValue(value: unknown): string { return JSON.stringify(value, null, 2); } + +export const padExecutions = (items: WorkflowExecutionPhase[]) => { + if (items.length >= 10) { + return items.slice(0, 10).reverse(); + } + const emptyExecutions = new Array(10 - items.length).fill(WorkflowExecutionPhase.QUEUED); + return [...items, ...emptyExecutions].reverse(); +}; + +export const padExecutionPaths = (items: WorkflowExecutionIdentifier[]) => { + if (items.length >= 10) { + return items + .slice(0, 10) + .map((id) => Routes.ExecutionDetails.makeUrl(id)) + .reverse(); + } + const emptyExecutions = new Array(10 - items.length).fill(null); + return [...items.map((id) => Routes.ExecutionDetails.makeUrl(id)), ...emptyExecutions].reverse(); +}; diff --git a/packages/zapp/console/src/components/Entities/EntityDetails.tsx b/packages/zapp/console/src/components/Entities/EntityDetails.tsx index 16a9c1dfd..62d15fcaf 100644 --- a/packages/zapp/console/src/components/Entities/EntityDetails.tsx +++ b/packages/zapp/console/src/components/Entities/EntityDetails.tsx @@ -8,6 +8,7 @@ import { ResourceIdentifier } from 'models/Common/types'; import * as React from 'react'; import { entitySections } from './constants'; import { EntityDetailsHeader } from './EntityDetailsHeader'; +import { EntityInputs } from './EntityInputs'; import { EntityExecutions } from './EntityExecutions'; import { EntitySchedules } from './EntitySchedules'; import { EntityVersions } from './EntityVersions'; @@ -16,7 +17,7 @@ import { EntityExecutionsBarChart } from './EntityExecutionsBarChart'; const useStyles = makeStyles((theme: Theme) => ({ metadataContainer: { display: 'flex', - marginBottom: theme.spacing(5), + marginBottom: theme.spacing(2), marginTop: theme.spacing(2), width: '100%', }, @@ -39,6 +40,10 @@ const useStyles = makeStyles((theme: Theme) => ({ flex: '1 2 auto', marginRight: theme.spacing(30), }, + inputsContainer: { + display: 'flex', + flexDirection: 'column', + }, })); interface EntityDetailsProps { @@ -67,13 +72,19 @@ export const EntityDetails: React.FC = ({ id }) => { ) : null} - {sections.schedules ? ( + {!sections.inputs && sections.schedules ? (
) : null} + {sections.inputs ? ( +
+ +
+ ) : null} + {sections.versions ? (
diff --git a/packages/zapp/console/src/components/Entities/EntityDetailsHeader.tsx b/packages/zapp/console/src/components/Entities/EntityDetailsHeader.tsx index c22b16291..3dd1c7824 100644 --- a/packages/zapp/console/src/components/Entities/EntityDetailsHeader.tsx +++ b/packages/zapp/console/src/components/Entities/EntityDetailsHeader.tsx @@ -68,6 +68,7 @@ export const EntityDetailsHeader: React.FC = ({ const domain = getProjectDomain(project, id.domain); const headerText = `${domain.name} / ${id.name}`; + return ( <>
diff --git a/packages/zapp/console/src/components/Entities/EntityExecutions.tsx b/packages/zapp/console/src/components/Entities/EntityExecutions.tsx index da2e701b8..56ae01473 100644 --- a/packages/zapp/console/src/components/Entities/EntityExecutions.tsx +++ b/packages/zapp/console/src/components/Entities/EntityExecutions.tsx @@ -1,5 +1,4 @@ import * as React from 'react'; -import { Typography } from '@material-ui/core'; import { makeStyles, Theme } from '@material-ui/core/styles'; import { contentMarginGridUnits } from 'common/layout'; import { WaitForData } from 'components/common/WaitForData'; @@ -78,9 +77,6 @@ export const EntityExecutions: React.FC = ({ return ( <> - - {t(patternKey('allExecutionsChartTitle', entityStrings[id.resourceType]))} -
{ + if (typeof value === 'object') { + return JSON.stringify(value); + } + return value; +}; + +const useStyles = makeStyles((theme: Theme) => ({ + header: { + marginBottom: theme.spacing(1), + }, + divider: { + borderBottom: `1px solid ${theme.palette.divider}`, + marginBottom: theme.spacing(1), + }, + rowContainer: { + display: 'flex', + marginTop: theme.spacing(3), + }, + firstColumnContainer: { + width: '60%', + marginRight: theme.spacing(3), + }, + secondColumnContainer: { + width: '40%', + }, + configs: { + listStyleType: 'none', + paddingInlineStart: 0, + }, + config: { + display: 'flex', + }, + configName: { + color: theme.palette.grey[400], + marginRight: theme.spacing(2), + minWidth: '95px', + }, + configValue: { + color: '#333', + fontSize: '14px', + }, + headCell: { + color: theme.palette.grey[400], + }, + noInputs: { + color: theme.palette.grey[400], + }, +})); + +interface Input { + name: string; + type?: string; + required?: boolean; + defaultValue?: string; +} + +/** Fetches and renders the expected & fixed inputs for a given Entity (LaunchPlan) ID */ +export const EntityInputs: React.FC<{ + id: ResourceIdentifier; +}> = ({ id }) => { + const styles = useStyles(); + + const launchPlanState = useLaunchPlans( + { project: id.project, domain: id.domain }, + { + limit: 1, + filter: [ + { + key: 'launch_plan.name', + operation: FilterOperationName.EQ, + value: id.name, + }, + ], + }, + ); + + const closure = launchPlanState?.value?.length + ? launchPlanState.value[0].closure + : ({} as LaunchPlanClosure); + + const spec = launchPlanState?.value?.length + ? launchPlanState.value[0].spec + : ({} as LaunchPlanSpec); + + const expectedInputs = React.useMemo(() => { + const results = [] as Input[]; + Object.keys(closure?.expectedInputs?.parameters ?? {}).forEach((name) => { + const parameter = closure?.expectedInputs.parameters[name]; + if (parameter?.var?.type) { + const typeDefinition = getInputDefintionForLiteralType(parameter.var.type); + results.push({ + name, + type: formatType(typeDefinition), + required: !!parameter.required, + defaultValue: parameter.default?.value, + }); + } + }); + return results; + }, [closure]); + + const fixedInputs = React.useMemo(() => { + const inputsMap = transformLiterals(spec?.fixedInputs?.literals ?? {}); + return Object.keys(inputsMap).map((name) => ({ name, defaultValue: inputsMap[name] })); + }, [spec]); + + const configs = React.useMemo( + () => [ + { name: t('configType'), value: 'single (csv)' }, + { + name: t('configUrl'), + value: + 'https://raw.githubusercontent.com/jbrownlee/Datasets/master/pima-indians-diabetes.data.csv', + }, + { name: t('configSeed'), value: '7' }, + { name: t('configTestSplitRatio'), value: '0.33' }, + ], + [], + ); + + return ( + <> + + {t('launchPlanLatest')} + +
+
+
+ + {t('expectedInputs')} + + {expectedInputs.length ? ( + + + + + + + {t('inputsName')} + + + + + {t('inputsType')} + + + + + {t('inputsRequired')} + + + + + {t('inputsDefault')} + + + + + + {expectedInputs.map(({ name, type, required, defaultValue }) => ( + + {name} + {type} + + {required ? : ''} + + {coerceDefaultValue(defaultValue) || '-'} + + ))} + +
+
+ ) : ( +

{t('noExpectedInputs')}

+ )} +
+
+ + {t('fixedInputs')} + + {fixedInputs.length ? ( + + + + + + + {t('inputsName')} + + + + + {t('inputsDefault')} + + + + + + {fixedInputs.map(({ name, defaultValue }) => ( + + {name} + {coerceDefaultValue(defaultValue) || '-'} + + ))} + +
+
+ ) : ( +

{t('noFixedInputs')}

+ )} +
+
+ {/*
+
+ + {t('configuration')} + +
    + {configs.map(({ name, value }) => ( +
  • + {name}: + {value} +
  • + ))} +
+
+
+
*/} + + ); +}; diff --git a/packages/zapp/console/src/components/Entities/EntityVersions.tsx b/packages/zapp/console/src/components/Entities/EntityVersions.tsx index 4509f3090..a9c1339cf 100644 --- a/packages/zapp/console/src/components/Entities/EntityVersions.tsx +++ b/packages/zapp/console/src/components/Entities/EntityVersions.tsx @@ -11,7 +11,7 @@ import { isLoadingState } from 'components/hooks/fetchMachine'; import { useEntityVersions } from 'components/hooks/Entity/useEntityVersions'; import { interactiveTextColor } from 'components/Theme/constants'; import { SortDirection } from 'models/AdminEntity/types'; -import { Identifier, ResourceIdentifier } from 'models/Common/types'; +import { Identifier, ResourceIdentifier, ResourceType } from 'models/Common/types'; import { executionSortFields } from 'models/Execution/constants'; import { executionFilterGenerator, versionDetailsUrlGenerator } from './generators'; import { WorkflowVersionsTablePageSize, entityStrings } from './constants'; @@ -20,6 +20,7 @@ import t, { patternKey } from './strings'; const useStyles = makeStyles((theme: Theme) => ({ headerContainer: { display: 'flex', + marginTop: theme.spacing(3), }, collapseButton: { marginTop: theme.spacing(-0.5), @@ -114,7 +115,7 @@ export const EntityVersions: React.FC = ({ id, showAll = fa ) : ( diff --git a/packages/zapp/console/src/components/Entities/VersionDetails/EntityVersionDetailsContainer.tsx b/packages/zapp/console/src/components/Entities/VersionDetails/EntityVersionDetailsContainer.tsx index 931edeb09..91a274130 100644 --- a/packages/zapp/console/src/components/Entities/VersionDetails/EntityVersionDetailsContainer.tsx +++ b/packages/zapp/console/src/components/Entities/VersionDetails/EntityVersionDetailsContainer.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { withRouteParams } from 'components/common/withRouteParams'; -import { ResourceIdentifier } from 'models/Common/types'; +import { ResourceIdentifier, ResourceType } from 'models/Common/types'; import { makeStyles, Theme } from '@material-ui/core/styles'; import { WaitForData } from 'components/common/WaitForData'; import { useProject } from 'components/hooks/useProjects'; @@ -13,7 +13,11 @@ import { typeNameToEntityResource } from '../constants'; import { versionsDetailsSections } from './constants'; import { EntityVersionDetails } from './EntityVersionDetails'; -const useStyles = makeStyles((theme: Theme) => ({ +interface StyleProps { + resourceType: ResourceType; +} + +const useStyles = makeStyles((theme: Theme) => ({ verionDetailsContainer: { marginTop: theme.spacing(2), display: 'flex', @@ -39,9 +43,9 @@ const useStyles = makeStyles((theme: Theme) => ({ versionsContainer: { display: 'flex', flex: '0 1 auto', - height: '40%', + height: ({ resourceType }) => (resourceType === ResourceType.LAUNCH_PLAN ? '100%' : '40%'), flexDirection: 'column', - overflowY: 'scroll', + overflowY: 'auto', }, })); @@ -81,7 +85,7 @@ const EntityVersionsDetailsContainerImpl: React.FC diff --git a/packages/zapp/console/src/components/Entities/constants.ts b/packages/zapp/console/src/components/Entities/constants.ts index efef36a63..6729064c3 100644 --- a/packages/zapp/console/src/components/Entities/constants.ts +++ b/packages/zapp/console/src/components/Entities/constants.ts @@ -4,7 +4,7 @@ type EntityStringMap = { [k in ResourceType]: string }; export const entityStrings: EntityStringMap = { [ResourceType.DATASET]: 'dataset', - [ResourceType.LAUNCH_PLAN]: 'launch plan', + [ResourceType.LAUNCH_PLAN]: 'launch_plan', [ResourceType.TASK]: 'task', [ResourceType.UNSPECIFIED]: 'item', [ResourceType.WORKFLOW]: 'workflow', @@ -14,7 +14,7 @@ type TypeNameToEntityResourceType = { [key: string]: ResourceType }; export const typeNameToEntityResource: TypeNameToEntityResourceType = { ['dataset']: ResourceType.DATASET, - ['launch plan']: ResourceType.LAUNCH_PLAN, + ['launch_plan']: ResourceType.LAUNCH_PLAN, ['task']: ResourceType.TASK, ['item']: ResourceType.UNSPECIFIED, ['workflow']: ResourceType.WORKFLOW, @@ -27,15 +27,17 @@ interface EntitySectionsFlags { schedules?: boolean; versions?: boolean; descriptionInputsAndOutputs?: boolean; + inputs?: boolean; } export const entitySections: { [k in ResourceType]: EntitySectionsFlags } = { [ResourceType.DATASET]: { description: true }, [ResourceType.LAUNCH_PLAN]: { - description: true, executions: true, - launch: true, + launch: false, + inputs: true, schedules: true, + versions: true, }, [ResourceType.TASK]: { description: true, diff --git a/packages/zapp/console/src/components/Entities/generators.ts b/packages/zapp/console/src/components/Entities/generators.ts index f10c41fee..c84aefc8c 100644 --- a/packages/zapp/console/src/components/Entities/generators.ts +++ b/packages/zapp/console/src/components/Entities/generators.ts @@ -6,10 +6,30 @@ import { entityStrings } from './constants'; const noFilters = () => []; export const executionFilterGenerator: { - [k in ResourceType]: (id: ResourceIdentifier) => FilterOperation[]; + [k in ResourceType]: (id: ResourceIdentifier, version?: string) => FilterOperation[]; } = { [ResourceType.DATASET]: noFilters, - [ResourceType.LAUNCH_PLAN]: noFilters, + [ResourceType.LAUNCH_PLAN]: ({ name }, version) => + version + ? [ + { + key: 'launch_plan.name', + operation: FilterOperationName.EQ, + value: name, + }, + { + key: 'launch_plan.version', + operation: FilterOperationName.EQ, + value: version, + }, + ] + : [ + { + key: 'launch_plan.name', + operation: FilterOperationName.EQ, + value: name, + }, + ], [ResourceType.TASK]: ({ name }) => [ { key: 'task.name', @@ -29,6 +49,8 @@ export const executionFilterGenerator: { const workflowListGenerator = ({ project, domain }: ResourceIdentifier) => Routes.ProjectDetails.sections.workflows.makeUrl(project, domain); +const launchPlanListGenerator = ({ project, domain }: ResourceIdentifier) => + Routes.ProjectDetails.sections.launchPlans.makeUrl(project, domain); const taskListGenerator = ({ project, domain }: ResourceIdentifier) => Routes.ProjectDetails.sections.tasks.makeUrl(project, domain); const unspecifiedGenerator = ({ project, domain }: ResourceIdentifier | Identifier) => { @@ -42,7 +64,7 @@ export const backUrlGenerator: { [k in ResourceType]: (id: ResourceIdentifier) => string; } = { [ResourceType.DATASET]: unimplementedGenerator, - [ResourceType.LAUNCH_PLAN]: unimplementedGenerator, + [ResourceType.LAUNCH_PLAN]: launchPlanListGenerator, [ResourceType.TASK]: taskListGenerator, [ResourceType.UNSPECIFIED]: unspecifiedGenerator, [ResourceType.WORKFLOW]: workflowListGenerator, @@ -50,6 +72,8 @@ export const backUrlGenerator: { const workflowDetailGenerator = ({ project, domain, name }: ResourceIdentifier) => Routes.WorkflowDetails.makeUrl(project, domain, name); +const launchPlanDetailGenerator = ({ project, domain, name }: ResourceIdentifier) => + Routes.LaunchPlanDetails.makeUrl(project, domain, name); const taskDetailGenerator = ({ project, domain, name }: ResourceIdentifier) => Routes.TaskDetails.makeUrl(project, domain, name); @@ -57,7 +81,7 @@ export const backToDetailUrlGenerator: { [k in ResourceType]: (id: ResourceIdentifier) => string; } = { [ResourceType.DATASET]: unimplementedGenerator, - [ResourceType.LAUNCH_PLAN]: unimplementedGenerator, + [ResourceType.LAUNCH_PLAN]: launchPlanDetailGenerator, [ResourceType.TASK]: taskDetailGenerator, [ResourceType.UNSPECIFIED]: unspecifiedGenerator, [ResourceType.WORKFLOW]: workflowDetailGenerator, @@ -79,12 +103,20 @@ const taskVersionDetailsGenerator = ({ project, domain, name, version }: Identif entityStrings[ResourceType.TASK], version, ); +const launchPlanVersionDetailsGenerator = ({ project, domain, name, version }: Identifier) => + Routes.EntityVersionDetails.makeUrl( + project, + domain, + name, + entityStrings[ResourceType.LAUNCH_PLAN], + version, + ); const entityMapVersionDetailsUrl: { [k in ResourceType]: (id: Identifier) => string; } = { [ResourceType.DATASET]: unimplementedGenerator, - [ResourceType.LAUNCH_PLAN]: unimplementedGenerator, + [ResourceType.LAUNCH_PLAN]: launchPlanVersionDetailsGenerator, [ResourceType.TASK]: taskVersionDetailsGenerator, [ResourceType.UNSPECIFIED]: unspecifiedGenerator, [ResourceType.WORKFLOW]: workflowVersopmDetailsGenerator, diff --git a/packages/zapp/console/src/components/Entities/strings.ts b/packages/zapp/console/src/components/Entities/strings.ts index e12b28fd3..fd0bb7840 100644 --- a/packages/zapp/console/src/components/Entities/strings.ts +++ b/packages/zapp/console/src/components/Entities/strings.ts @@ -11,9 +11,14 @@ const str = { noSchedules_workflow: 'This workflow has no schedules.', noSchedules_task: 'This task has no schedules.', allExecutionsChartTitle_workflow: 'All Executions in the Workflow', - allExecutionsChartTitle_task: 'All Execuations in the Task', + allExecutionsChartTitle_task: 'All Executions in the Task', + allExecutionsChartTitle_launch_plan: 'All Executions Using Launch Plan', versionsTitle_workflow: 'Recent Workflow Versions', versionsTitle_task: 'Recent Task Versions', + versionsTitle_launch_plan: 'Launch Plan Versions', + searchName_launch_plan: 'Search Launch Plan Name', + searchName_task: 'Search Task Name', + searchName_workflow: 'Search Workflow Name', details_task: 'Task Details', inputsFieldName: 'Inputs', outputsFieldName: 'Outputs', @@ -25,6 +30,20 @@ const str = { value: 'Value', basicInformation: 'Basic Information', description: 'Description', + launchPlanLatest: 'Launch Plan Detail (Latest Version)', + expectedInputs: 'Expected Inputs', + fixedInputs: 'Fixed Inputs', + inputsName: 'Name', + inputsType: 'Type', + inputsRequired: 'Required', + inputsDefault: 'Default Value', + configuration: 'Configuration', + configType: 'type', + configUrl: 'url', + configSeed: 'seed', + configTestSplitRatio: 'test_split_ratio', + noExpectedInputs: 'This launch plan has no expected inputs.', + noFixedInputs: 'This launch plan has no fixed inputs.', }; export { patternKey } from '@flyteconsole/locale'; diff --git a/packages/zapp/console/src/components/Executions/ExecutionDetails/ExecutionChildrenLoader.tsx b/packages/zapp/console/src/components/Executions/ExecutionDetails/ExecutionChildrenLoader.tsx deleted file mode 100644 index 9da436efe..000000000 --- a/packages/zapp/console/src/components/Executions/ExecutionDetails/ExecutionChildrenLoader.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { WaitForQuery } from 'components/common/WaitForQuery'; -import { DataError } from 'components/Errors/DataError'; -import * as React from 'react'; -import { NodeExecution } from 'models/Execution/types'; -import { useAllChildNodeExecutionGroupsQuery } from '../nodeExecutionQueries'; -import { NodeExecutionsRequestConfigContext } from '../contexts'; -import { ExecutionWorkflowGraph } from './ExecutionWorkflowGraph'; - -export const ExecutionChildrenLoader = ({ nodeExecutions, workflowId }) => { - const requestConfig = React.useContext(NodeExecutionsRequestConfigContext); - const childGroupsQuery = useAllChildNodeExecutionGroupsQuery(nodeExecutions, requestConfig); - - const renderGraphComponent = (childGroups) => { - const output: any[] = []; - for (let i = 0; i < childGroups.length; i++) { - for (let j = 0; j < childGroups[i].length; j++) { - for (let k = 0; k < childGroups[i][j].nodeExecutions.length; k++) { - output.push(childGroups[i][j].nodeExecutions[k] as NodeExecution); - } - } - } - const executions: NodeExecution[] = output.concat(nodeExecutions); - return nodeExecutions.length > 0 ? ( - - ) : null; - }; - - return ( - - {renderGraphComponent} - - ); -}; diff --git a/packages/zapp/console/src/components/Executions/ExecutionDetails/ExecutionDetailsActions.tsx b/packages/zapp/console/src/components/Executions/ExecutionDetails/ExecutionDetailsActions.tsx index 0a62435a0..a4b39da2d 100644 --- a/packages/zapp/console/src/components/Executions/ExecutionDetails/ExecutionDetailsActions.tsx +++ b/packages/zapp/console/src/components/Executions/ExecutionDetails/ExecutionDetailsActions.tsx @@ -1,23 +1,70 @@ -import { Button } from '@material-ui/core'; +import { Button, Dialog, IconButton } from '@material-ui/core'; import * as React from 'react'; import { ResourceIdentifier, Identifier } from 'models/Common/types'; +import { makeStyles, Theme } from '@material-ui/core/styles'; import { getTask } from 'models/Task/api'; import { LaunchFormDialog } from 'components/Launch/LaunchForm/LaunchFormDialog'; import { NodeExecutionIdentifier } from 'models/Execution/types'; -import { useNodeExecutionData } from 'components/hooks/useNodeExecution'; +import { useNodeExecution, useNodeExecutionData } from 'components/hooks/useNodeExecution'; import { literalsToLiteralValueMap } from 'components/Launch/LaunchForm/utils'; import { TaskInitialLaunchParameters } from 'components/Launch/LaunchForm/types'; +import { NodeExecutionPhase } from 'models/Execution/enums'; +import Close from '@material-ui/icons/Close'; import { NodeExecutionDetails } from '../types'; import t from './strings'; +import { ExecutionNodeDeck } from './ExecutionNodeDeck'; + +const useStyles = makeStyles((theme: Theme) => { + return { + actionsContainer: { + borderTop: `1px solid ${theme.palette.divider}`, + marginTop: theme.spacing(2), + paddingTop: theme.spacing(2), + '& button': { + marginRight: theme.spacing(1), + }, + }, + dialog: { + maxWidth: `calc(100% - ${theme.spacing(12)}px)`, + maxHeight: `calc(100% - ${theme.spacing(12)}px)`, + height: theme.spacing(90), + width: theme.spacing(110), + '& iframe': { + border: 'none', + }, + }, + dialogTitle: { + display: 'flex', + alignItems: 'center', + padding: theme.spacing(2), + paddingBottom: theme.spacing(0), + fontFamily: 'Open sans', + }, + deckTitle: { + flexGrow: 1, + textAlign: 'center', + fontSize: '24px', + lineHeight: '32px', + marginBlock: 0, + paddingTop: theme.spacing(2), + paddingBottom: theme.spacing(2), + }, + close: { + position: 'absolute', + right: theme.spacing(2), + }, + }; +}); interface ExecutionDetailsActionsProps { - className?: string; details: NodeExecutionDetails; nodeExecutionId: NodeExecutionIdentifier; + phase?: NodeExecutionPhase; } export const ExecutionDetailsActions = (props: ExecutionDetailsActionsProps): JSX.Element => { - const { className, details, nodeExecutionId } = props; + const { details, nodeExecutionId, phase } = props; + const styles = useStyles(); const [showLaunchForm, setShowLaunchForm] = React.useState(false); @@ -26,6 +73,7 @@ export const ExecutionDetailsActions = (props: ExecutionDetailsActionsProps): JS >(undefined); const executionData = useNodeExecutionData(nodeExecutionId); + const execution = useNodeExecution(nodeExecutionId); const id = details.taskTemplate?.id; React.useEffect(() => { @@ -48,6 +96,9 @@ export const ExecutionDetailsActions = (props: ExecutionDetailsActionsProps): JS })(); }, [details]); + const [showDeck, setShowDeck] = React.useState(false); + const onCloseDeck = () => setShowDeck(false); + if (!id || !initialParameters) { return <>; } @@ -59,7 +110,17 @@ export const ExecutionDetailsActions = (props: ExecutionDetailsActionsProps): JS return ( <> -
+
+ {execution?.value?.closure?.deckUri && ( + + )} @@ -70,6 +131,17 @@ export const ExecutionDetailsActions = (props: ExecutionDetailsActionsProps): JS showLaunchForm={showLaunchForm} setShowLaunchForm={setShowLaunchForm} /> + {execution?.value?.closure?.deckUri ? ( + +
+

{t('flyteDeck')}

+ + + +
+ +
+ ) : null} ); }; diff --git a/packages/zapp/console/src/components/Executions/ExecutionDetails/ExecutionNodeDeck.tsx b/packages/zapp/console/src/components/Executions/ExecutionDetails/ExecutionNodeDeck.tsx new file mode 100644 index 000000000..e0b702915 --- /dev/null +++ b/packages/zapp/console/src/components/Executions/ExecutionDetails/ExecutionNodeDeck.tsx @@ -0,0 +1,14 @@ +import { useDownloadLocation } from 'components/hooks/useDataProxy'; +import { WaitForData } from 'components/common/WaitForData'; +import * as React from 'react'; + +/** Fetches and renders the deck data for a given `deckUri` */ +export const ExecutionNodeDeck: React.FC<{ deckUri: string }> = ({ deckUri }) => { + const downloadLocation = useDownloadLocation(deckUri); + + return ( + +