Deprecation message in Console Folder Readme

PR-URL: https://github.com/hasura/graphql-engine-mono/pull/8131
Co-authored-by: Nicolas Beaussart <7281023+beaussan@users.noreply.github.com>
GitOrigin-RevId: d0c187bb322e26105a8ecdb19da4e8478b7d09dd
This commit is contained in:
Manas Agarwal 2023-02-28 17:33:34 +05:30 committed by hasura-bot
parent 7872be0e82
commit fbb92230bb
2409 changed files with 5 additions and 421446 deletions

View File

@ -38,7 +38,7 @@ consisting of 3 components. Each has their own contributing guides:
2. [CLI (Go)](cli/CONTRIBUTING.md)
3. [Console (JavaScript)](console/README.md#contributing-to-hasura-console)
3. [Console (JavaScript)](frontend/docs/generic-info.md#contributing-to-hasura-console)
All of the three components have a single version, denoted by either the git tag or a combination of branch name and git commit SHA.

1
cli-ext/.gitignore vendored
View File

@ -1,6 +1,5 @@
bin
build
src/shared
_tmptests
node_modules
version.json

View File

@ -4,8 +4,6 @@
"description": "A service to generate Hasura action scaffolds",
"main": "src/server.js",
"scripts": {
"get-shared-modules": "rm -rf src/shared && cp -r ../console/src/shared ./src/shared",
"pretranspile": "npm run get-shared-modules",
"transpile": "rm -rf build/* && babel --extensions '.ts,.js' ./src ./tests --out-dir build",
"prebuild": "npm run transpile",
"build": "rm -rf ./bin/* && pkg ./build/command.js --output ./bin/cli-ext -t node12-linux-x64,node12-macos-x64,node12-win-x64,node12-linux-arm64,node16-macos-arm64",

View File

@ -1,39 +0,0 @@
{
"presets": [
[
"@babel/preset-env",
{
"loose": true
}
],
"@babel/preset-react",
"@babel/preset-typescript"
],
"plugins": [
"@babel/plugin-transform-runtime",
"babel-plugin-styled-components",
"transform-react-remove-prop-types",
[
"@babel/plugin-proposal-class-properties",
{
"loose": true
}
],
[
"@babel/plugin-proposal-private-methods",
{
"loose": true
}
],
[
"@babel/plugin-proposal-private-property-in-object",
{
"loose": true
}
],
"@babel/plugin-proposal-nullish-coalescing-operator",
"extract-hoc/babel",
"react-hot-loader/babel",
"istanbul"
]
}

View File

@ -1,2 +0,0 @@
webpack/*
*typegen*

View File

@ -1,298 +0,0 @@
{
"extends": ["airbnb", "prettier"],
"env": {
"browser": true,
"node": true,
"mocha": true,
"jest": true
},
"parser": "babel-eslint",
"rules": {
"no-useless-escape": 0,
"allowForLoopAfterthoughts": 0,
"react/no-multi-comp": 0,
"import/order": 0,
"import/default": 0,
"import/no-duplicates": 0,
"import/named": 0,
"import/first": 0,
"import/namespace": 0,
"import/no-unresolved": 0,
"import/no-named-as-default": 2,
"import/extensions": 0,
"import/no-extraneous-dependencies": 0,
"import/prefer-default-export": 0,
"comma-dangle": 0,
"id-length": [
1,
{
"min": 1,
"properties": "never"
}
],
"consistent-return": "off",
"indent": "off",
"no-console": 0,
"arrow-parens": 0,
"no-alert": 0,
"no-plusplus": 0,
"no-unsafe-negation": 0,
"no-loop-func": 0,
"no-lonely-if": 0,
"no-bitwise": 0,
"global-require": 0,
"no-param-reassign": 0,
"no-underscore-dangle": 0,
"no-useless-return": 0,
"no-restricted-syntax": 0,
"no-prototype-builtins": 0,
"array-callback-return": 0,
"no-useless-concat": 0,
"class-methods-use-this": 0,
"arrow-body-style": 0,
"prefer-template": 0,
"prefer-spread": 0,
"object-shorthand": 0,
"camelcase": 0,
"object-curly-newline": 0,
"spaced-comment": 0,
"prefer-destructuring": [
"error",
{
"object": false,
"array": false
}
],
"prefer-rest-params": 0,
"function-paren-newline": 0,
"no-case-declarations": 0,
"no-restricted-globals": 0,
"no-unneeded-ternary": 0,
"no-mixed-operators": 0,
"no-return-assign": 0,
"operator-assignment": 0,
"strict": 0,
"react/jsx-no-duplicate-props": 0,
"react/jsx-filename-extension": 0,
"react/jsx-curly-brace-presence": 0,
"react/forbid-prop-types": 0,
"react/require-default-props": 0,
"react/no-unused-prop-types": 0,
"react/no-string-refs": 0,
"react/no-unused-state": 0,
"react/no-array-index-key": 0,
"react/jsx-no-bind": 0,
"react/prop-types": 0,
"react/prefer-stateless-function": 0,
"react/no-unescaped-entities": 0,
"react/sort-comp": 0,
"react/jsx-indent": 0,
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn",
"jsx-a11y/click-events-have-key-events": 0,
"jsx-a11y/no-static-element-interactions": 0,
"jsx-a11y/no-noninteractive-element-interactions": 0,
"jsx-a11y/label-has-for": 0,
"jsx-a11y/anchor-is-valid": 0,
"jsx-a11y/lang": 0,
"jsx-a11y/alt-text": 0,
"jsx-a11y/no-autofocus": 0,
"max-len": 0,
"no-continue": 0,
"no-new": 0,
"eqeqeq": 0,
"no-nested-ternary": 0,
"react/forbid-dom-props": [
"error",
{
"forbid": [
{
"propName": "data-analytics-name",
"message": "Analytics attributes (data-analytics-name) should be added through the Analytics component/utilities"
},
{
"propName": "data-trackid",
"message": "Analytics attributes (data-trackid) should be added through the Analytics component/utilities"
},
{
"propName": "data-heap-redact-text",
"message": "Analytics attributes (data-heap-redact-text) should be added through the Analytics component/utilities"
},
{
"propName": "data-heap-redact-attributes",
"message": "Analytics attributes (data-heap-redact-attributes) should be added through the Analytics component/utilities"
}
]
}
],
"react/forbid-component-props": [
"error",
{
"forbid": [
{
"propName": "data-analytics-name",
"message": "Analytics attributes (data-analytics-name) should be added through the Analytics component/utilities"
},
{
"propName": "data-trackid",
"message": "Analytics attributes (data-trackid) should be added through the Analytics component/utilities"
},
{
"propName": "data-heap-redact-text",
"message": "Analytics attributes (data-heap-redact-text) should be added through the Analytics component/utilities"
},
{
"propName": "data-heap-redact-attributes",
"message": "Analytics attributes (data-heap-redact-attributes) should be added through the Analytics component/utilities"
}
]
}
]
},
"plugins": [
"react",
"import",
"@typescript-eslint/eslint-plugin",
"react-hooks",
"testing-library"
],
"settings": {
"import/resolve": {
"moduleDirectory": ["node_modules", "src"]
}
},
"globals": {
"__DEVELOPMENT__": true,
"__CLIENT__": true,
"__SERVER__": true,
"__DISABLE_SSR__": true,
"__DEVTOOLS__": true,
"socket": true,
"webpackIsomorphicTools": true,
"CONSOLE_ASSET_VERSION": true
},
"overrides": [
{
"extends": [
"plugin:@typescript-eslint/recommended",
"airbnb",
"prettier"
],
"parser": "@typescript-eslint/parser",
"files": ["*.ts", "*.tsx"],
"rules": {
"no-useless-escape": 0,
"no-restricted-imports": [
"error",
{
"patterns": ["@/features/*/*", "@/new-components/*/*"]
}
],
"jsx-a11y/label-has-for": [
0,
{
"required": {
"some": ["nesting", "id"]
}
}
],
"import/order": 0,
"import/extensions": 0,
"import/prefer-default-export": "off",
"react/prop-types": "off",
"react/jsx-filename-extension": 0,
"no-unused-vars": "off",
"camelcase": "off",
"no-param-reassign": "off",
"no-use-before-define": "off",
"consistent-return": "off",
"import/no-extraneous-dependencies": [
"error",
{
"devDependencies": [
"src/storybook/**/*",
"**/__tests__/**/*",
"**/*.test.js",
"**/*.test.jsx",
"**/*.spec.js",
"**/*.spec.jsx",
"**/*.test.ts",
"**/*.test.tsx",
"**/*.spec.ts",
"**/*.spec.tsx",
"**/*.stories.tsx",
"**/*.stories.mdx",
"**/*.mock.tsx",
"**/*.mock.ts"
]
}
],
"@typescript-eslint/no-unused-vars": 2,
"@typescript-eslint/no-use-before-define": "warn",
"@typescript-eslint/indent": 0,
"@typescript-eslint/explicit-function-return-type": 0,
"@typescript-eslint/prefer-interface": 0,
"@typescript-eslint/no-namespace": "off",
"@typescript-eslint/camelcase": 0,
"@typescript-eslint/explicit-member-accessibility": 0,
"@typescript-eslint/no-non-null-assertion": 0,
"@typescript-eslint/no-object-literal-type-assertion": "off",
"@typescript-eslint/no-empty-interface": "off",
"@typescript-eslint/no-parameter-properties": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-var-requires": "off",
"@typescript-eslint/no-shadow": "error",
"@typescript-eslint/no-useless-constructor": "error",
"@typescript-eslint/no-unused-expressions": ["error"],
"@typescript-eslint/no-empty-function": "off",
"@typescript-eslint/explicit-module-boundary-types": "off",
"no-restricted-syntax": "warn",
/**
* Disable things that are checked by TypeScript
*/
"import/no-unresolved": 0,
"getter-return": "off",
"no-dupe-args": "off",
"no-dupe-keys": "off",
"no-unreachable": "off",
"valid-typeof": "off",
"no-const-assign": "off",
"no-new-symbol": "off",
"no-this-before-super": "off",
"no-undef": "off",
"no-dupe-class-members": "off",
"no-redeclare": "off",
"no-restricted-globals": "warn",
"no-useless-constructor": "off",
"no-unused-expressions": "off",
"no-console": "off",
"no-shadow": "off",
"prefer-destructuring": "off",
"no-plusplus": "off",
"jsx-a11y/anchor-is-valid": "off",
"jsx-a11y/click-events-have-key-events": "off",
"jsx-a11y/no-static-element-interactions": "off",
"no-new": "off",
"no-nested-ternary": "off",
"jsx-a11y/interactive-supports-focus": "off",
"no-restricted-properties": "off",
"react/no-danger": "off",
"react/no-array-index-key": "off",
"no-case-declarations": 0,
"react/jsx-indent": "off",
"arrow-body-style": "off",
"react/require-default-props": "warn",
"react/no-unused-prop-types": "warn",
"jsx-a11y/role-supports-aria-props": "warn"
}
},
{
// 3) Now we enable eslint-plugin-testing-library rules or preset only for matching files!
"files": [
"src/**/__tests__/**/*.[jt]s?(x)",
"src/**/?(*.)+(spec|test).[jt]s?(x)"
],
"extends": ["plugin:testing-library/react", "plugin:jest-dom/recommended"]
}
]
}

41
console/.gitignore vendored
View File

@ -1,41 +0,0 @@
# Sensitive stuff
.env
docker-compose.yml
# NPM-related stuff
node_modules
npm-debug.log
.nvm
# Build-related stuff
static/dist
hasura-console-oss-1.0.0.tgz
packages/console-oss/build/
storybook-static
lib/
!src/lib
# Tests-related stuff
cypress/videos
cypress/screenshots
.nyc_output
coverage
# is it needed anymore?
test
cypress.env.json
# Webpack-related stuff
webpack-assets.json
webpack-stats.json
# Text-editors-related stuff
*.swp
.idea/*
**/.tern-port
*.iml
*.ipr
*.iws
# Hasura stuff
packages/console-oss/build/
hasura-console-oss-1.0.0.tgz

View File

@ -1,19 +0,0 @@
exports/
src/
scripts/
node_modules/
packages/
webpack/
webpack-assets.json
Makefile
coverage
hasuraconfig
pro-table.js
static/
tsconfig.json
bin/
cypress/screenshots/
.nyc_output/out.json
.*
hasuraconfig
CODE_SHARING_BW_OSS_PRO.md

View File

@ -1 +0,0 @@
legacy-peer-deps=true

View File

@ -1 +0,0 @@
v16.15.1

View File

@ -1 +0,0 @@
*.min.js

View File

@ -1,115 +0,0 @@
const util = require('util');
const webpack = require('webpack');
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
const path = require('path');
const remarkSlug = require('remark-slug');
const remarkExternalLinks = require('remark-external-links');
const isConfigDebugMode = process.env.STORYBOOK_CONFIG_LOG === 'debug';
module.exports = {
stories: ['../src/*.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx|mdx)'],
babel: async options => {
if (isConfigDebugMode) {
console.log('------BABEL--------');
console.log(util.inspect(options, { showHidden: false, depth: null }));
}
return options;
},
webpackFinal: async config => {
config.module.rules.push(
{
test: /\.scss$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 2,
modules: {
localIdentName: '[local]___[hash:base64:5]',
},
},
},
{
loader: 'sass-loader',
options: {
// Prefer `dart-sass`
implementation: require('sass'),
sassOptions: {
outputStyle: 'expanded',
},
sourceMap: true,
},
},
],
},
{
test: /\.mjs$/,
include: /node_modules/,
type: 'javascript/auto',
}
);
config.plugins.push(
new webpack.DefinePlugin({
CONSOLE_ASSET_VERSION: Date.now().toString(),
'process.hrtime': () => null,
__CLIENT__: true,
__SERVER__: false,
__DEVELOPMENT__: true,
__DEVTOOLS__: true, // <-------- DISABLE redux-devtools HERE
})
);
config.resolve.plugins.push(
new TsconfigPathsPlugin({
configFile: path.resolve(__dirname, '../tsconfig.json'),
})
);
// Fix storybook bug preventing ids to be added to MDX items https://github.com/storybookjs/storybook/issues/18395
config.module.rules.map(rule => {
const useWithMdxCsfLoader = rule?.use?.find(use =>
use?.loader?.includes('@storybook/mdx1-csf')
);
if (useWithMdxCsfLoader) {
useWithMdxCsfLoader.options = {
skipCsf: false,
remarkPlugins: [remarkSlug, remarkExternalLinks],
};
}
return rule;
});
config.resolve.alias['@'] = path.resolve(__dirname, '../src');
config.node = { fs: 'empty' };
if (isConfigDebugMode) {
console.log('------WEBPACK--------');
console.log(util.inspect(newConfig, { showHidden: false, depth: null }));
}
// Return the altered config
return config;
},
addons: [
{
name: '@storybook/addon-postcss',
options: {
postcssLoaderOptions: {
implementation: require('postcss'),
postcssOptions: {
config: path.resolve(__dirname, '../postcss-storybook.config.js'),
},
},
},
},
'@storybook/addon-docs',
'@storybook/addon-links',
'@storybook/addon-essentials',
'@storybook/addon-interactions',
'storybook-dark-mode/register',
],
features: {
interactionsDebugger: true,
babelModeV7: true,
},
};

View File

@ -1,12 +0,0 @@
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:ital,wght@0,300;0,400;0,500;0,600;0,700;1,300;1,400;1,500;1,600;1,700&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:ital,wght@0,300;0,400;0,500;0,700;1,300;1,400;1,600;1,700&family=IBM+Plex+Sans:ital,wght@0,300;0,400;0,500;0,600;0,700;1,300;1,400;1,500;1,600;1,700&display=swap" rel="stylesheet">
<link rel="apple-touch-icon" sizes="180x180" href="./images/favicon/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="./images/favicon/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="./images/favicon/favicon-16x16.png">
<link rel="manifest" href="./images/favicon/site.webmanifest">
<link rel="mask-icon" href="./images/favicon/safari-pinned-tab.svg" color="#5bbad5">
<meta name="msapplication-TileColor" content="#da532c">
<meta name="theme-color" content="#ffffff">

View File

@ -1,9 +0,0 @@
<script>
window.__env = {
dataApiUrl: 'http://localhost:8080',
apiHost: 'http://localhost',
apiPort: '8080',
nodeEnv: 'development',
};
</script>

View File

@ -1,49 +0,0 @@
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link
href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:ital,wght@0,300;0,400;0,500;0,600;0,700;1,300;1,400;1,500;1,600;1,700&display=swap"
rel="stylesheet">
<link
href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:ital,wght@0,300;0,400;0,500;0,700;1,300;1,400;1,600;1,700&family=IBM+Plex+Sans:ital,wght@0,300;0,400;0,500;0,600;0,700;1,300;1,400;1,500;1,600;1,700&display=swap"
rel="stylesheet">
<style>
/* Fix background bug on ArgsTable header background color */
thead.docblock-argstable-head tr th {
background-color: transparent;
}
/* Fix docblock-source background-color */
.docblock-source,
.prismjs {
background-color: #1c262f !important;
}
/* Add collapsible sections */
details .mdx-collapsible-section {
display: flex;
}
details .mdx-collapsible-section::marker {
display: none;
}
details .mdx-collapsible-section:hover {
cursor: pointer;
}
details .mdx-collapsible-section__chevron {
margin: 0;
transform: rotate(0deg);
transition: transform 0.1s ease-out;
}
details[open] .mdx-collapsible-section__chevron {
transform: rotate(90deg);
}
details .mdx-collapsible-section__label {
margin: 0;
padding-left: 0.5rem;
}
</style>

View File

@ -1,93 +0,0 @@
import React from 'react';
import { Provider } from 'react-redux';
import produce from 'immer';
import addons from '@storybook/addons';
import { DocsContainer, DocsPage } from '@storybook/addon-docs';
import { initialize, mswDecorator } from 'msw-storybook-addon';
import theme from './theme';
import '../src/theme/tailwind.css';
import { store } from '../src/store';
import '../src/components/Common/Common.module.scss';
import { DARK_MODE_EVENT_NAME } from 'storybook-dark-mode';
import 'react-loading-skeleton/dist/skeleton.css';
import { ToastsHub } from '../src/new-components/Toasts';
// Needed to use Jest mocks (jest.fn() and so on) in Storybook stories
import * as jest from 'jest-mock';
global.jest = jest;
const channel = addons.getChannel();
initialize();
export const parameters = {
actions: { argTypesRegex: '^on.*' },
options: {
storySort: {
order: ['Design system', 'Dev', 'Components', 'Hooks'],
},
},
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
docs: {
container: props => {
// Sync Docs dark mode with Storybook Manager
const [isDark, setDark] = React.useState();
React.useEffect(() => {
channel.on(DARK_MODE_EVENT_NAME, setDark);
return () => channel.removeListener(DARK_MODE_EVENT_NAME, setDark);
}, [channel, setDark]);
return (
<div className={(isDark ? 'dark' : 'light') + ' hasura-tailwind-on'}>
<DocsContainer
{...props}
context={{
...props.context,
storyById: id => {
return produce(props.context.storyById(id), draft => {
draft.parameters.docs.theme = isDark
? theme.dark
: theme.light;
});
},
}}
>
{props.children}
</DocsContainer>
</div>
);
},
page: DocsPage,
},
darkMode: {
dark: { ...theme.dark },
light: { ...theme.light },
},
};
export const decorators = [
(fn, c) => <Provider store={store}>{fn(c)}</Provider>,
mswDecorator,
Story => {
document.body.classList.add('hasura-tailwind-on');
return (
<>
<ToastsHub />
<div className={'bg-legacybg'}>{Story()}</div>
</>
);
},
];
export const argTypes = {
disableSnapshotTesting: {
table: {
disable: true,
},
},
};

View File

@ -1,78 +0,0 @@
const commonThemeProperties = {
fontBase:
'"IBM Plex Sans",system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,Noto Sans,sans-serif,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol"',
fontCode:
'"IBM Plex Mono Regular","IBM Plex Mono",SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace',
brandTitle: 'Hasura',
brandUrl: 'http://hasura.io',
brandTarget: '_self',
};
// Color theme reversed engineered from Hasura docs (https://hasura.io/docs/latest/graphql/core/index/)
export default {
light: {
...commonThemeProperties,
base: 'light',
colorPrimary: '#1599e2',
colorSecondary: '#1599e2',
// UI
appBg: '#f4f5f7',
appContentBg: 'white',
appBorderColor: '#dadde1',
appBorderRadius: 4,
// Text colors
textColor: '#344658',
textInverseColor: '#cbb9a7',
textMutedColor: '#8a929b',
// Toolbar default and active colors
barTextColor: '#141c22',
barSelectedColor: '#1699e2',
barBg: 'white',
// Form colors
inputBg: 'white',
inputBorder: '#dadde1',
inputTextColor: '#344658',
inputBorderRadius: 4,
brandImage: './images/hasura-storybook-dark.svg',
},
dark: {
...commonThemeProperties,
base: 'dark',
colorPrimary: '#1599e2',
colorSecondary: '#1599e2',
// UI
appBg: '#23303d',
appContentBg: '#131c22',
appBorderColor: '#616770',
appBorderRadius: 4,
// Text colors
textColor: '#dce2e8',
textInverseColor: '#231d17',
textMutedColor: '#92979c',
// Toolbar default and active colors
barTextColor: '#dce2e8',
barSelectedColor: '#dce2e8',
barBg: '#131c22',
// Form colors
inputBg: '#23303d',
inputBorder: '#616770',
inputTextColor: '#dce2e8',
inputBorderRadius: 4,
brandImage: './images/hasura-storybook-light.svg',
},
};

View File

@ -1,43 +0,0 @@
export PATH := node_modules/.bin:$(PATH)
DIST_PATH ?= ./static/dist
BUCKET_NAME ?= graphql-engine-cdn.hasura.io
VERSION ?= $(shell ../scripts/get-version.sh)
NODE_OPTIONS=--max-old-space-size=4096
all: deps build
deps:
NODE_OPTIONS=$(NODE_OPTIONS) npm ci
ci-deps:
if [ ! -d "node_modules" ]; then NODE_OPTIONS=$(NODE_OPTIONS) npm ci; fi
build:
NODE_OPTIONS=$(NODE_OPTIONS) npm run build
jest:
NODE_OPTIONS=$(NODE_OPTIONS) npm run jest -- --runInBand
test:
NODE_OPTIONS=$(NODE_OPTIONS) npm run dev & NODE_OPTIONS=$(NODE_OPTIONS) npm run test
# to be run inside circle-ci
ci-copy-assets:
mkdir -p /build/_console_output
cp $(DIST_PATH)/* /build/_console_output/
echo "$(VERSION)" > /build/_console_output/version.txt
server-build: node_modules $(DIST_PATH)/common
rm -rf "$(DIST_PATH)/versioned"
npm run build
mkdir -p "$(DIST_PATH)/versioned"
cp "$(DIST_PATH)"/*.js "$(DIST_PATH)"/*.css "$(DIST_PATH)/versioned/"
gzip -r -f "$(DIST_PATH)/versioned"
$(DIST_PATH)/common:
mkdir -p $(DIST_PATH)
gsutil -m cp -r gs://$(BUCKET_NAME)/console/assets/common "$(DIST_PATH)"
touch $@
node_modules: package.json package-lock.json
NODE_OPTIONS=$(NODE_OPTIONS) npm ci

View File

@ -1,14 +1,6 @@
<!-- prettier-ignore-start -->
# Hasura Console
# Deprecated
Please note that all console related code has been moved to `frontend` folder in this repo, which uses NX based build system. All old code (now deleted) in this directory has been deprecated as of Feb 7, 2023, or commits since the [v2.18.0-beta.1 tag](https://github.com/hasura/graphql-engine/tree/v2.18.0-beta.1). Please refer to this new [Readme](https://github.com/hasura/graphql-engine/blob/master/frontend/README.md).
The Hasura console is an admin dashboard to manage the connected database and to try out GraphQL APIs. It is a React application bundled with Webpack and the state is managed mostly using Redux.
## Table of contents
- [Generic info](./docs/console-generic-info.md)
- [Development Tooling](https://main--614d7904644d03004addd43b.chromatic.com/?path=/story/dev-tooling--page)
- [Design System's Storybook](https://main--614d7904644d03004addd43b.chromatic.com)
- [How to create/document new Components](./src/docs/dev/ComponentDoc.stories.mdx)
- [Cypress Dashboard](https://dashboard.cypress.io/projects/5yiuic)
- [Cypress README](./cypress/README.md)
More details on how to contribute can be found [here](https://github.com/hasura/graphql-engine/blob/master/CONTRIBUTING.md)

View File

@ -1,15 +0,0 @@
{
"folders": [
{
"path": "."
}
],
"settings": {
"cSpell.words": ["clsx"],
"tailwindCSS.experimental.classRegex": [
"tw\\w+ ?= ?`([^`]*)`",
"tw\\w+: ?`([^`]*)`"
],
"tailwindCSS.rootFontSize": 14
}
}

View File

@ -1,6 +0,0 @@
overwrite: true
schema: "src/features/ControlPlane/schema.json"
documents: "src/features/ControlPlane/queries.ts"
generates:
src/features/ControlPlane/generatedGraphQLTypes.ts:
plugins: ["typescript", "typescript-operations"]

View File

@ -1,40 +0,0 @@
import { defineConfig } from 'cypress';
import * as customTasks from './cypress/support/tasks';
type ConfigOptions = Parameters<typeof defineConfig>[0];
interface MyConfigOptions extends ConfigOptions {
useRelativeSnapshots?: boolean;
}
const myDefineConfig = (config: MyConfigOptions) => defineConfig(config);
export default myDefineConfig({
env: {
TEST_MODE: 'parallel',
MIGRATE_URL: 'http://localhost:9693/apis/migrate',
},
viewportWidth: 1280,
viewportHeight: 720,
chromeWebSecurity: false,
video: false,
projectId: '5yiuic',
numTestsKeptInMemory: 10,
e2e: {
// We've imported your old cypress plugins here.
// You may want to clean this up later by importing these.
setupNodeEvents(on, config) {
on('task', {
...customTasks,
});
return config;
},
baseUrl: 'http://localhost:3000',
specPattern: [
'cypress/e2e/**/*test.{js,jsx,ts,tsx}',
'cypress/support/**/*unit.test.{js,ts}',
],
},
useRelativeSnapshots: true,
});

View File

@ -1,18 +0,0 @@
{
"plugins": ["cypress", "chai-friendly"],
"env": {
"cypress/globals": true
},
"rules": {
"no-unused-expressions": "off",
"chai-friendly/no-unused-expressions": "error",
"no-underscore-dangle": "off",
"@typescript-eslint/no-unused-expressions": "off",
"no-plusplus": [
"error",
{
"allowForLoopAfterthoughts": true
}
]
}
}

View File

@ -1,57 +0,0 @@
# Test
## Useful resources
- [Cypress Dashboard for the Console project](https://dashboard.cypress.io/projects/5yiuic)
## Running all tests to generate coverage
1. Set the `TEST_MODE` field in `cypress.json` to `cli`
2. Run the command `npm run test` from the `console` directory to run all the tests.
## Running tests individually
Tests are modularized into following modules:
- API-Explorer
- Data
- Migration Mode
- Create Table
- Insert Browse
- Modify Table
- Table Relationships
- Table and View Permissions
- Views
To run the tests for the modules individually (say for create table),
- Go to the `cypress.json` and set the `env > TEST_MODE` variable to `ui`.
```json
{
"env": {
"TEST_MODE": "ui"
}
}
```
- Run the command `npm run cy:open` and click on `create-table > test.js`
## Writing Tests
- Read ups
- If this is your first time with cypress, check out this getting started [guide](https://docs.cypress.io/guides/getting-started/writing-your-first-test.html)
- Read cypress [best practices](https://docs.cypress.io/guides/references/best-practices.html)
- File Structure
The top-level directories in [console/cypress](../../console/cypress) are auto-generated by cypress except [helpers](../../console/cypress/helpers). To understand the use of each directory check out [Folder Structure](https://docs.cypress.io/guides/core-concepts/writing-and-organizing-tests.html#Folder-Structure)
[helpers](../../console/cypress/helpers) directory is used for sharing reusable functions/constants across tests. Before adding a resubale function in this directory, consider if it will be better as a custom cypress command, if so, then add it to [Support](../../console/cypress/support) directory following this [guide](https://docs.cypress.io/api/cypress-api/custom-commands.html), preferrably to [command.ts](../../console/cypress/support/commands.ts) file.
- Adding a Test
Tests go to [integration](../../console/cypress/integration) directory, where there are folders corresponding to Components in [Services](../../console/src/components/Services) directory (The top-level routes on the console).
Each of these folders contains different test folders, named after the particular feature they are testing. For example [create-table](../../console/cypress/integration/data/create-table) folder tests the functionality of creating a table from the console UI.

View File

@ -1,3 +0,0 @@
module.exports = {
"__version": "10.4.0"
}

View File

@ -1,25 +0,0 @@
import { getElementFromAlias } from '../../helpers/dataHelpers';
export const viewOnboarding = () => {
// Click on create
cy.get(getElementFromAlias('onboarding-popup'))
.should('be.visible')
.should('contain.text', `Hi there, let's get started with Hasura!`);
// cy.get(getElementFromAlias('btn-hide-for-now')).click();
};
export const hideNow = () => {
// Click on create
cy.get(getElementFromAlias('btn-hide-for-now')).click();
cy.get(getElementFromAlias('onboarding-popup')).should('not.exist');
};
export const dontShowAgain = () => {
// Click on create
cy.reload();
cy.get(getElementFromAlias('onboarding-popup')).should('be.visible');
cy.get(getElementFromAlias('btn-ob-dont-show-again')).click();
cy.get(getElementFromAlias('onboarding-popup')).should('not.exist');
cy.reload();
cy.get(getElementFromAlias('onboarding-popup')).should('not.exist');
};

View File

@ -1,39 +0,0 @@
import { viewOnboarding, hideNow, dontShowAgain } from './spec';
import { testMode } from '../../helpers/common';
import { setMetaData } from '../validators/validators';
import { getIndexRoute } from '../../helpers/dataHelpers';
const setup = () => {
// Temporarily skipped because of its flakiness, see: https://github.com/hasura/graphql-engine-mono/issues/5433
// TODO: Fix and restore it
describe.skip('Setup route', () => {
it('Visit the index route', () => {
cy.visit(getIndexRoute());
setMetaData();
});
});
};
export const runActionsTests = () => {
describe('onboarding', () => {
// Temporarily skipped because of its flakiness, see: https://github.com/hasura/graphql-engine-mono/issues/5433
// TODO: Fix and restore it
it.skip('should show onboarding guide', viewOnboarding);
// Temporarily skipped because of its flakiness, see: https://github.com/hasura/graphql-engine-mono/issues/5433
// TODO: Fix and restore it
it.skip('should hide when user click on Hide Now', hideNow);
// Temporarily skipped because of its flakiness, see: https://github.com/hasura/graphql-engine-mono/issues/5433
// TODO: Fix and restore it
it.skip(
'should hide forever when user click on Dont Show again',
dontShowAgain
);
});
};
if (testMode !== 'cli') {
setup();
runActionsTests();
}

View File

@ -1,164 +0,0 @@
import { testMode } from '../../../helpers/common';
import { logMetadataRequests } from './utils/requests/logMetadataRequests';
import { addNumbersActionMustNotExist } from './utils/testState/addNumbersActionMustNotExist';
// NOTE: This test suite does not include cases for relationships, headers and the codegen part
if (testMode !== 'cli') {
// Temporarily skipped because of its flakiness, see: https://github.com/hasura/graphql-engine-mono/issues/5433
// TODO: Fix and restore it
describe.skip('Query Actions', () => {
before(() => {
addNumbersActionMustNotExist();
logMetadataRequests();
cy.visit('/actions/manage/actions');
});
after(() => {
// Cleanup after the whole test file run
// Ensure the application is not there when manually deleting the created action to avoid any
// potential client-side error that makes the test fail
cy.visitEmptyPage();
// Delete the created action, if any
addNumbersActionMustNotExist();
});
it('When the users create, edit, and delete a Query Action, everything should work', () => {
cy.log('**------------------------------**');
cy.log('**------------------------------**');
cy.log('**------------------------------**');
cy.log('**--- Step 1: Query Action creation**');
cy.log('**------------------------------**');
cy.log('**------------------------------**');
cy.log('**------------------------------**');
// --------------------
cy.log('**--- Click on the Create button of the Actions panel**');
cy.getBySel('data-create-actions').click();
// --------------------
// Assign an alias to the most unclear selectors for future references
cy.get('textarea').eq(0).as('actionDefinitionTextarea');
cy.get('textarea').eq(1).as('typeConfigurationTextarea');
// --------------------
cy.log('**--- Type in the Action Definition textarea**');
cy.get('@actionDefinitionTextarea')
.clearConsoleTextarea()
.type(
`type Query {
addNumbers (numbers: [Int]): AddResult
}`,
{ force: true, delay: 0 }
);
// --------------------
cy.log('**--- Type in the Type Configuration textarea**');
cy.get('@typeConfigurationTextarea')
.clearConsoleTextarea()
.type(
`type AddResult {
sum: Int
}`,
{ force: true, delay: 0 }
);
// --------------------
cy.log('**--- Type in the Webhook Handler field**');
cy.getBySel('action-create-handler-input')
.clearConsoleTextarea()
.type('https://hasura-actions-demo.glitch.me/addNumbers', {
delay: 0,
parseSpecialCharSequences: false,
});
// Due to the double server/cli mode behavior, we do not assert about the XHR request payload here
// --------------------
cy.log('**--- Click the Create button**');
cy.getBySel('create-action-btn').click();
// Due to the double server/cli mode behavior, we do not assert about the XHR request payload here
// --------------------
cy.log('**--- Check if the success notification is visible**');
cy.expectSuccessNotificationWithTitle('Created action successfully');
cy.log('**------------------------------**');
cy.log('**------------------------------**');
cy.log('**------------------------------**');
cy.log('**--- Step 2: Permission add and Handler change**');
cy.log('**------------------------------**');
cy.log('**------------------------------**');
cy.log('**------------------------------**');
// --------------------
cy.log('**--- Go the the action page**');
cy.getBySel('actions-table-links').within(() => {
cy.getBySel('addNumbers').click();
});
// --------------------
cy.log('**--- Type in the Webhook Handler field**');
cy.getBySel('action-create-handler-input')
.clearConsoleTextarea()
.type('http://host.docker.internal:3000', {
delay: 0,
// parseSpecialCharSequences: false,
});
// --------------------
cy.log('**--- Click on the Save button**');
cy.getBySel('save-modify-action-changes').click();
// --------------------
cy.log('**--- Click the Permissions tab**');
cy.getBySel('actions-permissions').click();
// --------------------
cy.log('**--- Enter a new role**');
cy.getBySel('role-textbox').type('manager');
cy.getBySel('manager-Permission').click();
// --------------------
cy.log('**--- Click Save Permissions**');
cy.getBySel('save-permissions-for-action').click();
// --------------------
cy.log('**--- Check if the success notification is visible**');
cy.expectSuccessNotificationWithTitle('Permission saved successfully');
cy.log('**------------------------------**');
cy.log('**------------------------------**');
cy.log('**------------------------------**');
cy.log('**--- Step 3: Query Action delete**');
cy.log('**------------------------------**');
cy.log('**------------------------------**');
cy.log('**------------------------------**');
// --------------------
cy.log('**--- Go the the action page**');
cy.getBySel('actions-table-links').within(() => {
cy.getBySel('addNumbers').click();
});
// --------------------
cy.log('**--- Set the prompt value**');
cy.window().then(win => cy.stub(win, 'prompt').returns('addNumbers'));
cy.log('**--- Click the Delete button**');
cy.getBySel('delete-action').click();
// Due to the double server/cli mode behavior, we do not assert about the XHR request payload here
// --------------------
cy.log('**--- Check if the success notification is visible**');
cy.expectSuccessNotificationWithTitle('Action deleted successfully');
});
});
}

View File

@ -1,3 +0,0 @@
module.exports = {
"__version": "10.4.0"
}

View File

@ -1,36 +0,0 @@
interface SingleMetadataRequest {
type: string;
// There are a lot of other fields, but tracking them is not important for the purpose of this module
}
interface BulkMetadataRequest {
type: 'bulk';
args: SingleMetadataRequest[];
}
type MetadataRequest = SingleMetadataRequest | BulkMetadataRequest;
/*
* Log all the requests outgoing to the Metadata endpoint.
* This is useful to have a glance of the requests that are going to the server.
*/
export function logMetadataRequests() {
cy.intercept('POST', 'http://localhost:8080/v1/metadata', req => {
const noArgs = !req.body.args;
if (noArgs) return;
const requestBody = req.body as MetadataRequest;
if (requestBody.type === 'bulk' || requestBody.type === 'concurrent_bulk') {
const request = requestBody as BulkMetadataRequest;
Cypress.log({ message: '*--- Bulk request*' });
request.args.forEach(arg =>
Cypress.log({ message: `*--- Request: ${arg.type}*` })
);
} else {
Cypress.log({ message: `*--- Request: ${requestBody.type}*` });
}
});
}

View File

@ -1,107 +0,0 @@
/**
* Wait for a bunch of requests to be settled before proceeding with the test.
*
* Alternatively, https://github.com/bahmutov/cypress-network-idle could be used
*
* This is a workaround for "element is 'detached' from the DOM" Cypress' error (see the issue
* linked below). Since the UI gets re-rendered because of the requests, this utility ensures that
* all the requests parallelly made by the UI are settled before proceeding with the test. Hance, it
* ensure the UI won't re-render during the next interaction.
*
* What are the requests that must be awaited? By looking at the Cypress Test Runner, they are the
* following, made parallelly or in a rapid series.
* 1. export_metadata
* 2. export_metadata
* 3. export_metadata
* 4. test_webhook_transform
* 5. test_webhook_transform
* 6. test_webhook_transform
* 7. test_webhook_transform
* At the moment of writing, I'm not sure the number of requests are fixed or not. If they are fixed,
* using the cy.intercept `times` options would result in a more expressive and less convoluted code.
*
* To give you an overall idea, this is a timeline of the requests
*
* all requests start all requests end
* | | | |
* |--🚦🔴--1--2--3--4--5--6--7----------------------------1--2--3--4--5--6-7--🚦🟢--|
*
*
* ATTENTION: Despite the defensive approach and the flakiness-removal purpose, this function could
* introduced even more flakiness because of its empiric approach. In case of failures, it must be
* carefully evaluated when/if keeping it or thinking about a better approach.
* In generale, this solution does not scale, at should not be spread among the tests.
*
* @see https://github.com/cypress-io/cypress/issues/7306
* @see https://glebbahmutov.com/blog/detached/
* @see https://github.com/bahmutov/cypress-network-idle
*/
import 'cypress-wait-until';
export function waitForPostCreationRequests() {
let waitCompleted = false;
cy.log('*--- All requests must be settled*');
const pendingRequests = new Map();
cy.intercept('POST', 'http://localhost:8080/v1/metadata', req => {
if (waitCompleted) return;
Cypress.log({ message: '*--- Request pending*' });
pendingRequests.set(req, true);
req.continue(() => {
Cypress.log({ message: '*--- Request settled*' });
pendingRequests.delete(req);
});
});
Cypress.log({ message: '*--- Waiting for the first request to start*' });
// Check if at least one request has been caught. This check must protect from the following case
//
// check requests start test failure, the requests got the UI re-rendered
// | | |
// |--🚦🔴----⚠️---🚦🟢-------1-2-3-4-5-6-7-1----------💥
//
// where checking that "there are no pending requests" falls in the false positive case where
// there are no pending requests because no one started at all.
//
// The check runs every millisecond to be 100% sure that no request can escape (ex. because of a
// super fast server). A false-negative case represented here
//
// requests start requests end check check test failure, no first request caught
// | | | | | | |
// |--🚦🔴--1-2-3-4-5-6-7-1-2-3-4-5-6-7--⚠️------------------⚠️------------------💥
cy.waitUntil(() => pendingRequests.size > 0, {
timeout: 5000, // 5 seconds is the default Cypress wait for a request to start
interval: 1,
errorMsg: 'No first request caught',
});
Cypress.log({ message: '*--- Waiting for all the requests to start*' });
// Let pass some time to collect all the requests. Otherwise, it could detect that the first
// request complete and go on with the test, even if another one will be performed in a while.
//
// This fixed wait protects from the following timeline
//
// 1st request start first request end other requests start test failure, the requests got the UI re-rendered
// | | | |
// |--🚦🔴---1---------------------1----🚦🟢----------------2-3-4-5-6-7-1----------💥
//
// Obviously, it is an empiric waiting, that also slows down the test.
cy.wait(500);
Cypress.log({ message: '*--- Waiting for all the requests to be settled*' });
cy.waitUntil(() => pendingRequests.size === 0, {
timeout: 30000, // 30 seconds is the default Cypress wait for the request to complete
errorMsg: 'Some requests are not settled yet',
}).then(() => {
waitCompleted = true;
});
}

View File

@ -1,13 +0,0 @@
/**
* Delete the Action straight from the server.
*/
export function deleteAddNumbersAction() {
Cypress.log({ message: '**--- Action delete: start**' });
return cy
.request('POST', 'http://localhost:8080/v1/metadata', {
type: 'drop_action',
args: { name: 'addNumbers' },
})
.then(() => Cypress.log({ message: '**--- Action delete: end**' }));
}

View File

@ -1,15 +0,0 @@
/**
* Read the Metadata straight from the server.
*/
export function readMetadata() {
Cypress.log({ message: '**--- Metadata read: start**' });
return cy
.request('POST', 'http://localhost:8080/v1/metadata', {
args: {},
type: 'export_metadata',
})
.then(_response => {
Cypress.log({ message: '**--- Metadata read: end**' });
});
}

View File

@ -1,21 +0,0 @@
import { readMetadata } from '../services/readMetadata';
import { deleteAddNumbersAction } from '../services/deleteAddNumbersAction';
/**
* Ensure the Action does not exist.
*/
export function addNumbersActionMustNotExist() {
Cypress.log({ message: '**--- Action check: start**' });
readMetadata().then(response => {
const actionExists = !!response.body.actions?.find(
// TODO: properly type it
action => action.name === 'addNumbers'
);
if (actionExists) {
Cypress.log({ message: '**--- The Action must be deleted**' });
deleteAddNumbersAction();
}
});
}

View File

@ -1,59 +0,0 @@
// const setup = () => {
// describe.skip('Setup route', () => {
// it('Visit the index route', () => {
// cy.visit('/actions/manage/actions');
// // Get and set validation metadata
// setMetaData();
// });
// });
// };
// TODO: what about the codegen part? Why is it not tested?
// export const runActionsTests = () => {
// describe.skip('Actions', () => {
// The test has been moved to mutationAction.e2e.test
// it('Create Mutation Action', createMutationAction);
// The test was commented before moving the other ones to mutationAction.e2e.test
// it('Verify Mutation Actions on GraphiQL', verifyMutation);
// The test has been moved to mutationAction.e2e.test
// it('Modify Mutation Action', modifyMutationAction);
// The test has been moved to mutationAction.e2e.test
// it('Delete Mutation Action', deleteMutationAction);
// The test has been moved to queryAction.e2e.test.e2e.test
// it('Create Query Action', createQueryAction);
// The test was commented before moving the other ones to queryAction.e2e.test
// it('Verify Query Actions on GraphiQL', verifyQuery);
// The test has been moved to queryAction.e2e.test.e2e.test
// it('Modify Query Action', modifyQueryAction);
// The test has been moved to queryAction.e2e.test.e2e.test
// it('Delete Query Action', deleteQueryAction);
// The test has been moved to actionWithTransform.e2e.test.ts
// it('Create Action With Transform', createActionTransform);
// The test has been moved to actionWithTransform.e2e.test.ts
// it('Update Action With Transform', modifyActionTransform);
// The test has been moved to actionWithTransform.e2e.test.ts
// it('Delete Action With Transform', deleteActionTransform);
// The test has been moved to v1ActionWithTransform.e2e.test.ts
// it(
// 'Create an action with V1 Transform and edit it through console, which will lead to the action being saved as V2',
// modifyV1ActionTransform
// );
// });
// };
// if (testMode !== 'cli') {
// setup();
// runActionsTests();
// }

View File

@ -1,350 +0,0 @@
import { testMode } from '../../../helpers/common';
import { logMetadataRequests } from './utils/requests/logMetadataRequests';
import { readMetadata } from './utils/services/readMetadata';
import { loginActionMustNotExist } from './utils/testState/loginActionMustNotExist';
import { Metadata } from '../../../../src/features/hasura-metadata-types';
import { checkMetadataPayload } from '../../utils/checkMetadataPayload';
if (testMode !== 'cli') {
describe('Actions with Transform', () => {
before(() => {
loginActionMustNotExist();
logMetadataRequests();
cy.visit('/actions/manage/actions');
});
after(() => {
// Delete the created action, if any
loginActionMustNotExist();
});
it('When the users create, and delete a Action with Transform, everything should work', () => {
cy.log('**------------------------------**');
cy.log('**------------------------------**');
cy.log('**------------------------------**');
cy.log('**--- Step 1: Action with Transform creation**');
cy.log('**------------------------------**');
cy.log('**------------------------------**');
cy.log('**------------------------------**');
// --------------------
cy.log('**--- Click on the Create button of the Actions panel**');
cy.getBySel('data-create-actions').click();
// Assign an alias to the most unclear selectors for future references
cy.get('textarea').eq(0).as('actionDefinitionTextarea');
cy.get('textarea').eq(1).as('typeConfigurationTextarea');
// --------------------
cy.log('**--- Type in the Action Definition textarea**');
cy.get('@actionDefinitionTextarea')
.clearConsoleTextarea()
.type(
`type Mutation {
login (username: String!, password: String!): LoginResponse
}`,
{ force: true, delay: 0 }
);
// --------------------
cy.log('**--- Type in the Type Configuration textarea**');
cy.get('@typeConfigurationTextarea')
.clearConsoleTextarea()
.type(
`type LoginResponse {
accessToken: String!
}`,
{ force: true, delay: 0 }
);
// --------------------
cy.log('**--- Click the Add Request Options Transform button**');
cy.contains('Add Request Options Transform').click();
cy.log('**------------------------------**');
cy.log('**--- Step 1.1: Add URL**');
cy.log('**------------------------------**');
cy.get('[data-cy="Change Request Options"]').within(() => {
// --------------------
cy.log('**--- Choose POST**');
cy.contains('POST').click();
// --------------------
cy.log('**--- Type in the Request URL Template field**');
cy.get('[placeholder="URL Template (Optional)..."]').type('/users');
});
cy.log('**------------------------------**');
cy.log('**--- Step 1.2: Add Env Var**');
cy.log('**------------------------------**');
// --------------------
cy.log('**--- Type in the Webhook Handler field**');
cy.getBySel('action-create-handler-input')
.clearConsoleTextarea()
.type('{{MY_WEBHOOK}}', {
delay: 0,
parseSpecialCharSequences: false,
});
// --------------------
cy.log('**--- Click the Show Sample Context button**');
cy.contains('Show Sample Context').click();
// --------------------
cy.log('**--- Type in the Env Variables Key field**');
cy.getBySel('transform-env-vars-kv-key-0').type('MY_WEBHOOK', {
delay: 1,
});
cy.log('**--- Type in the Env Variables Value field**');
cy.getBySel('transform-env-vars-kv-value-0').type('https://handler.com', {
delay: 1,
});
// --------------------
cy.get('[data-cy="Change Request Options"]').within(() => {
cy.log('**--- Check the Preview of the Request URL Template**');
cy.getBySel('transform-requestUrl-preview').should(
'have.value',
'https://handler.com/users'
);
});
cy.log('**------------------------------**');
cy.log('**--- Step 1.3: Add path**');
cy.log('**------------------------------**');
// --------------------
cy.log('**--- Type in the Webhook Handler field**');
cy.getBySel('action-create-handler-input')
.clearConsoleTextarea()
.type('https://hasura-actions-demo.glitch.me', {
delay: 0,
parseSpecialCharSequences: false,
});
cy.log('**------------------------------**');
cy.log('**--- Step 1.4: Query Params add**');
cy.log('**------------------------------**');
cy.get('[placeholder="URL Template (Optional)..."]')
.clearConsoleTextarea()
.type('/{{$body.action.name}}', { parseSpecialCharSequences: false });
// --------------------
cy.log('**--- Type in the first Query Params Key field**');
cy.getBySel('transform-query-params-kv-key-0').type('id');
cy.log('**--- Type in the first Query Params Value field**');
cy.getBySel('transform-query-params-kv-value-0').type('5');
// --------------------
cy.log('**--- Type in the second Query Params Key field**');
cy.getBySel('transform-query-params-kv-key-1').type('name');
cy.log('**--- Type in the second Query Params Value field**');
cy.getBySel('transform-query-params-kv-value-1').type(
'{{$body.action.name}}',
{
parseSpecialCharSequences: false,
delay: 0,
}
);
// --------------------
cy.get('[data-cy="Change Request Options"]').within(() => {
cy.log('**--- Check the Preview of the Request URL Template**');
cy.findByLabelText('Preview').should(
'have.value',
'https://hasura-actions-demo.glitch.me/login?name=login&id=5'
);
});
cy.log('**------------------------------**');
cy.log('**--- Step 1.5: Add Payload Transform**');
cy.log('**------------------------------**');
// --------------------
cy.log('**--- Click the Add Payload Transform button**');
cy.contains('Add Payload Transform').click();
// --------------------
cy.get('[data-cy="Change Payload"]').within(() => {
// Assign an alias to the most unclear selectors for future references
cy.get('textarea').eq(1).as('payloadTransformRequestBody');
cy.log('**--- Type in the Payload Transform Request Body textarea**');
cy.get('@payloadTransformRequestBody')
.wait(500)
.clearConsoleTextarea()
.wait(1000) // Work around the fact that this test fails in CI but not locally
.clearConsoleTextarea()
.wait(1000) // Work around the fact that this test fails in CI but not locally
.type(
`{
"userInfo": {
"name": {{$body.input.username}},
"password": {{$body.input.password}},
"type": {{$body.action.name}}
`,
// delay is set to 1 because setting it to 0 causes the test to fail because writes
// something like
// "name": {{$body.input.username}}name
// in the textarea (the closing "name" is a mistake)
{ force: true, delay: 1, parseSpecialCharSequences: false }
);
});
cy.log('**------------------------------**');
cy.log('**--- Step 1.5: Add Response Transform**');
cy.log('**------------------------------**');
// --------------------
cy.log('**--- Click the Add Response Transform button**');
cy.contains('Add Response Transform').click({ force: true });
// --------------------
cy.get('[data-cy="Change Response"]').within(() => {
// Assign an alias to the most unclear selectors for future references
cy.get('textarea').eq(0).as('responseTransformResponseBody');
cy.log('**--- Type in the Response Transform Response Body textarea**');
cy.get('@responseTransformResponseBody')
.wait(500)
.clearConsoleTextarea()
.wait(500)
.clearConsoleTextarea()
.wait(500)
.type(
`{
"userInfo": {
"name": {{$body.input.username}},
"password": {{$body.input.password}},
"type": {{$body.action.name}}
`,
// delay is set to 1 because setting it to 0 causes the test to fail because writes
// something like
// "name": {{$body.input.username}}name
// in the textarea (the closing "name" is a mistake)
{ force: true, delay: 1, parseSpecialCharSequences: false }
);
});
// --------------------
cy.log('**--- Click the Create button**');
// cy.wait(1000) because of debounce
cy.intercept('POST', 'http://localhost:8080/v1/metadata', req => {
if (JSON.stringify(req.body).includes('create_action')) {
req.alias = 'createAction';
}
req.continue();
});
cy.intercept('POST', 'http://localhost:9693/apis/migrate', req => {
if (JSON.stringify(req.body).includes('create_action')) {
req.alias = 'createAction';
}
});
cy.wait(1000);
cy.getBySel('create-action-btn').click();
cy.wait('@createAction').then(interception => {
checkMetadataPayload(interception, { name: 'Action payload' });
});
// --------------------
cy.log('**--- Check if the success notification is visible**');
cy.expectSuccessNotificationWithTitle('Created action successfully');
// -------------------------------------------------------------------------
// see: https://github.com/hasura/graphql-engine-mono/issues/5433
// The "Action change" part has been removed since it caused Cypress to crash
// TODO: identify the crashing reason
// -------------------------------------------------------------------------
// cy.log('**------------------------------**');
// cy.log('**------------------------------**');
// cy.log('**------------------------------**');
// cy.log('**--- Step 2: Action change**');
// cy.log('**------------------------------**');
// cy.log('**------------------------------**');
// cy.log('**------------------------------**');
readMetadata().then((md: { body: Metadata['metadata'] }) => {
cy.wrap(
(md.body.actions || []).find(action => action.name === 'login')
).snapshot({
name: 'Action metadata',
});
});
// // --------------------
cy.log('**--- Wait all the requests to be settled**');
// cy.get('[data-cy="Change Request Options"]').within(() => {
// // --------------------
// cy.log('**--- Choose GET**');
// cy.contains('GET').click();
// // --------------------
// cy.log('**--- Type in the Request URL Template field**');
// cy.get('[placeholder="URL Template (Optional)..."]')
// .clearConsoleTextarea()
// .type('/{{$body.action.name}}/actions', {
// delay: 0,
// parseSpecialCharSequences: false,
// });
// // --------------------
// cy.log('**--- Click on the first Remove Query Param button**');
// cy.getBySel('transform-query-params-kv-remove-button-0').click();
// });
// // --------------------
// cy.log('**--- Click the Remove Payload Transform button**');
// cy.contains('Remove Payload Transform').click();
// // --------------------
// cy.log('**--- Click on the Save button**');
// cy.getBySel('save-modify-action-changes').click();
// // --------------------
// cy.log('**--- Check if the success notification is visible**');
// cy.expectSuccessNotificationWithTitle('Action saved successfully');
cy.log('**------------------------------**');
cy.log('**------------------------------**');
cy.log('**------------------------------**');
cy.log('**--- Step 3: Action delete**');
cy.log('**------------------------------**');
cy.log('**------------------------------**');
cy.log('**------------------------------**');
// --------------------
cy.log('**--- Go the the action page**');
cy.getBySel('actions-table-links').within(() => {
cy.getBySel('login').click();
});
// --------------------
cy.log('**--- Set the prompt value**');
cy.window().then(win => cy.stub(win, 'prompt').returns('login'));
cy.log('**--- Click the Delete button**');
cy.getBySel('delete-action').click();
// --------------------
cy.log('**--- Check the prompt has been called**');
cy.window().its('prompt').should('be.called');
// --------------------
cy.log('**--- Check if the success notification is visible**');
cy.expectSuccessNotificationWithTitle('Action deleted successfully');
});
});
}

View File

@ -1,123 +0,0 @@
module.exports = {
"__version": "10.4.0",
"Actions with Transform": {
"When the users create, and delete a Action with Transform, everything should work": {
"Action payload": {
"bodyToSnapshot": [
{
"type": "set_custom_types",
"args": {
"scalars": [],
"input_objects": [],
"objects": [
{
"name": "LoginResponse",
"description": null,
"fields": [
{
"name": "accessToken",
"type": "String!",
"description": null
}
]
}
],
"enums": []
}
},
{
"type": "create_action",
"args": {
"name": "login",
"definition": {
"arguments": [
{
"name": "username",
"type": "String!",
"description": null
},
{
"name": "password",
"type": "String!",
"description": null
}
],
"kind": "synchronous",
"output_type": "LoginResponse",
"handler": "https://hasura-actions-demo.glitch.me",
"type": "mutation",
"headers": [],
"forward_client_headers": false,
"timeout": null,
"request_transform": {
"version": 2,
"template_engine": "Kriti",
"method": "POST",
"url": "{{$base_url}}/{{$body.action.name}}",
"query_params": {
"id": "5",
"name": "{{$body.action.name}}"
},
"body": {
"action": "transform",
"template": "{\n \"userInfo\": {\n \"name\": {{$body.input.username}},\n \"password\": {{$body.input.password}},\n \"type\": {{$body.action.name}}\n \n }\n}"
}
},
"response_transform": {
"version": 2,
"body": {
"action": "transform",
"template": "{\n \"userInfo\": {\n \"name\": {{$body.input.username}},\n \"password\": {{$body.input.password}},\n \"type\": {{$body.action.name}}\n \n }\n}"
},
"template_engine": "Kriti"
}
},
"comment": null
}
}
]
},
"Action metadata": {
"name": "login",
"definition": {
"handler": "https://hasura-actions-demo.glitch.me",
"output_type": "LoginResponse",
"arguments": [
{
"name": "username",
"type": "String!"
},
{
"name": "password",
"type": "String!"
}
],
"request_transform": {
"body": {
"action": "transform",
"template": "{\n \"userInfo\": {\n \"name\": {{$body.input.username}},\n \"password\": {{$body.input.password}},\n \"type\": {{$body.action.name}}\n \n }\n}"
},
"method": "POST",
"query_params": {
"id": "5",
"name": "{{$body.action.name}}"
},
"template_engine": "Kriti",
"url": "{{$base_url}}/{{$body.action.name}}",
"version": 2
},
"response_transform": {
"body": {
"action": "transform",
"template": "{\n \"userInfo\": {\n \"name\": {{$body.input.username}},\n \"password\": {{$body.input.password}},\n \"type\": {{$body.action.name}}\n \n }\n}"
},
"template_engine": "Kriti",
"version": 2
},
"type": "mutation",
"kind": "synchronous"
}
}
}
}
}

View File

@ -1,36 +0,0 @@
interface SingleMetadataRequest {
type: string;
// There are a lot of other fields, but tracking them is not important for the purpose of this module
}
interface BulkMetadataRequest {
type: 'bulk';
args: SingleMetadataRequest[];
}
type MetadataRequest = SingleMetadataRequest | BulkMetadataRequest;
/*
* Log all the requests outgoing to the Metadata endpoint.
* This is useful to have a glance of the requests that are going to the server.
*/
export function logMetadataRequests() {
cy.intercept('POST', 'http://localhost:8080/v1/metadata', req => {
const noArgs = !req.body.args;
if (noArgs) return;
const requestBody = req.body as MetadataRequest;
if (requestBody.type === 'bulk' || requestBody.type === 'concurrent_bulk') {
const request = requestBody as BulkMetadataRequest;
Cypress.log({ message: '*--- Bulk request*' });
request.args.forEach(arg =>
Cypress.log({ message: `*--- Request: ${arg.type}*` })
);
} else {
Cypress.log({ message: `*--- Request: ${requestBody.type}*` });
}
});
}

View File

@ -1,13 +0,0 @@
/**
* Delete the Action straight from the server.
*/
export function deleteLoginAction() {
Cypress.log({ message: '**--- Action delete: start**' });
return cy
.request('POST', 'http://localhost:8080/v1/metadata', {
type: 'drop_action',
args: { name: 'login' },
})
.then(() => Cypress.log({ message: '**--- Action delete: end**' }));
}

View File

@ -1,13 +0,0 @@
/**
* Delete the Action straight from the server.
*/
export function deleteV1LoginAction() {
Cypress.log({ message: '**--- Action delete: start**' });
return cy
.request('POST', 'http://localhost:8080/v1/metadata', {
type: 'drop_action',
args: { name: 'v1Login' },
})
.then(() => Cypress.log({ message: '**--- Action delete: end**' }));
}

View File

@ -1,15 +0,0 @@
/**
* Read the Metadata straight from the server.
*/
export function readMetadata() {
Cypress.log({ message: '**--- Metadata read: start**' });
return cy
.request('POST', 'http://localhost:8080/v1/metadata', {
args: {},
type: 'export_metadata',
})
.then(_response => {
Cypress.log({ message: '**--- Metadata read: end**' });
});
}

View File

@ -1,21 +0,0 @@
import { readMetadata } from '../services/readMetadata';
import { deleteLoginAction } from '../services/deleteLoginAction';
/**
* Ensure the Action does not exist.
*/
export function loginActionMustNotExist() {
Cypress.log({ message: '**--- Action check: start**' });
readMetadata().then(response => {
const actionExists = !!response.body.actions?.find(
// TODO: properly type it
action => action.name === 'login'
);
if (actionExists) {
Cypress.log({ message: '**--- The Action must be deleted**' });
deleteLoginAction();
}
});
}

View File

@ -1,3 +0,0 @@
module.exports = {
"__version": "10.4.0"
}

View File

@ -1,158 +0,0 @@
import {
getElementFromAlias,
baseUrl,
tableColumnTypeSelector,
makeDataAPIOptions,
getIndexRoute,
} from '../../../helpers/dataHelpers';
import { validateCT, ResultType } from '../../validators/validators';
import { toggleOnMigrationMode } from '../../data/migration-mode/utils';
import { setPromptValue } from '../../../helpers/common';
// ***************** UTIL FUNCTIONS **************************
let adminSecret: string;
let dataApiUrl: string;
export const createTestTable = () => {
cy.window().then(win => {
adminSecret = win.__env.adminSecret;
dataApiUrl = win.__env.dataApiUrl;
const { consoleMode } = win.__env;
if (consoleMode === 'cli') {
toggleOnMigrationMode();
}
});
// Click on the create table button
cy.visit(getIndexRoute());
cy.wait(15000);
cy.get(getElementFromAlias('data-create-table')).click();
// Enter the table name
cy.get(getElementFromAlias('tableName')).type('users');
// Set first column
cy.get(getElementFromAlias('column-0')).clear().type('id');
tableColumnTypeSelector('col-type-0');
cy.get(getElementFromAlias('data_test_column_type_value_serial'))
.first()
.click();
cy.get(getElementFromAlias('column-1')).clear().type('name');
tableColumnTypeSelector('col-type-1');
cy.get(getElementFromAlias('data_test_column_type_value_text'))
.first()
.click();
// Set primary key
cy.get(getElementFromAlias('primary-key-select-0')).select('0');
// Click on create
cy.get(getElementFromAlias('table-create')).click();
cy.wait(10000);
// Check if the table got created and navigatied to modify table
cy.url().should(
'eq',
`${baseUrl}/data/default/schema/public/tables/users/modify`
);
// Validate
validateCT('users', ResultType.SUCCESS);
};
export const insertValue = () => {
cy.get(getElementFromAlias('table-insert-rows')).click();
// Insert a row
cy.get(getElementFromAlias('typed-input-1')).type('someName');
cy.get(getElementFromAlias('insert-save-button')).click();
};
export const openAPIExplorer = () => {
// Open API Explorer
cy.get(getElementFromAlias('api')).click();
cy.wait(3000);
};
export const checkQuery = () => {
if (adminSecret) {
cy.get(getElementFromAlias('header-key-2')).type('someKey');
cy.get(getElementFromAlias('header-value-2')).type('someValue');
} else {
cy.get(getElementFromAlias('header-key-1')).type('someKey');
cy.get(getElementFromAlias('header-value-1')).type('someValue');
}
cy.get('textarea')
.first()
.type('{enter}{uparrow}query{{}users{{}id}}', { force: true });
cy.wait(1000);
cy.get('.execute-button').click();
cy.get('.cm-property').contains('id');
cy.get('.cm-number').contains('1');
};
export const checkMutation = () => {
cy.get('textarea')
.first()
.type(
'{enter}{uparrow}#{leftarrow}{enter}{uparrow}mutation insert_user{{}insert_users(objects:[{{}name:"someName"}]){{}returning{{}id}}}',
{ force: true }
);
cy.wait(1000);
cy.get('.execute-button').click();
cy.wait(5000);
cy.get('.cm-property').contains('id');
cy.get('.cm-number').contains('2');
};
export const checkSub = () => {
// Make a subscription
cy.get('textarea')
.first()
.type(
'{enter}{uparrow}#{leftarrow}{enter}{uparrow}subscription{{}users{{}name}}',
{ force: true }
);
cy.wait(1000);
cy.get('.execute-button').click();
cy.wait(5000);
cy.get('.cm-property').contains('name');
cy.get('.cm-string').contains('someName');
// Update the user with id 1
const reqBody = {
type: 'update',
args: {
table: {
name: 'users',
},
where: {
id: '1',
},
$set: {
name: 'someOtherName',
},
},
};
// Make the request
const requestOptions = makeDataAPIOptions(dataApiUrl, adminSecret, reqBody);
cy.request(requestOptions).then(res => {
cy.log(JSON.stringify(res));
cy.wait(3000);
cy.get('.cm-string').contains('someOtherName');
});
};
export const delTestTable = () => {
cy.get(getElementFromAlias('data-tab-link')).click();
// Go to the modify section of the table
cy.get(getElementFromAlias('users')).click();
cy.get(getElementFromAlias('table-modify')).click();
setPromptValue('users');
// Click on delete
cy.get(getElementFromAlias('delete-table')).click();
// Confirm
cy.window().its('prompt').should('be.called');
cy.wait(5000);
// Temporarily disabled, until it's fixed on the main branch
// Match the URL
// cy.url().should('eq', `${baseUrl}/data/default/schema/public`);
// Validate
validateCT('users', ResultType.FAILURE);
};

View File

@ -1,44 +0,0 @@
import {
openAPIExplorer,
checkQuery,
checkMutation,
createTestTable,
insertValue,
checkSub,
delTestTable,
} from './spec';
import { setMetaData } from '../../validators/validators';
import { testMode } from '../../../helpers/common';
const setup = () => {
// Temporarily skipped because of its flakiness, see: https://github.com/hasura/graphql-engine-mono/issues/5433
// TODO: Fix and restore it
describe.skip('Setup route', () => {
it('Visit the index route', () => {
// Visit the index route
cy.visit('/');
// Get and set validation metadata
setMetaData();
});
});
};
export const runApiExplorerTests = () => {
// Temporarily skipped because of its flakiness, see: https://github.com/hasura/graphql-engine-mono/issues/5433
// TODO: Fix and restore it
describe.skip('API Explorer', () => {
it('Create test table', createTestTable);
it('Insert row into test table', insertValue);
it('Open API Explorer', openAPIExplorer);
it('Check query result', checkQuery);
it('Check mutation result', checkMutation);
it('Check subscription result', checkSub);
it('Delete test table', delTestTable);
});
};
if (testMode !== 'cli') {
setup();
runApiExplorerTests();
}

View File

@ -1,3 +0,0 @@
module.exports = {
"__version": "10.4.0"
}

View File

@ -1,28 +0,0 @@
import { testMode } from '../../../helpers/common';
import { setMetaData } from '../../validators/validators';
import { getIndexRoute } from '../../../helpers/dataHelpers';
const setup = () => {
describe('Setup route', () => {
it('Visit the index route', () => {
// Visit the index route
cy.visit(getIndexRoute());
// Get and set validation metadata
setMetaData();
});
});
};
export const run404Test = () => {
describe('404', () => {
it('Open random page', () => {
cy.visit('/someRandomPage');
cy.get('h1').contains('404');
});
});
};
if (testMode !== 'cli') {
setup();
run404Test();
}

View File

@ -1,3 +0,0 @@
module.exports = {
"__version": "10.4.0"
}

View File

@ -1,154 +0,0 @@
import {
baseUrl,
getElementFromAlias,
getElementFromClassName,
} from '../../../helpers/dataHelpers';
const statements = {
createTableSql:
'CREATE TABLE a_test_test_author (id serial PRIMARY KEY, first_name text, last_name text);',
createCustomFuncSql: `CREATE OR REPLACE FUNCTION test_get_author_full_name(a_test_test_author_row a_test_test_author)
RETURNS TEXT AS $function$
SELECT a_test_test_author_row.first_name || ' ' || a_test_test_author_row.last_name
$function$
LANGUAGE sql STABLE;`,
insertData_a1: `INSERT INTO a_test_test_author(first_name, last_name) VALUES ('ruskin', 'bond');`,
insertData_a2: `INSERT INTO a_test_test_author(first_name, last_name) VALUES ('enid', 'blyton');`,
cleanUpSql: 'DROP TABLE a_test_test_author CASCADE;',
graphql: {
query: `{
a_test_test_author {
full_name # this is the computed field`,
},
};
export const openRawSQL = () => {
cy.get('a').contains('Data').click();
cy.wait(3000);
cy.get(getElementFromAlias('sql-link')).click();
cy.wait(3000);
cy.url().should('eq', `${baseUrl}/data/sql`);
};
const clearText = () => {
cy.get('textarea').type('{selectall}', { force: true });
cy.get('textarea').trigger('keydown', {
keyCode: 46,
which: 46,
force: true,
});
cy.wait(2000);
};
// helper to type into the SQL textarea on rawsql page
const typeStatement = (
statement: string,
shouldClearText = false,
waitTimeUponType = 2000,
endWaitTime = 5000,
uncheckTrackCheckbox = false
) => {
if (shouldClearText) {
clearText();
}
cy.get('textarea').type(statement, { force: true });
cy.wait(waitTimeUponType);
if (uncheckTrackCheckbox) {
cy.get(getElementFromAlias('raw-sql-track-check')).uncheck();
}
cy.get(getElementFromAlias('run-sql')).click();
// FIXME: maybe necessary for CLI mode
// cy.get(getElementFromAlias('raw-sql-statement-timeout')).should('be.disabled');
cy.wait(endWaitTime);
};
export const createTableAuthor = () => typeStatement(statements.createTableSql);
export const createCustomFunction = () =>
typeStatement(statements.createCustomFuncSql, true, 2000, 5000, true);
export const insertAuthorsIntoTable = () => {
typeStatement(statements.insertData_a1, true);
typeStatement(statements.insertData_a2, true);
clearText();
};
export const searchForTable = () => {
// ADD LATER: after search functionality is implemented
// cy.get(getElementFromAlias('search-tables')).type('a_test_test_author');
// cy.get(getElementFromAlias('table-links')).should(
// 'contain',
// 'a_test_test_author'
// );
cy.get(getElementFromAlias('a_test_test_author')).click();
};
export const openModifySection = () => {
// open modify section
cy.get(getElementFromAlias('table-modify')).click();
// click on computed field section
cy.get(getElementFromAlias('modify-table-edit-computed-field-0')).click();
// type name
cy.get(getElementFromAlias('computed-field-name-input')).type('{selectall}', {
force: true,
});
cy.get(getElementFromAlias('computed-field-name-input')).trigger('keydown', {
keyCode: 46,
which: 46,
force: true,
});
cy.get(getElementFromAlias('computed-field-name-input')).type('full_name', {
force: true,
delay: 10,
});
cy.wait(2000);
// type & select function name
cy.get(getElementFromClassName('function-name-select__control'))
.children('div')
.click({ multiple: true })
.find('input')
.focus()
.type('test_get_author_full_name', { force: true })
.get(getElementFromClassName('function-name-select__menu'))
.first()
.click();
// enter comment
cy.get(getElementFromAlias('computed-field-comment-input')).type(
'this is a test comment',
{ force: true }
);
// saving the computed field
cy.get(getElementFromAlias('modify-table-computed-field-0-save')).click();
// verify that a computed field exists
cy.wait(5000);
cy.get(getElementFromAlias('computed-field-full_name')).contains('full_name');
cy.wait(5000);
};
export const routeToGraphiql = () => {
cy.visit('/api/api-explorer');
cy.wait(7000);
cy.url().should('eq', `${baseUrl}/api/api-explorer`);
};
export const verifyComputedFieldsResult = () => {
// type the query
cy.get('textarea')
.first()
.type(`{enter}{uparrow}${statements.graphql.query}`, { force: true });
cy.wait(2000);
// execute the query
cy.get('.execute-button').click();
// verify if full_name is present
cy.get('.cm-property').contains('full_name');
cy.get('.cm-string').contains('ruskin bond');
cy.wait(2000);
};
export const cleanUpSql = () => typeStatement(statements.cleanUpSql, true);
export const routeToSQLPage = () => {
cy.visit('/data/sql');
cy.wait(7000);
cy.url().should('eq', `${baseUrl}/data/sql`);
};

View File

@ -1,51 +0,0 @@
import {
openRawSQL,
createTableAuthor,
createCustomFunction,
insertAuthorsIntoTable,
searchForTable,
cleanUpSql,
openModifySection,
routeToGraphiql,
verifyComputedFieldsResult,
routeToSQLPage,
} from './spec';
import { testMode } from '../../../helpers/common';
import { setMetaData } from '../../validators/validators';
import { getIndexRoute } from '../../../helpers/dataHelpers';
const setup = () => {
describe('Setup route', () => {
it('Visit the index route', () => {
cy.visit(getIndexRoute());
// Get and set validation metadata
setMetaData();
});
});
};
export const runComputedFieldTests = () => {
describe('Computed Fields', () => {
it('Open Raw SQL page', openRawSQL);
it('Create test table', createTableAuthor);
it('Run SQL for custom function', createCustomFunction);
it('Insert authors into table', insertAuthorsIntoTable);
it('Search for table', searchForTable);
// Temporarily skipped because of its flakiness, see: https://github.com/hasura/graphql-engine-mono/issues/5433
// TODO: Fix and restore them
it.skip('Open Modify page and add computed field', openModifySection);
it.skip('Route to GraphiQL page', routeToGraphiql);
it.skip(
'Check computed field results on GraphiQL',
verifyComputedFieldsResult
);
it.skip('Route to Raw SQL page', routeToSQLPage);
it.skip('Test cleanup', cleanUpSql);
});
};
if (testMode !== 'cli') {
setup();
runComputedFieldTests();
}

View File

@ -1,3 +0,0 @@
module.exports = {
"__version": "10.4.0"
}

View File

@ -1,246 +0,0 @@
import {
tableColumnTypeSelector,
getElementFromAlias,
getTableName,
getColName,
baseUrl,
getIndexRoute,
} from '../../../helpers/dataHelpers';
import {
setMetaData,
validateCT,
ResultType,
} from '../../validators/validators';
import { setPromptValue } from '../../../helpers/common';
const testName = 'ct';
export const checkCreateTableRoute = () => {
// Click on the create table button
cy.visit(getIndexRoute());
cy.wait(15000);
cy.get(getElementFromAlias('data-create-table')).click();
// Match the URL
cy.url().should('eq', `${baseUrl}/data/default/schema/public/table/add`);
};
export const failCTWithoutColumns = () => {
// Type table name
cy.get(getElementFromAlias('tableName')).type(getTableName(0, testName));
// Click on create
cy.get(getElementFromAlias('table-create')).click();
// Check if the route didn't change
cy.url().should('eq', `${baseUrl}/data/default/schema/public/table/add`);
// Validate
validateCT(getTableName(0, testName), ResultType.FAILURE);
};
export const failCTWithoutPK = () => {
// Set first column
cy.get(getElementFromAlias('column-0')).type(getColName(0));
tableColumnTypeSelector('col-type-0');
cy.get(getElementFromAlias('data_test_column_type_value_serial'))
.first()
.click();
// Click on create
cy.get(getElementFromAlias('table-create')).click();
// Check if the route didn't change
cy.url().should('eq', `${baseUrl}/data/default/schema/public/table/add`);
// Validate
validateCT(getTableName(0, testName), ResultType.FAILURE);
};
export const failCTDuplicateColumns = () => {
// Set second column
cy.get(getElementFromAlias('column-1')).type(getColName(0));
tableColumnTypeSelector('col-type-1');
cy.get(getElementFromAlias('data_test_column_type_value_serial'))
.first()
.click();
// Set primary key
cy.get(getElementFromAlias('primary-key-select-0')).select('0');
// Click on create
cy.get(getElementFromAlias('table-create')).click();
// Check for an alert
cy.on('window:alert', str => {
expect(
str === `You have the following column names repeated: [${getColName(0)}]`
).to.be.true;
});
// Check if the route didn't change
cy.url().should('eq', `${baseUrl}/data/default/schema/public/table/add`);
// Validate
validateCT(getTableName(0, testName), ResultType.FAILURE);
};
export const failCTWrongDefaultValue = () => {
// Set second column
cy.get(getElementFromAlias('column-1')).clear().type(getColName(1));
tableColumnTypeSelector('col-type-1');
cy.get(getElementFromAlias('data_test_column_type_value_integer'))
.first()
.click();
cy.get(getElementFromAlias('col-default-1')).type('qwerty');
// Set primary key
cy.get(getElementFromAlias('primary-key-select-0')).select('0');
// Click on create
cy.get(getElementFromAlias('table-create')).click();
// Check if the route didn't change
cy.url().should('eq', `${baseUrl}/data/default/schema/public/table/add`);
// Validate
validateCT(getTableName(0, testName), ResultType.FAILURE);
};
export const passCT = () => {
cy.get(getElementFromAlias('frequently-used-columns')).first().should('exist');
// Set second column
cy.get(getElementFromAlias('column-1')).clear().type(getColName(1));
tableColumnTypeSelector('col-type-1');
cy.get(getElementFromAlias('data_test_column_type_value_text'))
.first()
.click();
cy.get(getElementFromAlias('col-default-1')).clear();
// Set primary key
cy.get(getElementFromAlias('primary-key-select-0')).select('0');
cy.get(getElementFromAlias('primary-key-select-1')).select('1');
// Click on create
cy.get(getElementFromAlias('table-create')).click();
cy.wait(10000);
// Check if the table got created and navigatied to modify table
cy.url().should(
'eq',
`${baseUrl}/data/default/schema/public/tables/${getTableName(
0,
testName
)}/modify`
);
cy.get(getElementFromAlias(getTableName(0, testName)));
// Validate
validateCT(getTableName(0, testName), ResultType.SUCCESS);
};
export const passCTWithFK = () => {
// go to create-table
cy.visit(getIndexRoute());
cy.wait(5000);
cy.get(getElementFromAlias('data-create-table')).click();
// cy.get(getElementFromAlias('table-create')).click();
// Set tablename
cy.get(getElementFromAlias('tableName'))
.clear()
.type(getTableName(1, testName));
// Set first column
cy.get(getElementFromAlias('column-0')).type(getColName(0));
tableColumnTypeSelector('col-type-0');
cy.get(getElementFromAlias('data_test_column_type_value_serial'))
.first()
.click();
// Set second column
cy.get(getElementFromAlias('column-1')).type(getColName(1));
tableColumnTypeSelector('col-type-1');
cy.get(getElementFromAlias('data_test_column_type_value_text'))
.first()
.click();
// Set third column
cy.get(getElementFromAlias('column-2')).type(getColName(2));
tableColumnTypeSelector('col-type-2');
cy.get(getElementFromAlias('data_test_column_type_value_text'))
.first()
.click();
// Set primary key
cy.get(getElementFromAlias('primary-key-select-0')).select('0');
// Set foreign key
cy.get(getElementFromAlias('add-table-edit-fk-0')).click();
cy.get(getElementFromAlias('foreign-key-ref-table-0')).select(
getTableName(0, testName)
);
cy.get(getElementFromAlias('foreign-key-0-lcol-0')).select('0');
cy.get(getElementFromAlias('foreign-key-0-rcol-0')).select(getColName(0));
cy.get(getElementFromAlias('foreign-key-0-lcol-1')).select('1');
cy.get(getElementFromAlias('foreign-key-0-rcol-1')).select(getColName(1));
cy.get(getElementFromAlias('foreign-key-0-onUpdate-cascade')).check();
cy.get(getElementFromAlias('foreign-key-0-onDelete-cascade')).check();
// set unique key 1
cy.get(getElementFromAlias('add-table-edit-unique-key-0')).click();
cy.get(getElementFromAlias('unique-key-0-column-0')).select('1');
// set unique key 2
cy.get(getElementFromAlias('add-table-edit-unique-key-1')).click();
cy.get(getElementFromAlias('unique-key-1-column-0')).select('1');
cy.get(getElementFromAlias('unique-key-1-column-1')).select('2');
cy.get(getElementFromAlias('unique-key-1-column-2')).select('0');
cy.get(getElementFromAlias('remove-uk-1-column-1')).click();
// Click on create
cy.get(getElementFromAlias('table-create')).click();
cy.wait(10000);
// Check if the table got created and navigatied to modify table
cy.url().should(
'eq',
`${baseUrl}/data/default/schema/public/tables/${getTableName(
1,
testName
)}/modify`
);
cy.get('div').contains(
`${getTableName(1, testName)}_${getColName(1)}_${getColName(0)}`
);
cy.get(getElementFromAlias(getTableName(1, testName)));
// Validate
validateCT(getTableName(1, testName), ResultType.SUCCESS);
};
export const failCTDuplicateTable = () => {
// Visit data page
cy.visit(getIndexRoute());
cy.wait(5000);
cy.get(getElementFromAlias('data-create-table')).click();
// Type table name
cy.get(getElementFromAlias('tableName')).type(getTableName(0, testName));
// Set column
cy.get(getElementFromAlias('column-0')).type(getColName(1));
tableColumnTypeSelector('col-type-0');
cy.get(getElementFromAlias('data_test_column_type_value_serial'))
.first()
.click();
// Set primary key
cy.get(getElementFromAlias('primary-key-select-0')).select('0');
// Click on create
cy.get(getElementFromAlias('table-create')).click();
cy.wait(7000);
};
const deleteTable = (tableName: string) => {
cy.get(getElementFromAlias(tableName)).click();
cy.get(getElementFromAlias('table-modify')).click();
setPromptValue(tableName);
// Click on delete
cy.get(getElementFromAlias('delete-table')).click();
// Confirm
cy.window().its('prompt').should('be.called');
cy.wait(5000);
validateCT(tableName, ResultType.FAILURE);
};
export const deleteCTTestTables = () => {
// Go to the modify section of the second table
const secondTableName = getTableName(1, testName);
deleteTable(secondTableName);
// Go to the modify section of the first table
const firstTableName = getTableName(0, testName);
deleteTable(firstTableName);
// Match the URL
// FIXME: Temporarily disabling this.
// cy.url().should('eq', `${baseUrl}/data/schema`);
};
export const setValidationMetaData = () => {
setMetaData();
};

View File

@ -1,48 +0,0 @@
import { testMode } from '../../../helpers/common';
import { setMetaData } from '../../validators/validators';
import {
checkCreateTableRoute,
failCTWithoutColumns,
failCTWithoutPK,
failCTDuplicateColumns,
failCTWrongDefaultValue,
passCT,
failCTDuplicateTable,
deleteCTTestTables,
passCTWithFK,
} from './spec';
import { getIndexRoute } from '../../../helpers/dataHelpers';
const setup = () => {
describe('Setup route', () => {
it('Visit the index route', () => {
// Visit the index route
cy.visit(getIndexRoute());
// Get and set validation metadata
setMetaData();
});
});
};
export const runCreateTableTests = () => {
describe('Create Table', () => {
it('Create table button opens the correct route', checkCreateTableRoute);
it('Fails to create table without columns', failCTWithoutColumns);
it('Fails to create table without primary key', failCTWithoutPK);
it('Fails to create with duplicate columns', failCTDuplicateColumns);
it('Fails to create with wrong default value', failCTWrongDefaultValue);
it('Successfuly creates table', passCT);
it(
'Successfuly creates table with composite foreign and unique key',
passCTWithFK
);
it('Fails to create duplicate table', failCTDuplicateTable);
it('Delete the test tables', deleteCTTestTables);
});
};
if (testMode !== 'cli') {
setup();
runCreateTableTests();
}

View File

@ -1,3 +0,0 @@
module.exports = {
"__version": "10.4.0"
}

View File

@ -1,125 +0,0 @@
import { baseUrl, getElementFromAlias } from '../../../helpers/dataHelpers';
const statements = {
createTableSql:
'CREATE TABLE a_test_test_article (id serial PRIMARY KEY, title text, content text);',
createCustomFuncSql: `CREATE FUNCTION a_test_test_search_articles(search text)
RETURNS SETOF a_test_test_article AS $function$
SELECT *
FROM a_test_test_article
WHERE
title ilike ('%' || search || '%')
OR content ilike ('%' || search || '%')
$function$ LANGUAGE sql STABLE;`,
insertData_a1: `INSERT INTO a_test_test_article(title, content) VALUES ('hasura is awesome', 'I mean duh?!');`,
insertData_a2: `INSERT INTO a_test_test_article(title, content) VALUES ('cloud lauched', 'hasura <3 the cloud');`,
deleteFunction: 'DROP FUNCTION a_test_test_search_articles(search text);',
cleanUpSql: 'DROP TABLE a_test_test_article CASCADE;',
graphql: {
query: `{
a_test_test_search_articles
(args: {{} search: "hasura" }) {
id
title
content
`,
},
};
export const openRawSQL = () => {
cy.get('a').contains('Data').click();
cy.wait(3000);
cy.get(getElementFromAlias('sql-link')).click();
cy.wait(3000);
cy.url().should('eq', `${baseUrl}/data/sql`);
};
const clearText = () => {
cy.get('textarea').type('{selectall}', { force: true });
cy.get('textarea').trigger('keydown', {
keyCode: 46,
which: 46,
force: true,
});
cy.wait(2000);
};
// helper to type into the SQL textarea on rawsql page
const typeStatement = (
statement: string,
shouldClearText = false,
waitTimeUponType = 2000,
endWaitTime = 5000,
unCheckTrackFunction = false
) => {
if (shouldClearText) {
clearText();
}
cy.get('textarea').type(statement, { force: true });
cy.wait(waitTimeUponType);
if (unCheckTrackFunction) {
cy.get(getElementFromAlias('raw-sql-track-check')).uncheck();
}
cy.get(getElementFromAlias('run-sql')).click();
// FIXME: maybe necessary for CLI mode
// cy.get(getElementFromAlias('raw-sql-statement-timeout')).should('be.disabled');
cy.wait(endWaitTime);
};
export const createTableArticle = () =>
typeStatement(statements.createTableSql);
export const createCustomFunction = () =>
typeStatement(statements.createCustomFuncSql, true, 2000, 5000, true);
export const insertAuthorsIntoTable = () => {
typeStatement(statements.insertData_a1, true);
typeStatement(statements.insertData_a2, true);
clearText();
};
export const trackCustomFn = () => {
cy.visit('/data/default/schema/public');
cy.wait(7000);
cy.url().should('eq', `${baseUrl}/data/default/schema/public`);
// Track Function
cy.get(
getElementFromAlias('add-track-function-a_test_test_search_articles')
).click();
cy.wait(5000);
};
export const routeToGraphiql = () => {
cy.visit('/api/api-explorer');
cy.wait(7000);
cy.url().should('eq', `${baseUrl}/api/api-explorer`);
};
export const verifyCustomFnResult = () => {
// Type the query
cy.get('textarea')
.first()
.type(`{enter}{uparrow}${statements.graphql.query}`, { force: true });
cy.wait(2000);
cy.get('.execute-button').click();
// verify if article is present
cy.get('.cm-property').contains('title');
cy.get('.cm-property').contains('content');
cy.wait(2000);
};
export const cleanUpSql = () => {
typeStatement(statements.deleteFunction, true);
typeStatement(statements.cleanUpSql, true);
clearText();
cy.wait(2000);
};
export const routeToSQLPage = () => {
cy.visit('/data/sql');
cy.wait(7000);
cy.url().should('eq', `${baseUrl}/data/sql`);
};

View File

@ -1,43 +0,0 @@
import {
openRawSQL,
createTableArticle,
createCustomFunction,
insertAuthorsIntoTable,
cleanUpSql,
trackCustomFn,
routeToGraphiql,
verifyCustomFnResult,
routeToSQLPage,
} from './spec';
import { testMode } from '../../../helpers/common';
import { setMetaData } from '../../validators/validators';
import { getIndexRoute } from '../../../helpers/dataHelpers';
const setup = () => {
describe('Setup route', () => {
it('Visit the index route', () => {
cy.visit(getIndexRoute());
// Get and set validation metadata
setMetaData();
});
});
};
export const runCustomFunctionTests = () => {
describe('Custom Functions', () => {
it('Open Raw SQL page', openRawSQL);
it('Create test table', createTableArticle);
it('Run SQL for custom function', createCustomFunction);
it('Insert articles into table', insertAuthorsIntoTable);
it('Track custom function', trackCustomFn);
it('Route to GraphiQL page', routeToGraphiql);
it('Check custom function results on GraphiQL', verifyCustomFnResult);
it('Route to Raw SQL page', routeToSQLPage);
it('Test cleanup', cleanUpSql);
});
};
if (testMode !== 'cli') {
setup();
runCustomFunctionTests();
}

View File

@ -1,3 +0,0 @@
module.exports = {
"__version": "10.4.0"
}

View File

@ -1,198 +0,0 @@
import {
getElementFromAlias,
baseUrl,
getCustomFunctionName,
getSchema,
dropTable,
testCustomFunctionSQLWithSessArg,
getTrackFnPayload,
createFunctionTable,
trackCreateFunctionTable,
getCreateTestFunctionQuery,
getTrackTestFunctionQuery,
createSampleTable,
getTrackSampleTableQuery,
createVolatileFunction,
dropTableIfExists,
} from '../../../helpers/dataHelpers';
import {
dataRequest,
validateCFunc,
validateUntrackedFunc,
ResultType,
trackFunctionRequest,
} from '../../validators/validators';
import { setPromptValue } from '../../../helpers/common';
export const createCustomFunctionSuccess = () => {
// Round about way to create a function
dataRequest(createFunctionTable(), ResultType.SUCCESS, 'query');
cy.wait(5000);
dataRequest(trackCreateFunctionTable(), ResultType.SUCCESS, 'metadata');
cy.wait(5000);
dataRequest(getCreateTestFunctionQuery(1), ResultType.SUCCESS, 'query');
cy.wait(5000);
dataRequest(getTrackTestFunctionQuery(1), ResultType.SUCCESS, 'metadata');
cy.wait(5000);
// Check if the track checkbox is clicked or not
validateCFunc(getCustomFunctionName(1), getSchema(), ResultType.SUCCESS);
cy.wait(5000);
};
export const unTrackFunction = () => {
cy.visit(
`data/default/schema/public/functions/${getCustomFunctionName(1)}/modify`
);
cy.wait(5000);
cy.get(getElementFromAlias('custom-function-edit-untrack-btn')).click();
cy.wait(5000);
validateUntrackedFunc(
getCustomFunctionName(1),
getSchema(),
ResultType.SUCCESS
);
cy.wait(5000);
};
export const trackFunction = () => {
cy.get(
getElementFromAlias(`add-track-function-${getCustomFunctionName(1)}`)
).should('exist');
cy.get(
getElementFromAlias(`add-track-function-${getCustomFunctionName(1)}`)
).click();
cy.get(getElementFromAlias(`track-as-mutation`)).should('exist');
cy.get(getElementFromAlias(`track-as-mutation`)).click();
cy.wait(5000);
validateCFunc(getCustomFunctionName(1), getSchema(), ResultType.SUCCESS);
cy.wait(5000);
};
export const testSessVariable = () => {
const fN = 'customFunctionWithSessionArg'.toLowerCase();
dataRequest(
dropTableIfExists({ name: 'text_result', schema: 'public' }),
ResultType.SUCCESS
);
cy.wait(3000);
dataRequest(createSampleTable(), ResultType.SUCCESS, 'query');
cy.wait(3000);
dataRequest(getTrackSampleTableQuery(), ResultType.SUCCESS, 'metadata');
cy.wait(3000);
dataRequest(testCustomFunctionSQLWithSessArg(fN), ResultType.SUCCESS);
cy.wait(3000);
trackFunctionRequest(getTrackFnPayload(fN), ResultType.SUCCESS);
cy.wait(5000);
cy.visit(`data/default/schema/public/functions/${fN}/modify`);
cy.get(getElementFromAlias(`${fN}-session-argument-btn`), {
timeout: 5000,
}).click();
// invalid data should fail
cy.get(getElementFromAlias(`${fN}-edit-sessvar-function-field`))
.clear()
.type('invalid');
cy.get(getElementFromAlias(`${fN}-session-argument-save`)).click();
cy.expectErrorNotificationWithTitle(
'Updating Session argument variable failed'
);
cy.get(getElementFromAlias(`${fN}-session-argument-btn`), {
timeout: 1000,
}).click();
cy.get(getElementFromAlias(`${fN}-edit-sessvar-function-field`))
.clear()
.type('hasura_session');
cy.get(getElementFromAlias(`${fN}-session-argument-save`)).click();
cy.wait(3000);
cy.get(getElementFromAlias(fN)).should('be.visible');
cy.visit(`data/default/schema/public/functions/${fN}/modify`);
cy.wait(3000);
cy.get(getElementFromAlias(`${fN}-session-argument`)).should(
'contain',
'hasura_session'
);
dataRequest(dropTable('text_result', true), ResultType.SUCCESS);
cy.wait(3000);
cy.visit(`data/default/schema/public/`);
};
export const verifyPermissionTab = () => {
cy.get(getElementFromAlias('functions-data/default-permissions')).click();
cy.wait(5000);
cy.get(getElementFromAlias('custom-function-permission-link')).should(
'exist'
);
cy.wait(5000);
};
export const deleteCustomFunction = () => {
cy.get(getElementFromAlias('functions-data/default-modify')).click();
setPromptValue(getCustomFunctionName(1));
cy.get(getElementFromAlias('custom-function-edit-delete-btn')).click();
cy.window().its('prompt').should('be.called');
cy.wait(5000);
cy.get(getElementFromAlias('delete-confirmation-error')).should('not.exist');
cy.url().should('eq', `${baseUrl}/data/default/schema/public`);
cy.wait(5000);
dataRequest(dropTable(), ResultType.SUCCESS);
cy.wait(5000);
};
export const trackVolatileFunction = () => {
const fN = 'customVolatileFunc'.toLowerCase();
dataRequest(
dropTableIfExists({ name: 'text_result', schema: 'public' }),
ResultType.SUCCESS
);
cy.wait(5000);
dataRequest(createSampleTable(), ResultType.SUCCESS);
cy.wait(5000);
dataRequest(getTrackSampleTableQuery(), ResultType.SUCCESS, 'metadata');
dataRequest(createVolatileFunction(fN), ResultType.SUCCESS);
cy.wait(5000);
cy.visit(`data/default/schema/public`);
cy.get(getElementFromAlias(`add-track-function-${fN}`)).click();
cy.get(getElementFromAlias('track-as-mutation')).click();
cy.wait(2000);
cy.url().should(
'eq',
`${baseUrl}/data/default/schema/public/functions/${fN}/modify`
);
dataRequest(dropTable('text_result', true), ResultType.SUCCESS);
};
export const trackVolatileFunctionAsQuery = () => {
const fN = 'customVolatileFunc'.toLowerCase();
dataRequest(
dropTableIfExists({ name: 'text_result', schema: 'public' }),
ResultType.SUCCESS
);
cy.wait(5000);
dataRequest(createSampleTable(), ResultType.SUCCESS);
cy.wait(5000);
dataRequest(getTrackSampleTableQuery(), ResultType.SUCCESS, 'metadata');
dataRequest(createVolatileFunction(fN), ResultType.SUCCESS);
cy.wait(5000);
cy.visit(`data/default/schema/public`);
cy.get(getElementFromAlias(`add-track-function-${fN}`)).click();
cy.get(getElementFromAlias('track-as-query')).click();
cy.wait(2000);
cy.get(getElementFromAlias('track-as-query-confirm')).click();
cy.wait(2000);
cy.url().should(
'eq',
`${baseUrl}/data/default/schema/public/functions/${fN}/modify`
);
dataRequest(dropTable('text_result', true), ResultType.SUCCESS);
};

View File

@ -1,43 +0,0 @@
import { testMode } from '../../../helpers/common';
import { setMetaData } from '../../validators/validators';
import {
createCustomFunctionSuccess,
deleteCustomFunction,
unTrackFunction,
trackFunction,
verifyPermissionTab,
trackVolatileFunction,
trackVolatileFunctionAsQuery,
} from './spec';
import { getIndexRoute } from '../../../helpers/dataHelpers';
const setup = () => {
describe('Setup route', () => {
it('Visit the index route', () => {
// Visit the index route
cy.visit(getIndexRoute());
cy.wait(5000);
// Get and set validation metadata
setMetaData();
});
});
};
export const runCreateCustomFunctionsTableTests = () => {
describe('Custom Function Tests', () => {
it('Create a custom function and track', createCustomFunctionSuccess);
it('Untrack custom function', unTrackFunction);
it('Track custom function', trackFunction);
it('Verify permission tab', verifyPermissionTab);
it('Delete custom function', deleteCustomFunction);
// TODO it('Test custom function with Session Argument', testSessVariable);
it('Tracks VOLATILE function as mutation', trackVolatileFunction);
it('Tracks VOLATILE function as query', trackVolatileFunctionAsQuery);
});
};
if (testMode !== 'cli') {
setup();
runCreateCustomFunctionsTableTests();
}

View File

@ -1,3 +0,0 @@
module.exports = {
"__version": "10.4.0"
}

View File

@ -1,533 +0,0 @@
import {
baseUrl,
getColName,
getTableName,
dataTypes,
getElementFromAlias,
typeDefaults,
tableColumnTypeSelector,
getIndexRoute,
} from '../../../helpers/dataHelpers';
import {
validateInsert,
setMetaData,
validateCT,
ResultType,
} from '../../validators/validators';
import { setPromptValue } from '../../../helpers/common';
const numOfDataTypes = dataTypes.length;
const testName = 'ib';
//* ******************** Util functions ************************
const setColumns = () => {
for (let i = 0; i < numOfDataTypes; i += 1) {
// Type column name
cy.get(getElementFromAlias(`column-${i}`)).type(getColName(i));
// Select column type
tableColumnTypeSelector(`col-type-${i}`);
cy.get(getElementFromAlias(`data_test_column_type_value_${dataTypes[i]}`))
.first()
.click();
if (i === dataTypes.indexOf('text')) {
cy.get(getElementFromAlias(`unique-${i}`)).check({ force: true });
}
// Set appropriate default if the type is not serial
if (i > 1) {
cy.get(getElementFromAlias(`col-default-${i}`)).type(
typeDefaults[dataTypes[i]]
);
}
}
};
const clickSaveOrInsert = (firstIndex: number, currentIndex: number) => {
if (currentIndex === firstIndex) {
cy.get(getElementFromAlias('insert-save-button')).click();
} else {
cy.get(getElementFromAlias('insert-save-button')).click();
}
cy.wait(2000);
};
const checkQuerySuccess = () => {
// Expect only 4 rows i.e. expect fifth element to not exist
cy.get('[role=gridcell]').contains('filter-text');
cy.get('[role=row]').eq(2).should('not.exist');
};
const checkOrder = (order: string) => {
// Utility function to get right element
if (order === 'asc') {
cy.get('[role=row]').each(($el, index) => {
if (index !== 0) {
cy.wrap($el)
.find('[role=gridcell]')
.first()
.next()
.next()
.contains(index);
}
});
} else {
cy.get('[role=row]').each(($el, index) => {
if (index !== 0) {
cy.wrap($el)
.find('[role=gridcell]')
.first()
.next()
.next()
.contains(22 - index);
}
});
}
};
//* ******************** Test functions ***************************
export const passBICreateTable = () => {
cy.wait(7000);
// Click create table button
cy.get(getElementFromAlias('data-create-table')).click();
// Type table name
cy.get(getElementFromAlias('tableName')).type(getTableName(0, testName));
// Set columns with all fields
setColumns();
// Set primary key
cy.get(getElementFromAlias('primary-key-select-0')).select('0');
// Click on create
cy.get(getElementFromAlias('table-create')).click();
cy.wait(7000);
validateCT(getTableName(0, testName), ResultType.SUCCESS);
};
export const passSearchTables = () => {
// Click add table button
cy.visit(getIndexRoute());
cy.wait(5000);
cy.get(getElementFromAlias('data-create-table')).click();
// Type table name
cy.get(getElementFromAlias('tableName')).type(getTableName(1, testName));
// Type column name
cy.get(getElementFromAlias('column-0')).type(getColName(0));
tableColumnTypeSelector('col-type-0');
cy.get(getElementFromAlias('data_test_column_type_value_integer'))
.first()
.click();
// Set primary key
cy.get(getElementFromAlias('primary-key-select-0')).select('0');
// Click on create
cy.get(getElementFromAlias('table-create')).click();
cy.wait(7000);
validateCT(getTableName(0, testName), ResultType.SUCCESS);
// cy.get(getElementFromAlias('search-tables')).type('0');
// cy.get(getElementFromAlias('table-links')).should('not.contain', '1');
// cy.get(getElementFromAlias('search-tables')).type('{home}{del}');
};
export const checkInsertRoute = () => {
// Click on Insert tab
cy.get(getElementFromAlias(getTableName(0, testName))).click();
cy.get(getElementFromAlias('table-insert-rows')).click();
// Match URL
cy.url().should(
'eq',
`${baseUrl}/data/default/schema/public/tables/${getTableName(
0,
testName
)}/insert`
);
};
export const failBIWrongDataType = () => {
// Check if the table creation fails for wrong inputs of each data type
for (let i = 2; i < numOfDataTypes; i += 1) {
// Text and Boolean always succeed, so we check only for others
if (dataTypes[i] !== 'text' && dataTypes[i] !== 'boolean') {
const sureFailString = 'abcd1234';
// Type a string that fails
cy.get(getElementFromAlias(`typed-input-${i}`)).type(sureFailString);
// Click the Save/Insert Again button.
clickSaveOrInsert(2, i);
cy.get(getElementFromAlias(`typed-input-${i}`)).clear();
// Check the default radio of current column
cy.get(getElementFromAlias(`typed-input-default-${i}`)).check();
}
validateInsert(getTableName(0, testName), 0);
}
};
export const passBIInsert20Rows = () => {
for (let i = 0; i < 20; i += 1) {
// Type a string in the text type fields of some rows (to be tested in Browse rows)
const textIndex = dataTypes.indexOf('text');
// Click the Insert Again button.
if (i === 0) {
cy.get(getElementFromAlias(`typed-input-${textIndex}`)).type(
'{selectall}{del}'
);
cy.get(getElementFromAlias(`typed-input-${textIndex}`)).type(
'filter-text'
);
cy.get(getElementFromAlias('insert-save-button')).click();
} else {
cy.get(getElementFromAlias(`typed-input-${textIndex}`)).type(
'{selectall}{del}'
);
cy.get(getElementFromAlias(`typed-input-${textIndex}`))
.type('{selectall}{del}')
.type(Math.random().toString(36).substring(7));
cy.get(
getElementFromAlias(`typed-input-default-${textIndex + 1}`)
).check();
cy.get(getElementFromAlias('insert-save-button')).click();
cy.wait(300);
validateInsert(getTableName(0, testName), i + 1);
}
}
// Wait for insert notifications to disappear
cy.wait(7000);
};
export const checkBrowseRoute = () => {
// Click on Browse tab
cy.get(getElementFromAlias(getTableName(0, testName))).click();
cy.get(getElementFromAlias('table-browse-rows')).click();
cy.wait(2000);
// Match URL
cy.url().should(
'eq',
`${baseUrl}/data/default/schema/public/tables/${getTableName(
0,
testName
)}/browse`
);
};
export const passBI20RowsExist = () => {
// Check if the 20 inserted elements reflect in the UI
cy.get(getElementFromAlias('pagination-select')).select('20');
// cy.get(getElementFromAlias('table-browse-rows')).contains('20');
};
export const checkPagination = () => {
// Check if the current page is 1
cy.get('.-pageJump > input').should('have.value', '1');
// Check if the total number of pages is 3
cy.get('.-totalPages').contains('3');
// Check if the default value of rows displayed is 10
cy.get('.-pageSizeOptions > select').should('have.value', '10');
cy.get('.-next > button').click({ force: true });
cy.wait(3000);
// Check if the page changed
cy.get(
'.rt-tbody > div:nth-child(1) > div > div:nth-child(3) > div'
).contains('11');
cy.get('.-pageJump > input').should('have.value', '2');
cy.get('.-previous > button').click({ force: true });
cy.wait(3000);
// Check if the page changed
cy.get('.-pageJump > input').should('have.value', '1');
cy.get('.-pageSizeOptions > select').select('5 rows');
cy.wait(3000);
// Check if the total number of pages changed
cy.get('.-totalPages').contains('5');
};
export const passBISort = (order: string) => {
cy.wait(7000);
// Select column with type 'serial'
const serialIndex = dataTypes.indexOf('serial');
cy.get(getElementFromAlias('sort-column-0')).select(getColName(serialIndex), {
force: true,
});
// Select order as `descending`
cy.get(getElementFromAlias('sort-order-0')).select(
order === 'asc' ? 'Asc' : 'Desc',
{ force: true }
);
// Run query
cy.get(getElementFromAlias('run-query')).click();
cy.wait(5000);
// Check order
checkOrder(order);
// Clear filter
cy.get(getElementFromAlias('clear-sorts-0')).click({ force: true });
// Run query
cy.get(getElementFromAlias('run-query')).click();
cy.wait(5000);
};
export const passBIFilterQueryEq = () => {
// Select column with type "text"
const textIndex = dataTypes.indexOf('text');
cy.get(getElementFromAlias('filter-column-0')).select(getColName(textIndex));
// Select operator as `eq`
cy.get(getElementFromAlias('filter-op-0')).select('$eq');
// Type value as "filter-text"
cy.get("input[placeholder='-- value --']").last().type('filter-text');
// Run query
cy.get(getElementFromAlias('run-query')).click();
cy.wait(2000);
// Check if the query was successful
checkQuerySuccess();
// Clear filter
cy.get(getElementFromAlias('clear-filter-0')).click();
// Run query
cy.get(getElementFromAlias('run-query')).click();
cy.wait(5000);
};
export const deleteBITestTable = () => {
cy.get(getElementFromAlias(getTableName(2, testName))).click();
// Go to the modify section of the table
cy.get(getElementFromAlias('table-modify')).click();
cy.wait(2000);
setPromptValue(getTableName(2, testName));
// Click on delete
cy.get(getElementFromAlias('delete-table')).click();
// Confirm
cy.window().its('prompt').should('be.called');
cy.wait(7000);
// Match the URL
// FIXME: change this later
// cy.url().should('eq', `${baseUrl}/data/default/schema/public`);
validateCT(getTableName(2, testName), ResultType.FAILURE);
cy.get(getElementFromAlias(getTableName(1, testName))).click();
// Go to the modify section of the table
cy.get(getElementFromAlias('table-modify')).click();
cy.wait(2000);
setPromptValue(getTableName(1, testName));
// Click on delete
cy.get(getElementFromAlias('delete-table')).click();
// Confirm
cy.window().its('prompt').should('be.called');
cy.wait(7000);
// Match the URL
// FIXME: change this later
// cy.url().should('eq', `${baseUrl}/data/schema`);
validateCT(getTableName(1, testName), ResultType.FAILURE);
cy.get(getElementFromAlias(getTableName(0, testName))).click();
// Go to the modify section of the table
cy.get(getElementFromAlias('table-modify')).click();
setPromptValue(getTableName(0, testName));
cy.wait(2000);
// Click on delete
cy.get(getElementFromAlias('delete-table')).click();
// Confirm
cy.window().its('prompt').should('be.called');
cy.wait(7000);
// Match the URL
// FIXME: change later
// cy.url().should('eq', `${baseUrl}/data/schema`);
validateCT(getTableName(0, testName), ResultType.FAILURE);
};
export const failBIUniqueKeys = () => {
// Type a string in the text type fields of some rows (to be tested in Browse rows)
const textIndex = dataTypes.indexOf('text');
const floatIndex = dataTypes.indexOf('numeric');
cy.get(getElementFromAlias(`typed-input-${floatIndex}`)).type(`${0.5555}`);
cy.get(getElementFromAlias(`typed-input-${textIndex}`))
.clear()
.type('filter-text');
// Click the Insert Again button.
cy.get(getElementFromAlias(`typed-input-${textIndex}`)).type(
'{selectall}{del}'
);
cy.get(getElementFromAlias(`typed-input-${textIndex}`)).type('name');
cy.get(getElementFromAlias('insert-save-button')).click();
// Check default for next insert
cy.get(getElementFromAlias(`typed-input-default-${textIndex}`)).check();
validateInsert(getTableName(0, testName), 21);
cy.wait(7000);
cy.get(getElementFromAlias(`typed-input-${textIndex}`))
.clear()
.type('filter-text');
// Click the Insert Again button.
cy.get(getElementFromAlias(`typed-input-${textIndex}`)).type(
'{selectall}{del}'
);
cy.get(getElementFromAlias(`typed-input-${textIndex}`)).type('name');
cy.get(getElementFromAlias('insert-save-button')).click();
cy.wait(7000);
validateInsert(getTableName(0, testName), 21);
};
export const setValidationMetaData = () => {
setMetaData();
};
export const passEditButton = () => {
cy.get(getElementFromAlias('table-browse-rows')).click();
cy.wait(2000);
cy.get(getElementFromAlias('row-edit-button-0')).click();
cy.wait(2000);
cy.url().should(
'eq',
`${baseUrl}/data/default/schema/public/tables/${getTableName(
0,
testName
)}/edit`
);
const textIndex = dataTypes.indexOf('text');
cy.get(getElementFromAlias(`typed-input-${textIndex}`)).type(
'{selectall}{del}'
);
cy.get(getElementFromAlias(`typed-input-${textIndex}`)).type('new-text');
cy.get(getElementFromAlias('edit-save-button')).click();
cy.wait(7000);
};
export const passCloneButton = () => {
cy.get(getElementFromAlias('table-browse-rows')).click();
cy.wait(5000);
cy.get(getElementFromAlias('row-clone-button-0')).click();
cy.url().should(
'eq',
`${baseUrl}/data/default/schema/public/tables/${getTableName(
0,
testName
)}/insert`
);
cy.get(getElementFromAlias('clear-button')).click();
cy.get(getElementFromAlias('typed-input-0')).should('have.value', '');
};
export const checkViewRelationship = () => {
cy.visit(getIndexRoute());
cy.wait(5000);
cy.get(getElementFromAlias('data-create-table')).click();
// Type table name
cy.get(getElementFromAlias('tableName')).type(getTableName(2, testName));
cy.get(getElementFromAlias('column-0')).type('id');
tableColumnTypeSelector('col-type-0');
cy.get(getElementFromAlias('data_test_column_type_value_serial'))
.first()
.click();
cy.get(getElementFromAlias('column-1')).type('someID');
tableColumnTypeSelector('col-type-1');
cy.get(getElementFromAlias('data_test_column_type_value_integer'))
.first()
.click();
// Set primary key
cy.get(getElementFromAlias('primary-key-select-0')).select('0');
// Click on create
cy.get(getElementFromAlias('table-create')).click();
cy.wait(7000);
validateCT(getTableName(0, testName), ResultType.SUCCESS);
// Add foreign key
cy.get(getElementFromAlias('modify-table-edit-fk-0')).click();
cy.get(getElementFromAlias('foreign-key-ref-table-0')).select(
getTableName(0, testName)
);
cy.get(getElementFromAlias('foreign-key-0-lcol-0')).select('0');
cy.get(getElementFromAlias('foreign-key-0-rcol-0')).select(getColName(0));
cy.get(getElementFromAlias('modify-table-fk-0-save')).click();
cy.wait(5000);
// Add relationship
cy.get(getElementFromAlias('table-relationships')).click();
cy.get(getElementFromAlias('obj-rel-add-0')).click();
cy.get(getElementFromAlias('suggested-rel-name')).clear().type('someRel');
cy.get(getElementFromAlias('obj-rel-save-0')).click();
cy.wait(2000);
// Insert a row
cy.get(getElementFromAlias('table-insert-rows')).click();
cy.get(getElementFromAlias('typed-input-1')).type('1');
cy.get(getElementFromAlias('insert-save-button')).click();
cy.wait(1000);
cy.get(getElementFromAlias('table-browse-rows')).click();
cy.wait(1000);
cy.get('.rt-table').within(() => {
cy.get('a').contains('View').click();
cy.wait(1000);
});
cy.get('a').contains('Close').first().click();
};
export const passDeleteRow = () => {
cy.get(getElementFromAlias('table-browse-rows')).click();
cy.wait(5000);
// cy.get(getElementFromAlias('table-browse-rows')).contains('21');
cy.get(getElementFromAlias('row-delete-button-0')).click();
cy.on('window:confirm', str => {
expect(
str.indexOf('This will permanently delete this row from this table') !==
-1
).to.be.true;
});
// cy.get(getElementFromAlias('table-browse-rows')).contains('20');
cy.wait(14000);
};
export const passBulkDeleteRows = () => {
cy.get(getElementFromAlias('table-browse-rows')).click();
cy.wait(5000);
// cy.get(getElementFromAlias('table-browse-rows')).contains('20');
cy.get(getElementFromAlias('row-checkbox-0')).click();
cy.get(getElementFromAlias('row-checkbox-1')).click();
cy.get(getElementFromAlias('bulk-delete')).click();
cy.wait(1000);
cy.on('window:confirm', str => {
expect(
str.indexOf('This will permanently delete rows from this table') !== -1
).to.be.true;
});
// cy.get(getElementFromAlias('table-browse-rows')).contains('18');
cy.wait(14000);
};
export const passBulkDeleteAllRows = () => {
cy.get(getElementFromAlias('table-browse-rows')).click();
cy.wait(5000);
// cy.get(getElementFromAlias('table-browse-rows')).contains('18');
cy.get(getElementFromAlias('select-all-rows')).click();
cy.get(getElementFromAlias('bulk-delete')).click();
cy.wait(1000);
cy.on('window:confirm', str => {
expect(
str.indexOf('This will permanently delete rows from this table') !== -1
).to.be.true;
});
// cy.get(getElementFromAlias('table-browse-rows')).contains('(8)');
cy.wait(14000);
};
export const passArrayDataType = () => {
// create new column
cy.get(getElementFromAlias('table-modify')).click();
cy.wait(1000);
cy.get(getElementFromAlias('modify-table-edit-add-new-column')).click();
cy.get(getElementFromAlias('column-name')).type('array_column');
cy.get(getElementFromAlias('col-type-0'))
.children('div')
.click()
.find('input')
.type('text[]', { force: true });
cy.get(getElementFromAlias('modify-table-add-new-column-save')).click();
// insert new row
cy.get(getElementFromAlias('table-insert-rows')).click();
cy.wait(5000);
cy.get(getElementFromAlias('typed-input-11')).type('["a", "b"]');
cy.get(getElementFromAlias('insert-save-button')).click();
// go to browse rows and check if row was added
cy.get(getElementFromAlias('table-browse-rows')).click();
cy.wait(5000);
// cy.get(getElementFromAlias('table-browse-rows')).contains('(9)');
};

View File

@ -1,68 +0,0 @@
import { testMode } from '../../../helpers/common';
import {
passBICreateTable,
deleteBITestTable,
checkInsertRoute,
failBIWrongDataType,
failBIUniqueKeys,
passBIInsert20Rows,
checkBrowseRoute,
passBISort,
passBIFilterQueryEq,
passEditButton,
passSearchTables,
passCloneButton,
checkViewRelationship,
passDeleteRow,
passBulkDeleteRows,
passBulkDeleteAllRows,
passArrayDataType,
} from './spec';
import { setMetaData } from '../../validators/validators';
import { getIndexRoute } from '../../../helpers/dataHelpers';
const setup = () => {
// Temporarily skipped because of its flakiness, see: https://github.com/hasura/graphql-engine-mono/issues/5433
// TODO: Fix and restore it
describe.skip('Setup route', () => {
it('Visit the index route', () => {
cy.visit(getIndexRoute());
// Get and set validation metadata
setMetaData();
});
});
};
export const runInsertBrowseTests = () => {
// Temporarily skipped because of its flakiness, see: https://github.com/hasura/graphql-engine-mono/issues/5433
// TODO: Fix and restore it
describe.skip('Table: Browse and Insert', () => {
it('Create a table with fields of all data types', passBICreateTable);
it('Search for tables', passSearchTables);
it('Check Insert Route', checkInsertRoute);
it('Fails when entered wrong data type', failBIWrongDataType);
it('Insert 20 rows', passBIInsert20Rows);
it('Fail for adding same data for Unique keys', failBIUniqueKeys);
it('Check browser rows route', checkBrowseRoute);
// it('20 Inserted rows reflect in browse rows', passBI20RowsExist);
// it('Check pagination in Browse Rows table', checkPagination);
it('Ascending sort works as expected', () => passBISort('asc'));
it('Descending sort works as expected', () => passBISort('desc'));
it('Filter query works as expected with $eq', passBIFilterQueryEq);
it('Check edit button', passEditButton);
it('Check for clone clear', passCloneButton);
it('Delete the row', passDeleteRow);
it('Bulk delete rows', passBulkDeleteRows);
it('Bulk delete all rows', passBulkDeleteAllRows);
it('Handle array data types', passArrayDataType);
it('Check view relationship', checkViewRelationship);
it('Delete test table', deleteBITestTable);
});
};
if (testMode !== 'cli') {
setup();
runInsertBrowseTests();
}

View File

@ -1,52 +0,0 @@
import { setPromptWithCb } from '../../../helpers/common';
import { setMetaData } from '../../validators/validators';
export const navigateAndOpenConnectDatabasesForm = () => {
cy.location('pathname').then(currentPage => {
const alreadyOnThePage = currentPage.startsWith('/data/manage');
if (!alreadyOnThePage) {
cy.log('**--- Navigate to Connect Databases Form**');
cy.log('**--- visit index route and set metadata**');
cy.visit('/data/default/schema/public').then(setMetaData);
cy.log('**--- Click on the manage database menu**');
cy.findByRole('button', { name: 'Manage' }).click();
cy.location('pathname').should('eq', '/data/manage');
}
});
cy.log('**--- Click on the Connect Database button**');
cy.findByRole('button', { name: 'Connect Database' }).click();
cy.location('pathname').should('eq', '/data/manage/connect');
cy.get('form').within(() => {
cy.log('**--- Click on Connection Settings section**');
cy.contains('Connection Settings').click();
});
cy.get('form').within(() => {
cy.log('**--- Click on GraphQL Field Customization section**');
cy.contains('GraphQL Field Customization').click();
});
};
export const navigateToManageDatabases = () => {
cy.log('**--- Visit index route and set metadata**');
cy.visit('/data/default/schema/public').then(setMetaData);
cy.log('**--- Visit the manage database route**');
cy.getBySel('sidebar-manage-database').click();
cy.location('pathname').should('eq', '/data/manage');
};
export const submitConnectDBForm = () => {
cy.log('**--- Click on the Connect Database button**');
cy.getBySel('connect-database-btn').click();
};
export const removeDBFromList = (dbName: string) => {
setPromptWithCb(dbName, () => {
cy.log('**--- Click on the Remove Database button**');
cy.getBySel(dbName).find('button').contains('Remove').click();
});
};

View File

@ -1,160 +0,0 @@
import { baseUrl, testMode } from '../../../helpers/common';
import {
navigateAndOpenConnectDatabasesForm,
navigateToManageDatabases,
removeDBFromList,
submitConnectDBForm,
} from './common.spec';
import {
createDB,
fillDetailsForPgConnParamsForm,
fillDetailsForPgDbUrlForm,
fillDetailsForPgEnvVarForm,
removeDB,
} from './postgres.spec';
const connectPgDatabaseFormTests = () => {
// Temporarily skipped because of its flakiness, see: https://github.com/hasura/graphql-engine-mono/issues/5433
// TODO: Fix and restore it
describe.skip('Add a database via connect form', () => {
describe('can successfully add', () => {
describe('a postgres database', () => {
it('using a connection string', () => {
cy.log('**------------------------------**');
cy.log('**--- Add postgres DB via connection string**');
cy.log('**------------------------------**');
navigateAndOpenConnectDatabasesForm();
fillDetailsForPgDbUrlForm('postgres_db_with_url');
submitConnectDBForm();
cy.log('**--- Notifies that DB is being added');
cy.expectSuccessNotificationWithTitle('Adding data source...');
cy.log('**--- Redirects to Data Manager page');
cy.getBySel('manage-database-section').within(() => {
cy.findByText('Data Manager');
});
cy.url().should('eq', `${baseUrl}/data/manage`);
cy.log('**--- has postgres_db_with_url on manage page');
cy.findByText('postgres_db_with_url');
cy.log('**--- has success notification displayed');
cy.expectSuccessNotificationWithTitle(
'Data source added successfully!'
);
cy.log('**--- Remove database**');
removeDB('postgres_db_with_url');
});
it('using connection parameters', () => {
cy.log('**------------------------------**');
cy.log('**--- Add postgres DB via connection parameters**');
cy.log('**------------------------------**');
navigateAndOpenConnectDatabasesForm();
fillDetailsForPgConnParamsForm('postgres_db_with_conn_param');
submitConnectDBForm();
cy.log('**--- notifies that db is being added');
cy.expectSuccessNotificationWithTitle('Adding data source...');
cy.log('**--- redirects to Data Manager page');
cy.getBySel('manage-database-section').within(() => {
cy.findByText('Data Manager');
});
cy.url().should('eq', `${baseUrl}/data/manage`);
cy.log('**--- has postgres_db_with_conn_param on manage page');
cy.findByText('postgres_db_with_conn_param');
cy.log('**--- has success notification displayed');
cy.expectSuccessNotificationWithTitle(
'Data source added successfully!'
);
cy.log('**--- Remove database**');
removeDB('postgres_db_with_conn_param');
});
it('using environment variables', () => {
cy.log('**------------------------------**');
cy.log('**--- Add postgres DB via env vars**');
cy.log('**------------------------------**');
navigateAndOpenConnectDatabasesForm();
fillDetailsForPgEnvVarForm('postgres_db_with_env_var');
submitConnectDBForm();
cy.log('**--- notifies that db is being added');
cy.expectSuccessNotificationWithTitle('Adding data source...');
cy.log('**--- redirects to Data Manager page');
cy.getBySel('manage-database-section').within(() => {
cy.findByText('Data Manager');
});
cy.url().should('eq', `${baseUrl}/data/manage`);
cy.log('**--- has postgres_db_with_env_var on manage page');
cy.findByText('postgres_db_with_env_var');
cy.log('**--- has success notification displayed');
cy.expectSuccessNotificationWithTitle(
'Data source added successfully!'
);
cy.log('**--- Remove database**');
removeDB('postgres_db_with_env_var');
});
});
});
describe('fails on submitting', () => {
describe('an empty form', () => {
it('submit with no inputs filled in', () => {
navigateAndOpenConnectDatabasesForm();
cy.getBySel('connect-database-btn').click();
cy.expectErrorNotification();
});
});
it('with a duplicate database name', () => {
navigateAndOpenConnectDatabasesForm();
createDB('test');
fillDetailsForPgDbUrlForm('test');
submitConnectDBForm();
cy.log('**--- verify error notification');
cy.expectErrorNotificationWithTitle('Adding data source failed');
cy.log('**--- Remove database**');
removeDB('test');
});
});
});
};
const manageDatabasesPageTests = () => {
// Temporarily skipped because of its flakiness, see: https://github.com/hasura/graphql-engine-mono/issues/5433
// TODO: Fix and restore it
describe.skip('Connected Databases list page', () => {
it('can successfully remove db', () => {
cy.log('**--- Create database**');
createDB('db_for_removal');
navigateToManageDatabases();
cy.log('**--- use the remove button');
removeDBFromList('db_for_removal');
cy.log('**--- verify success notification');
cy.expectSuccessNotificationWithTitle('Data source removed successfully');
});
});
};
if (testMode !== 'cli') {
connectPgDatabaseFormTests();
manageDatabasesPageTests();
}

View File

@ -1,228 +0,0 @@
const config = {
host: 'postgres',
port: '5432',
dbName: 'postgres',
username: 'postgres',
password: 'postgrespassword',
};
const dbUrl = `postgres://${config.username}:${config.password}@${config.host}:${config.port}/${config.dbName}`;
const graphqlCustomizationTest = () => {
cy.log('**--- Select a graphql naming convention**');
cy.get('[data-test="GraphQL Field Customization"]').within(() => {
cy.get('[data-test="radio-select-hasura-default"]').should('be.visible');
cy.get('[data-test="radio-select-graphql-default"]').should('be.visible');
cy.get('label[for="radio-select-graphql-default"]').click();
});
cy.log('**--- Fill Root Fields Customizations**');
cy.get('form[aria-label="rootFields"]').within(() => {
cy.findByPlaceholderText('Namespace...').type('name_space');
cy.findByPlaceholderText('prefix_').type('prefix_');
cy.findByPlaceholderText('_suffix').type('_suffix');
});
cy.log('**--- Fill Type Names Customizations**');
cy.get('form[aria-label="typeNames"]').within(() => {
cy.findByPlaceholderText('prefix_').type('prefix_');
cy.findByPlaceholderText('_suffix').type('_suffix');
});
};
export const fillDetailsForPgDbUrlForm = (dbName: string) => {
cy.log('**--- Fill Form using db url**');
cy.getBySel('database-display-name').type(dbName);
cy.getBySel('database-type').select('postgres');
cy.getBySel('database-url').type(dbUrl);
cy.getBySel('max-connections').type('50');
cy.getBySel('idle-timeout').type('180');
cy.getBySel('retries').type('1');
graphqlCustomizationTest();
};
export const fillDetailsForPgConnParamsForm = (dbName: string) => {
cy.log('**--- Fill Form using connection parameter**');
cy.get("input[type='radio']").eq(2).click();
cy.getBySel('database-display-name').type(dbName);
cy.getBySel('database-type').select('postgres');
cy.getBySel('host').type(config.host);
cy.getBySel('port').type(config.port);
cy.getBySel('username').type(config.username);
cy.getBySel('password').type(config.password);
cy.getBySel('database-name').type(config.dbName);
graphqlCustomizationTest();
};
export const fillDetailsForPgEnvVarForm = (dbName: string) => {
cy.log('**--- Fill Form using env vars**');
cy.get("input[type='radio']").eq(0).click();
cy.getBySel('database-display-name').type(dbName);
cy.getBySel('database-type').select('postgres');
cy.getBySel('database-url-env').type('HASURA_GRAPHQL_DATABASE_URL');
graphqlCustomizationTest();
};
export const createDB = (dbName: string) => {
const postBody = {
type: 'pg_add_source',
args: {
name: dbName,
configuration: {
connection_info: {
database_url: dbUrl,
},
},
},
};
cy.request('POST', 'http://localhost:8080/v1/metadata', postBody).then(
response => {
expect(response.body).to.have.property('message', 'success'); // true
}
);
};
export const removeDB = (dbName: string) => {
const postBody = { type: 'pg_drop_source', args: { name: dbName } };
cy.request('POST', 'http://localhost:8080/v1/metadata', postBody).then(
response => {
expect(response.body).to.have.property('message', 'success'); // true
}
);
cy.reload();
};
const createTable = (tableName: string) => {
const postBody = {
type: 'run_sql',
args: {
source: 'default',
sql: `CREATE TABLE "public"."${tableName}" ("id" serial NOT NULL, "name" text NOT NULL, "countryCode" text DEFAULT 'IN', PRIMARY KEY ("id") );`,
cascade: false,
read_only: false,
},
};
cy.request('POST', 'http://localhost:8080/v2/query', postBody).then(
response => {
expect(response.body).to.have.property('result_type', 'CommandOk'); // true
}
);
};
const trackTable = (tableName: string) => {
const postBody = {
type: 'pg_track_table',
args: {
table: {
name: tableName,
schema: 'public',
},
source: 'default',
},
};
cy.request('POST', 'http://localhost:8080/v1/metadata', postBody).then(
response => {
expect(response.body).to.have.property('message', 'success'); // true
}
);
};
const untrackTable = (tableName: string) => {
const postBody = {
type: 'pg_untrack_table',
args: {
table: {
schema: 'public',
name: tableName,
},
source: 'default',
},
};
cy.request('POST', 'http://localhost:8080/v1/metadata', postBody).then(
response => {
expect(response.body).to.have.property('message', 'success'); // true
}
);
};
const deleteTable = (tableName: string) => {
const postBody = {
type: 'run_sql',
args: {
source: 'default',
sql: `DROP table "public"."${tableName}";`,
cascade: false,
read_only: false,
},
};
cy.request('POST', 'http://localhost:8080/v2/query', postBody).then(
response => {
expect(response.body).to.have.property('result_type', 'CommandOk'); // true
}
);
};
const createRemoteSchema = (remoteSchemaName: string) => {
const postBody = {
type: 'add_remote_schema',
args: {
name: remoteSchemaName,
definition: {
timeout_seconds: 60,
forward_client_headers: false,
headers: [],
url: 'https://countries.trevorblades.com/',
},
comment: '',
},
};
cy.request('POST', 'http://localhost:8080/v1/metadata', postBody).then(
response => {
expect(response.body).to.have.property('message', 'success'); // true
}
);
};
const deleteRemoteSchema = (remoteSchemaName: string) => {
const postBody = {
type: 'remove_remote_schema',
args: {
name: remoteSchemaName,
},
};
cy.request('POST', 'http://localhost:8080/v1/metadata', postBody).then(
response => {
expect(response.body).to.have.property('message', 'success'); // true
}
);
};
type DriverSpec = {
name: 'postgres';
helpers: {
createDB: (dbName: string) => void;
removeDB: (dbName: string) => void;
createTable: (tableName: string) => void;
createRemoteSchema: (remoteSchemaName: string) => void;
deleteRemoteSchema: (remoteSchemaName: string) => void;
deleteTable: (tableName: string) => void;
trackTable: (tableName: string) => void;
untrackTable: (tableName: string) => void;
};
};
const postgres: DriverSpec = {
name: 'postgres',
helpers: {
createDB,
removeDB,
createTable,
createRemoteSchema,
deleteRemoteSchema,
deleteTable,
trackTable,
untrackTable,
},
};
export { postgres };

View File

@ -1,3 +0,0 @@
module.exports = {
"__version": "10.4.0"
}

View File

@ -1,3 +0,0 @@
module.exports = {
"__version": "10.4.0"
}

View File

@ -1,424 +0,0 @@
import {
getElementFromAlias,
baseUrl,
tableColumnTypeSelector,
getIndexRoute,
} from '../../../helpers/dataHelpers';
import {
setMetaData,
validateCT,
createView,
validateColumn,
validateView,
ResultType,
TableFields,
} from '../../validators/validators';
import { setPromptValue } from '../../../helpers/common';
const userId = 5555;
export const createTable = (name: string, dict: TableFields) => {
cy.url().should('eq', `${baseUrl}/data/default/schema/public/table/add`);
cy.get(getElementFromAlias('tableName')).type(`${name}_table_vt`);
const keys = Object.keys(dict).map(k => k);
const values = Object.keys(dict).map(k => dict[k]);
for (let i = 0; i < keys.length; i += 1) {
cy.get(getElementFromAlias(`column-${i}`)).type(keys[i]);
tableColumnTypeSelector(`col-type-${i}`);
cy.get(getElementFromAlias(`data_test_column_type_value_${values[i]}`))
.first()
.click();
}
cy.get(getElementFromAlias('primary-key-select-0')).select('id');
cy.get(getElementFromAlias('table-create')).click();
cy.wait(7000);
cy.url().should(
'eq',
`${baseUrl}/data/default/schema/public/tables/${name}_table_vt/modify`
);
validateCT(`${name}_table_vt`, ResultType.SUCCESS);
};
export const passVCreateTables = () => {
cy.get(getElementFromAlias('data-create-table')).click();
createTable('author', { id: 'integer', name: 'text' });
cy.visit(getIndexRoute());
cy.wait(5000);
cy.get(getElementFromAlias('data-create-table')).click();
createTable('article', {
id: 'integer',
title: 'text',
Content: 'text',
author_id: 'integer',
rating: 'integer',
});
cy.visit(getIndexRoute());
cy.wait(5000);
cy.get(getElementFromAlias('data-create-table')).click();
createTable('comment', {
id: 'integer',
user_id: 'integer',
article_id: 'integer',
comment: 'text',
});
};
export const passVCreateMaterializedViews = () => {
createView(`CREATE MATERIALIZED VIEW author_average_rating_vt AS
SELECT author_table_vt.id, avg(article_table_vt.rating)
From author_table_vt, article_table_vt
WHERE author_table_vt.id = article_table_vt.author_id
GROUP BY author_table_vt.id`);
};
export const passTrackTable = () => {
cy.visit('/data/default/schema/public/');
cy.wait(7000);
cy.get(
getElementFromAlias('add-track-table-author_average_rating_vt')
).click();
cy.wait(7000);
validateView('author_average_rating_vt', ResultType.SUCCESS);
};
export const passMaterializedViewRoute = () => {
cy.get(getElementFromAlias('author_average_rating_vt')).click();
cy.url().should(
'eq',
`${baseUrl}/data/default/schema/public/views/author_average_rating_vt/browse`
);
};
export const passVAddDataarticle = (
data: (string | number)[],
index: number
) => {
// Click the Insert Again button.
cy.get('label')
.contains('id')
.next()
.find('input')
.last()
.type('{selectall}{del}');
cy.get('label').contains('id').next().find('input').last().type(`${data[0]}`);
cy.get('label')
.contains('title')
.next()
.find('input')
.last()
.type('{selectall}{del}');
cy.get('label')
.contains('title')
.next()
.find('input')
.last()
.type(`${data[1]}`);
cy.get('label')
.contains('Content')
.next()
.find('input')
.last()
.type('{selectall}{del}');
cy.get('label')
.contains('Content')
.next()
.find('input')
.last()
.type(`${data[2]}`);
cy.get('label')
.contains('author_id')
.next()
.find('input')
.last()
.type('{selectall}{del}');
cy.get('label')
.contains('author_id')
.next()
.find('input')
.last()
.type(`${data[3]}`);
cy.get('label')
.contains('rating')
.next()
.find('input')
.last()
.type('{selectall}{del}');
cy.get('label')
.contains('rating')
.next()
.find('input')
.last()
.type(`${data[4]}`);
if (index) {
cy.get(getElementFromAlias('insert-save-button')).click();
} else {
cy.get(getElementFromAlias('insert-save-button')).click();
}
cy.wait(5000);
};
export const passVAddDataauthor = (
data: (string | number)[],
index: number
) => {
cy.get('label')
.contains('id')
.next()
.find('input')
.last()
.type('{selectall}{del}');
cy.get('label').contains('id').next().find('input').last().type(`${data[0]}`);
cy.get('label')
.contains('name')
.next()
.find('input')
.last()
.type('{selectall}{del}');
cy.get('label')
.contains('name')
.next()
.find('input')
.last()
.type(`${data[1]}`);
if (index) {
cy.get(getElementFromAlias('insert-save-button')).click();
} else {
cy.get(getElementFromAlias('insert-save-button')).click();
}
cy.wait(5000);
};
export const passVAddDatacomment = (
data: (string | number)[],
index: number
) => {
cy.get('label')
.contains('id')
.next()
.find('input')
.last()
.type('{selectall}{del}');
cy.get('label').contains('id').next().find('input').last().type(`${data[0]}`);
cy.get('label')
.contains('user_id')
.next()
.find('input')
.last()
.type('{selectall}{del}');
cy.get('label')
.contains('user_id')
.next()
.find('input')
.last()
.type(`${data[1]}`);
cy.get('label')
.contains('article_id')
.next()
.find('input')
.last()
.type('{selectall}{del}');
cy.get('label')
.contains('article_id')
.next()
.find('input')
.last()
.type(`${data[2]}`);
cy.get('label')
.contains('comment')
.next()
.find('input')
.last()
.type('{selectall}{del}');
cy.get('label')
.contains('comment')
.next()
.find('input')
.last()
.type(`${data[3]}`);
if (index) {
cy.get(getElementFromAlias('insert-save-button')).click();
} else {
cy.get(getElementFromAlias('insert-save-button')).click();
}
cy.wait(5000);
};
const checkQuerySuccess = () => {
// Expect only 4 rows i.e. expect fifth element to not exist
cy.get('[role=gridcell]').contains(userId);
cy.get('[role=row]').eq(2).should('not.exist');
};
export const passVAddData = () => {
let data;
cy.get(getElementFromAlias('article_table_vt')).click();
cy.get(getElementFromAlias('table-insert-rows')).click();
data = [1, 'A', 'Sontent', userId, 4];
passVAddDataarticle(data, 0);
data = [2, 'B', 'Sontenta', 2, 4];
passVAddDataarticle(data, 1);
data = [3, 'C', 'Sontentb', userId, 4];
passVAddDataarticle(data, 2);
cy.get(getElementFromAlias('author_table_vt')).click();
cy.get(getElementFromAlias('table-insert-rows')).click();
data = [userId, 'A'];
passVAddDataauthor(data, 0);
data = [2, 'B'];
passVAddDataauthor(data, 1);
cy.get(getElementFromAlias('comment_table_vt')).click();
cy.get(getElementFromAlias('table-insert-rows')).click();
data = [1, 1, 1, 'new comment'];
passVAddDatacomment(data, 0);
data = [2, 2, 2, 'new comment'];
passVAddDatacomment(data, 1);
data = [3, 1, 2, 'new comment'];
passVAddDatacomment(data, 2);
};
export const passVFilterQueryEq = () => {
// Select column with type `text`
cy.get('select')
.find('option')
.contains('-- column --')
.parent()
.first()
.select('id');
// Type value as `filter-text`
cy.get("input[placeholder='-- value --']").last().type(`${userId}`);
// Run query
cy.get(getElementFromAlias('run-query')).click();
cy.wait(5000);
// Check if the query was successful
checkQuerySuccess();
};
const checkOrder = (order: string) => {
// Utility function to get right element
if (order === 'asc') {
cy.get('[role=row]').each(($el, index) => {
if (index === 1) {
cy.wrap($el).find('[role=gridcell]').first().next().next().contains(2);
}
if (index === 2) {
cy.wrap($el)
.find('[role=gridcell]')
.first()
.next()
.next()
.contains(userId);
}
});
} else {
cy.get('[role=row]').each(($el, index) => {
if (index === 2) {
cy.wrap($el).find('[role=gridcell]').first().next().next().contains(2);
}
if (index === 1) {
cy.wrap($el)
.find('[role=gridcell]')
.first()
.next()
.next()
.contains(userId);
}
});
}
};
export const passVAscendingSort = () => {
cy.wait(7000);
// cy.scrollTo('top');
// Select column with type 'serial'
cy.get('select')
.find('option')
.contains('-- column --')
.parent()
.last()
.select('id');
// Run query
cy.get(getElementFromAlias('run-query')).click();
// Check order
checkOrder('asc');
};
export const passModifyMaterializedView = () => {
cy.get(getElementFromAlias('table-modify')).click();
cy.get('button').contains('Modify').last().click();
cy.url().should('eq', `${baseUrl}/data/sql`);
};
export const passVAddManualObjRel = () => {
cy.get(getElementFromAlias('author_average_rating_vt')).click();
cy.wait(2000);
cy.get(getElementFromAlias('table-relationships')).click();
cy.wait(2000);
cy.get(getElementFromAlias('create-edit-manual-rel')).click();
cy.get(getElementFromAlias('manual-relationship-type')).select('object');
cy.get("input[placeholder='Enter relationship name']").type('author');
cy.get(getElementFromAlias('manual-relationship-ref-schema')).select(
'public'
);
cy.get(getElementFromAlias('manual-relationship-ref-table')).select(
'author_table_vt'
);
cy.get(getElementFromAlias('manual-relationship-lcol-0')).select('id');
cy.get(getElementFromAlias('manual-relationship-rcol-0')).select('id');
cy.get(getElementFromAlias('create-manual-rel-save')).click();
cy.wait(7000);
validateColumn(
'author_average_rating_vt',
['avg', { name: 'author', columns: ['name'] }],
ResultType.SUCCESS
);
};
export const passVDeleteRelationships = () => {
cy.get(getElementFromAlias('author_average_rating_vt')).click();
cy.get(getElementFromAlias('table-relationships')).click();
cy.get(getElementFromAlias('relationship-toggle-editor-author')).click();
cy.get(getElementFromAlias('relationship-remove-author')).click();
cy.on('window:alert', str => {
return expect(str === 'Are you sure?').to.be.true;
});
cy.wait(7000);
validateColumn(
'author_average_rating_vt',
['avg', { name: 'author', columns: ['name'] }],
ResultType.FAILURE
);
};
export const passVDeleteMaterializedView = () => {
cy.get(getElementFromAlias('table-modify')).click();
setPromptValue('author_average_rating_vt');
cy.get(getElementFromAlias('delete-view')).click();
cy.window().its('prompt').should('be.called');
cy.wait(7000);
validateView('author_average_rating_vt', ResultType.FAILURE);
};
export const deleteTable = (name: string) => {
cy.get(getElementFromAlias(name)).click();
cy.get(getElementFromAlias('table-modify')).click();
setPromptValue(name);
cy.get(getElementFromAlias('delete-table')).click();
cy.window().its('prompt').should('be.called');
cy.wait(7000);
validateCT(name, ResultType.FAILURE);
cy.wait(7000);
};
export const passVDeleteTables = () => {
deleteTable('comment_table_vt');
deleteTable('article_table_vt');
deleteTable('author_table_vt');
};
export const setValidationMetaData = () => {
setMetaData();
};

View File

@ -1,54 +0,0 @@
import {
passVCreateTables,
passVCreateMaterializedViews,
passVAddData,
passTrackTable,
passVAddManualObjRel,
passVAscendingSort,
passModifyMaterializedView,
passVFilterQueryEq,
passMaterializedViewRoute,
passVDeleteRelationships,
passVDeleteMaterializedView,
passVDeleteTables,
} from './spec';
import { testMode } from '../../../helpers/common';
import { setMetaData } from '../../validators/validators';
import { getIndexRoute } from '../../../helpers/dataHelpers';
const setup = () => {
// Temporarily skipped because of its flakiness, see: https://github.com/hasura/graphql-engine-mono/issues/5433
// TODO: Fix and restore it
describe.skip('Setup route', () => {
it('Visit the index route', () => {
// Visit the index route
cy.visit(getIndexRoute());
// Get and set validation metadata
setMetaData();
});
});
};
export const runMaterializedViewsTest = () => {
// Temporarily skipped because of its flakiness, see: https://github.com/hasura/graphql-engine-mono/issues/5433
// TODO: Fix and restore it
describe.skip('Materialized Views', () => {
it('Create Tables', passVCreateTables);
it('Add data to table', passVAddData);
it('Create MaterializedView', passVCreateMaterializedViews);
it('Adding it to the table', passTrackTable);
it('Check the materializedview route', passMaterializedViewRoute);
it('Ascending order MaterializedView Table', passVAscendingSort);
it('Filter the MaterializedView table', passVFilterQueryEq);
it('Modify the View', passModifyMaterializedView);
it('Adding Object Relationship to MaterializedView', passVAddManualObjRel);
it('Deleting Relationship', passVDeleteRelationships);
it('Deleting MaterializedView', passVDeleteMaterializedView);
it('Deleting Tables', passVDeleteTables);
});
};
if (testMode !== 'cli') {
setup();
runMaterializedViewsTest();
}

View File

@ -1,3 +0,0 @@
module.exports = {
"__version": "10.4.0"
}

View File

@ -1,29 +0,0 @@
import { validateMigrationMode } from '../../validators/validators';
import { toggleOnMigrationMode, toggleOffMigrationMode } from './utils';
export const testToggleButton = () => {
// Turn off migration mode
toggleOffMigrationMode();
cy.wait(10000);
// Validate
validateMigrationMode(false);
cy.wait(7000);
// Turn on migration mode
toggleOnMigrationMode();
cy.wait(10000);
// Validate
validateMigrationMode(true);
cy.wait(7000);
};
export const checkToggleButton = () => {
cy.window().then(win => {
const { consoleMode } = win.__env;
if (consoleMode === 'cli') {
testToggleButton();
} else {
cy.get('[class=react-toggle-track]').should('not.exist');
}
});
};

View File

@ -1,27 +0,0 @@
import { checkToggleButton } from './spec';
import { testMode } from '../../../helpers/common';
import { setMetaData } from '../../validators/validators';
import { getIndexRoute } from '../../../helpers/dataHelpers';
const setup = () => {
describe('Setup route', () => {
it('Visit the index route', () => {
// Visit the index route
cy.visit(getIndexRoute());
// Get and set validation metadata
setMetaData();
});
});
};
export const runMigrationModeTests = () => {
describe('Migration mode', () => {
it('Check the toggle button', checkToggleButton);
});
};
if (testMode !== 'cli') {
setup();
runMigrationModeTests();
}

View File

@ -1,37 +0,0 @@
import { migrateModeUrl } from '../../../helpers/common';
export const toggleOnMigrationMode = () => {
cy.request({
method: 'GET',
url: migrateModeUrl,
}).then(response => {
if (response.body.migration_mode === 'false') {
// Go to migrations section
cy.get('a')
.contains('Migrations')
.click();
cy.wait(3000);
// Toggle Migration mode
cy.get('[class=react-toggle-track]').click();
cy.wait(10000);
}
});
};
export const toggleOffMigrationMode = () => {
cy.request({
method: 'GET',
url: migrateModeUrl,
}).then(response => {
if (response.body.migration_mode === 'true') {
// Go to migrations section
cy.get('a')
.contains('Migrations')
.click();
cy.wait(3000);
// Toggle Migration mode
cy.get('[class=react-toggle-track]').click();
cy.wait(10000);
}
});
};

View File

@ -1,3 +0,0 @@
module.exports = {
"__version": "10.4.0"
}

View File

@ -1,439 +0,0 @@
import {
tableColumnTypeSelector,
baseUrl,
getTableName,
getColName,
getElementFromAlias,
createUntrackedFunctionSQL,
dropUntrackedFunctionSQL,
getIndexRoute,
} from '../../../helpers/dataHelpers';
import {
setMetaData,
validateCT,
validateColumn,
ResultType,
dataRequest,
} from '../../validators/validators';
import { setPromptValue } from '../../../helpers/common';
const testName = 'mod';
export const passMTFunctionList = () => {
const tableName = getTableName(0, testName);
dataRequest(
createUntrackedFunctionSQL(`${tableName}_id_fn`, tableName),
ResultType.SUCCESS
);
cy.wait(5000);
cy.get(getElementFromAlias('modify-table-edit-computed-field-0')).click();
cy.get(getElementFromAlias('functions-dropdown')).click();
cy.get('[data-test^="data_test_column_type_value_"]').should(
'have.length',
1
);
cy.get('[data-test^="data_test_column_type_value_"]')
.first()
.should('have.text', `${getTableName(0, testName)}_id_fn`.toLowerCase());
dataRequest(
dropUntrackedFunctionSQL(`${tableName}_id_fn`),
ResultType.SUCCESS
);
};
export const passMTCreateTable = () => {
cy.get(getElementFromAlias('data-create-table')).click();
cy.url().should('eq', `${baseUrl}/data/default/schema/public/table/add`);
cy.get(getElementFromAlias('tableName')).type(getTableName(0, testName));
cy.get(getElementFromAlias('column-0')).type('id');
tableColumnTypeSelector('col-type-0');
cy.get(getElementFromAlias('data_test_column_type_value_integer'))
.first()
.click();
cy.get(getElementFromAlias('primary-key-select-0')).select('id');
cy.get(getElementFromAlias('table-create')).click();
cy.wait(7000);
cy.url().should(
'eq',
`${baseUrl}/data/default/schema/public/tables/${getTableName(
0,
testName
)}/modify`
);
validateCT(getTableName(0, testName), ResultType.SUCCESS);
};
export const passMTCheckRoute = () => {
// Click on the create table button
cy.get(getElementFromAlias('table-modify')).click();
// Match the URL
cy.url().should(
'eq',
`${baseUrl}/data/default/schema/public/tables/${getTableName(
0,
testName
)}/modify`
);
};
export const passMTRenameTable = () => {
cy.get(getElementFromAlias('heading-edit-table')).click();
cy.get(getElementFromAlias('heading-edit-table-input'))
.clear()
.type(getTableName(3, testName));
cy.get(getElementFromAlias('heading-edit-table-save')).click();
cy.wait(15000);
validateCT(getTableName(3, testName), ResultType.SUCCESS);
cy.get(getElementFromAlias('heading-edit-table')).click();
cy.get(getElementFromAlias('heading-edit-table-input'))
.clear()
.type(getTableName(0, testName));
cy.get(getElementFromAlias('heading-edit-table-save')).click();
cy.wait(15000);
validateCT(getTableName(0, testName), ResultType.SUCCESS);
};
export const passMTRenameColumn = () => {
cy.wait(10000);
cy.get(getElementFromAlias('modify-table-edit-column-0')).click();
cy.get(getElementFromAlias('edit-col-name')).clear().type(getColName(3));
cy.get(getElementFromAlias('modify-table-column-0-save')).click();
cy.wait(15000);
validateColumn(
getTableName(0, testName),
[getColName(3)],
ResultType.SUCCESS
);
cy.get(getElementFromAlias('modify-table-edit-column-0')).click();
cy.get(getElementFromAlias('edit-col-name')).clear().type('id');
cy.get(getElementFromAlias('modify-table-column-0-save')).click();
cy.wait(15000);
validateColumn(getTableName(0, testName), ['id'], ResultType.SUCCESS);
};
export const passMTChangeDefaultValueForPKey = () => {
cy.wait(10000);
cy.get(getElementFromAlias('modify-table-edit-column-0')).click();
cy.get(getElementFromAlias('edit-col-default')).clear().type('1234');
cy.get(getElementFromAlias('modify-table-column-0-save')).click();
cy.wait(15000);
};
export const passMTMoveToTable = () => {
cy.get(getElementFromAlias(getTableName(0, testName))).click();
cy.url().should(
'eq',
`${baseUrl}/data/default/schema/public/tables/${getTableName(
0,
testName
)}/browse`
);
};
export const failMTWithoutColName = () => {
cy.get(getElementFromAlias('modify-table-edit-add-new-column')).click();
cy.get(getElementFromAlias('modify-table-add-new-column-save')).click();
cy.url().should(
'eq',
`${baseUrl}/data/default/schema/public/tables/${getTableName(
0,
testName
)}/modify`
);
validateColumn(
getTableName(0, testName),
[getColName(2)],
ResultType.FAILURE
);
};
export const failMTWithoutColType = () => {
cy.get(getElementFromAlias('column-name')).type(getColName(2));
cy.get(getElementFromAlias('modify-table-add-new-column-save')).click();
cy.url().should(
'eq',
`${baseUrl}/data/default/schema/public/tables/${getTableName(
0,
testName
)}/modify`
);
validateColumn(
getTableName(0, testName),
[getColName(2)],
ResultType.FAILURE
);
};
export const Addcolumnnullable = () => {
cy.get(getElementFromAlias('column-name')).type('{selectall}{del}');
cy.get(getElementFromAlias('column-name')).type(getColName(3));
tableColumnTypeSelector('col-type-0');
cy.get(getElementFromAlias('data_test_column_type_value_text'))
.first()
.click();
cy.get(getElementFromAlias('nullable-checkbox')).uncheck({ force: true });
cy.get(getElementFromAlias('modify-table-add-new-column-save')).click();
cy.wait(2500);
cy.url().should(
'eq',
`${baseUrl}/data/default/schema/public/tables/${getTableName(
0,
testName
)}/modify`
);
validateColumn(
getTableName(0, testName),
[getColName(3)],
ResultType.FAILURE
);
};
export const Addcolumnname = (name: string) => {
cy.get(getElementFromAlias('column-name')).type('{selectall}{del}');
cy.get(getElementFromAlias('column-name')).type(name);
tableColumnTypeSelector('col-type-0');
cy.get(getElementFromAlias('data_test_column_type_value_integer'))
.first()
.click();
cy.get(getElementFromAlias('modify-table-add-new-column-save')).click();
cy.wait(5000);
validateColumn(getTableName(0, testName), [name], ResultType.SUCCESS);
};
export const passMTAddColumn = () => {
cy.get(getElementFromAlias('frequently-used-columns')).first().should('exist');
cy.get(getElementFromAlias('column-name')).type('{selectall}{del}');
cy.get(getElementFromAlias('column-name')).type(getColName(0));
tableColumnTypeSelector('col-type-0');
cy.get(getElementFromAlias('data_test_column_type_value_integer'))
.first()
.click();
cy.get(getElementFromAlias('modify-table-add-new-column-save')).click();
cy.wait(5000);
validateColumn(
getTableName(0, testName),
[getColName(0)],
ResultType.SUCCESS
);
};
export const Movetocolumn = () => {
Addcolumnname(getColName(1));
cy.get(getElementFromAlias('modify-table-edit-column-1')).click();
};
export const failMCWithWrongDefaultValue = () => {
cy.get(getElementFromAlias('modify-table-edit-column-1')).click();
cy.get(getElementFromAlias('edit-col-default')).type('abcd');
cy.get(getElementFromAlias('modify-table-column-1-save')).click();
};
export const passMCWithRightDefaultValue = () => {
cy.get(getElementFromAlias('edit-col-default')).clear().type('1234');
cy.get(getElementFromAlias('modify-table-column-1-save')).click();
cy.wait(10000);
};
export const passCreateForeignKey = () => {
cy.get(getElementFromAlias('modify-table-edit-fk-0')).click();
cy.get(getElementFromAlias('foreign-key-ref-table-0')).select(
getTableName(0, testName)
);
cy.get(getElementFromAlias('foreign-key-0-lcol-0')).select('0');
cy.get(getElementFromAlias('foreign-key-0-rcol-0')).select('id');
cy.get(getElementFromAlias('modify-table-fk-0-save')).click();
cy.wait(10000);
};
export const passRemoveForeignKey = () => {
cy.get(getElementFromAlias('modify-table-edit-fk-0')).click();
cy.get(getElementFromAlias('modify-table-fk-0-remove')).click();
cy.wait(10000);
};
export const passModifyPkey = () => {
cy.get(getElementFromAlias('modify-table-edit-pks')).click();
cy.get(getElementFromAlias('primary-key-select-1')).select('1');
cy.get(getElementFromAlias('modify-table-pks-save')).click();
cy.get(getElementFromAlias('pk-config-text')).within(() => {
cy.get('b').contains(getColName(0));
cy.get('b').contains('id');
});
cy.wait(5000);
cy.get(getElementFromAlias('remove-pk-column-1')).click();
cy.get(getElementFromAlias('modify-table-pks-save')).click();
cy.get(getElementFromAlias('pk-config-text')).within(() => {
cy.get('b').contains('id');
});
cy.get(getElementFromAlias('pk-config-text')).within(() => {
cy.get('b').should('not.contain', getColName(0));
});
cy.get(getElementFromAlias('modify-table-close-pks')).click();
cy.wait(3000);
};
export const passCreateUniqueKey = () => {
cy.get(getElementFromAlias('modify-table-edit-unique-key-0')).click();
cy.get(getElementFromAlias('unique-key-0-column-0')).select('0');
cy.get(getElementFromAlias('unique-key-0-column-1')).select('1');
cy.wait(1000);
cy.get(getElementFromAlias('modify-table-unique-key-0-save')).click();
cy.wait(5000);
cy.get('div').contains(
`${getTableName(0, testName)}_id_${getColName(0)}_key`
);
};
export const passModifyUniqueKey = () => {
cy.get(getElementFromAlias('modify-table-edit-unique-key-0')).click();
cy.get(getElementFromAlias('remove-uk-0-column-0')).click();
cy.get(getElementFromAlias('modify-table-unique-key-0-save')).click();
cy.wait(5000);
cy.get('div').contains(`${getTableName(0, testName)}_${getColName(0)}_key`);
};
export const passRemoveUniqueKey = () => {
cy.get(getElementFromAlias('modify-table-edit-unique-key-0')).click();
cy.get(getElementFromAlias('modify-table-unique-key-0-remove')).click();
cy.wait(5000);
};
export const passMTDeleteCol = () => {
setPromptValue(getColName(0));
cy.get(getElementFromAlias('modify-table-edit-column-1')).click();
cy.get(getElementFromAlias('modify-table-column-1-remove')).click();
cy.window().its('prompt').should('be.called');
cy.wait(5000);
cy.url().should(
'eq',
`${baseUrl}/data/default/schema/public/tables/${getTableName(
0,
testName
)}/modify`
);
validateColumn(
getTableName(0, testName),
[getColName(0)],
ResultType.FAILURE
);
};
export const passMTDeleteTableCancel = () => {
setPromptValue(null);
cy.get(getElementFromAlias('delete-table')).click();
cy.window().its('prompt').should('be.called');
cy.url().should(
'eq',
`${baseUrl}/data/default/schema/public/tables/${getTableName(
0,
testName
)}/modify`
);
validateCT(getTableName(0, testName), ResultType.SUCCESS);
};
export const passMTDeleteTable = () => {
setPromptValue(getTableName(0, testName));
cy.get(getElementFromAlias('delete-table')).click();
cy.window().its('prompt').should('be.called');
cy.wait(5000);
cy.url().should('eq', `${baseUrl}/data/default/schema/public`);
validateCT(getTableName(0, testName), ResultType.FAILURE);
};
export const setValidationMetaData = () => {
setMetaData();
};
// Views Modify /////////////////////////////////////////////////
export const createTable = (name: string, dict: { [key: string]: any }) => {
cy.url().should('eq', `${baseUrl}/data/schema/public/table/add`);
cy.get(getElementFromAlias('tableName')).type(`${name}_table_mod`);
const keys = Object.keys(dict).map(k => k);
const values = Object.keys(dict).map(k => dict[k]);
for (let i = 0; i < keys.length; i += 1) {
cy.get('input[placeholder="column_name"]').last().type(keys[i]);
cy.get('select')
.find('option')
.contains('-- type --')
.parent()
.last()
.select(values[i]);
}
cy.get('select').last().select('id');
cy.get(getElementFromAlias('table-create')).click();
cy.wait(7000);
cy.url().should(
'eq',
`${baseUrl}/data/default/schema/public/tables/${name}_table_mod/modify`
);
validateCT(`${name}_table_mod`, ResultType.SUCCESS);
};
export const Createtables = () => {
cy.get(getElementFromAlias('data-create-table')).click();
createTable('author', { id: 'integer', name: 'Text' });
cy.visit(getIndexRoute());
cy.wait(5000);
cy.get(getElementFromAlias('data-create-table')).click();
createTable('article', {
id: 'integer',
title: 'text',
Content: 'text',
author_id: 'integer',
rating: 'integer',
});
};
export const Createview = () => {
cy.get(getElementFromAlias('sql-link')).click();
cy.get('textarea').type(`CREATE VIEW author_average_rating_mod AS
SELECT author_table_mod.id, avg(article_table.rating)
From author_table_mod, article_table_mod
WHERE author_table_mod.id = article_table_mod.author_id
GROUP BY author_table_mod.id`);
cy.get(getElementFromAlias('run-sql')).click();
validateCT('author_average_rating_mod', ResultType.SUCCESS);
};
export const Checkviewtable = () => {
cy.get(getElementFromAlias('author_average_rating_mod')).click();
cy.url().should(
'eq',
`${baseUrl}/data/default/schema/public/views/author_average_rating_mod/browse`
);
cy.get(getElementFromAlias('table-modify')).click();
cy.get(getElementFromAlias('modify-view')).click();
cy.url().should('eq', `${baseUrl}/data/sql`);
};
export const Checkviewtabledelete = () => {
cy.get(getElementFromAlias('author_average_rating_mod')).click();
cy.url().should(
'eq',
`${baseUrl}/data/default/schema/public/views/author_average_rating_mod/browse`
);
cy.get(getElementFromAlias('table-modify')).click();
setPromptValue('author_average_rating_mod');
cy.get(getElementFromAlias('delete-view')).click();
cy.window().its('prompt').should('be.called');
cy.wait(7000);
validateCT('author_average_rating_mod', ResultType.FAILURE);
};
export const Issue = () => {
cy.get('.ace_text-input').first().type('#include');
};

View File

@ -1,75 +0,0 @@
import {
passMTCheckRoute,
passMTMoveToTable,
passMTCreateTable,
failMTWithoutColName,
failMTWithoutColType,
passMTAddColumn,
passMTDeleteTableCancel,
passMTDeleteCol,
passMTDeleteTable,
passMCWithRightDefaultValue,
failMCWithWrongDefaultValue,
passCreateForeignKey,
passRemoveForeignKey,
passMTRenameTable,
passMTRenameColumn,
passModifyPkey,
passCreateUniqueKey,
passModifyUniqueKey,
passRemoveUniqueKey,
passMTChangeDefaultValueForPKey,
passMTFunctionList,
} from './spec';
import { testMode } from '../../../helpers/common';
import { setMetaData } from '../../validators/validators';
import { getIndexRoute } from '../../../helpers/dataHelpers';
const setup = () => {
describe('Check Data Tab', () => {
it('Clicking on Data tab opens the correct route', () => {
// Visit the index route
cy.visit(getIndexRoute());
// Get and set validation metadata
setMetaData();
});
});
};
export const runModifyTableTests = () => {
describe('Modify Table', () => {
it('Creating a table', passMTCreateTable);
it('Moving to the table', passMTMoveToTable);
it('Modify table button opens the correct route', passMTCheckRoute);
it(
'Can create computed field with compatible functions',
passMTFunctionList
);
it('Pass renaming table', passMTRenameTable);
it('Pass renaming column', passMTRenameColumn);
it('Fails to add column without column name', failMTWithoutColName);
it('Fails without type selected', failMTWithoutColType);
it('Add a column', passMTAddColumn);
it('Fail modify with wrong default value', failMCWithWrongDefaultValue);
it('Pass modify with wrong default value', passMCWithRightDefaultValue);
it('Pass create foreign-key', passCreateForeignKey);
it('Pass remove foreign-key', passRemoveForeignKey);
it(
'Pass edit default value for primary key',
passMTChangeDefaultValueForPKey
);
it('Pass modifying a primary key', passModifyPkey);
it('Pass creating a unique key', passCreateUniqueKey);
it('Pass modifying a unique key', passModifyUniqueKey);
it('Pass removing a unique key', passRemoveUniqueKey);
it('Delete the column', passMTDeleteCol);
it('Delete Table Cancel', passMTDeleteTableCancel);
it('Delete table', passMTDeleteTable);
});
};
if (testMode !== 'cli') {
setup();
runModifyTableTests();
}

View File

@ -1,3 +0,0 @@
module.exports = {
"__version": "10.4.0"
}

View File

@ -1,133 +0,0 @@
import {
tableColumnTypeSelector,
baseUrl,
getTableName,
getElementFromAlias,
getColName,
queryTypes,
} from '../../../helpers/dataHelpers';
import { setMetaData } from '../../validators/validators';
import { testPermissions, permRemove, createView, trackView } from './utils';
import { setPromptValue } from '../../../helpers/common';
const testName = 'perm';
export const passPTCreateTable = () => {
// Click on create table
cy.get(getElementFromAlias('data-create-table')).click();
// Match the URL
cy.url().should('eq', `${baseUrl}/data/default/schema/public/table/add`);
// Type table name
cy.get(getElementFromAlias('tableName')).type(getTableName(0, testName));
// Set first column
cy.get(getElementFromAlias('column-0')).type(getColName(0));
tableColumnTypeSelector('col-type-0');
cy.get(getElementFromAlias('data_test_column_type_value_serial'))
.first()
.click();
// Set second column
cy.get(getElementFromAlias('column-1')).type(getColName(1));
tableColumnTypeSelector('col-type-1');
cy.get(getElementFromAlias('data_test_column_type_value_integer'))
.first()
.click();
// Set third column
cy.get(getElementFromAlias('column-2')).type(getColName(2));
tableColumnTypeSelector('col-type-2');
cy.get(getElementFromAlias('data_test_column_type_value_text'))
.first()
.click();
// Set primary key
cy.get(getElementFromAlias('primary-key-select-0')).select('0');
// Create
cy.get(getElementFromAlias('table-create')).click();
cy.wait(7000);
cy.url().should(
'eq',
`${baseUrl}/data/default/schema/public/tables/${getTableName(0, testName)}/modify`
);
};
export const passPTCheckRoute = () => {
// Go to permissiosn tab
cy.get(getElementFromAlias('table-permissions')).click();
// Match the URL
cy.url().should(
'eq',
`${baseUrl}/data/default/schema/public/tables/${getTableName(
0,
testName
)}/permissions`
);
};
export const passPTNoChecks = () => {
// Type role
cy.get(getElementFromAlias('role-textbox')).type('role0');
// Set permissions
testPermissions(getTableName(0, testName), 'none');
};
export const passPTCustomChecks = () => {
testPermissions(getTableName(0, testName), 'custom');
};
export const passPTRemovePerms = () => {
queryTypes.forEach(query => {
permRemove(getTableName(0, testName), query);
});
};
export const passPVCreateView = () => {
// create a view
createView(getTableName(1, testName), getTableName(0, testName));
cy.wait(5000);
};
export const passPVPermissions = () => {
// Track the view
trackView();
// Type role
cy.get(getElementFromAlias('role-textbox')).type('role0');
// Test permissions
testPermissions(getTableName(1, testName), 'none', true);
testPermissions(getTableName(1, testName), 'custom', true);
};
export const passPVRemovePerms = () => {
permRemove(getTableName(1, testName), 'select');
};
export const passPVDeleteView = () => {
// Go to modify view
cy.get(getElementFromAlias('table-modify')).click();
// Delete view
setPromptValue(getTableName(1, testName));
cy.get(getElementFromAlias('delete-view')).click();
cy.window()
.its('prompt')
.should('be.called');
cy.wait(7000);
};
export const passPTDeleteTable = () => {
// Go to the table
cy.get(getElementFromAlias(getTableName(0, testName))).click();
cy.wait(7000);
// Go to modify table
cy.get(getElementFromAlias('table-modify')).click();
// Delete table
setPromptValue(getTableName(0, testName));
cy.get(getElementFromAlias('delete-table')).click();
cy.window()
.its('prompt')
.should('be.called');
cy.wait(7000);
};
export const setValidationMetaData = () => {
setMetaData();
};

View File

@ -1,50 +0,0 @@
import {
passPTCreateTable,
passPTCheckRoute,
passPTNoChecks,
passPTCustomChecks,
passPTRemovePerms,
passPVCreateView,
passPVPermissions,
passPVRemovePerms,
passPVDeleteView,
passPTDeleteTable,
} from './spec';
import { testMode } from '../../../helpers/common';
import { setMetaData } from '../../validators/validators';
import { getIndexRoute } from '../../../helpers/dataHelpers';
const setup = () => {
// Temporarily skipped because of its flakiness, see: https://github.com/hasura/graphql-engine-mono/issues/5433
// TODO: Fix and restore it
describe.skip('Check Data Tab', () => {
it('Visiting the data URL opens the correct route', () => {
// Visit the index route
cy.visit(getIndexRoute());
// Get and set validation metadata
setMetaData();
});
});
};
export const runPermissionsTests = () => {
describe.skip('Permissions', () => {
// Temporarily skipped because of its flakiness, see: https://github.com/hasura/graphql-engine-mono/issues/5433
// TODO: Fix and restore it
it('Create a table', passPTCreateTable);
it('Create a view', passPVCreateView);
it('Check permission route', passPTCheckRoute);
it('Table No-check permissions work as expected', passPTNoChecks);
it('Table Custom-check permissions work as expected', passPTCustomChecks);
it('Table Permissions removal works as expected', passPTRemovePerms);
it('View permissions work as expected', passPVPermissions);
it('View Permissions removal works as expected', passPVRemovePerms);
it('Delete the views', passPVDeleteView);
it('Delete the test table', passPTDeleteTable);
});
};
if (testMode !== 'cli') {
setup();
runPermissionsTests();
}

View File

@ -1,158 +0,0 @@
import {
getElementFromAlias,
getTableName,
getColName,
queryTypes,
makeDataAPIOptions,
} from '../../../helpers/dataHelpers';
import {
validatePermission,
QueryType,
ResultType,
CheckType,
} from '../../validators/validators';
const testName = 'perm';
export const savePermission = () => {
cy.get(getElementFromAlias('Save-Permissions-button')).click();
cy.wait(7000);
};
export const permNoCheck = (tableName: string, query: QueryType) => {
// click on the query type to edit permission
cy.get(getElementFromAlias(`role0-${query}`)).click();
cy.get(getElementFromAlias('without-checks')).first().click();
// set filter { }
// Toggle all columns in case
if (query === 'select' || query === 'update') {
cy.get(getElementFromAlias('toggle-all-col-btn')).click();
}
if (query === 'insert' || query === 'update') {
cy.get(getElementFromAlias('toggle-presets-permission')).click();
cy.get(getElementFromAlias('column-presets-column-0')).select(
getColName(0)
);
cy.get(getElementFromAlias('column-presets-type-0')).select('static');
cy.get(getElementFromAlias('column-presets-value-0')).type('1').blur();
cy.get(getElementFromAlias('column-presets-column-1')).select(
getColName(1)
);
cy.get(getElementFromAlias('column-presets-type-1')).select('session');
cy.get(getElementFromAlias('column-presets-value-1')).type('user-id');
}
// Save
savePermission();
// Validate
validatePermission(
tableName,
'role0',
query,
'none',
ResultType.SUCCESS,
null
);
};
export const permCustomCheck = (tableName: string, query: QueryType) => {
// click on the query type to edit permission
cy.get(getElementFromAlias(`role0-${query}`)).click();
// check the without checks textbox
cy.get(getElementFromAlias('toggle-row-permission')).click();
cy.get(getElementFromAlias('custom-check')).first().click();
cy.get(getElementFromAlias('qb_container'))
.first()
.within(() => {
// Select column
cy.get(getElementFromAlias('qb-select')).first().select(getColName(0));
// Select operator
cy.get(getElementFromAlias('qb-select'))
.last()
.select(`${getColName(0)}._eq`);
});
// Set filter to 1
cy.get(getElementFromAlias('perm-check-textbox')).first().type('1');
// Save
savePermission();
// Validate
validatePermission(
tableName,
'role0',
query,
'custom',
ResultType.SUCCESS,
[0, 1, 2].map(i => getColName(i))
);
// Do not allow users to make upset queries in case of Insert
};
export const permRemove = (tableName: string, query: QueryType) => {
// click on the query type to edit permission
cy.get(getElementFromAlias(`role0-${query}`)).click();
// Remove permission
cy.get(getElementFromAlias('Delete-Permissions-button')).click();
cy.wait(2500);
cy.wait(5000);
// Validate
validatePermission(
tableName,
'role0',
query,
'custom',
ResultType.FAILURE,
null
);
};
export const testPermissions = (
tableName: string,
check: CheckType,
isView?: boolean
) => {
let allQueryTypes: QueryType[] = queryTypes;
if (isView) {
allQueryTypes = ['select'];
}
if (check === 'none') {
allQueryTypes.forEach(query => {
permNoCheck(tableName, query);
});
} else {
allQueryTypes.forEach(query => {
permCustomCheck(tableName, query);
});
}
};
export const trackView = () => {
// track view data-tab-link
cy.get(getElementFromAlias('data-tab-link')).click();
cy.wait(7000);
cy.get(
getElementFromAlias(`add-track-table-${getTableName(1, testName)}`)
).click();
cy.wait(10000);
// Move to permissions
cy.get(getElementFromAlias('table-permissions')).click();
};
export const createView = (viewName: string, tableName: string) => {
const reqBody = {
type: 'run_sql',
args: {
sql: `create view "${viewName}" as select * from "${tableName}"`,
},
};
cy.window().then(win => {
const { __env } = win;
const requestOptions = makeDataAPIOptions(
__env.dataApiUrl,
__env.adminSecret,
reqBody
);
cy.request(requestOptions);
});
};

View File

@ -1,3 +0,0 @@
module.exports = {
"__version": "10.4.0"
}

View File

@ -1,96 +0,0 @@
import { baseUrl, getElementFromAlias } from '../../../helpers/dataHelpers';
let prevStr = '';
export const openRawSQL = () => {
// Open RawSQL
cy.get('a').contains('Data').click();
cy.wait(3000);
cy.get(getElementFromAlias('sql-link')).click();
cy.wait(3000);
// Match URL
cy.url().should('eq', `${baseUrl}/data/sql`);
};
const clearText = () => {
cy.get('textarea').type('{selectall}', { force: true });
cy.get('textarea').trigger('keydown', {
keyCode: 46,
which: 46,
force: true,
});
cy.wait(2000); // ace editor textarea doesn't expose the value to check, so wait
};
export const passCreateTable = () => {
prevStr = 'CREATE TABLE Apic_test_table_rsql (id serial PRIMARY KEY);';
cy.get('textarea').type(prevStr, { force: true });
cy.wait(1000); // debounce
cy.get(getElementFromAlias('run-sql')).click();
// cy.get(getElementFromAlias('raw-sql-statement-timeout'));
cy.wait(5000);
};
export const passInsertValues = () => {
clearText();
// eslint-disable-next-line prefer-spread
const str = Array.apply(null, Array(15))
.map(
(_, ix: number) => `INSERT INTO Apic_test_table_rsql VALUES (${+ix + 1});`
)
.join('\n');
cy.get('textarea').type(str, { force: true });
cy.wait(1000);
cy.get(getElementFromAlias('run-sql')).click();
cy.wait(5000);
};
export const readQuery = () => {
clearText();
prevStr = 'SELECT * FROM public.Apic_test_table_rsql;';
cy.get('textarea').type(prevStr, { force: true });
cy.wait(1000); // debounce
cy.get(getElementFromAlias('run-sql')).click();
cy.wait(3000); // debounce
cy.get('div.rt-tr-group').should('have.length', 10);
cy.get('button.-btn').last().click();
cy.wait(500);
cy.get('div.rt-tr-group').should('have.length', 5);
cy.get('div.rt-td').first().should('have.text', '11');
cy.get('div.rt-td').last().should('have.text', 'NULL');
};
export const passAlterTable = () => {
clearText();
prevStr = 'ALTER TABLE Apic_test_table_rsql ADD COLUMN name text;';
cy.get('textarea').type(prevStr, { force: true });
// Untrack table
cy.wait(1000);
cy.get(getElementFromAlias('raw-sql-track-check')).uncheck();
cy.get(getElementFromAlias('run-sql')).click();
cy.wait(5000);
};
export const passCreateView = () => {
clearText();
prevStr = 'CREATE VIEW abcd AS SELECT * FROM Apic_test_table_rsql;';
cy.get('textarea').type(prevStr, { force: true });
// Track table
cy.wait(1000);
cy.get(getElementFromAlias('raw-sql-track-check')).check();
cy.get(getElementFromAlias('run-sql')).click();
cy.wait(5000);
};
export const delTestTables = () => {
clearText();
prevStr = 'DROP TABLE Apic_test_table_rsql CASCADE;';
cy.get('textarea').type(prevStr, { force: true });
cy.wait(1000);
cy.get(getElementFromAlias('raw-sql-migration-check')).uncheck();
cy.get(getElementFromAlias('run-sql')).click();
// NOTE: This is only visible, when the console is in CLI mode
cy.get(getElementFromAlias('not-migration-confirm')).click();
// cy.get(getElementFromAlias('raw-sql-statement-timeout')).type('20', {
// force: true,
// });
cy.wait(5000);
};

View File

@ -1,40 +0,0 @@
import {
openRawSQL,
passCreateTable,
delTestTables,
passCreateView,
passInsertValues,
passAlterTable,
readQuery,
} from './spec';
import { testMode } from '../../../helpers/common';
import { setMetaData } from '../../validators/validators';
import { getIndexRoute } from '../../../helpers/dataHelpers';
const setup = () => {
describe('Setup route', () => {
it('Visit the index route', () => {
// Visit the index route
cy.visit(getIndexRoute());
// Get and set validation metadata
setMetaData();
});
});
};
export const runRawSQLTests = () => {
describe('Raw SQL', () => {
it('Open Raw SQL page', openRawSQL);
it('Pass create table', passCreateTable);
it('Pass insert values', passInsertValues);
it('Pass alter table', passAlterTable);
it('Read from table', readQuery);
it('Pass create view', passCreateView);
it('Delete test table', delTestTables);
});
};
if (testMode !== 'cli') {
setup();
runRawSQLTests();
}

View File

@ -1,3 +0,0 @@
module.exports = {
"__version": "10.4.0"
}

View File

@ -1,326 +0,0 @@
import {
baseUrl,
getElementFromAlias,
tableColumnTypeSelector,
getIndexRoute,
} from '../../../helpers/dataHelpers';
import {
setMetaData,
validateCT,
validateColumn,
ResultType,
TableFields,
} from '../../validators/validators';
import { setPromptValue } from '../../../helpers/common';
const AWAIT_SHORT = 2000;
const delRel = (table: string, relname: string) => {
cy.get(getElementFromAlias(table)).click();
cy.get(getElementFromAlias('table-relationships')).click();
cy.get(getElementFromAlias(`relationship-toggle-editor-${relname}`)).click();
cy.get(getElementFromAlias(`relationship-remove-${relname}`)).click();
cy.on('window:alert', str => {
expect(str === 'Are you sure?').to.be.true;
});
cy.wait(15000);
};
export const createTable = (name: string, fields: TableFields) => {
// Click on the "Add table" button and input the table name
cy.visit(getIndexRoute());
cy.wait(5000);
cy.get(getElementFromAlias('data-create-table')).click();
cy.url().should('eq', `${baseUrl}/data/default/schema/public/table/add`);
cy.get(getElementFromAlias('tableName')).type(`${name}_table_rt`);
// Enter column info
let i = 0;
// eslint-disable-next-line no-restricted-syntax
for (const key in fields) {
// eslint-disable-next-line no-prototype-builtins
if (fields.hasOwnProperty(key)) {
cy.get(getElementFromAlias(`column-${i}`)).type(key);
tableColumnTypeSelector(`col-type-${i}`);
cy.get(getElementFromAlias(`data_test_column_type_value_${fields[key]}`))
.first()
.click();
i += 1;
}
}
// Select primary key
cy.get(getElementFromAlias('primary-key-select-0')).select('id');
if (name === 'article') {
cy.get(getElementFromAlias('add-table-edit-fk-0')).click();
cy.get(getElementFromAlias('foreign-key-ref-table-0')).select(
'author_table_rt'
);
cy.get(getElementFromAlias('foreign-key-0-lcol-0')).select('3');
cy.get(getElementFromAlias('foreign-key-0-rcol-0')).select('id');
cy.get(getElementFromAlias('foreign-key-0-onUpdate-cascade')).check();
cy.get(getElementFromAlias('foreign-key-0-onDelete-cascade')).check();
} else if (name === 'comment') {
cy.get(getElementFromAlias('add-table-edit-fk-0')).click();
cy.get(getElementFromAlias('foreign-key-ref-table-0')).select(
'author_table_rt'
);
cy.get(getElementFromAlias('foreign-key-0-lcol-0')).select('1');
cy.get(getElementFromAlias('foreign-key-0-rcol-0')).select('id');
cy.get(getElementFromAlias('foreign-key-0-onUpdate-cascade')).check();
cy.get(getElementFromAlias('foreign-key-0-onDelete-cascade')).check();
cy.get(getElementFromAlias('add-table-edit-fk-1')).click();
cy.get(getElementFromAlias('foreign-key-ref-table-1')).select(
'article_table_rt'
);
cy.get(getElementFromAlias('foreign-key-1-lcol-0')).select('2');
cy.get(getElementFromAlias('foreign-key-1-rcol-0')).select('id');
cy.get(getElementFromAlias('foreign-key-1-onUpdate-cascade')).check();
cy.get(getElementFromAlias('foreign-key-1-onDelete-cascade')).check();
}
cy.get(getElementFromAlias('table-create')).click();
cy.wait(15000);
cy.url().should(
'eq',
`${baseUrl}/data/default/schema/public/tables/${name}_table_rt/modify`
);
validateCT(`${name}_table_rt`, ResultType.SUCCESS);
};
export const passRTCreateTables = () => {
createTable('author', { id: 'integer', name: 'text' });
createTable('article', {
id: 'integer',
title: 'text',
Content: 'text',
author_id: 'integer',
rating: 'integer',
});
createTable('comment', {
id: 'integer',
user_id: 'integer',
article_id: 'integer',
comment: 'text',
});
};
export const Deletetable = (name: string) => {
cy.get(getElementFromAlias(name)).click();
cy.get(getElementFromAlias('table-modify')).click();
setPromptValue(name);
cy.get(getElementFromAlias('delete-table')).click();
cy.window().its('prompt').should('be.called');
cy.wait(15000);
validateCT(name, ResultType.FAILURE);
};
export const passRTDeleteTables = () => {
Deletetable('comment_table_rt');
Deletetable('article_table_rt');
Deletetable('author_table_rt');
};
export const passRTAddManualObjRel = () => {
cy.get(getElementFromAlias('article_table_rt')).click();
cy.get(getElementFromAlias('table-relationships')).click();
cy.wait(4000);
cy.get(getElementFromAlias('article_table_rt')).click();
cy.get(getElementFromAlias('table-relationships')).click();
cy.get(getElementFromAlias('create-edit-manual-rel')).click();
cy.get(getElementFromAlias('manual-relationship-type')).select('object');
cy.get("input[placeholder='Enter relationship name']").type('author');
cy.get(getElementFromAlias('manual-relationship-ref-schema')).select(
'public'
);
cy.get(getElementFromAlias('manual-relationship-ref-table')).select(
'author_table_rt'
);
cy.get(getElementFromAlias('manual-relationship-lcol-0')).select('author_id');
cy.get(getElementFromAlias('manual-relationship-rcol-0')).select('id');
cy.get(getElementFromAlias('create-manual-rel-save')).click();
cy.wait(15000);
validateColumn(
'article_table_rt',
['title', { name: 'author', columns: ['name'] }],
ResultType.SUCCESS
);
};
export const passRTAddManualArrayRel = () => {
cy.get(getElementFromAlias('article_table_rt')).click();
cy.wait(4000);
cy.get(getElementFromAlias('table-relationships')).click();
cy.get(getElementFromAlias('create-edit-manual-rel')).click();
cy.get(getElementFromAlias('manual-relationship-type')).select('array');
cy.get("input[placeholder='Enter relationship name']").type('comments');
cy.get(getElementFromAlias('manual-relationship-ref-schema')).select(
'public'
);
cy.get(getElementFromAlias('manual-relationship-ref-table')).select(
'comment_table_rt'
);
cy.get(getElementFromAlias('manual-relationship-lcol-0')).select('id');
cy.get(getElementFromAlias('manual-relationship-rcol-0')).select(
'article_id'
);
cy.get(getElementFromAlias('create-manual-rel-save')).click();
cy.wait(15000);
validateColumn(
'article_table_rt',
['title', { name: 'comments', columns: ['comment'] }],
ResultType.SUCCESS
);
};
export const passRTDeleteRelationships = () => {
delRel('article_table_rt', 'author');
validateColumn(
'article_table_rt',
['title', { name: 'author', columns: ['name'] }],
ResultType.FAILURE
);
delRel('article_table_rt', 'comments');
validateColumn(
'article_table_rt',
['title', { name: 'comments', columns: ['comment'] }],
ResultType.FAILURE
);
};
export const passRTAddSuggestedRel = () => {
cy.get(getElementFromAlias('article_table_rt')).click();
cy.get(getElementFromAlias('table-relationships')).click();
cy.get(getElementFromAlias('obj-rel-add-0')).click();
cy.get(getElementFromAlias('suggested-rel-name')).clear().type('author');
cy.get(getElementFromAlias('obj-rel-save-0')).click();
cy.wait(5000);
validateColumn(
'article_table_rt',
['title', { name: 'author', columns: ['name'] }],
ResultType.SUCCESS
);
cy.get(getElementFromAlias('article_table_rt')).click();
cy.wait(5000);
cy.get(getElementFromAlias('table-relationships')).click();
cy.get(getElementFromAlias('arr-rel-add-0')).click();
cy.get(getElementFromAlias('suggested-rel-name')).clear().type('comments');
cy.get(getElementFromAlias('arr-rel-save-0')).click();
cy.wait(5000);
validateColumn(
'article_table_rt',
['title', { name: 'comments', columns: ['comment'] }],
ResultType.SUCCESS
);
};
export const passRTRenameRelationship = () => {
cy.get(getElementFromAlias('relationship-toggle-editor-comments')).click();
cy.get(getElementFromAlias('relationship-name-input-comments'))
.clear()
.type('comments_renamed');
cy.get(getElementFromAlias('relationship-save-comments')).click();
cy.wait(5000);
validateColumn(
'article_table_rt',
['title', { name: 'comments_renamed', columns: ['comment'] }],
ResultType.SUCCESS
);
cy.get(
getElementFromAlias('relationship-toggle-editor-comments_renamed')
).click();
cy.get(getElementFromAlias('relationship-name-input-comments_renamed'))
.clear()
.type('comments');
cy.get(getElementFromAlias('relationship-save-comments_renamed')).click();
cy.wait(5000);
validateColumn(
'article_table_rt',
['title', { name: 'comments', columns: ['comment'] }],
ResultType.SUCCESS
);
};
export const failRTAddSuggestedRel = () => {
cy.get(getElementFromAlias('article_table_rt')).click();
cy.get(getElementFromAlias('table-relationships')).click();
cy.get(getElementFromAlias('obj-rel-add-0')).click();
cy.get(getElementFromAlias('suggested-rel-name')).clear();
cy.get(getElementFromAlias('obj-rel-save-0')).click();
cy.wait(15000);
cy.get(getElementFromAlias('suggested-rel-name')).clear().type(`${123123}`);
cy.get('button').contains('Save').click();
cy.wait(15000);
validateColumn(
'article_table_rt',
['title', { name: 'author', columns: ['name'] }],
ResultType.FAILURE
);
cy.get(getElementFromAlias('article_table_rt')).click();
cy.get(getElementFromAlias('table-relationships')).click();
cy.get(getElementFromAlias('obj-rel-add-0')).click();
cy.get(getElementFromAlias('suggested-rel-name')).clear().type('author');
cy.get(getElementFromAlias('obj-rel-save-0')).click();
cy.wait(15000);
validateColumn(
'article_table_rt',
['title', { name: 'author', columns: ['name'] }],
ResultType.SUCCESS
);
cy.get(getElementFromAlias('article_table_rt')).click();
cy.get(getElementFromAlias('table-relationships')).click();
cy.get(getElementFromAlias('arr-rel-add-0')).click();
cy.get(getElementFromAlias('suggested-rel-name')).clear().type('comments');
cy.get(getElementFromAlias('arr-rel-save-0')).click();
cy.wait(15000);
delRel('article_table_rt', 'author');
delRel('article_table_rt', 'comments');
};
export const passRSTSetup = () => {
createTable('countries', {
id: 'integer',
name: 'text',
countryCode: 'text',
});
};
export const passRSTReset = () => {
Deletetable('countries_table_rt');
};
const AWAIT_MINOR = 500;
export const passRSTAddRSRel = () => {
cy.reload();
cy.getBySel('countries_table_rt').click();
cy.getBySel('table-relationships').click();
cy.getBySel('table-relationship-edit-remote-relationship-add').click();
cy.wait(AWAIT_MINOR);
cy.getBySel('remote-rel-name-input').type('remote');
cy.getBySel('remote-rel-schema-input').select('remote_rel_test_rs');
cy.get('input[type="checkbox"]').eq(3).check();
cy.wait(AWAIT_MINOR);
cy.get('input[type="checkbox"]').eq(4).check();
cy.get('select').eq(2).select('countryCode');
cy.getBySel('table-relationship-remote-relationship-add-save').click();
cy.get('.notification', { timeout: AWAIT_SHORT })
.should('be.visible')
.and('contain', 'Successfully created remote relationship');
};
export const passRSTDeleteRSRel = () => {
cy.reload();
cy.getBySel('table-relationship-edit-remote-relationship-edit').click();
cy.getBySel('table-relationship-remote-relationship-edit-remove').click();
cy.get('.notification', { timeout: AWAIT_SHORT })
.should('be.visible')
.and('contain', 'Successfully deleted remote relationship');
};
export const setValidationMetaData = () => {
setMetaData();
};

View File

@ -1,46 +0,0 @@
import {
passRTCreateTables,
passRTDeleteTables,
passRTAddManualObjRel,
passRTAddManualArrayRel,
passRTDeleteRelationships,
passRTAddSuggestedRel,
failRTAddSuggestedRel,
passRTRenameRelationship,
} from './spec';
import { testMode } from '../../../helpers/common';
import { setMetaData } from '../../validators/validators';
import { getIndexRoute } from '../../../helpers/dataHelpers';
const setup = () => {
// Temporarily skipped because of its flakiness, see: https://github.com/hasura/graphql-engine-mono/issues/5433
// TODO: Fix and restore it
describe.skip('Check Data Tab', () => {
it('Clicking on Data tab opens the correct route', () => {
cy.visit(getIndexRoute());
// Get and set validation metadata
setMetaData();
});
});
};
export const runRelationshipsTests = () => {
// Temporarily skipped because of its flakiness, see: https://github.com/hasura/graphql-engine-mono/issues/5433
// TODO: Fix and restore it
describe.skip('Relationships Tests', () => {
it('Create testing tables', passRTCreateTables);
it('Add Manual Relationship Object', passRTAddManualObjRel);
it('Add Manual Relationship Array', passRTAddManualArrayRel);
it('Delete the relationships', passRTDeleteRelationships);
it('Add Suggested Relationships Error', failRTAddSuggestedRel);
it('Add Suggested Relationships', passRTAddSuggestedRel);
it('Rename relationships', passRTRenameRelationship);
it('Delete the relationships', passRTDeleteRelationships);
it('Delete test tables', passRTDeleteTables);
});
};
if (testMode !== 'cli') {
setup();
runRelationshipsTests();
}

View File

@ -1,3 +0,0 @@
module.exports = {
"__version": "10.4.0"
}

View File

@ -1,34 +0,0 @@
import { getDbRoute } from '../../../helpers/dataHelpers';
import { setMetaData } from '../../validators/validators';
import { setPromptValue, testMode } from '../../../helpers/common';
const setup = () => {
describe('Setup route', () => {
it('Visit the index route', () => {
cy.visit(getDbRoute());
cy.wait(7000);
setMetaData();
});
});
};
export const runSchemaSharingTests = () => {
describe('template gallery', () => {
it('display content', () => {
cy.get('[data-test=table-links]').contains('default').click();
cy.get('table').contains('Relationships: One-to-One').click();
cy.contains('Install Template').click();
cy.wait(1000);
cy.get('[data-test=table-links]').contains('_onetoone').click();
setPromptValue('_onetoone');
cy.contains('_onetoone').parent().parent().contains('owner');
cy.contains('_onetoone').parent().parent().contains('passport_info');
cy.get('[title="Delete current schema"]').click();
});
});
};
if (testMode !== 'cli') {
setup();
runSchemaSharingTests();
}

View File

@ -1,3 +0,0 @@
module.exports = {
"__version": "10.4.0"
}

View File

@ -1,415 +0,0 @@
import {
getElementFromAlias,
baseUrl,
tableColumnTypeSelector,
getIndexRoute,
} from '../../../helpers/dataHelpers';
import {
setMetaData,
validateCT,
createView,
validateColumn,
validateView,
ResultType,
TableFields,
} from '../../validators/validators';
import { setPromptValue } from '../../../helpers/common';
const userId = 5555;
export const createTable = (name: string, dict: TableFields) => {
cy.url().should('eq', `${baseUrl}/data/default/schema/public/table/add`);
cy.get(getElementFromAlias('tableName')).type(`${name}_table_vt`);
const keys = Object.keys(dict).map(k => k);
const values = Object.keys(dict).map(k => dict[k]);
for (let i = 0; i < keys.length; i += 1) {
cy.get(getElementFromAlias(`column-${i}`)).type(keys[i]);
tableColumnTypeSelector(`col-type-${i}`);
cy.get(getElementFromAlias(`data_test_column_type_value_${values[i]}`))
.first()
.click();
}
cy.get(getElementFromAlias('primary-key-select-0')).select('id');
cy.get(getElementFromAlias('table-create')).click();
cy.wait(7000);
cy.url().should(
'eq',
`${baseUrl}/data/default/schema/public/tables/${name}_table_vt/modify`
);
validateCT(`${name}_table_vt`, ResultType.SUCCESS);
};
export const passVCreateTables = () => {
cy.get(getElementFromAlias('data-create-table')).click();
createTable('author', { id: 'integer', name: 'text' });
cy.visit(getIndexRoute());
cy.wait(5000);
cy.get(getElementFromAlias('data-create-table')).click();
createTable('article', {
id: 'integer',
title: 'text',
Content: 'text',
author_id: 'integer',
rating: 'integer',
});
cy.visit(getIndexRoute());
cy.wait(5000);
cy.get(getElementFromAlias('data-create-table')).click();
createTable('comment', {
id: 'integer',
user_id: 'integer',
article_id: 'integer',
comment: 'text',
});
};
export const passVCreateViews = () => {
createView(`CREATE VIEW author_average_rating_vt AS
SELECT author_table_vt.id, avg(article_table_vt.rating)
From author_table_vt, article_table_vt
WHERE author_table_vt.id = article_table_vt.author_id
GROUP BY author_table_vt.id`);
};
export const passTrackTable = () => {
cy.visit('/data/default/schema/public/');
cy.wait(7000);
cy.get(
getElementFromAlias('add-track-table-author_average_rating_vt')
).click();
cy.wait(7000);
validateView('author_average_rating_vt', ResultType.SUCCESS);
};
export const passViewRoute = () => {
cy.get(getElementFromAlias('author_average_rating_vt')).click();
cy.url().should(
'eq',
`${baseUrl}/data/default/schema/public/views/author_average_rating_vt/browse`
);
};
type Data = (string | number)[];
export const passVAddDataarticle = (data: Data, index: number) => {
// Click the Insert Again button.
cy.get('label')
.contains('id')
.next()
.find('input')
.last()
.type('{selectall}{del}');
cy.get('label').contains('id').next().find('input').last().type(`${data[0]}`);
cy.get('label')
.contains('title')
.next()
.find('input')
.last()
.type('{selectall}{del}');
cy.get('label')
.contains('title')
.next()
.find('input')
.last()
.type(`${data[1]}`);
cy.get('label')
.contains('Content')
.next()
.find('input')
.last()
.type('{selectall}{del}');
cy.get('label')
.contains('Content')
.next()
.find('input')
.last()
.type(`${data[2]}`);
cy.get('label')
.contains('author_id')
.next()
.find('input')
.last()
.type('{selectall}{del}');
cy.get('label')
.contains('author_id')
.next()
.find('input')
.last()
.type(`${data[3]}`);
cy.get('label')
.contains('rating')
.next()
.find('input')
.last()
.type('{selectall}{del}');
cy.get('label')
.contains('rating')
.next()
.find('input')
.last()
.type(`${data[4]}`);
if (index) {
cy.get(getElementFromAlias('insert-save-button')).click();
} else {
cy.get(getElementFromAlias('insert-save-button')).click();
}
cy.wait(5000);
};
export const passVAddDataauthor = (data: Data, index: number) => {
cy.get('label')
.contains('id')
.next()
.find('input')
.last()
.type('{selectall}{del}');
cy.get('label').contains('id').next().find('input').last().type(`${data[0]}`);
cy.get('label')
.contains('name')
.next()
.find('input')
.last()
.type('{selectall}{del}');
cy.get('label')
.contains('name')
.next()
.find('input')
.last()
.type(`${data[1]}`);
if (index) {
cy.get(getElementFromAlias('insert-save-button')).click();
} else {
cy.get(getElementFromAlias('insert-save-button')).click();
}
cy.wait(5000);
};
export const passVAddDatacomment = (data: Data, index: number) => {
cy.get('label')
.contains('id')
.next()
.find('input')
.last()
.type('{selectall}{del}');
cy.get('label').contains('id').next().find('input').last().type(`${data[0]}`);
cy.get('label')
.contains('user_id')
.next()
.find('input')
.last()
.type('{selectall}{del}');
cy.get('label')
.contains('user_id')
.next()
.find('input')
.last()
.type(`${data[1]}`);
cy.get('label')
.contains('article_id')
.next()
.find('input')
.last()
.type('{selectall}{del}');
cy.get('label')
.contains('article_id')
.next()
.find('input')
.last()
.type(`${data[2]}`);
cy.get('label')
.contains('comment')
.next()
.find('input')
.last()
.type('{selectall}{del}');
cy.get('label')
.contains('comment')
.next()
.find('input')
.last()
.type(`${data[3]}`);
if (index) {
cy.get(getElementFromAlias('insert-save-button')).click();
} else {
cy.get(getElementFromAlias('insert-save-button')).click();
}
cy.wait(5000);
};
const checkQuerySuccess = () => {
// Expect only 4 rows i.e. expect fifth element to not exist
cy.get('[role=gridcell]').contains(userId);
cy.get('[role=row]').eq(2).should('not.exist');
};
export const passVAddData = () => {
let data;
cy.get(getElementFromAlias('article_table_vt')).click();
cy.get(getElementFromAlias('table-insert-rows')).click();
data = [1, 'A', 'Sontent', userId, 4];
passVAddDataarticle(data, 0);
data = [2, 'B', 'Sontenta', 2, 4];
passVAddDataarticle(data, 1);
data = [3, 'C', 'Sontentb', userId, 4];
passVAddDataarticle(data, 2);
cy.get(getElementFromAlias('author_table_vt')).click();
cy.get(getElementFromAlias('table-insert-rows')).click();
data = [userId, 'A'];
passVAddDataauthor(data, 0);
data = [2, 'B'];
passVAddDataauthor(data, 1);
cy.get(getElementFromAlias('comment_table_vt')).click();
cy.get(getElementFromAlias('table-insert-rows')).click();
data = [1, 1, 1, 'new comment'];
passVAddDatacomment(data, 0);
data = [2, 2, 2, 'new comment'];
passVAddDatacomment(data, 1);
data = [3, 1, 2, 'new comment'];
passVAddDatacomment(data, 2);
};
export const passVFilterQueryEq = () => {
// Select column with type `text`
cy.get('select')
.find('option')
.contains('-- column --')
.parent()
.first()
.select('id');
// Type value as `filter-text`
cy.get("input[placeholder='-- value --']").last().type(`${userId}`);
// Run query
cy.get(getElementFromAlias('run-query')).click();
cy.wait(5000);
// Check if the query was successful
checkQuerySuccess();
};
const checkOrder = (order: string) => {
// Utility function to get right element
if (order === 'asc') {
cy.get('[role=row]').each(($el, index) => {
if (index === 1) {
cy.wrap($el).find('[role=gridcell]').first().next().next().contains(2);
}
if (index === 2) {
cy.wrap($el)
.find('[role=gridcell]')
.first()
.next()
.next()
.contains(userId);
}
});
} else {
cy.get('[role=row]').each(($el, index) => {
if (index === 2) {
cy.wrap($el).find('[role=gridcell]').first().next().next().contains(2);
}
if (index === 1) {
cy.wrap($el)
.find('[role=gridcell]')
.first()
.next()
.next()
.contains(userId);
}
});
}
};
export const passVAscendingSort = () => {
cy.wait(7000);
// Select column with type 'serial'
cy.get('select')
.find('option')
.contains('-- column --')
.parent()
.last()
.select('id');
// Run query
cy.get(getElementFromAlias('run-query')).click();
// Check order
checkOrder('asc');
};
export const passModifyView = () => {
cy.get(getElementFromAlias('table-modify')).click();
cy.get('button').contains('Modify').last().click();
cy.url().should('eq', `${baseUrl}/data/sql`);
};
export const passVAddManualObjRel = () => {
cy.get(getElementFromAlias('author_average_rating_vt')).click();
cy.wait(2000);
cy.get(getElementFromAlias('table-relationships')).click();
cy.wait(2000);
cy.get(getElementFromAlias('create-edit-manual-rel')).click();
cy.get(getElementFromAlias('manual-relationship-type')).select('object');
cy.get("input[placeholder='Enter relationship name']").type('author');
cy.get(getElementFromAlias('manual-relationship-ref-schema')).select(
'public'
);
cy.get(getElementFromAlias('manual-relationship-ref-table')).select(
'author_table_vt'
);
cy.get(getElementFromAlias('manual-relationship-lcol-0')).select('id');
cy.get(getElementFromAlias('manual-relationship-rcol-0')).select('id');
cy.get(getElementFromAlias('create-manual-rel-save')).click();
cy.wait(7000);
validateColumn(
'author_average_rating_vt',
['avg', { name: 'author', columns: ['name'] }],
ResultType.SUCCESS
);
};
export const passVDeleteRelationships = () => {
cy.get(getElementFromAlias('author_average_rating_vt')).click();
cy.get(getElementFromAlias('table-relationships')).click();
cy.get(getElementFromAlias('relationship-toggle-editor-author')).click();
cy.get(getElementFromAlias('relationship-remove-author')).click();
cy.on('window:alert', str => {
return expect(str === 'Are you sure?').to.be.true;
});
cy.wait(7000);
validateColumn(
'author_average_rating_vt',
['avg', { name: 'author', columns: ['name'] }],
ResultType.FAILURE
);
};
export const passVDeleteView = () => {
cy.get(getElementFromAlias('table-modify')).click();
setPromptValue('author_average_rating_vt');
cy.get(getElementFromAlias('delete-view')).click();
cy.window().its('prompt').should('be.called');
cy.wait(7000);
validateView('author_average_rating_vt', ResultType.FAILURE);
};
export const deleteTable = (name: string) => {
cy.get(getElementFromAlias(name)).click();
cy.get(getElementFromAlias('table-modify')).click();
setPromptValue(name);
cy.get(getElementFromAlias('delete-table')).click();
cy.window().its('prompt').should('be.called');
cy.wait(7000);
validateCT(name, ResultType.FAILURE);
cy.wait(7000);
};
export const passVDeleteTables = () => {
deleteTable('comment_table_vt');
deleteTable('article_table_vt');
deleteTable('author_table_vt');
};
export const setValidationMetaData = () => {
setMetaData();
};

View File

@ -1,57 +0,0 @@
import {
passVCreateTables,
passVCreateViews,
passVAddData,
passTrackTable,
passVAddManualObjRel,
passVAscendingSort,
passVFilterQueryEq,
passViewRoute,
passModifyView,
passVDeleteRelationships,
passVDeleteView,
passVDeleteTables,
} from './spec';
import { testMode } from '../../../helpers/common';
import { setMetaData } from '../../validators/validators';
import { getIndexRoute } from '../../../helpers/dataHelpers';
const setup = () => {
// Temporarily skipped because of its flakiness, see: https://github.com/hasura/graphql-engine-mono/issues/5433
// TODO: Fix and restore it
describe.skip('Setup route', () => {
it('Visit the index route', () => {
// Visit the index route
cy.visit(getIndexRoute());
// Get and set validation metadata
setMetaData();
});
});
};
export const runViewsTest = () => {
// Temporarily skipped because of its flakiness, see: https://github.com/hasura/graphql-engine-mono/issues/5433
// TODO: Fix and restore it
describe.skip('Views', () => {
// NOTE: Ideally, we should be adding "should" at the beginning of
// the test descriptions. It will sound like this when you read it.
// eg. it should create test tables ...and so on
it('Create Tables', passVCreateTables);
it('Insert test data to table(s)', passVAddData);
it('Create View', passVCreateViews);
it('Add View to comment table', passTrackTable);
it('Visit the view route', passViewRoute);
it('Order Ascending order View Table', passVAscendingSort);
it('Apply Filters on the View', passVFilterQueryEq);
it('Modify the View', passModifyView);
it('Add Object Relationship to View', passVAddManualObjRel);
it('Delete Relationship(s)', passVDeleteRelationships);
it('Delete View', passVDeleteView);
it('Delete Tables', passVDeleteTables);
});
};
if (testMode !== 'cli') {
setup();
runViewsTest();
}

View File

@ -1,315 +0,0 @@
/**
* This e2e test is intended to be run manually to verify if a new database is compatible not to run on the CI
*
* NOTE: You need a local cypress.env.json file with some Env Variables set to make it running (connection strings).
*
* Example of cypress.env.json content:
* {
* "POSTGRES_DB_CONNECTION_STRING": "postgres://postgres:postgrespassword@postgres:5432/postgres",
* }
*/
import { Driver } from '../../../src/dataSources';
// NOTE: table name cannot start with number
const getRandomString = () => `a${Math.random().toString(36).slice(3)}`;
const connectionString = Cypress.env('POSTGRES_DB_CONNECTION_STRING');
const driverSelectLabel = 'Postgres';
const driverSelectValue: Driver = 'postgres';
const dataSourceDisplayName = getRandomString();
const tableName = getRandomString();
const consolePort = '3000';
describe.skip('Test Basic Data Source Compatibility', () => {
it(
'tests Connect Data Source',
{
defaultCommandTimeout: 20000,
},
() => {
cy.visit(`http://localhost:${consolePort}/data/manage/connect`);
cy.log('/** Type the data source name **/');
cy.get('[data-test=database-display-name]').click();
cy.get('[data-test=database-display-name]').type(dataSourceDisplayName);
cy.log('/** Select the Driver **/');
cy.get('[data-test=database-type]')
.select(driverSelectLabel, { force: true })
.should('have.value', driverSelectValue);
cy.log('/** Type the connection string **/');
cy.get('[data-test=database-url]').click();
cy.get('[data-test=database-url]').type(connectionString);
cy.log('/** Click connect **/');
cy.get('[data-test=connect-database-btn] > span').click();
cy.log('/** Expect to see a success notification **/');
cy.wait(5000);
cy.get('.notification-success.notification-visible').should(
'contain',
'Data source added successfully!'
);
cy.get('.notification-success').should('be.visible');
}
);
it(
'tests create table',
{
defaultCommandTimeout: 20000,
},
() => {
cy.visit(`http://localhost:${consolePort}/data/manage`);
cy.log('/** Click on the database in the Nav Tree **/');
cy.contains('Data Manager')
.parent('li')
.within(() => {
cy.findByText(dataSourceDisplayName).click();
});
cy.log('/** Click on schema in the Nav Tree **/');
cy.get('[data-test=table-links]').within(() => {
cy.findByText('public').click({ force: true });
});
cy.log('/** Click on create table **/');
cy.get('[data-test=data-create-table]').click();
cy.log('/** Type the table name **/');
cy.get('[data-test=tableName]').type(tableName);
cy.log('/** Click on the table comment input **/');
cy.get('[data-test=tableComment]').click();
cy.log('/** Type the table comment **/');
cy.get('[data-test=tableComment]').type('My table comment');
cy.log('/** Click on column name **/');
cy.get('[data-test=column-0]').click();
cy.log('/** Click on "Frequently used columns" **/');
cy.get(
'[data-test=frequently-used-columns] > div > div > button > span'
).click();
cy.log('/** Select "id" **/');
cy.get(
'[data-test=frequently-used-columns] > div > ul > li:nth-child(1) > button > div > div:nth-child(1)'
).click();
cy.log('/** Click on the second column name **/');
cy.get('[data-test=column-1]').click();
cy.log('/** Type column name "name" **/');
cy.get('[data-test=column-1]').type('name');
cy.log('/** Click on the second column column_type **/');
cy.get(
'[data-test=col-type-1] > div > div > div.css-1hwfws3.col-type-1.add_table_column_selector__value-container'
).click();
cy.log('/** Type text **/');
cy.get('#react-select-3-input').type('text');
cy.log('/** Select option "Text" **/');
cy.get('#react-select-3-option-1-2').click();
cy.log('/** Click "Create table" **/');
cy.get('[data-test=table-create] > span').click();
cy.wait(5000);
cy.get('.notification-success').should('be.visible');
}
);
it(
'tests GraphQL Mutation',
{
defaultCommandTimeout: 20000,
},
() => {
cy.visit(
`http://localhost:${consolePort}/data/${dataSourceDisplayName}/schema/public`
);
cy.log('/** Click API tab **/');
cy.get('[data-test=api-tab-link] > p').click();
cy.log('/** Click GraphiQL Query input field **/');
cy.get(
'#apiRequestBlock > div > div > div > div > div.graphiql-container > div.editorWrap > div.editorBar > div.queryWrap > section.query-editor > div > div.CodeMirror-scroll > div.CodeMirror-sizer'
).click();
cy.log('/** Select all and delete **/');
cy.get('body').type('{cmd}a');
cy.get('body').type('{del}');
cy.log('/** Click GraphiQL query input **/');
cy.get(
'#apiRequestBlock > div > div > div > div > div.graphiql-container > div.editorWrap > div.editorBar > div.queryWrap > section.query-editor > div > div.CodeMirror-scroll'
).click();
cy.log('/** Select Mutation **/');
cy.get(
'#apiRequestBlock > div > div > div > div > div.gqlexplorer > div.docExplorerWrap > div.doc-explorer-contents > div > div:nth-child(2) > form > select'
).select('mutation');
cy.log('/** Click "+" **/');
cy.get(
'#apiRequestBlock > div > div > div > div > div.gqlexplorer > div.docExplorerWrap > div.doc-explorer-contents > div > div:nth-child(2) > form > button > span'
).click();
cy.log(`/** Click "insert_${tableName}_one" **/`);
cy.get(
`#mutation-MyMutation > div.graphiql-explorer-node.graphiql-explorer-insert_${tableName}_one > span > span.graphiql-explorer-field-view`
).click();
cy.log('/** Click "name" **/');
cy.get(
`#mutation-MyMutation > div.graphiql-explorer-insert_${tableName}_one > div.graphiql-explorer-node.graphiql-explorer-insert_${tableName}_one > div > div.graphiql-explorer-object > div > div.graphiql-explorer-name > span:nth-child(1) > svg`
).click();
cy.log('/** Click "name" mutation parameter **/');
cy.get(
'#apiRequestBlock > div > div > div > div > div.graphiql-container > div.editorWrap > div.editorBar > div.queryWrap > section.query-editor > div > div.CodeMirror-scroll > div.CodeMirror-sizer > div > div > div > div.CodeMirror-code > div:nth-child(2) > pre > span > span.cm-string'
).click();
cy.log('/** Type name input parameter **/');
cy.get(
'#apiRequestBlock > div > div > div > div > div.graphiql-container > div.editorWrap > div.editorBar > div.queryWrap > section.query-editor > div > div:nth-child(1) > textarea'
).type('john doe');
cy.log('/** Click execute **/');
cy.get(
'#apiRequestBlock > div > div > div > div > div.graphiql-container > div.editorWrap > div.topBarWrap > div > div.execute-button-wrap > button'
).click();
cy.get(
'#apiRequestBlock > div > div > div > div > div.graphiql-container > div.editorWrap > div.editorBar > div.resultWrap > section > div > div.CodeMirror-scroll > div.CodeMirror-sizer > div > div > div > div.CodeMirror-code'
)
.should('contain.text', '"data": {')
.should('contain.text', `"insert_${tableName}_one": {`)
.should('contain.text', '"id":');
}
);
it(
'tests GraphQL Query',
{
defaultCommandTimeout: 20000,
},
() => {
cy.visit(
`http://localhost:${consolePort}/data/${dataSourceDisplayName}/schema/public`
);
cy.log('/** Click API tab **/');
cy.get('[data-test=api-tab-link] > p').click();
cy.log('/** Click GraphiQL Query input field **/');
cy.get(
'#apiRequestBlock > div > div > div > div > div.graphiql-container > div.editorWrap > div.editorBar > div.queryWrap > section.query-editor > div > div.CodeMirror-scroll > div.CodeMirror-sizer'
).click();
cy.log('/** Select all and delete **/');
cy.get('body').type('{cmd}a');
cy.get('body').type('{del}');
cy.log('/** Click "+" **/');
cy.get(
'#apiRequestBlock > div > div > div > div > div.gqlexplorer > div.docExplorerWrap > div.doc-explorer-contents > div > div:nth-child(2) > form > button > span'
).click();
cy.log(`/** Expand ${tableName} **/`);
cy.get(
`#query-MyQuery > div.graphiql-explorer-node.graphiql-explorer-${tableName} > span > span:nth-child(1) > svg > path`
).click();
cy.log('/** Click "id" **/');
cy.get(
`#query-MyQuery > div.graphiql-explorer-${tableName} > div:nth-child(2) > div.graphiql-explorer-node.graphiql-explorer-id > span > svg`
).click();
cy.log('/** Click "name" **/');
cy.get(
`#query-MyQuery > div.graphiql-explorer-${tableName} > div:nth-child(2) > div.graphiql-explorer-node.graphiql-explorer-name > span > svg`
).click();
cy.log('/** Click execute **/');
cy.get(
'#apiRequestBlock > div > div > div > div > div.graphiql-container > div.editorWrap > div.topBarWrap > div > div.execute-button-wrap > button'
).click();
cy.get(
'#apiRequestBlock > div > div > div > div > div.graphiql-container > div.editorWrap > div.editorBar > div.resultWrap > section > div > div.CodeMirror-scroll > div.CodeMirror-sizer > div > div > div > div.CodeMirror-code'
)
.should('contain.text', '"data": {')
.should('contain.text', `"${tableName}": [`)
.should('contain.text', '"id":');
}
);
it(
'deletes table',
{
defaultCommandTimeout: 20000,
},
() => {
cy.visit(
`http://localhost:${consolePort}/data/${dataSourceDisplayName}/schema/public/tables/${tableName}/modify`,
{
onBeforeLoad(win) {
cy.stub(win, 'prompt').returns(tableName);
},
}
);
cy.log('/** Click table in the Nav Tree **/');
cy.get(`[data-test=${tableName}]`).click();
cy.log('/** Click Modify tab **/');
cy.get('[data-test=table-modify]').click();
cy.log('/** Click Delete **/');
cy.get('[data-test=delete-table] > span').click();
cy.window().its('prompt').should('be.called');
cy.get('.notification-success').should('be.visible');
}
);
it(
'deletes a data source',
{
defaultCommandTimeout: 20000,
},
() => {
cy.visit(`http://localhost:${consolePort}/data/manage`, {
onBeforeLoad(win) {
cy.stub(win, 'prompt').returns(dataSourceDisplayName);
},
});
cy.log('/** Click "Manage" **/');
cy.get('[data-test=sidebar-manage-database]').click();
cy.log('/** Click "Remove" Data Source **/');
cy.get(`[data-test="remove-${dataSourceDisplayName}"]`).click({
force: true,
});
cy.window().its('prompt').should('be.called');
cy.get('.notification-success').should('be.visible');
}
);
});

View File

@ -1,3 +0,0 @@
module.exports = {
"__version": "10.4.0"
}

View File

@ -1,3 +0,0 @@
module.exports = {
"__version": "10.4.0"
}

Some files were not shown because too many files have changed in this diff Show More