mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-09-22 07:58:52 +03:00
Merge branch 'master' into event-trigger-lock-timeout
This commit is contained in:
commit
ff5786e5c1
110
.bulldozer.yml
110
.bulldozer.yml
@ -1,110 +0,0 @@
|
|||||||
##
|
|
||||||
## This defines how our `hasura-bulldozer-mergebot` behaves on this repository.
|
|
||||||
## See: https://github.com/palantir/bulldozer
|
|
||||||
##
|
|
||||||
## Comments from the original example config are left here as documentation.
|
|
||||||
## "HASURA NOTE:" precedes comments about our choices here.
|
|
||||||
##
|
|
||||||
|
|
||||||
#
|
|
||||||
# "version" is the configuration version, currently "1".
|
|
||||||
version: 1
|
|
||||||
|
|
||||||
# "merge" defines how and when pull requests are merged. If the section is
|
|
||||||
# missing, bulldozer will consider all pull requests and use default settings.
|
|
||||||
merge:
|
|
||||||
# "trigger" defines the set of pull requests considered by bulldozer. If
|
|
||||||
# the section is missing, bulldozer considers all pull requests not excluded
|
|
||||||
# by the ignore conditions.
|
|
||||||
trigger:
|
|
||||||
# Pull requests with any of these labels (case-insensitive) are added to
|
|
||||||
# the trigger.
|
|
||||||
#
|
|
||||||
# HASURA NOTE: for now switch on only via opt-in with this label:
|
|
||||||
# CAREFUL!: if untrusted contributors can add labels, this would
|
|
||||||
# effectively give them write permissions so long as CI passed!
|
|
||||||
labels: ["auto-update-auto-merge"]
|
|
||||||
|
|
||||||
################## HASURA CAREFUL!: I think these are all dangerous (see above)
|
|
||||||
# Pull requests where the body or any comment contains any of these
|
|
||||||
# substrings are added to the trigger.
|
|
||||||
# comment_substrings: ["==MERGE_WHEN_READY=="]
|
|
||||||
|
|
||||||
# Pull requests where any comment matches one of these exact strings are
|
|
||||||
# added to the trigger.
|
|
||||||
# comments: ["Please merge this pull request!"]
|
|
||||||
|
|
||||||
# Pull requests where the body contains any of these substrings are added
|
|
||||||
# to the trigger.
|
|
||||||
# pr_body_substrings: ["==MERGE_WHEN_READY=="]
|
|
||||||
##################
|
|
||||||
|
|
||||||
# Pull requests targeting any of these branches are added to the trigger.
|
|
||||||
# branches: ["develop"]
|
|
||||||
|
|
||||||
# "ignore" defines the set of pull request ignored by bulldozer. If the
|
|
||||||
# section is missing, bulldozer considers all pull requests. It takes the
|
|
||||||
# same keys as the "trigger" section.
|
|
||||||
ignore:
|
|
||||||
# HASURA NOTE: some existing semantic tags; not sure how widely in use they are:
|
|
||||||
labels: ["s/do-not-merge", "s/wip"]
|
|
||||||
# comment_substrings: ["==DO_NOT_MERGE=="]
|
|
||||||
|
|
||||||
# "method" defines the merge method. The available options are "merge",
|
|
||||||
# "rebase", "squash", and "ff-only".
|
|
||||||
method: merge
|
|
||||||
|
|
||||||
# Allows the merge method that is used when auto-merging a PR to be different based on the
|
|
||||||
# target branch. The keys of the hash are the target branch name, and the values are the merge method that
|
|
||||||
# will be used for PRs targeting that branch. The valid values are the same as for the "method" key.
|
|
||||||
# Note: If the target branch does not match any of the specified keys, the "method" key is used instead.
|
|
||||||
branch_method:
|
|
||||||
# develop: squash
|
|
||||||
master: merge
|
|
||||||
|
|
||||||
# "options" defines additional options for the individual merge methods.
|
|
||||||
options:
|
|
||||||
# "squash" options are only used when the merge method is "squash"
|
|
||||||
# squash:
|
|
||||||
# # "title" defines how the title of the commit message is created when
|
|
||||||
# # generating a squash commit. The options are "pull_request_title",
|
|
||||||
# # "first_commit_title", and "github_default_title". The default is
|
|
||||||
# # "pull_request_title".
|
|
||||||
# title: "pull_request_title"
|
|
||||||
|
|
||||||
# # "body" defines how the body of the commit message is created when
|
|
||||||
# # generating a squash commit. The options are "pull_request_body",
|
|
||||||
# # "summarize_commits", and "empty_body". The default is "empty_body".
|
|
||||||
# body: "empty_body"
|
|
||||||
|
|
||||||
# # If "body" is "pull_request_body", then the commit message will be the
|
|
||||||
# # part of the pull request body surrounded by "message_delimiter"
|
|
||||||
# # strings. This is disabled (empty string) by default.
|
|
||||||
# message_delimiter: ==COMMIT_MSG==
|
|
||||||
|
|
||||||
# "required_statuses" is a list of additional status contexts that must pass
|
|
||||||
# before bulldozer can merge a pull request. This is useful if you want to
|
|
||||||
# require extra testing for automated merges, but not for manual merges.
|
|
||||||
# required_statuses:
|
|
||||||
# - "ci/circleci: ete-tests"
|
|
||||||
|
|
||||||
# If true, bulldozer will delete branches after their pull requests merge.
|
|
||||||
#
|
|
||||||
# HASURA NOTE: this is easily undone in the github UI:
|
|
||||||
delete_after_merge: true
|
|
||||||
|
|
||||||
# "update" defines how and when to update pull request branches. Unlike with
|
|
||||||
# merges, if this section is missing, bulldozer will not update any pull requests.
|
|
||||||
update:
|
|
||||||
# "trigger" defines the set of pull requests that should be updated by
|
|
||||||
# bulldozer. It accepts the same keys as the trigger in the "merge" block.
|
|
||||||
#
|
|
||||||
# HASURA NOTE: we definitely don't want to always update. auto-update
|
|
||||||
# (without merge) might be convenient and no harm adding it here.
|
|
||||||
trigger:
|
|
||||||
labels: ["auto-update-auto-merge", "auto-update"]
|
|
||||||
|
|
||||||
# "ignore" defines the set of pull requests that should not be updated by
|
|
||||||
# bulldozer. It accepts the same keys as the ignore in the "merge" block.
|
|
||||||
# ignore:
|
|
||||||
# labels: ["Do Not Update"]
|
|
@ -1,9 +1,7 @@
|
|||||||
# anchor refs to be used elsewhere
|
# anchor refs to be used elsewhere
|
||||||
refs:
|
refs:
|
||||||
constants:
|
constants:
|
||||||
# TODO upload to hasura docker hub:
|
- &server_builder_image hasura/graphql-engine-server-builder:2020-08-26
|
||||||
# - &server_builder_image hasura/graphql-engine-server-builder:2020-01-14
|
|
||||||
- &server_builder_image jberryman/graphql-engine-server-builder-8.10:2020-04-29
|
|
||||||
skip_job_on_ciignore: &skip_job_on_ciignore
|
skip_job_on_ciignore: &skip_job_on_ciignore
|
||||||
run:
|
run:
|
||||||
name: checking if job should be terminated or not
|
name: checking if job should be terminated or not
|
||||||
@ -165,10 +163,10 @@ jobs:
|
|||||||
- checkout
|
- checkout
|
||||||
- restore_cache:
|
- restore_cache:
|
||||||
keys:
|
keys:
|
||||||
- cabal-store-v2-{{ checksum "server/cabal.project" }}-{{ checksum "server/graphql-engine.cabal" }}-{{ checksum "server/cabal.project.freeze" }}
|
- cabal-store-v3-{{ checksum "server/cabal.project" }}-{{ checksum "server/graphql-engine.cabal" }}-{{ checksum "server/cabal.project.freeze" }}
|
||||||
- cabal-store-v2-{{ checksum "server/cabal.project" }}-{{ checksum "server/graphql-engine.cabal" }}-
|
- cabal-store-v3-{{ checksum "server/cabal.project" }}-{{ checksum "server/graphql-engine.cabal" }}-
|
||||||
- cabal-store-v2-{{ checksum "server/cabal.project" }}-
|
- cabal-store-v3-{{ checksum "server/cabal.project" }}-
|
||||||
- cabal-store-v2-
|
- cabal-store-v3-
|
||||||
- run:
|
- run:
|
||||||
name: Install latest postgresql client tools
|
name: Install latest postgresql client tools
|
||||||
command: |
|
command: |
|
||||||
@ -187,7 +185,7 @@ jobs:
|
|||||||
make enable_coverage=true ci-build
|
make enable_coverage=true ci-build
|
||||||
fi
|
fi
|
||||||
- save_cache:
|
- save_cache:
|
||||||
key: cabal-store-v2-{{ checksum "server/cabal.project" }}-{{ checksum "server/graphql-engine.cabal" }}-{{ checksum "server/cabal.project.freeze" }}
|
key: cabal-store-v3-{{ checksum "server/cabal.project" }}-{{ checksum "server/graphql-engine.cabal" }}-{{ checksum "server/cabal.project.freeze" }}
|
||||||
paths:
|
paths:
|
||||||
- ~/.cabal/packages
|
- ~/.cabal/packages
|
||||||
- ~/.cabal/store
|
- ~/.cabal/store
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
# Don't update this without updating the
|
# Don't update this without updating the
|
||||||
# packager imager of graphql-engine
|
# packager imager of graphql-engine
|
||||||
FROM phadej/ghc:8.10.1-stretch
|
FROM haskell:8.10.2-stretch
|
||||||
# TODO https://github.com/haskell/docker-haskell/issues/17
|
|
||||||
#FROM haskell:8.10.1
|
|
||||||
|
|
||||||
ARG docker_ver="17.09.0-ce"
|
ARG docker_ver="17.09.0-ce"
|
||||||
ARG postgres_ver="12"
|
ARG postgres_ver="12"
|
||||||
@ -14,7 +12,10 @@ RUN apt-get -y update \
|
|||||||
&& echo "deb http://apt.postgresql.org/pub/repos/apt/ stretch-pgdg main" > /etc/apt/sources.list.d/pgdg.list \
|
&& echo "deb http://apt.postgresql.org/pub/repos/apt/ stretch-pgdg main" > /etc/apt/sources.list.d/pgdg.list \
|
||||||
&& curl -s https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - \
|
&& curl -s https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - \
|
||||||
&& apt-get -y update \
|
&& apt-get -y update \
|
||||||
&& apt-get install -y g++ gcc libc6-dev libpq-dev libffi-dev libgmp-dev make xz-utils zlib1g-dev git gnupg upx netcat python3 python3-pip postgresql-client-${postgres_ver} postgresql-client-common \
|
&& apt-get install -y \
|
||||||
|
g++ gcc git gnupg libc6-dev libffi-dev libgmp-dev libkrb5-dev \
|
||||||
|
libpq-dev libssl-dev make netcat postgresql-client-${postgres_ver} \
|
||||||
|
postgresql-client-common python3 python3-pip upx xz-utils zlib1g-dev \
|
||||||
&& curl -sL https://deb.nodesource.com/setup_${node_ver} | bash - \
|
&& curl -sL https://deb.nodesource.com/setup_${node_ver} | bash - \
|
||||||
&& apt-get install -y nodejs \
|
&& apt-get install -y nodejs \
|
||||||
&& curl -Lo /tmp/docker-${docker_ver}.tgz https://download.docker.com/linux/static/stable/x86_64/docker-${docker_ver}.tgz \
|
&& curl -Lo /tmp/docker-${docker_ver}.tgz https://download.docker.com/linux/static/stable/x86_64/docker-${docker_ver}.tgz \
|
||||||
|
13
.circleci/server-upgrade-downgrade/err_msg.patch
Normal file
13
.circleci/server-upgrade-downgrade/err_msg.patch
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
diff --git a/server/tests-py/validate.py b/server/tests-py/validate.py
|
||||||
|
index 3eecd52a..a18b3113 100644
|
||||||
|
--- a/server/tests-py/validate.py
|
||||||
|
+++ b/server/tests-py/validate.py
|
||||||
|
@@ -318,7 +318,7 @@ def assert_graphql_resp_expected(resp_orig, exp_response_orig, query, resp_hdrs=
|
||||||
|
# If it is a batch GraphQL query, compare each individual response separately
|
||||||
|
for (exp, out) in zip(as_list(exp_response), as_list(resp)):
|
||||||
|
matched_ = equal_CommentedMap(exp, out)
|
||||||
|
- if is_err_msg(exp):
|
||||||
|
+ if is_err_msg(exp) and is_err_msg(out):
|
||||||
|
if not matched_:
|
||||||
|
warnings.warn("Response does not have the expected error message\n" + dump_str.getvalue())
|
||||||
|
return resp, matched
|
@ -6,7 +6,9 @@
|
|||||||
# and sets some of the required variables that run.sh needs,
|
# and sets some of the required variables that run.sh needs,
|
||||||
# before executing run.sh
|
# before executing run.sh
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
ROOT="${BASH_SOURCE[0]%/*}"
|
cd "${BASH_SOURCE[0]%/*}"
|
||||||
|
ROOT="${PWD}"
|
||||||
|
cd - > /dev/null
|
||||||
|
|
||||||
SERVER_DIR="$ROOT/../../server"
|
SERVER_DIR="$ROOT/../../server"
|
||||||
|
|
||||||
@ -18,8 +20,8 @@ echo "server binary: $SERVER_BINARY"
|
|||||||
cd -
|
cd -
|
||||||
set +x
|
set +x
|
||||||
|
|
||||||
export SERVER_OUTPUT_DIR="server-output"
|
export SERVER_OUTPUT_DIR="$ROOT/server-output"
|
||||||
export LATEST_SERVER_BINARY="./graphql-engine-latest"
|
export LATEST_SERVER_BINARY="$ROOT/graphql-engine-latest"
|
||||||
|
|
||||||
# Create Python virtualenv
|
# Create Python virtualenv
|
||||||
if ! [ -f ".venv/bin/activate" ] ; then
|
if ! [ -f ".venv/bin/activate" ] ; then
|
||||||
@ -40,7 +42,8 @@ log_duration=on
|
|||||||
port=$PG_PORT
|
port=$PG_PORT
|
||||||
EOF
|
EOF
|
||||||
)
|
)
|
||||||
export HASURA_GRAPHQL_DATABASE_URL="postgres://postgres:$PGPASSWORD@127.0.0.1:$PG_PORT/postgres"
|
# Pytest is giving out deprecated warnings when postgres:// is used
|
||||||
|
export HASURA_GRAPHQL_DATABASE_URL="postgresql://postgres:$PGPASSWORD@127.0.0.1:$PG_PORT/postgres"
|
||||||
|
|
||||||
DOCKER_PSQL="docker exec -u postgres -it $PG_CONTAINER_NAME psql -p $PG_PORT"
|
DOCKER_PSQL="docker exec -u postgres -it $PG_CONTAINER_NAME psql -p $PG_PORT"
|
||||||
|
|
||||||
|
@ -12,7 +12,9 @@ set -euo pipefail
|
|||||||
# # echo an error message before exiting
|
# # echo an error message before exiting
|
||||||
# trap 'echo "\"${last_command}\" command filed with exit code $?."' EXIT
|
# trap 'echo "\"${last_command}\" command filed with exit code $?."' EXIT
|
||||||
|
|
||||||
ROOT="${BASH_SOURCE[0]%/*}"
|
cd "${BASH_SOURCE[0]%/*}"
|
||||||
|
ROOT="${PWD}"
|
||||||
|
cd - > /dev/null
|
||||||
|
|
||||||
download_with_etag_check() {
|
download_with_etag_check() {
|
||||||
URL="$1"
|
URL="$1"
|
||||||
@ -119,6 +121,17 @@ trap rm_worktree ERR
|
|||||||
|
|
||||||
make_latest_release_worktree() {
|
make_latest_release_worktree() {
|
||||||
git worktree add --detach "$WORKTREE_DIR" "$RELEASE_VERSION"
|
git worktree add --detach "$WORKTREE_DIR" "$RELEASE_VERSION"
|
||||||
|
cd "$WORKTREE_DIR"
|
||||||
|
# FIX ME: Remove the patch below after the next stable release
|
||||||
|
# The --avoid-error-message-checks in pytest was implementated as a rather relaxed check than
|
||||||
|
# what we intended to have. In versions <= v1.3.0,
|
||||||
|
# this check allows response to be success even if the expected response is a failure.
|
||||||
|
# The patch below fixes that issue.
|
||||||
|
# The `git apply` should give errors from next release onwards,
|
||||||
|
# since this change is going to be included in the next release version
|
||||||
|
git apply "${ROOT}/err_msg.patch" || \
|
||||||
|
(log "Remove the git apply in make_latest_release_worktree function" && false)
|
||||||
|
cd - > /dev/null
|
||||||
}
|
}
|
||||||
|
|
||||||
cleanup_hasura_metadata_if_present() {
|
cleanup_hasura_metadata_if_present() {
|
||||||
@ -148,7 +161,18 @@ get_server_upgrade_tests() {
|
|||||||
cd $RELEASE_PYTEST_DIR
|
cd $RELEASE_PYTEST_DIR
|
||||||
tmpfile="$(mktemp --dry-run)"
|
tmpfile="$(mktemp --dry-run)"
|
||||||
set -x
|
set -x
|
||||||
python3 -m pytest -q --collect-only --collect-upgrade-tests-to-file "$tmpfile" -m 'allow_server_upgrade_test and not skip_server_upgrade_test' "${args[@]}" 1>/dev/null 2>/dev/null
|
# FIX ME: Deselecting some introspection tests from the previous test suite
|
||||||
|
# which throw errors on the latest build. Even when the output of the current build is more accurate.
|
||||||
|
# Remove these deselects after the next stable release
|
||||||
|
python3 -m pytest -q --collect-only --collect-upgrade-tests-to-file "$tmpfile" \
|
||||||
|
-m 'allow_server_upgrade_test and not skip_server_upgrade_test' \
|
||||||
|
--deselect test_schema_stitching.py::TestRemoteSchemaBasic::test_introspection \
|
||||||
|
--deselect test_schema_stitching.py::TestAddRemoteSchemaCompareRootQueryFields::test_schema_check_arg_default_values_and_field_and_arg_types \
|
||||||
|
--deselect test_graphql_mutations.py::TestGraphqlInsertPermission::test_user_with_no_backend_privilege \
|
||||||
|
--deselect test_graphql_mutations.py::TestGraphqlInsertPermission::test_backend_user_no_admin_secret_fail \
|
||||||
|
--deselect test_graphql_mutations.py::TestGraphqlMutationCustomSchema::test_update_article \
|
||||||
|
--deselect test_graphql_queries.py::TestGraphQLQueryEnums::test_introspect_user_role \
|
||||||
|
"${args[@]}" 1>/dev/null 2>/dev/null
|
||||||
set +x
|
set +x
|
||||||
cat "$tmpfile"
|
cat "$tmpfile"
|
||||||
cd - >/dev/null
|
cd - >/dev/null
|
||||||
@ -174,11 +198,12 @@ run_server_upgrade_pytest() {
|
|||||||
set -x
|
set -x
|
||||||
|
|
||||||
# With --avoid-error-message-checks, we are only going to throw warnings if the error message has changed between releases
|
# With --avoid-error-message-checks, we are only going to throw warnings if the error message has changed between releases
|
||||||
# FIX ME: Remove the deselect below after the next stable release
|
|
||||||
pytest --hge-urls "${HGE_URL}" --pg-urls "$HASURA_GRAPHQL_DATABASE_URL" \
|
pytest --hge-urls "${HGE_URL}" --pg-urls "$HASURA_GRAPHQL_DATABASE_URL" \
|
||||||
--avoid-error-message-checks "$@" \
|
--avoid-error-message-checks "$@" \
|
||||||
-m 'allow_server_upgrade_test and not skip_server_upgrade_test' \
|
-m 'allow_server_upgrade_test and not skip_server_upgrade_test' \
|
||||||
--deselect test_graphql_mutations.py::TestGraphqlUpdateBasic::test_numerics_inc \
|
--deselect test_graphql_mutations.py::TestGraphqlInsertPermission::test_user_with_no_backend_privilege \
|
||||||
|
--deselect test_graphql_mutations.py::TestGraphqlMutationCustomSchema::test_update_article \
|
||||||
|
--deselect test_graphql_queries.py::TestGraphQLQueryEnums::test_introspect_user_role \
|
||||||
-v $tests_to_run
|
-v $tests_to_run
|
||||||
set +x
|
set +x
|
||||||
cd -
|
cd -
|
||||||
|
@ -191,9 +191,7 @@ pip3 install -r requirements.txt
|
|||||||
# node js deps
|
# node js deps
|
||||||
curl -sL https://deb.nodesource.com/setup_8.x | bash -
|
curl -sL https://deb.nodesource.com/setup_8.x | bash -
|
||||||
apt-get install -y nodejs
|
apt-get install -y nodejs
|
||||||
npm_config_loglevel=error npm install $PYTEST_ROOT/remote_schemas/nodejs/
|
(cd remote_schemas/nodejs && npm_config_loglevel=error npm ci)
|
||||||
|
|
||||||
npm install apollo-server graphql
|
|
||||||
|
|
||||||
mkdir -p "$OUTPUT_FOLDER/hpc"
|
mkdir -p "$OUTPUT_FOLDER/hpc"
|
||||||
|
|
||||||
@ -385,6 +383,51 @@ kill_hge_servers
|
|||||||
|
|
||||||
unset HASURA_GRAPHQL_JWT_SECRET
|
unset HASURA_GRAPHQL_JWT_SECRET
|
||||||
|
|
||||||
|
# test JWT with Claims map
|
||||||
|
echo -e "\n$(time_elapsed): <########## TEST GRAPHQL-ENGINE WITH ADMIN SECRET AND JWT (with claims_map and values are json path) #####################################>\n"
|
||||||
|
TEST_TYPE="jwt-claims-map-with-json-path-values"
|
||||||
|
|
||||||
|
export HASURA_GRAPHQL_JWT_SECRET="$(jq -n --arg key "$(cat $OUTPUT_FOLDER/ssl/jwt_public.key)" '{ type: "RS512", key: $key , claims_map: {"x-hasura-user-id": {"path":"$.['"'"'https://myapp.com/jwt/claims'"'"'].user.id"}, "x-hasura-allowed-roles": {"path":"$.['"'"'https://myapp.com/jwt/claims'"'"'].role.allowed"}, "x-hasura-default-role": {"path":"$.['"'"'https://myapp.com/jwt/claims'"'"'].role.default"}}}')"
|
||||||
|
|
||||||
|
run_hge_with_args serve
|
||||||
|
wait_for_port 8080
|
||||||
|
|
||||||
|
pytest -n 1 -vv --hge-urls "$HGE_URL" --pg-urls "$HASURA_GRAPHQL_DATABASE_URL" --hge-key="$HASURA_GRAPHQL_ADMIN_SECRET" --hge-jwt-key-file="$OUTPUT_FOLDER/ssl/jwt_private.key" --hge-jwt-conf="$HASURA_GRAPHQL_JWT_SECRET" test_jwt_claims_map.py::TestJWTClaimsMapBasic
|
||||||
|
|
||||||
|
kill_hge_servers
|
||||||
|
|
||||||
|
unset HASURA_GRAPHQL_JWT_SECRET
|
||||||
|
|
||||||
|
echo -e "\n$(time_elapsed): <########## TEST GRAPHQL-ENGINE WITH ADMIN SECRET AND JWT (with claims_map and values are json path with default values set) #####################################>\n"
|
||||||
|
TEST_TYPE="jwt-claims-map-with-json-path-values-with-default-values"
|
||||||
|
|
||||||
|
export HASURA_GRAPHQL_JWT_SECRET="$(jq -n --arg key "$(cat $OUTPUT_FOLDER/ssl/jwt_public.key)" '{ type: "RS512", key: $key , claims_map: {"x-hasura-user-id": {"path":"$.['"'"'https://myapp.com/jwt/claims'"'"'].user.id", "default":"1"}, "x-hasura-allowed-roles": {"path":"$.['"'"'https://myapp.com/jwt/claims'"'"'].role.allowed", "default":["user","editor"]}, "x-hasura-default-role": {"path":"$.['"'"'https://myapp.com/jwt/claims'"'"'].role.default","default":"user"}}}')"
|
||||||
|
|
||||||
|
run_hge_with_args serve
|
||||||
|
wait_for_port 8080
|
||||||
|
|
||||||
|
pytest -n 1 -vv --hge-urls "$HGE_URL" --pg-urls "$HASURA_GRAPHQL_DATABASE_URL" --hge-key="$HASURA_GRAPHQL_ADMIN_SECRET" --hge-jwt-key-file="$OUTPUT_FOLDER/ssl/jwt_private.key" --hge-jwt-conf="$HASURA_GRAPHQL_JWT_SECRET" test_jwt_claims_map.py::TestJWTClaimsMapBasic
|
||||||
|
|
||||||
|
kill_hge_servers
|
||||||
|
|
||||||
|
unset HASURA_GRAPHQL_JWT_SECRET
|
||||||
|
|
||||||
|
|
||||||
|
echo -e "\n$(time_elapsed): <########## TEST GRAPHQL-ENGINE WITH ADMIN SECRET AND JWT (with claims_map and values are literal values) #####################################>\n"
|
||||||
|
TEST_TYPE="jwt-claims-map-with-literal-values"
|
||||||
|
|
||||||
|
export HASURA_GRAPHQL_JWT_SECRET="$(jq -n --arg key "$(cat $OUTPUT_FOLDER/ssl/jwt_public.key)" '{ type: "RS512", key: $key , claims_map: {"x-hasura-user-id": {"path":"$.['"'"'https://myapp.com/jwt/claims'"'"'].user.id"}, "x-hasura-allowed-roles": ["user","editor"], "x-hasura-default-role": "user","x-hasura-custom-header":"custom-value"}}')"
|
||||||
|
|
||||||
|
run_hge_with_args serve
|
||||||
|
wait_for_port 8080
|
||||||
|
|
||||||
|
pytest -n 1 -vv --hge-urls "$HGE_URL" --pg-urls "$HASURA_GRAPHQL_DATABASE_URL" --hge-key="$HASURA_GRAPHQL_ADMIN_SECRET" --hge-jwt-key-file="$OUTPUT_FOLDER/ssl/jwt_private.key" --hge-jwt-conf="$HASURA_GRAPHQL_JWT_SECRET" test_jwt_claims_map.py::TestJWTClaimsMapWithStaticHasuraClaimsMapValues
|
||||||
|
|
||||||
|
kill_hge_servers
|
||||||
|
|
||||||
|
unset HASURA_GRAPHQL_JWT_SECRET
|
||||||
|
|
||||||
|
|
||||||
# test with CORS modes
|
# test with CORS modes
|
||||||
|
|
||||||
echo -e "\n$(time_elapsed): <########## TEST GRAPHQL-ENGINE WITH CORS DOMAINS ########>\n"
|
echo -e "\n$(time_elapsed): <########## TEST GRAPHQL-ENGINE WITH CORS DOMAINS ########>\n"
|
||||||
|
128
.kodiak.toml
Normal file
128
.kodiak.toml
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
###############################################################################
|
||||||
|
## Configuration for auto-merge / auto-update bot
|
||||||
|
##
|
||||||
|
## See: https://kodiakhq.com/
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
# Kodiak's configuration file should be placed at `.kodiak.toml` (repository
|
||||||
|
# root) or `.github/.kodiak.toml`.
|
||||||
|
# docs: https://kodiakhq.com/docs/config-reference
|
||||||
|
|
||||||
|
# version is the only required setting in a kodiak config.
|
||||||
|
# `1` is the only valid setting for this field.
|
||||||
|
version = 1
|
||||||
|
|
||||||
|
[merge]
|
||||||
|
# Label to enable Kodiak to merge a PR.
|
||||||
|
|
||||||
|
# By default, Kodiak will only act on PRs that have this label. You can disable
|
||||||
|
# this requirement via `merge.require_automerge_label`.
|
||||||
|
automerge_label = "auto-update-auto-merge"
|
||||||
|
|
||||||
|
# Require that the automerge label (`merge.automerge_label`) be set for Kodiak
|
||||||
|
# to merge a PR.
|
||||||
|
#
|
||||||
|
# When disabled, Kodiak will immediately attempt to merge any PR that passes all
|
||||||
|
# GitHub branch protection requirements.
|
||||||
|
require_automerge_label = true
|
||||||
|
|
||||||
|
# If a PR's title matches this regex, Kodiak will not merge the PR. This is
|
||||||
|
# useful to prevent merging work-in-progress PRs.
|
||||||
|
#
|
||||||
|
# Setting `merge.blocking_title_regex = ""` disables this option.
|
||||||
|
blocking_title_regex = "" # default: "^WIP:.*", options: "" (disables regex), a regex string (e.g. ".*DONT\s*MERGE.*")
|
||||||
|
|
||||||
|
# Kodiak will not merge a PR with any of these labels.
|
||||||
|
blocking_labels = ["s/do-not-merge", "s/wip"] # default: []
|
||||||
|
|
||||||
|
# Choose merge method for Kodiak to use.
|
||||||
|
#
|
||||||
|
# Kodiak will report a configuration error if the selected merge method is
|
||||||
|
# disabled for a repository.
|
||||||
|
#
|
||||||
|
# If you're using the "Require signed commits" GitHub Branch Protection setting
|
||||||
|
# to require commit signatures, "merge" or "squash" are the only compatible options. "rebase" will cause Kodiak to raise a configuration error.
|
||||||
|
method = "merge" # default: "merge", options: "merge", "squash", "rebase"
|
||||||
|
|
||||||
|
# Once a PR is merged, delete the branch. This option behaves like the GitHub
|
||||||
|
# repository setting "Automatically delete head branches", which automatically
|
||||||
|
# deletes head branches after pull requests are merged.
|
||||||
|
delete_branch_on_merge = true # default: false
|
||||||
|
|
||||||
|
# If there is a merge conflict, make a comment on the PR and remove the
|
||||||
|
# automerge label. This option only applies when `merge.require_automerge_label`
|
||||||
|
# is enabled.
|
||||||
|
notify_on_conflict = true # default: true
|
||||||
|
|
||||||
|
# Don't wait for in-progress status checks on a PR to finish before updating the
|
||||||
|
# branch.
|
||||||
|
optimistic_updates = true # default: true
|
||||||
|
|
||||||
|
# Don't wait for specified status checks when merging a PR. If a configured
|
||||||
|
# status check is incomplete when a PR is being merged, Kodiak will skip the PR.
|
||||||
|
# Use this option for status checks that run indefinitely, like deploy jobs or
|
||||||
|
# the WIP GitHub App.
|
||||||
|
dont_wait_on_status_checks = [] # default: [], options: list of check names (e.g. ["ci/circleci: lint_api"])
|
||||||
|
|
||||||
|
# If a PR is passing all checks and is able to be merged, merge it without
|
||||||
|
# placing it in the merge queue. This option adds some unfairness where PRs
|
||||||
|
# waiting in the queue the longest are not served first.
|
||||||
|
prioritize_ready_to_merge = true # default: false
|
||||||
|
|
||||||
|
# Never merge a PR. This option can be used with `update.always` to
|
||||||
|
# automatically update a PR without merging.
|
||||||
|
do_not_merge = false # default: false
|
||||||
|
|
||||||
|
[merge.message]
|
||||||
|
# By default (`"github_default"`), GitHub uses the title of a PR's first commit
|
||||||
|
# for the merge commit title. `"pull_request_title"` uses the PR title for the
|
||||||
|
# merge commit.
|
||||||
|
title = "github_default" # default: "github_default", options: "github_default", "pull_request_title"
|
||||||
|
|
||||||
|
# By default (`"github_default"`), GitHub combines the titles of a PR's commits
|
||||||
|
# to create the body text of a merge commit. `"pull_request_body"` uses the
|
||||||
|
# content of the PR to generate the body content while `"empty"` sets an empty
|
||||||
|
# body.
|
||||||
|
body = "github_default" # default: "github_default", options: "github_default", "pull_request_body", "empty"
|
||||||
|
|
||||||
|
# Append the Pull Request URL to the merge message. Makes navigating to the PR
|
||||||
|
# from the commit easier.
|
||||||
|
include_pull_request_url = true # default: false
|
||||||
|
|
||||||
|
# Add the PR number to the merge commit title. This setting replicates GitHub's
|
||||||
|
# behavior of automatically adding the PR number to the title of merges created
|
||||||
|
# through the UI. This option only applies when `merge.message.title` does not
|
||||||
|
# equal `"github_default"`.
|
||||||
|
include_pr_number = true # default: true
|
||||||
|
|
||||||
|
# Control the text used in the merge commit. The GitHub default is markdown, but
|
||||||
|
# `"plain_text"` or `"html"` can be used to render the pull request body as text
|
||||||
|
# or HTML. This option only applies when `merge.message.body = "pull_request_body"`.
|
||||||
|
body_type = "markdown" # default: "markdown", options: "plain_text", "markdown", "html"
|
||||||
|
|
||||||
|
|
||||||
|
# Strip HTML comments (`<!-- some HTML comment -->`) from merge commit body.
|
||||||
|
# This setting is useful for stripping HTML comments created by PR templates.
|
||||||
|
# This option only applies when `merge.message.body_type = "markdown"`.
|
||||||
|
strip_html_comments = false # default: false
|
||||||
|
|
||||||
|
[update]
|
||||||
|
|
||||||
|
# Update a PR whenever out of date with the base branch. The PR will be updated
|
||||||
|
# regardless of merge requirements (e.g. failing status checks, missing reviews,
|
||||||
|
# blacklist labels).
|
||||||
|
#
|
||||||
|
# Kodiak will only update PRs with the `merge.automerge_label` label or if
|
||||||
|
# `update.require_automerge_label = false`.
|
||||||
|
#
|
||||||
|
# When enabled, _Kodiak will not be able to efficiently update PRs._ If you have
|
||||||
|
# multiple PRs against a target like `master`, any time a commit is added to
|
||||||
|
# `master` _all_ of those PRs against `master` will update. For `N` PRs against
|
||||||
|
# a target you will see at least `N(N-1)/2` updates. If this configuration
|
||||||
|
# option was disabled you would only see at least `N-1` updates.
|
||||||
|
always = false # default: false
|
||||||
|
|
||||||
|
# When enabled, Kodiak will only update PRs that have an automerge label
|
||||||
|
# (configured via `merge.automerge_label`). When disable, Kodiak will update any
|
||||||
|
# PR. This option only applies when `update.always = true`.
|
||||||
|
require_automerge_label = true # default: true
|
71
CHANGELOG.md
71
CHANGELOG.md
@ -2,6 +2,75 @@
|
|||||||
|
|
||||||
## Next release
|
## Next release
|
||||||
|
|
||||||
|
### Server - Support for mapping session variables to default JWT claims
|
||||||
|
|
||||||
|
Some auth providers do not let users add custom claims in JWT. In such cases, the server can take a JWT configuration option called `claims_map` to specify a mapping of Hasura session variables to values in existing claims via JSONPath or literal values.
|
||||||
|
|
||||||
|
Example:-
|
||||||
|
|
||||||
|
Consider the following JWT claim:
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"sub": "1234567890",
|
||||||
|
"name": "John Doe",
|
||||||
|
"admin": true,
|
||||||
|
"iat": 1516239022,
|
||||||
|
"user": {
|
||||||
|
"id": "ujdh739kd",
|
||||||
|
"appRoles": ["user", "editor"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The corresponding JWT config can be:
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"type":"RS512",
|
||||||
|
"key": "<The public Key>",
|
||||||
|
"claims_map": {
|
||||||
|
"x-hasura-allowed-roles": {"path":"$.user.appRoles"},
|
||||||
|
"x-hasura-default-role": {"path":"$.user.appRoles[0]","default":"user"},
|
||||||
|
"x-hasura-user-id": {"path":"$.user.id"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Breaking changes
|
||||||
|
|
||||||
|
This release contains the [PDV refactor (#4111)](https://github.com/hasura/graphql-engine/pull/4111), a significant rewrite of the internals of the server, which did include some breaking changes:
|
||||||
|
|
||||||
|
- The semantics of explicit `null` values in `where` filters have changed according to the discussion in [issue 704](https://github.com/hasura/graphql-engine/issues/704#issuecomment-635571407): an explicit `null` value in a comparison input object will be treated as an error rather than resulting in the expression being evaluated to `True`. For instance: `delete_users(where: {id: {_eq: $userId}}) { name }` will yield an error if `$userId` is `null` instead of deleting all users.
|
||||||
|
- The validation of required headers has been fixed (closing #14 and #3659):
|
||||||
|
- if a query selects table `bar` through table `foo` via a relationship, the required permissions headers will be the union of the required headers of table `foo` and table `bar` (we used to only check the headers of the root table);
|
||||||
|
- if an insert does not have an `on_conflict` clause, it will not require the update permissions headers.
|
||||||
|
|
||||||
|
### Bug fixes and improvements
|
||||||
|
|
||||||
|
(Add entries here in the order of: server, console, cli, docs, others)
|
||||||
|
|
||||||
|
- server: some mutations that cannot be performed will no longer be in the schema (for instance, `delete_by_pk` mutations won't be shown to users that do not have select permissions on all primary keys) (#4111)
|
||||||
|
- server: miscellaneous description changes (#4111)
|
||||||
|
- server: treat the absence of `backend_only` configuration and `backend_only: false` equally (closing #5059) (#4111)
|
||||||
|
- server: allow remote relationships joining `type` column with `[type]` input argument as spec allows this coercion (fixes #5133)
|
||||||
|
- console: allow user to cascade Postgres dependencies when dropping Postgres objects (close #5109) (#5248)
|
||||||
|
- console: mark inconsistent remote schemas in the UI (close #5093) (#5181)
|
||||||
|
- cli: add missing global flags for seeds command (#5565)
|
||||||
|
- docs: add docs page on networking with docker (close #4346) (#4811)
|
||||||
|
|
||||||
|
|
||||||
|
## `v1.3.2`
|
||||||
|
|
||||||
|
### Bug fixes and improvements
|
||||||
|
|
||||||
|
(Add entries here in the order of: server, console, cli, docs, others)
|
||||||
|
|
||||||
|
- server: fixes column masking in select permission for computed fields regression (fix #5696)
|
||||||
|
|
||||||
|
|
||||||
|
## `v1.3.1`, `v1.3.1-beta.1`
|
||||||
|
|
||||||
### Breaking change
|
### Breaking change
|
||||||
|
|
||||||
Headers from environment variables starting with `HASURA_GRAPHQL_` are not allowed
|
Headers from environment variables starting with `HASURA_GRAPHQL_` are not allowed
|
||||||
@ -24,6 +93,7 @@ If you do have such headers configured, then you must update the header configur
|
|||||||
- console: handle nested fragments in allowed queries (close #5137) (#5252)
|
- console: handle nested fragments in allowed queries (close #5137) (#5252)
|
||||||
- console: update sidebar icons for different action and trigger types (#5445)
|
- console: update sidebar icons for different action and trigger types (#5445)
|
||||||
- console: make add column UX consistent with others (#5486)
|
- console: make add column UX consistent with others (#5486)
|
||||||
|
- console: add "identity" to frequently used columns (close #4279) (#5360)
|
||||||
- cli: improve error messages thrown when metadata apply fails (#5513)
|
- cli: improve error messages thrown when metadata apply fails (#5513)
|
||||||
- cli: fix issue with creating seed migrations while using tables with capital letters (closes #5532) (#5549)
|
- cli: fix issue with creating seed migrations while using tables with capital letters (closes #5532) (#5549)
|
||||||
- build: introduce additional log kinds for cli-migrations image (#5529)
|
- build: introduce additional log kinds for cli-migrations image (#5529)
|
||||||
@ -66,7 +136,6 @@ If you do have such headers configured, then you must update the header configur
|
|||||||
|
|
||||||
## `v1.3.0-beta.3`
|
## `v1.3.0-beta.3`
|
||||||
|
|
||||||
|
|
||||||
### Bug fixes and improvements
|
### Bug fixes and improvements
|
||||||
|
|
||||||
(Add entries here in the order of: server, console, cli, docs, others)
|
(Add entries here in the order of: server, console, cli, docs, others)
|
||||||
|
@ -12,7 +12,7 @@ import (
|
|||||||
const completionCmdExample = `# Bash
|
const completionCmdExample = `# Bash
|
||||||
# Linux
|
# Linux
|
||||||
# Add Bash completion file using:
|
# Add Bash completion file using:
|
||||||
$ sudo hasura completion bash --file=/etc/bash.completion.d/hasura
|
$ sudo hasura completion bash --file=/etc/bash_completion.d/hasura
|
||||||
# Mac
|
# Mac
|
||||||
# Install bash-completion using homebrew:
|
# Install bash-completion using homebrew:
|
||||||
$ brew install bash-completion
|
$ brew install bash-completion
|
||||||
|
@ -2,6 +2,7 @@ package commands
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/hasura/graphql-engine/cli"
|
"github.com/hasura/graphql-engine/cli"
|
||||||
|
"github.com/hasura/graphql-engine/cli/util"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
@ -28,14 +29,21 @@ func NewSeedCmd(ec *cli.ExecutionContext) *cobra.Command {
|
|||||||
newSeedCreateCmd(ec),
|
newSeedCreateCmd(ec),
|
||||||
newSeedApplyCmd(ec),
|
newSeedApplyCmd(ec),
|
||||||
)
|
)
|
||||||
seedCmd.PersistentFlags().String("endpoint", "", "http(s) endpoint for Hasura GraphQL Engine")
|
|
||||||
seedCmd.PersistentFlags().String("admin-secret", "", "admin secret for Hasura GraphQL Engine")
|
|
||||||
seedCmd.PersistentFlags().String("access-key", "", "access key for Hasura GraphQL Engine")
|
|
||||||
seedCmd.PersistentFlags().MarkDeprecated("access-key", "use --admin-secret instead")
|
|
||||||
|
|
||||||
v.BindPFlag("endpoint", seedCmd.PersistentFlags().Lookup("endpoint"))
|
f := seedCmd.PersistentFlags()
|
||||||
v.BindPFlag("admin_secret", seedCmd.PersistentFlags().Lookup("admin-secret"))
|
|
||||||
v.BindPFlag("access_key", seedCmd.PersistentFlags().Lookup("access-key"))
|
f.String("endpoint", "", "http(s) endpoint for Hasura GraphQL Engine")
|
||||||
|
f.String("admin-secret", "", "admin secret for Hasura GraphQL Engine")
|
||||||
|
f.String("access-key", "", "access key for Hasura GraphQL Engine")
|
||||||
|
f.MarkDeprecated("access-key", "use --admin-secret instead")
|
||||||
|
f.Bool("insecure-skip-tls-verify", false, "skip TLS verification and disable cert checking (default: false)")
|
||||||
|
f.String("certificate-authority", "", "path to a cert file for the certificate authority")
|
||||||
|
|
||||||
|
util.BindPFlag(v, "endpoint", f.Lookup("endpoint"))
|
||||||
|
util.BindPFlag(v, "admin_secret", f.Lookup("admin-secret"))
|
||||||
|
util.BindPFlag(v, "access_key", f.Lookup("access-key"))
|
||||||
|
util.BindPFlag(v, "insecure_skip_tls_verify", f.Lookup("insecure-skip-tls-verify"))
|
||||||
|
util.BindPFlag(v, "certificate_authority", f.Lookup("certificate-authority"))
|
||||||
|
|
||||||
return seedCmd
|
return seedCmd
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,7 @@ func newSeedCreateCmd(ec *cli.ExecutionContext) *cobra.Command {
|
|||||||
}
|
}
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "create seed_name",
|
Use: "create seed_name",
|
||||||
Short: "create a new seed file",
|
Short: "Create a new seed file",
|
||||||
Example: ` # Create a new seed file and use editor to add SQL:
|
Example: ` # Create a new seed file and use editor to add SQL:
|
||||||
hasura seed create new_table_seed
|
hasura seed create new_table_seed
|
||||||
|
|
||||||
|
@ -1,22 +1,19 @@
|
|||||||
# svelte-graphql-app
|
# svelte-graphql-app
|
||||||
|
|
||||||
A sample [Svelte 3](https://svelte.dev) app to demonstrate usage of GraphQL Queries, Mutations and Subscriptions with [svelte-apollo](https://github.com/timhall/svelte-apollo), Hasura GraphQL engine and Postgres as database. Forked from the standard svelte [template](https://github.com/sveltejs/template)
|
A sample [Svelte 3](https://svelte.dev) app to demonstrate usage of GraphQL Queries, Mutations and Subscriptions with [svelte-apollo](https://github.com/timhall/svelte-apollo), Hasura Cloud and Postgres as database. Forked from the standard svelte [template](https://github.com/sveltejs/template)
|
||||||
|
|
||||||
[![Edit svelte-graphql](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/github/hasura/graphql-engine/tree/master/community/sample-apps/svelte-apollo?fontsize=14)
|
[![Edit svelte-graphql](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/github/hasura/graphql-engine/tree/master/community/sample-apps/svelte-apollo?fontsize=14)
|
||||||
|
|
||||||
## Deploy Hasura
|
## Create new Hasura Cloud project
|
||||||
|
|
||||||
- Deploy Postgres and GraphQL Engine on Heroku:
|
- Create new Hasura Cloud project with the `Try a free database with Heroku` option.
|
||||||
|
|
||||||
[![Deploy to
|
Please check out our [docs](https://hasura.io/docs/cloud/1.0/manual/getting-started/index.html) for the detailed steps.
|
||||||
heroku](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/hasura/graphql-engine-heroku)
|
|
||||||
|
|
||||||
Please checkout our [docs](https://hasura.io/docs/1.0/graphql/manual/deployment/index.html) for other deployment methods
|
- Get the app URL (something like `https://<my-project-name>.hasura.app`)
|
||||||
|
|
||||||
- Get the Heroku app URL (say `my-app.herokuapp.com`)
|
|
||||||
- Create `author` table:
|
- Create `author` table:
|
||||||
|
|
||||||
Open Hasura console: visit https://my-app.herokuapp.com on a browser
|
Open your Hasura Cloud project's console: visit `https://<my-project-name>.hasura.app` on a browser
|
||||||
Navigate to `Data` section in the top nav bar and create a table as follows:
|
Navigate to `Data` section in the top nav bar and create a table as follows:
|
||||||
|
|
||||||
![Create author table](../gatsby-postgres-graphql/assets/add_table.jpg)
|
![Create author table](../gatsby-postgres-graphql/assets/add_table.jpg)
|
||||||
@ -72,7 +69,7 @@ npm install
|
|||||||
});
|
});
|
||||||
|
|
||||||
```
|
```
|
||||||
Replace the `uri` argument with your Hasura GraphQL Endpoint for both `wsLink` and `httpLink`
|
Replace the `uri` argument with your Hasura GraphQL Endpoint (something like `https://<my-project-name>.hasura.app/v1/graphql`) for both `wsLink` and `httpLink`
|
||||||
|
|
||||||
Start [Rollup](https://rollupjs.org):
|
Start [Rollup](https://rollupjs.org):
|
||||||
|
|
||||||
@ -99,4 +96,3 @@ now
|
|||||||
```
|
```
|
||||||
|
|
||||||
This will deploy the app on Now 2.0 Platform and you have the Svetle app running live :)
|
This will deploy the app on Now 2.0 Platform and you have the Svetle app running live :)
|
||||||
|
|
||||||
|
@ -214,12 +214,20 @@ export const passModifyPkey = () => {
|
|||||||
cy.get(getElementFromAlias('modify-table-edit-pks')).click();
|
cy.get(getElementFromAlias('modify-table-edit-pks')).click();
|
||||||
cy.get(getElementFromAlias('primary-key-select-1')).select('1');
|
cy.get(getElementFromAlias('primary-key-select-1')).select('1');
|
||||||
cy.get(getElementFromAlias('modify-table-pks-save')).click();
|
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.wait(5000);
|
||||||
// TODO
|
|
||||||
// test disappearance expect
|
|
||||||
// (cy.get(getElementFromAlias('modify-table-column-1-remove'))).not.to.exist;
|
|
||||||
cy.get(getElementFromAlias('remove-pk-column-1')).click();
|
cy.get(getElementFromAlias('remove-pk-column-1')).click();
|
||||||
cy.get(getElementFromAlias('modify-table-pks-save')).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.get(getElementFromAlias('modify-table-close-pks')).click();
|
||||||
cy.wait(3000);
|
cy.wait(3000);
|
||||||
};
|
};
|
||||||
|
86
console/package-lock.json
generated
86
console/package-lock.json
generated
@ -3627,22 +3627,67 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@typescript-eslint/eslint-plugin": {
|
"@typescript-eslint/eslint-plugin": {
|
||||||
"version": "2.24.0",
|
"version": "3.8.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.24.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-3.8.0.tgz",
|
||||||
"integrity": "sha512-wJRBeaMeT7RLQ27UQkDFOu25MqFOBus8PtOa9KaT5ZuxC1kAsd7JEHqWt4YXuY9eancX0GK9C68i5OROnlIzBA==",
|
"integrity": "sha512-lFb4VCDleFSR+eo4Ew+HvrJ37ZH1Y9ZyE+qyP7EiwBpcCVxwmUc5PAqhShCQ8N8U5vqYydm74nss+a0wrrCErw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@typescript-eslint/experimental-utils": "2.24.0",
|
"@typescript-eslint/experimental-utils": "3.8.0",
|
||||||
"eslint-utils": "^1.4.3",
|
"debug": "^4.1.1",
|
||||||
"functional-red-black-tree": "^1.0.1",
|
"functional-red-black-tree": "^1.0.1",
|
||||||
"regexpp": "^3.0.0",
|
"regexpp": "^3.0.0",
|
||||||
|
"semver": "^7.3.2",
|
||||||
"tsutils": "^3.17.1"
|
"tsutils": "^3.17.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@typescript-eslint/experimental-utils": {
|
||||||
|
"version": "3.8.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-3.8.0.tgz",
|
||||||
|
"integrity": "sha512-o8T1blo1lAJE0QDsW7nSyvZHbiDzQDjINJKyB44Z3sSL39qBy5L10ScI/XwDtaiunoyKGLiY9bzRk4YjsUZl8w==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@types/json-schema": "^7.0.3",
|
||||||
|
"@typescript-eslint/types": "3.8.0",
|
||||||
|
"@typescript-eslint/typescript-estree": "3.8.0",
|
||||||
|
"eslint-scope": "^5.0.0",
|
||||||
|
"eslint-utils": "^2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@typescript-eslint/typescript-estree": {
|
||||||
|
"version": "3.8.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-3.8.0.tgz",
|
||||||
|
"integrity": "sha512-MTv9nPDhlKfclwnplRNDL44mP2SY96YmPGxmMbMy6x12I+pERcxpIUht7DXZaj4mOKKtet53wYYXU0ABaiXrLw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@typescript-eslint/types": "3.8.0",
|
||||||
|
"@typescript-eslint/visitor-keys": "3.8.0",
|
||||||
|
"debug": "^4.1.1",
|
||||||
|
"glob": "^7.1.6",
|
||||||
|
"is-glob": "^4.0.1",
|
||||||
|
"lodash": "^4.17.15",
|
||||||
|
"semver": "^7.3.2",
|
||||||
|
"tsutils": "^3.17.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"eslint-utils": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"eslint-visitor-keys": "^1.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"regexpp": {
|
"regexpp": {
|
||||||
"version": "3.0.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz",
|
||||||
"integrity": "sha512-Z+hNr7RAVWxznLPuA7DIh8UNX1j9CDrUQxskw9IrBE1Dxue2lyXT+shqEIeLUjrokxIP8CMy1WkjgG3rTsd5/g==",
|
"integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"semver": {
|
||||||
|
"version": "7.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz",
|
||||||
|
"integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -3682,6 +3727,12 @@
|
|||||||
"eslint-visitor-keys": "^1.1.0"
|
"eslint-visitor-keys": "^1.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@typescript-eslint/types": {
|
||||||
|
"version": "3.8.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-3.8.0.tgz",
|
||||||
|
"integrity": "sha512-8kROmEQkv6ss9kdQ44vCN1dTrgu4Qxrd2kXr10kz2NP5T8/7JnEfYNxCpPkArbLIhhkGLZV3aVMplH1RXQRF7Q==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"@typescript-eslint/typescript-estree": {
|
"@typescript-eslint/typescript-estree": {
|
||||||
"version": "2.24.0",
|
"version": "2.24.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-2.24.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-2.24.0.tgz",
|
||||||
@ -3705,6 +3756,15 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@typescript-eslint/visitor-keys": {
|
||||||
|
"version": "3.8.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-3.8.0.tgz",
|
||||||
|
"integrity": "sha512-gfqQWyVPpT9NpLREXNR820AYwgz+Kr1GuF3nf1wxpHD6hdxI62tq03ToomFnDxY0m3pUB39IF7sil7D5TQexLA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"eslint-visitor-keys": "^1.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@webassemblyjs/ast": {
|
"@webassemblyjs/ast": {
|
||||||
"version": "1.8.5",
|
"version": "1.8.5",
|
||||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.8.5.tgz",
|
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.8.5.tgz",
|
||||||
@ -7678,6 +7738,16 @@
|
|||||||
"integrity": "sha1-NfDVy8ZMLj7WLpO0saevBbp+1Nc=",
|
"integrity": "sha1-NfDVy8ZMLj7WLpO0saevBbp+1Nc=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"eslint-scope": {
|
||||||
|
"version": "5.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.0.tgz",
|
||||||
|
"integrity": "sha512-iiGRvtxWqgtx5m8EyQUJihBloE4EnYeGE/bz1wSPwJE6tZuJUtHlhqDM4Xj2ukE8Dyy1+HCZ4hE0fzIVMzb58w==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"esrecurse": "^4.1.0",
|
||||||
|
"estraverse": "^4.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"eslint-utils": {
|
"eslint-utils": {
|
||||||
"version": "1.4.3",
|
"version": "1.4.3",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz",
|
||||||
|
@ -19,7 +19,6 @@
|
|||||||
"build": "webpack --progress -p --colors --display-error-details --config webpack/prod.config.js",
|
"build": "webpack --progress -p --colors --display-error-details --config webpack/prod.config.js",
|
||||||
"server-build": "make server-build",
|
"server-build": "make server-build",
|
||||||
"build-unused": "webpack --verbose --colors --display-error-details --config webpack/prod.config.js --json | webpack-unused -s src",
|
"build-unused": "webpack --verbose --colors --display-error-details --config webpack/prod.config.js --json | webpack-unused -s src",
|
||||||
"postinstall": "webpack --display-error-details --config webpack/prod.config.js",
|
|
||||||
"cypress": "cypress open",
|
"cypress": "cypress open",
|
||||||
"test": "cypress run --spec 'cypress/integration/**/**/test.ts' --key $CYPRESS_KEY --parallel --record",
|
"test": "cypress run --spec 'cypress/integration/**/**/test.ts' --key $CYPRESS_KEY --parallel --record",
|
||||||
"lint": "eslint -c .eslintrc src --ext .js,.ts,.tsx",
|
"lint": "eslint -c .eslintrc src --ext .js,.ts,.tsx",
|
||||||
@ -147,7 +146,7 @@
|
|||||||
"@types/webpack-dev-middleware": "3.7.0",
|
"@types/webpack-dev-middleware": "3.7.0",
|
||||||
"@types/webpack-hot-middleware": "2.25.0",
|
"@types/webpack-hot-middleware": "2.25.0",
|
||||||
"@types/ws": "7.2.3",
|
"@types/ws": "7.2.3",
|
||||||
"@typescript-eslint/eslint-plugin": "2.24.0",
|
"@typescript-eslint/eslint-plugin": "3.8.0",
|
||||||
"@typescript-eslint/parser": "2.24.0",
|
"@typescript-eslint/parser": "2.24.0",
|
||||||
"autoprefixer": "9.7.6",
|
"autoprefixer": "9.7.6",
|
||||||
"babel-eslint": "10.1.0",
|
"babel-eslint": "10.1.0",
|
||||||
|
@ -28,7 +28,7 @@ export const requireAsyncGlobals = ({ dispatch }) => {
|
|||||||
return (nextState, finalState, callback) => {
|
return (nextState, finalState, callback) => {
|
||||||
Promise.all([
|
Promise.all([
|
||||||
dispatch(loadConsoleOpts()),
|
dispatch(loadConsoleOpts()),
|
||||||
dispatch(fetchServerConfig()),
|
dispatch(fetchServerConfig),
|
||||||
]).finally(callback);
|
]).finally(callback);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -11,7 +11,7 @@ type DropDownButtonProps = {
|
|||||||
}[];
|
}[];
|
||||||
dataKey: string;
|
dataKey: string;
|
||||||
dataIndex?: string;
|
dataIndex?: string;
|
||||||
onButtonChange: (e: React.MouseEvent<{}>) => void;
|
onButtonChange: (e: React.MouseEvent<unknown>) => void;
|
||||||
onInputChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
onInputChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||||
value?: string;
|
value?: string;
|
||||||
inputVal: string;
|
inputVal: string;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
@import "~bootstrap-sass/assets/stylesheets/bootstrap/variables";
|
@import '~bootstrap-sass/assets/stylesheets/bootstrap/variables';
|
||||||
@import "../../Common.scss";
|
@import '../../Common.scss';
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
}
|
}
|
||||||
@ -28,8 +28,8 @@
|
|||||||
// background: #444;
|
// background: #444;
|
||||||
// color: $navbar-inverse-color;
|
// color: $navbar-inverse-color;
|
||||||
color: #333;
|
color: #333;
|
||||||
border: 1px solid #E5E5E5;
|
border: 1px solid #e5e5e5;
|
||||||
background-color: #F8F8F8;
|
background-color: #f8f8f8;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
a,a:visited {
|
a,a:visited {
|
||||||
@ -66,7 +66,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: #767E93;
|
color: #767e93;
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -75,7 +75,7 @@
|
|||||||
padding: 7px 0;
|
padding: 7px 0;
|
||||||
// color: $navbar-inverse-link-hover-color;
|
// color: $navbar-inverse-link-hover-color;
|
||||||
transition: color 0.5s;
|
transition: color 0.5s;
|
||||||
pointer: cursor;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -113,7 +113,7 @@
|
|||||||
.sidebarHeading {
|
.sidebarHeading {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
color: #767E93;
|
color: #767e93;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -148,19 +148,23 @@
|
|||||||
padding-left: 5px !important;
|
padding-left: 5px !important;
|
||||||
display: initial !important;
|
display: initial !important;
|
||||||
|
|
||||||
.tableIcon, .functionIcon {
|
.tableIcon,
|
||||||
//display: inline;
|
.functionIcon {
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
width: 12px;
|
width: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.icon_mar_left {
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.noChildren {
|
.noChildren {
|
||||||
font-weight: 400 !important;
|
font-weight: 400 !important;
|
||||||
padding-bottom: 10px !important;
|
padding-bottom: 10px !important;
|
||||||
color: #767E93 !important;
|
color: #767e93 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
li:first-child {
|
li:first-child {
|
||||||
@ -170,11 +174,16 @@
|
|||||||
|
|
||||||
.activeLink {
|
.activeLink {
|
||||||
a {
|
a {
|
||||||
// border-left: 4px solid #FFC627;
|
color: #fd9540 !important;
|
||||||
color: #FD9540!important;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.padLeft4 {
|
||||||
|
margin-left: 8px;
|
||||||
|
top: 12px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
.floatRight {
|
.floatRight {
|
||||||
float: right;
|
float: right;
|
||||||
margin-right: 20px;
|
margin-right: 20px;
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Connect } from 'react-redux';
|
|
||||||
import styles from './RightContainer.scss';
|
import styles from './RightContainer.scss';
|
||||||
|
|
||||||
const RightContainer: React.FC = ({ children }) => {
|
const RightContainer: React.FC = ({ children }) => {
|
||||||
@ -16,6 +15,4 @@ const RightContainer: React.FC = ({ children }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const rightContainerConnector = (connect: Connect) => connect()(RightContainer);
|
export default RightContainer;
|
||||||
|
|
||||||
export default rightContainerConnector;
|
|
||||||
|
@ -0,0 +1,3 @@
|
|||||||
|
import RightContainer from './RightContainer';
|
||||||
|
|
||||||
|
export { RightContainer };
|
@ -1,3 +0,0 @@
|
|||||||
import rightContainerConnector from './RightContainer/RightContainer';
|
|
||||||
|
|
||||||
export { rightContainerConnector };
|
|
@ -91,7 +91,7 @@ const SearchableSelect: React.FC<SearchableSelectProps> = ({
|
|||||||
const customStyles: Record<string, any> = {};
|
const customStyles: Record<string, any> = {};
|
||||||
if (styleOverrides) {
|
if (styleOverrides) {
|
||||||
Object.keys(styleOverrides).forEach(comp => {
|
Object.keys(styleOverrides).forEach(comp => {
|
||||||
customStyles[comp] = (provided: object) => ({
|
customStyles[comp] = (provided: Record<string, unknown>) => ({
|
||||||
...provided,
|
...provided,
|
||||||
...styleOverrides[comp],
|
...styleOverrides[comp],
|
||||||
});
|
});
|
||||||
|
@ -11,3 +11,6 @@
|
|||||||
width: 18px;
|
width: 18px;
|
||||||
height: 18px;
|
height: 18px;
|
||||||
}
|
}
|
||||||
|
.react-toggle--focus .react-toggle-thumb {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
export const UNSAFE_keys = <T extends object>(source: T) =>
|
export const UNSAFE_keys = <T extends Record<string, unknown>>(source: T) =>
|
||||||
Object.keys(source) as Array<keyof T>;
|
Object.keys(source) as Array<keyof T>;
|
||||||
|
|
||||||
export type Json =
|
export type Json =
|
||||||
|
@ -11,9 +11,9 @@ import RuntimeError from './RuntimeError';
|
|||||||
import { registerRunTimeError } from '../Main/Actions';
|
import { registerRunTimeError } from '../Main/Actions';
|
||||||
|
|
||||||
export interface Metadata {
|
export interface Metadata {
|
||||||
inconsistentObjects: object[];
|
inconsistentObjects: Record<string, unknown>[];
|
||||||
ongoingRequest: boolean;
|
ongoingRequest: boolean;
|
||||||
allowedQueries: object[];
|
allowedQueries: Record<string, unknown>[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ErrorBoundaryProps {
|
export interface ErrorBoundaryProps {
|
||||||
|
@ -3,17 +3,26 @@ import { UPDATE_DATA_HEADERS } from '../Services/Data/DataActions';
|
|||||||
import { saveAdminSecretState } from '../AppState';
|
import { saveAdminSecretState } from '../AppState';
|
||||||
import { ADMIN_SECRET_HEADER_KEY, CLI_CONSOLE_MODE } from '../../constants';
|
import { ADMIN_SECRET_HEADER_KEY, CLI_CONSOLE_MODE } from '../../constants';
|
||||||
import requestAction from '../../utils/requestAction';
|
import requestAction from '../../utils/requestAction';
|
||||||
|
import { Dispatch } from '../../types';
|
||||||
import globals from '../../Globals';
|
import globals from '../../Globals';
|
||||||
|
|
||||||
|
type VerifyLoginOptions = {
|
||||||
|
adminSecret: string;
|
||||||
|
shouldPersist: boolean;
|
||||||
|
successCallback: () => void;
|
||||||
|
errorCallback: (err: Error) => void;
|
||||||
|
dispatch: Dispatch;
|
||||||
|
};
|
||||||
|
|
||||||
export const verifyLogin = ({
|
export const verifyLogin = ({
|
||||||
adminSecret,
|
adminSecret,
|
||||||
shouldPersist,
|
shouldPersist,
|
||||||
successCallback,
|
successCallback,
|
||||||
errorCallback,
|
errorCallback,
|
||||||
dispatch,
|
dispatch,
|
||||||
}) => {
|
}: VerifyLoginOptions) => {
|
||||||
const url = Endpoints.getSchema;
|
const url = Endpoints.getSchema;
|
||||||
const requestOptions = {
|
const requestOptions: RequestInit = {
|
||||||
credentials: globalCookiePolicy,
|
credentials: globalCookiePolicy,
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
@ -4,6 +4,7 @@ import requestAction from '../../utils/requestAction';
|
|||||||
import requestActionPlain from '../../utils/requestActionPlain';
|
import requestActionPlain from '../../utils/requestActionPlain';
|
||||||
import Endpoints, { globalCookiePolicy } from '../../Endpoints';
|
import Endpoints, { globalCookiePolicy } from '../../Endpoints';
|
||||||
import { getFeaturesCompatibility } from '../../helpers/versionUtils';
|
import { getFeaturesCompatibility } from '../../helpers/versionUtils';
|
||||||
|
import { getRunSqlQuery } from '../Common/utils/v1QueryUtils';
|
||||||
|
|
||||||
const SET_MIGRATION_STATUS_SUCCESS = 'Main/SET_MIGRATION_STATUS_SUCCESS';
|
const SET_MIGRATION_STATUS_SUCCESS = 'Main/SET_MIGRATION_STATUS_SUCCESS';
|
||||||
const SET_MIGRATION_STATUS_ERROR = 'Main/SET_MIGRATION_STATUS_ERROR';
|
const SET_MIGRATION_STATUS_ERROR = 'Main/SET_MIGRATION_STATUS_ERROR';
|
||||||
@ -22,6 +23,8 @@ const EXPORT_METADATA_ERROR = 'Main/EXPORT_METADATA_ERROR';
|
|||||||
const UPDATE_ADMIN_SECRET_INPUT = 'Main/UPDATE_ADMIN_SECRET_INPUT';
|
const UPDATE_ADMIN_SECRET_INPUT = 'Main/UPDATE_ADMIN_SECRET_INPUT';
|
||||||
const LOGIN_IN_PROGRESS = 'Main/LOGIN_IN_PROGRESS';
|
const LOGIN_IN_PROGRESS = 'Main/LOGIN_IN_PROGRESS';
|
||||||
const LOGIN_ERROR = 'Main/LOGIN_ERROR';
|
const LOGIN_ERROR = 'Main/LOGIN_ERROR';
|
||||||
|
const POSTGRES_VERSION_SUCCESS = 'Main/POSTGRES_VERSION_SUCCESS';
|
||||||
|
const POSTGRES_VERSION_ERROR = 'Main/POSTGRES_VERSION_ERROR';
|
||||||
|
|
||||||
const RUN_TIME_ERROR = 'Main/RUN_TIME_ERROR';
|
const RUN_TIME_ERROR = 'Main/RUN_TIME_ERROR';
|
||||||
const registerRunTimeError = data => ({
|
const registerRunTimeError = data => ({
|
||||||
@ -52,6 +55,29 @@ const setReadOnlyMode = data => ({
|
|||||||
data,
|
data,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const fetchPostgresVersion = (dispatch, getState) => {
|
||||||
|
const req = getRunSqlQuery('SELECT version()');
|
||||||
|
const options = {
|
||||||
|
method: 'POST',
|
||||||
|
credentials: globalCookiePolicy,
|
||||||
|
body: JSON.stringify(req),
|
||||||
|
headers: getState().tables.dataHeaders,
|
||||||
|
};
|
||||||
|
|
||||||
|
return dispatch(requestAction(Endpoints.query, options)).then(
|
||||||
|
({ result }) => {
|
||||||
|
if (result.length > 1 && result[1].length) {
|
||||||
|
const matchRes = result[1][0].match(/[0-9]{1,}(\.[0-9]{1,})?/);
|
||||||
|
if (matchRes.length) {
|
||||||
|
dispatch({ type: POSTGRES_VERSION_SUCCESS, payload: matchRes[0] });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dispatch({ type: POSTGRES_VERSION_ERROR });
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const featureCompatibilityInit = () => {
|
const featureCompatibilityInit = () => {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
const { serverVersion } = getState().main;
|
const { serverVersion } = getState().main;
|
||||||
@ -110,7 +136,7 @@ const loadServerVersion = () => dispatch => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchServerConfig = () => (dispatch, getState) => {
|
const fetchServerConfig = (dispatch, getState) => {
|
||||||
const url = Endpoints.serverConfig;
|
const url = Endpoints.serverConfig;
|
||||||
const options = {
|
const options = {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
@ -318,6 +344,16 @@ const mainReducer = (state = defaultState, action) => {
|
|||||||
...state,
|
...state,
|
||||||
featuresCompatibility: { ...action.data },
|
featuresCompatibility: { ...action.data },
|
||||||
};
|
};
|
||||||
|
case POSTGRES_VERSION_SUCCESS:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
postgresVersion: action.payload,
|
||||||
|
};
|
||||||
|
case POSTGRES_VERSION_ERROR:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
postgresVersion: null,
|
||||||
|
};
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,6 @@ import Tooltip from 'react-bootstrap/lib/Tooltip';
|
|||||||
import * as tooltips from './Tooltips';
|
import * as tooltips from './Tooltips';
|
||||||
import globals from '../../Globals';
|
import globals from '../../Globals';
|
||||||
import { getPathRoot } from '../Common/utils/urlUtils';
|
import { getPathRoot } from '../Common/utils/urlUtils';
|
||||||
|
|
||||||
import Spinner from '../Common/Spinner/Spinner';
|
import Spinner from '../Common/Spinner/Spinner';
|
||||||
import WarningSymbol from '../Common/WarningSymbol/WarningSymbol';
|
import WarningSymbol from '../Common/WarningSymbol/WarningSymbol';
|
||||||
import logo from './images/white-logo.svg';
|
import logo from './images/white-logo.svg';
|
||||||
@ -21,6 +20,7 @@ import {
|
|||||||
loadLatestServerVersion,
|
loadLatestServerVersion,
|
||||||
featureCompatibilityInit,
|
featureCompatibilityInit,
|
||||||
emitProClickedEvent,
|
emitProClickedEvent,
|
||||||
|
fetchPostgresVersion,
|
||||||
} from './Actions';
|
} from './Actions';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -45,7 +45,7 @@ import {
|
|||||||
import ToolTip from '../Common/Tooltip/Tooltip';
|
import ToolTip from '../Common/Tooltip/Tooltip';
|
||||||
import { setPreReleaseNotificationOptOutInDB } from '../../telemetry/Actions';
|
import { setPreReleaseNotificationOptOutInDB } from '../../telemetry/Actions';
|
||||||
import { Icon } from '../UIKit/atoms/Icon';
|
import { Icon } from '../UIKit/atoms/Icon';
|
||||||
import { ProPopup } from './components/ProPopup';
|
import { Help, ProPopup } from './components/';
|
||||||
|
|
||||||
class Main extends React.Component {
|
class Main extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@ -82,7 +82,9 @@ class Main extends React.Component {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
dispatch(fetchServerConfig());
|
dispatch(fetchPostgresVersion);
|
||||||
|
|
||||||
|
dispatch(fetchServerConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleProPopup = () => {
|
toggleProPopup = () => {
|
||||||
@ -655,14 +657,7 @@ class Main extends React.Component {
|
|||||||
{getSettingsSelectedMarker()}
|
{getSettingsSelectedMarker()}
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
<a
|
<Help isSelected={currentActiveBlock === 'support'} />
|
||||||
id="help"
|
|
||||||
href="https://hasura.io/help"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
<div className={styles.headerRightNavbarBtn}>HELP</div>
|
|
||||||
</a>
|
|
||||||
{getLoveSection()}
|
{getLoveSection()}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -897,18 +897,15 @@
|
|||||||
.headerRightNavbarBtn {
|
.headerRightNavbarBtn {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
// background-color: #22283b;
|
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
position: relative;
|
position: relative;
|
||||||
color: #fff;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
color: #fff;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
span {
|
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.selected {
|
.selected {
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
|
@ -25,7 +25,8 @@ export interface MainState {
|
|||||||
error: Error | null;
|
error: Error | null;
|
||||||
isFetching: boolean;
|
isFetching: boolean;
|
||||||
};
|
};
|
||||||
featuresCompatibility: object;
|
featuresCompatibility: Record<string, unknown>;
|
||||||
|
postgresVersion: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultState: MainState = {
|
const defaultState: MainState = {
|
||||||
@ -56,6 +57,7 @@ const defaultState: MainState = {
|
|||||||
isFetching: false,
|
isFetching: false,
|
||||||
},
|
},
|
||||||
featuresCompatibility: {},
|
featuresCompatibility: {},
|
||||||
|
postgresVersion: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default defaultState;
|
export default defaultState;
|
||||||
|
20
console/src/components/Main/components/Help.tsx
Normal file
20
console/src/components/Main/components/Help.tsx
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Link } from 'react-router';
|
||||||
|
|
||||||
|
import styles from '../Main.scss';
|
||||||
|
|
||||||
|
export const Help = ({ isSelected }: { isSelected: boolean }) => {
|
||||||
|
return (
|
||||||
|
<Link to="/support/forums/">
|
||||||
|
<div className={styles.headerRightNavbarBtn}>
|
||||||
|
HELP
|
||||||
|
{isSelected ? (
|
||||||
|
<span
|
||||||
|
className={styles.selected}
|
||||||
|
style={{ width: '90%', marginLeft: '5%' }}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
};
|
4
console/src/components/Main/components/index.ts
Normal file
4
console/src/components/Main/components/index.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import { Help } from './Help';
|
||||||
|
import { ProPopup } from './ProPopup';
|
||||||
|
|
||||||
|
export { Help, ProPopup };
|
@ -0,0 +1,9 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" id="baseline-arrow_forward-24px" width="16" height="16" viewBox="0 0 16 16">
|
||||||
|
<defs>
|
||||||
|
<style>
|
||||||
|
.cls-1{fill:none}.cls-2{fill:#303030}
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
<path id="Path_290" d="M0 0h16v16H0z" class="cls-1" data-name="Path 290"/>
|
||||||
|
<path id="Path_291" d="M9.333 4l-.94.94 3.72 3.727H4V10h8.113l-3.72 3.727.94.94 5.333-5.333z" class="cls-2" data-name="Path 291" transform="translate(-1.333 -1.333)"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 481 B |
3
console/src/components/Main/images/information.svg
Normal file
3
console/src/components/Main/images/information.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" id="prefix__streamline-icon-interface-alert-information-circle_16x16" width="12.5" height="12.5" data-name="streamline-icon-interface-alert-information-circle@16x16" viewBox="0 0 12.5 12.5">
|
||||||
|
<path id="prefix__Path_5128" d="M6.25 0a6.25 6.25 0 1 0 6.25 6.25A6.25 6.25 0 0 0 6.25 0zm.781 9.375a.781.781 0 0 1-1.562 0v-2.5a.781.781 0 0 1 1.563 0zM6.25 5A1.25 1.25 0 1 1 7.5 3.75 1.25 1.25 0 0 1 6.25 5z" data-name="Path 5128" style="fill:#292822"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 497 B |
@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Route, IndexRedirect } from 'react-router';
|
import { Route, IndexRedirect } from 'react-router';
|
||||||
import { rightContainerConnector } from '../../Common/Layout';
|
import { RightContainer } from '../../Common/Layout/RightContainer';
|
||||||
import Container from './Containers/Main';
|
import Container from './Containers/Main';
|
||||||
import { fetchActions } from './ServerIO';
|
import { fetchActions } from './ServerIO';
|
||||||
import globals from '../../../Globals';
|
import globals from '../../../Globals';
|
||||||
@ -36,7 +36,7 @@ const getActionsRouter = (connect, store, composeOnEnterHooks) => {
|
|||||||
onChange={actionsInit(store)}
|
onChange={actionsInit(store)}
|
||||||
>
|
>
|
||||||
<IndexRedirect to="manage" />
|
<IndexRedirect to="manage" />
|
||||||
<Route path="manage" component={rightContainerConnector(connect)}>
|
<Route path="manage" component={RightContainer}>
|
||||||
<IndexRedirect to="actions" />
|
<IndexRedirect to="actions" />
|
||||||
<Route path="actions" component={ActionsLandingPage(connect)} />
|
<Route path="actions" component={ActionsLandingPage(connect)} />
|
||||||
<Route path="add" component={AddAction(connect)} />
|
<Route path="add" component={AddAction(connect)} />
|
||||||
@ -51,7 +51,7 @@ const getActionsRouter = (connect, store, composeOnEnterHooks) => {
|
|||||||
component={ActionPermissions(connect)}
|
component={ActionPermissions(connect)}
|
||||||
/>
|
/>
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="types" component={rightContainerConnector(connect)}>
|
<Route path="types" component={RightContainer}>
|
||||||
<IndexRedirect to="manage" />
|
<IndexRedirect to="manage" />
|
||||||
<Route path="manage" component={TypesManage(connect)} />
|
<Route path="manage" component={TypesManage(connect)} />
|
||||||
<Route path="relationships" component={TypesRelationships(connect)} />
|
<Route path="relationships" component={TypesRelationships(connect)} />
|
||||||
|
@ -272,6 +272,8 @@ const analyzeFetcher = (headers, mode) => {
|
|||||||
|
|
||||||
const changeRequestHeader = (index, key, newValue, isDisabled) => {
|
const changeRequestHeader = (index, key, newValue, isDisabled) => {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
|
websocketSubscriptionClient = null;
|
||||||
|
|
||||||
const currentState = getState().apiexplorer;
|
const currentState = getState().apiexplorer;
|
||||||
|
|
||||||
const updatedHeader = {
|
const updatedHeader = {
|
||||||
|
@ -11,14 +11,14 @@ export type OperationData = {
|
|||||||
displayName: string;
|
displayName: string;
|
||||||
type: OperationTypeNode;
|
type: OperationTypeNode;
|
||||||
variableName: string;
|
variableName: string;
|
||||||
variables: object;
|
variables: Record<string, unknown>;
|
||||||
operationDefinition: OperationDefinitionNode;
|
operationDefinition: OperationDefinitionNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type GenerateOptions = {
|
export type GenerateOptions = {
|
||||||
serverUrl: string;
|
serverUrl: string;
|
||||||
headers: { [name: string]: string };
|
headers: { [name: string]: string };
|
||||||
context: object;
|
context: Record<string, unknown>;
|
||||||
operationDataList: Array<OperationData>;
|
operationDataList: Array<OperationData>;
|
||||||
options: OptionValues;
|
options: OptionValues;
|
||||||
};
|
};
|
||||||
|
@ -110,7 +110,8 @@ export const getErrorMessage = (
|
|||||||
notificationMessage = error.code;
|
notificationMessage = error.code;
|
||||||
}
|
}
|
||||||
} else if ('internal' in error && 'error' in error.internal) {
|
} else if ('internal' in error && 'error' in error.internal) {
|
||||||
notificationMessage = `${error.code} : ${error.internal.error.message}`;
|
notificationMessage = `${error.internal.error.message}.
|
||||||
|
${error.internal.error.description}`;
|
||||||
} else if ('custom' in error) {
|
} else if ('custom' in error) {
|
||||||
notificationMessage = error.custom;
|
notificationMessage = error.custom;
|
||||||
} else if ('code' in error && 'error' in error && 'path' in error) {
|
} else if ('code' in error && 'error' in error && 'path' in error) {
|
||||||
|
@ -443,6 +443,7 @@ class AddTable extends Component {
|
|||||||
columnDefaultFunctions,
|
columnDefaultFunctions,
|
||||||
columnTypeCasts,
|
columnTypeCasts,
|
||||||
checkConstraints,
|
checkConstraints,
|
||||||
|
postgresVersion,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const getCreateBtnText = () => {
|
const getCreateBtnText = () => {
|
||||||
@ -493,6 +494,7 @@ class AddTable extends Component {
|
|||||||
onSelect={setFreqUsedColumn}
|
onSelect={setFreqUsedColumn}
|
||||||
action={'add'}
|
action={'add'}
|
||||||
dispatch={dispatch}
|
dispatch={dispatch}
|
||||||
|
postgresVersion={postgresVersion}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<hr />
|
<hr />
|
||||||
@ -586,6 +588,7 @@ const mapStateToProps = state => ({
|
|||||||
columnTypeCasts: state.tables.columnTypeCasts,
|
columnTypeCasts: state.tables.columnTypeCasts,
|
||||||
columnDataTypeFetchErr: state.tables.columnDataTypeFetchErr,
|
columnDataTypeFetchErr: state.tables.columnDataTypeFetchErr,
|
||||||
schemaList: state.tables.schemaList,
|
schemaList: state.tables.schemaList,
|
||||||
|
postgresVersion: state.main.postgresVersion,
|
||||||
});
|
});
|
||||||
|
|
||||||
const addTableConnector = connect => connect(mapStateToProps)(AddTable);
|
const addTableConnector = connect => connect(mapStateToProps)(AddTable);
|
||||||
|
@ -1,8 +1,27 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Dropdown from '../../../../Common/Dropdown/Dropdown';
|
import Dropdown from '../../../../Common/Dropdown/Dropdown';
|
||||||
import Button from '../../../../Common/Button/Button';
|
import Button from '../../../../Common/Button/Button';
|
||||||
|
import { Dispatch } from '../../../../../types';
|
||||||
|
import { Column } from '../../../../../utils/postgresColumnTypes';
|
||||||
|
|
||||||
const frequentlyUsedColumns = [
|
type ColumnAction = 'add' | 'modify';
|
||||||
|
interface FrequentlyUsedColumn {
|
||||||
|
name: string;
|
||||||
|
validFor: ColumnAction[];
|
||||||
|
type: Column | string;
|
||||||
|
typeText: string;
|
||||||
|
primary?: boolean;
|
||||||
|
default?: string;
|
||||||
|
defaultText?: string;
|
||||||
|
dependentSQLGenerator?: (
|
||||||
|
schemaName: string,
|
||||||
|
tableName: string,
|
||||||
|
columnName: string
|
||||||
|
) => { upSql: string; downSql: string };
|
||||||
|
minPGVersion?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const frequentlyUsedColumns: FrequentlyUsedColumn[] = [
|
||||||
{
|
{
|
||||||
name: 'id',
|
name: 'id',
|
||||||
validFor: ['add'],
|
validFor: ['add'],
|
||||||
@ -17,6 +36,14 @@ const frequentlyUsedColumns = [
|
|||||||
typeText: 'bigint (auto-increment)',
|
typeText: 'bigint (auto-increment)',
|
||||||
primary: true,
|
primary: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'id',
|
||||||
|
validFor: ['add'],
|
||||||
|
type: 'int GENERATED BY DEFAULT AS IDENTITY',
|
||||||
|
typeText: 'int (identity, generated by default)',
|
||||||
|
primary: true,
|
||||||
|
minPGVersion: 10,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'id',
|
name: 'id',
|
||||||
validFor: ['add'],
|
validFor: ['add'],
|
||||||
@ -69,10 +96,10 @@ IS 'trigger to set value of column "${columnName}" to current timestamp on row u
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const getFreqUsedColDisplayInfo = c => {
|
const getFreqUsedColDisplayInfo = (c: FrequentlyUsedColumn) => {
|
||||||
const title = c.name;
|
const title = c.name;
|
||||||
|
|
||||||
const typeText = c.typeText + '; ';
|
const typeText = `${c.typeText}; `;
|
||||||
const defaultText =
|
const defaultText =
|
||||||
c.defaultText || c.default
|
c.defaultText || c.default
|
||||||
? `default: ${c.defaultText || c.default}; `
|
? `default: ${c.defaultText || c.default}; `
|
||||||
@ -87,14 +114,26 @@ const getFreqUsedColDisplayInfo = c => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
interface FrequentlyUsedColumnSelectorProps {
|
||||||
|
onSelect: any;
|
||||||
|
action: ColumnAction | null;
|
||||||
|
dispatch: Dispatch | null;
|
||||||
|
postgresVersion: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
const FrequentlyUsedColumnSelector = ({
|
const FrequentlyUsedColumnSelector = ({
|
||||||
onSelect,
|
onSelect,
|
||||||
action = null,
|
action = null,
|
||||||
dispatch = null,
|
dispatch = null,
|
||||||
}) => {
|
postgresVersion,
|
||||||
const frequentlyUsedColumnsOptions = () => {
|
}: FrequentlyUsedColumnSelectorProps) => {
|
||||||
return frequentlyUsedColumns
|
const frequentlyUsedColumnsOptions = frequentlyUsedColumns
|
||||||
.filter(fuc => !action || fuc.validFor.includes(action))
|
.filter(fuc => !action || fuc.validFor.includes(action))
|
||||||
|
.filter(col =>
|
||||||
|
postgresVersion && col.minPGVersion
|
||||||
|
? parseFloat(postgresVersion) >= col.minPGVersion
|
||||||
|
: true
|
||||||
|
)
|
||||||
.map(fuc => {
|
.map(fuc => {
|
||||||
const { title, subTitle } = getFreqUsedColDisplayInfo(fuc);
|
const { title, subTitle } = getFreqUsedColDisplayInfo(fuc);
|
||||||
return {
|
return {
|
||||||
@ -109,15 +148,14 @@ const FrequentlyUsedColumnSelector = ({
|
|||||||
onClick: () => (dispatch ? dispatch(onSelect(fuc)) : onSelect(fuc)),
|
onClick: () => (dispatch ? dispatch(onSelect(fuc)) : onSelect(fuc)),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dropdown
|
<Dropdown
|
||||||
testId={'frequently-used-columns'}
|
testId="frequently-used-columns"
|
||||||
options={frequentlyUsedColumnsOptions()}
|
options={frequentlyUsedColumnsOptions}
|
||||||
position="bottom"
|
position="bottom"
|
||||||
key={'frequently-used-columns'}
|
key="frequently-used-columns"
|
||||||
keyPrefix={'frequently-used-columns'}
|
keyPrefix="frequently-used-columns"
|
||||||
>
|
>
|
||||||
<Button color="white" size="xs">
|
<Button color="white" size="xs">
|
||||||
+ Frequently used columns
|
+ Frequently used columns
|
@ -1,11 +1,10 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { ThunkDispatch } from 'redux-thunk';
|
|
||||||
import { AnyAction } from 'redux';
|
|
||||||
import ReloadMetadata from '../../../Settings/MetadataOptions/ReloadMetadata';
|
import ReloadMetadata from '../../../Settings/MetadataOptions/ReloadMetadata';
|
||||||
|
import { Dispatch } from '../../../../../types';
|
||||||
|
|
||||||
export interface ReloadEnumValuesButtonProps {
|
export interface ReloadEnumValuesButtonProps {
|
||||||
isEnum: boolean;
|
isEnum: boolean;
|
||||||
dispatch: ThunkDispatch<{}, {}, AnyAction>;
|
dispatch: Dispatch;
|
||||||
tooltipStyle?: string;
|
tooltipStyle?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,9 +56,9 @@ export interface TableRowProps {
|
|||||||
refName: 'valueNode' | 'nullNode' | 'defaultNode' | 'insertRadioNode',
|
refName: 'valueNode' | 'nullNode' | 'defaultNode' | 'insertRadioNode',
|
||||||
node: HTMLInputElement | null
|
node: HTMLInputElement | null
|
||||||
) => void;
|
) => void;
|
||||||
enumOptions: object;
|
enumOptions: Record<string, unknown>;
|
||||||
index: number;
|
index: number;
|
||||||
clone?: object;
|
clone?: Record<string, unknown>;
|
||||||
onChange?: (e: React.ChangeEvent<HTMLInputElement>, val: unknown) => void;
|
onChange?: (e: React.ChangeEvent<HTMLInputElement>, val: unknown) => void;
|
||||||
onFocus?: (e: React.FocusEvent<HTMLInputElement>) => void;
|
onFocus?: (e: React.FocusEvent<HTMLInputElement>) => void;
|
||||||
prevValue?: unknown;
|
prevValue?: unknown;
|
||||||
|
@ -659,7 +659,10 @@ const makeMigrationCall = (
|
|||||||
}
|
}
|
||||||
customOnSuccess(data, globals.consoleMode, currMigrationMode);
|
customOnSuccess(data, globals.consoleMode, currMigrationMode);
|
||||||
};
|
};
|
||||||
const retryMigration = (err = {}, errMsg = '') => {
|
const retryMigration = (err = {}, errMsg = '', isPgCascade = false) => {
|
||||||
|
const errorDetails = getErrorMessage('', err);
|
||||||
|
const errorDetailsLines = errorDetails.split('\n');
|
||||||
|
|
||||||
dispatch(
|
dispatch(
|
||||||
showNotification(
|
showNotification(
|
||||||
{
|
{
|
||||||
@ -667,8 +670,9 @@ const makeMigrationCall = (
|
|||||||
level: 'error',
|
level: 'error',
|
||||||
message: (
|
message: (
|
||||||
<p>
|
<p>
|
||||||
{getErrorMessage('', err)}
|
{errorDetailsLines.map((m, i) => (
|
||||||
<br />
|
<div key={i}>{m}</div>
|
||||||
|
))}
|
||||||
<br />
|
<br />
|
||||||
Do you want to drop the dependent items as well?
|
Do you want to drop the dependent items as well?
|
||||||
</p>
|
</p>
|
||||||
@ -680,7 +684,7 @@ const makeMigrationCall = (
|
|||||||
makeMigrationCall(
|
makeMigrationCall(
|
||||||
dispatch,
|
dispatch,
|
||||||
getState,
|
getState,
|
||||||
cascadeUpQueries(upQueries), // cascaded new up queries
|
cascadeUpQueries(upQueries, isPgCascade), // cascaded new up queries
|
||||||
downQueries,
|
downQueries,
|
||||||
migrationName,
|
migrationName,
|
||||||
customOnSuccess,
|
customOnSuccess,
|
||||||
@ -689,6 +693,7 @@ const makeMigrationCall = (
|
|||||||
successMsg,
|
successMsg,
|
||||||
errorMsg,
|
errorMsg,
|
||||||
shouldSkipSchemaReload,
|
shouldSkipSchemaReload,
|
||||||
|
false,
|
||||||
true // prevent further retry
|
true // prevent further retry
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@ -700,8 +705,10 @@ const makeMigrationCall = (
|
|||||||
|
|
||||||
const onError = err => {
|
const onError = err => {
|
||||||
if (!isRetry) {
|
if (!isRetry) {
|
||||||
const dependecyError = getDependencyError(err);
|
const { dependencyError, pgDependencyError } = getDependencyError(err);
|
||||||
if (dependecyError) return retryMigration(dependecyError, errorMsg);
|
if (dependencyError) return retryMigration(dependencyError, errorMsg);
|
||||||
|
if (pgDependencyError)
|
||||||
|
return retryMigration(pgDependencyError, errorMsg, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch(handleMigrationErrors(errorMsg, err));
|
dispatch(handleMigrationErrors(errorMsg, err));
|
||||||
|
@ -27,7 +27,7 @@ import {
|
|||||||
// metadataConnector,
|
// metadataConnector,
|
||||||
} from '.';
|
} from '.';
|
||||||
|
|
||||||
import { rightContainerConnector } from '../../Common/Layout';
|
import { RightContainer } from '../../Common/Layout/RightContainer';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
fetchDataInit,
|
fetchDataInit,
|
||||||
@ -51,7 +51,7 @@ const makeDataRouter = (
|
|||||||
return (
|
return (
|
||||||
<Route path="data" component={dataPageConnector(connect)}>
|
<Route path="data" component={dataPageConnector(connect)}>
|
||||||
<IndexRedirect to="schema/public" />
|
<IndexRedirect to="schema/public" />
|
||||||
<Route path="schema" component={rightContainerConnector(connect)}>
|
<Route path="schema" component={RightContainer}>
|
||||||
<IndexRedirect to="public" />
|
<IndexRedirect to="public" />
|
||||||
<Route path=":schema" component={schemaConnector(connect)} />
|
<Route path=":schema" component={schemaConnector(connect)} />
|
||||||
<Route path=":schema/tables" component={schemaConnector(connect)} />
|
<Route path=":schema/tables" component={schemaConnector(connect)} />
|
||||||
|
@ -42,6 +42,7 @@ const executeSQL = (isMigration, migrationName, statementTimeout) => (
|
|||||||
|
|
||||||
const { isTableTrackChecked, isCascadeChecked, sql } = getState().rawSQL;
|
const { isTableTrackChecked, isCascadeChecked, sql } = getState().rawSQL;
|
||||||
const { migrationMode, readOnlyMode } = getState().main;
|
const { migrationMode, readOnlyMode } = getState().main;
|
||||||
|
const isStatementTimeout = statementTimeout && !isMigration;
|
||||||
|
|
||||||
const migrateUrl = returnMigrateUrl(migrationMode);
|
const migrateUrl = returnMigrateUrl(migrationMode);
|
||||||
|
|
||||||
@ -49,7 +50,7 @@ const executeSQL = (isMigration, migrationName, statementTimeout) => (
|
|||||||
|
|
||||||
const schemaChangesUp = [];
|
const schemaChangesUp = [];
|
||||||
|
|
||||||
if (statementTimeout && !isMigration) {
|
if (isStatementTimeout) {
|
||||||
schemaChangesUp.push(
|
schemaChangesUp.push(
|
||||||
getRunSqlQuery(
|
getRunSqlQuery(
|
||||||
getStatementTimeoutSql(statementTimeout),
|
getStatementTimeoutSql(statementTimeout),
|
||||||
@ -111,7 +112,10 @@ const executeSQL = (isMigration, migrationName, statementTimeout) => (
|
|||||||
}
|
}
|
||||||
dispatch(showSuccessNotification('SQL executed!'));
|
dispatch(showSuccessNotification('SQL executed!'));
|
||||||
dispatch(fetchDataInit()).then(() => {
|
dispatch(fetchDataInit()).then(() => {
|
||||||
dispatch({ type: REQUEST_SUCCESS, data });
|
dispatch({
|
||||||
|
type: REQUEST_SUCCESS,
|
||||||
|
data: data && (isStatementTimeout ? data[1] : data[0]),
|
||||||
|
});
|
||||||
});
|
});
|
||||||
dispatch(fetchTrackedFunctions());
|
dispatch(fetchTrackedFunctions());
|
||||||
},
|
},
|
||||||
@ -168,11 +172,7 @@ const rawSQLReducer = (state = defaultState, action) => {
|
|||||||
lastSuccess: null,
|
lastSuccess: null,
|
||||||
};
|
};
|
||||||
case REQUEST_SUCCESS:
|
case REQUEST_SUCCESS:
|
||||||
if (
|
if (action.data && action.data.result_type === 'CommandOk') {
|
||||||
action.data &&
|
|
||||||
action.data[0] &&
|
|
||||||
action.data[0].result_type === 'CommandOk'
|
|
||||||
) {
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
ongoingRequest: false,
|
ongoingRequest: false,
|
||||||
@ -188,8 +188,8 @@ const rawSQLReducer = (state = defaultState, action) => {
|
|||||||
lastError: null,
|
lastError: null,
|
||||||
lastSuccess: true,
|
lastSuccess: true,
|
||||||
resultType: 'tuples',
|
resultType: 'tuples',
|
||||||
result: action.data[0].result.slice(1),
|
result: action.data.result.slice(1),
|
||||||
resultHeaders: action.data[0].result[0],
|
resultHeaders: action.data.result[0],
|
||||||
};
|
};
|
||||||
case REQUEST_ERROR:
|
case REQUEST_ERROR:
|
||||||
return {
|
return {
|
||||||
|
@ -692,7 +692,7 @@ class Schema extends Component {
|
|||||||
className={styles.add_mar_top}
|
className={styles.add_mar_top}
|
||||||
key={'non-trackable-custom-functions'}
|
key={'non-trackable-custom-functions'}
|
||||||
>
|
>
|
||||||
<CollapsibleToggle title={heading} isOpen>
|
<CollapsibleToggle title={heading}>
|
||||||
<div className={`${styles.padd_left_remove} col-xs-12`}>
|
<div className={`${styles.padd_left_remove} col-xs-12`}>
|
||||||
{getNonTrackableFuncList()}
|
{getNonTrackableFuncList()}
|
||||||
</div>
|
</div>
|
||||||
|
@ -126,6 +126,7 @@ const ColumnCreator = ({
|
|||||||
dataTypes: restTypes = [],
|
dataTypes: restTypes = [],
|
||||||
validTypeCasts,
|
validTypeCasts,
|
||||||
columnDefaultFunctions,
|
columnDefaultFunctions,
|
||||||
|
postgresVersion,
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
colName,
|
colName,
|
||||||
@ -256,6 +257,7 @@ const ColumnCreator = ({
|
|||||||
<FrequentlyUsedColumnSelector
|
<FrequentlyUsedColumnSelector
|
||||||
onSelect={frequentlyUsedColumn.onSelect}
|
onSelect={frequentlyUsedColumn.onSelect}
|
||||||
action={'modify'}
|
action={'modify'}
|
||||||
|
postgresVersion={postgresVersion}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -89,6 +89,7 @@ class ModifyTable extends React.Component {
|
|||||||
schemaList,
|
schemaList,
|
||||||
tableEnum,
|
tableEnum,
|
||||||
rootFieldsEdit,
|
rootFieldsEdit,
|
||||||
|
postgresVersion,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const dataTypeIndexMap = getAllDataTypeMap(dataTypes);
|
const dataTypeIndexMap = getAllDataTypeMap(dataTypes);
|
||||||
@ -249,6 +250,7 @@ class ModifyTable extends React.Component {
|
|||||||
dataTypes={dataTypes}
|
dataTypes={dataTypes}
|
||||||
validTypeCasts={validTypeCasts}
|
validTypeCasts={validTypeCasts}
|
||||||
columnDefaultFunctions={columnDefaultFunctions}
|
columnDefaultFunctions={columnDefaultFunctions}
|
||||||
|
postgresVersion={postgresVersion}
|
||||||
/>
|
/>
|
||||||
<hr />
|
<hr />
|
||||||
{getComputedFieldsSection()}
|
{getComputedFieldsSection()}
|
||||||
@ -352,6 +354,7 @@ const mapStateToProps = (state, ownProps) => ({
|
|||||||
validTypeCasts: state.tables.columnTypeCasts,
|
validTypeCasts: state.tables.columnTypeCasts,
|
||||||
columnDataTypeFetchErr: state.tables.columnDataTypeFetchErr,
|
columnDataTypeFetchErr: state.tables.columnDataTypeFetchErr,
|
||||||
schemaList: state.tables.schemaList,
|
schemaList: state.tables.schemaList,
|
||||||
|
postgresVersion: state.main.postgresVersion,
|
||||||
...state.tables.modify,
|
...state.tables.modify,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -55,7 +55,9 @@ const PrimaryKeyEditor = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
// label next to the button when the editor is expanded
|
// label next to the button when the editor is expanded
|
||||||
const pkEditorExpandedLabel = () => <div>{pkConfigText}</div>;
|
const pkEditorExpandedLabel = () => (
|
||||||
|
<div data-test="pk-config-text">{pkConfigText}</div>
|
||||||
|
);
|
||||||
|
|
||||||
// expanded editor content
|
// expanded editor content
|
||||||
const pkEditorExpanded = () => (
|
const pkEditorExpanded = () => (
|
||||||
@ -95,7 +97,9 @@ const PrimaryKeyEditor = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(setPkEditState, [columns]);
|
useEffect(() => {
|
||||||
|
setPkEditState();
|
||||||
|
}, [columns.length]);
|
||||||
|
|
||||||
// remove
|
// remove
|
||||||
const onRemove = () => {
|
const onRemove = () => {
|
||||||
|
@ -5,7 +5,7 @@ import RawSqlButton from '../Common/Components/RawSqlButton';
|
|||||||
|
|
||||||
export interface ViewDefinitionsProps {
|
export interface ViewDefinitionsProps {
|
||||||
dispatch: () => void;
|
dispatch: () => void;
|
||||||
sql: string | object;
|
sql: string | Record<string, unknown>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ViewDefinitions: React.FC<ViewDefinitionsProps> = ({ dispatch, sql }) => (
|
const ViewDefinitions: React.FC<ViewDefinitionsProps> = ({ dispatch, sql }) => (
|
||||||
|
@ -1766,7 +1766,7 @@ class Permissions extends Component {
|
|||||||
'Backend only',
|
'Backend only',
|
||||||
tooltip,
|
tooltip,
|
||||||
backendStatus,
|
backendStatus,
|
||||||
'https://docs.hasura.io/1.0/graphql/manual/auth/authorization/permission-rules.html#backend-only-inserts'
|
'https://hasura.io/docs/1.0/graphql/manual/auth/authorization/permission-rules.html#backend-only'
|
||||||
)}
|
)}
|
||||||
useDefaultTitleStyle
|
useDefaultTitleStyle
|
||||||
testId={'toggle-backend-only'}
|
testId={'toggle-backend-only'}
|
||||||
|
@ -112,11 +112,11 @@ class RelationshipEditor extends React.Component {
|
|||||||
<div className={styles.display_flex}>
|
<div className={styles.display_flex}>
|
||||||
{getEditBtn()}
|
{getEditBtn()}
|
||||||
<b className={styles.textNoNewLine}>{relName}</b>
|
<b className={styles.textNoNewLine}>{relName}</b>
|
||||||
</div>
|
|
||||||
<GqlCompatibilityWarning
|
<GqlCompatibilityWarning
|
||||||
identifier={relName}
|
identifier={relName}
|
||||||
className={styles.add_mar_left_small}
|
className={styles.add_mar_left_small}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
<div className={tableStyles.relationshipTopPadding}>
|
<div className={tableStyles.relationshipTopPadding}>
|
||||||
<p className={styles.textNoNewLine}>{getRelDef(relConfig)}</p>
|
<p className={styles.textNoNewLine}>{getRelDef(relConfig)}</p>
|
||||||
</div>
|
</div>
|
||||||
|
@ -791,13 +791,23 @@ WHERE
|
|||||||
export const isColTypeString = colType =>
|
export const isColTypeString = colType =>
|
||||||
['text', 'varchar', 'char', 'bpchar', 'name'].includes(colType);
|
['text', 'varchar', 'char', 'bpchar', 'name'].includes(colType);
|
||||||
|
|
||||||
export const cascadeUpQueries = (upQueries = []) =>
|
const cascadePGSqlQuery = sql => {
|
||||||
|
if (sql[sql.length - 1] === ';')
|
||||||
|
return sql.substr(0, sql.length - 1) + ' CASCADE;';
|
||||||
|
// SQL might have a " at the end
|
||||||
|
else if (sql[sql.length - 2] === ';')
|
||||||
|
return sql.substr(0, sql.length - 2) + ' CASCADE;';
|
||||||
|
return sql + ' CASCADE;';
|
||||||
|
};
|
||||||
|
|
||||||
|
export const cascadeUpQueries = (upQueries = [], isPgCascade = false) =>
|
||||||
upQueries.map((i = {}) => {
|
upQueries.map((i = {}) => {
|
||||||
if (i.type === 'run_sql' || i.type === 'untrack_table') {
|
if (i.type === 'run_sql' || i.type === 'untrack_table') {
|
||||||
return {
|
return {
|
||||||
...i,
|
...i,
|
||||||
args: {
|
args: {
|
||||||
...i.args,
|
...i.args,
|
||||||
|
...(isPgCascade && { sql: cascadePGSqlQuery(i.args.sql) }),
|
||||||
cascade: true,
|
cascade: true,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -808,14 +818,27 @@ export const cascadeUpQueries = (upQueries = []) =>
|
|||||||
export const getDependencyError = (err = {}) => {
|
export const getDependencyError = (err = {}) => {
|
||||||
if (err.code == ERROR_CODES.dependencyError.code) {
|
if (err.code == ERROR_CODES.dependencyError.code) {
|
||||||
// direct dependency error
|
// direct dependency error
|
||||||
return err;
|
return { dependencyError: err };
|
||||||
} else if (err.code == ERROR_CODES.dataApiError.code) {
|
|
||||||
// message is coming as error, further parssing willbe based on message key
|
|
||||||
const actualError = isJsonString(err.message)
|
|
||||||
? JSON.parse(err.message)
|
|
||||||
: {};
|
|
||||||
if (actualError.code == ERROR_CODES.dependencyError.code) {
|
|
||||||
return { ...actualError, message: actualError.error };
|
|
||||||
}
|
}
|
||||||
|
if (err.code == ERROR_CODES.dataApiError.code) {
|
||||||
|
// with CLI mode, error is getting as a string with the key `message`
|
||||||
|
err = isJsonString(err.message) ? JSON.parse(err.message) : {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (err.code == ERROR_CODES.dependencyError.code)
|
||||||
|
return {
|
||||||
|
dependencyError: { ...err, message: err.error },
|
||||||
|
};
|
||||||
|
if (
|
||||||
|
err.code === ERROR_CODES.postgresError.code &&
|
||||||
|
err?.internal?.error?.status_code === '2BP01' // pg dependent error > https://www.postgresql.org/docs/current/errcodes-appendix.html
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
pgDependencyError: {
|
||||||
|
...err,
|
||||||
|
message: `${err?.internal?.error?.message}:\n
|
||||||
|
${err?.internal?.error?.description || ''}`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return {};
|
||||||
};
|
};
|
||||||
|
@ -111,6 +111,7 @@ const RedeliverEvent: React.FC<Props> = ({ dispatch, eventId }) => {
|
|||||||
resizable
|
resizable
|
||||||
manual
|
manual
|
||||||
showPagination={false}
|
showPagination={false}
|
||||||
|
freezeWhenExpanded
|
||||||
SubComponent={(logRow: any) => {
|
SubComponent={(logRow: any) => {
|
||||||
const finalIndex = logRow.index;
|
const finalIndex = logRow.index;
|
||||||
const finalRow = logs[finalIndex];
|
const finalRow = logs[finalIndex];
|
||||||
|
@ -119,7 +119,7 @@ type ExternalProps = RouteComponentProps<
|
|||||||
{
|
{
|
||||||
triggerName: string;
|
triggerName: string;
|
||||||
},
|
},
|
||||||
{}
|
unknown
|
||||||
>;
|
>;
|
||||||
|
|
||||||
const mapStateToProps: MapStateToProps<PropsFromState, ExternalProps> = (
|
const mapStateToProps: MapStateToProps<PropsFromState, ExternalProps> = (
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Connect } from 'react-redux';
|
import { Connect } from 'react-redux';
|
||||||
import { Route, IndexRedirect, EnterHook, RouterState } from 'react-router';
|
import { Route, IndexRedirect, EnterHook, RouterState } from 'react-router';
|
||||||
import rightContainerConnector from '../../Common/Layout/RightContainer/RightContainer';
|
|
||||||
import Container from './Container';
|
import Container from './Container';
|
||||||
import { fetchTriggers } from './ServerIO';
|
import { fetchTriggers } from './ServerIO';
|
||||||
import globals from '../../../Globals';
|
import globals from '../../../Globals';
|
||||||
@ -52,6 +51,7 @@ import {
|
|||||||
AdhocEventLogs,
|
AdhocEventLogs,
|
||||||
AdhocEventsInfo,
|
AdhocEventsInfo,
|
||||||
} from './AdhocEvents';
|
} from './AdhocEvents';
|
||||||
|
import { RightContainer } from '../../Common/Layout/RightContainer';
|
||||||
|
|
||||||
const triggersInit = (dispatch: Dispatch): EnterHook => {
|
const triggersInit = (dispatch: Dispatch): EnterHook => {
|
||||||
return (
|
return (
|
||||||
@ -87,10 +87,7 @@ const getTriggersRouter = (
|
|||||||
onEnter={composeOnEnterHooks([triggersInit(store.dispatch)])}
|
onEnter={composeOnEnterHooks([triggersInit(store.dispatch)])}
|
||||||
>
|
>
|
||||||
<IndexRedirect to={dataEventsPrefix} />
|
<IndexRedirect to={dataEventsPrefix} />
|
||||||
<Route
|
<Route path={dataEventsPrefix} component={RightContainer}>
|
||||||
path={dataEventsPrefix}
|
|
||||||
component={rightContainerConnector(connect)}
|
|
||||||
>
|
|
||||||
<IndexRedirect to={getDataEventsLandingRoute('relative')} />
|
<IndexRedirect to={getDataEventsLandingRoute('relative')} />
|
||||||
<Route path={getAddETRoute('relative')} component={AddEventTrigger} />
|
<Route path={getAddETRoute('relative')} component={AddEventTrigger} />
|
||||||
<Route
|
<Route
|
||||||
@ -114,10 +111,7 @@ const getTriggersRouter = (
|
|||||||
component={EventTriggerLanding}
|
component={EventTriggerLanding}
|
||||||
/>
|
/>
|
||||||
</Route>
|
</Route>
|
||||||
<Route
|
<Route path={scheduledEventsPrefix} component={RightContainer}>
|
||||||
path={scheduledEventsPrefix}
|
|
||||||
component={rightContainerConnector(connect)}
|
|
||||||
>
|
|
||||||
<IndexRedirect to={getScheduledEventsLandingRoute('relative')} />
|
<IndexRedirect to={getScheduledEventsLandingRoute('relative')} />
|
||||||
<Route
|
<Route
|
||||||
path={getAddSTRoute('relative')}
|
path={getAddSTRoute('relative')}
|
||||||
@ -144,10 +138,7 @@ const getTriggersRouter = (
|
|||||||
component={ScheduledTriggeModify}
|
component={ScheduledTriggeModify}
|
||||||
/>
|
/>
|
||||||
</Route>
|
</Route>
|
||||||
<Route
|
<Route path={adhocEventsPrefix} component={RightContainer}>
|
||||||
path={adhocEventsPrefix}
|
|
||||||
component={rightContainerConnector(connect)}
|
|
||||||
>
|
|
||||||
<IndexRedirect to={getAdhocEventsInfoRoute('relative')} />
|
<IndexRedirect to={getAdhocEventsInfoRoute('relative')} />
|
||||||
<Route
|
<Route
|
||||||
path={getAddAdhocEventRoute('relative')}
|
path={getAddAdhocEventRoute('relative')}
|
||||||
|
@ -26,7 +26,7 @@ export type RouterTriggerProps = RouteComponentProps<
|
|||||||
{
|
{
|
||||||
triggerName: string;
|
triggerName: string;
|
||||||
},
|
},
|
||||||
{}
|
unknown
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export type TriggerKind = 'event' | 'cron';
|
export type TriggerKind = 'event' | 'cron';
|
||||||
|
@ -1,7 +1,4 @@
|
|||||||
/* */
|
|
||||||
import { listState } from './state';
|
import { listState } from './state';
|
||||||
/* */
|
|
||||||
|
|
||||||
import Endpoints, { globalCookiePolicy } from '../../../Endpoints';
|
import Endpoints, { globalCookiePolicy } from '../../../Endpoints';
|
||||||
import requestAction from '../../../utils/requestAction';
|
import requestAction from '../../../utils/requestAction';
|
||||||
import dataHeaders from '../Data/Common/Headers';
|
import dataHeaders from '../Data/Common/Headers';
|
||||||
@ -10,9 +7,7 @@ import returnMigrateUrl from '../Data/Common/getMigrateUrl';
|
|||||||
import { CLI_CONSOLE_MODE, SERVER_CONSOLE_MODE } from '../../../constants';
|
import { CLI_CONSOLE_MODE, SERVER_CONSOLE_MODE } from '../../../constants';
|
||||||
import { loadMigrationStatus } from '../../Main/Actions';
|
import { loadMigrationStatus } from '../../Main/Actions';
|
||||||
import { handleMigrationErrors } from '../../../utils/migration';
|
import { handleMigrationErrors } from '../../../utils/migration';
|
||||||
|
|
||||||
import { showSuccessNotification } from '../Common/Notification';
|
import { showSuccessNotification } from '../Common/Notification';
|
||||||
import { filterInconsistentMetadataObjects } from '../Settings/utils';
|
|
||||||
|
|
||||||
/* Action constants */
|
/* Action constants */
|
||||||
|
|
||||||
@ -51,20 +46,9 @@ const fetchRemoteSchemas = () => {
|
|||||||
dispatch({ type: FETCH_REMOTE_SCHEMAS });
|
dispatch({ type: FETCH_REMOTE_SCHEMAS });
|
||||||
return dispatch(requestAction(url, options)).then(
|
return dispatch(requestAction(url, options)).then(
|
||||||
data => {
|
data => {
|
||||||
let consistentRemoteSchemas = data;
|
|
||||||
const { inconsistentObjects } = getState().metadata;
|
|
||||||
|
|
||||||
if (inconsistentObjects.length > 0) {
|
|
||||||
consistentRemoteSchemas = filterInconsistentMetadataObjects(
|
|
||||||
data,
|
|
||||||
inconsistentObjects,
|
|
||||||
'remote_schemas'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: REMOTE_SCHEMAS_FETCH_SUCCESS,
|
type: REMOTE_SCHEMAS_FETCH_SUCCESS,
|
||||||
data: consistentRemoteSchemas,
|
data,
|
||||||
});
|
});
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
},
|
},
|
||||||
|
@ -318,7 +318,6 @@ const modifyRemoteSchema = () => {
|
|||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
const currState = getState().remoteSchemas.addData;
|
const currState = getState().remoteSchemas.addData;
|
||||||
const remoteSchemaName = currState.name.trim().replace(/ +/g, '');
|
const remoteSchemaName = currState.name.trim().replace(/ +/g, '');
|
||||||
// const url = Endpoints.getSchema;
|
|
||||||
const upQueryArgs = [];
|
const upQueryArgs = [];
|
||||||
const downQueryArgs = [];
|
const downQueryArgs = [];
|
||||||
const migrationName = 'update_remote_schema_' + remoteSchemaName;
|
const migrationName = 'update_remote_schema_' + remoteSchemaName;
|
||||||
@ -345,9 +344,10 @@ const modifyRemoteSchema = () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
resolveObj.definition.headers = [
|
resolveObj.definition.headers = getReqHeader(
|
||||||
...getReqHeader(getState().remoteSchemas.headerData.headers),
|
getState().remoteSchemas.headerData.headers
|
||||||
];
|
);
|
||||||
|
|
||||||
if (resolveObj.definition.url) {
|
if (resolveObj.definition.url) {
|
||||||
delete resolveObj.definition.url_from_env;
|
delete resolveObj.definition.url_from_env;
|
||||||
} else {
|
} else {
|
||||||
|
@ -22,6 +22,7 @@ import { NotFoundError } from '../../../Error/PageNotFound';
|
|||||||
|
|
||||||
import globals from '../../../../Globals';
|
import globals from '../../../../Globals';
|
||||||
import { getConfirmation } from '../../../Common/utils/jsUtils';
|
import { getConfirmation } from '../../../Common/utils/jsUtils';
|
||||||
|
import styles from '../RemoteSchema.scss';
|
||||||
|
|
||||||
const prefixUrl = globals.urlPrefix + appPrefix;
|
const prefixUrl = globals.urlPrefix + appPrefix;
|
||||||
|
|
||||||
@ -49,17 +50,17 @@ class Edit extends React.Component {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
UNSAFE_componentWillReceiveProps(nextProps) {
|
componentDidUpdate(prevProps) {
|
||||||
if (
|
if (
|
||||||
nextProps.params.remoteSchemaName !== this.props.params.remoteSchemaName
|
prevProps.params.remoteSchemaName !== this.props.params.remoteSchemaName
|
||||||
) {
|
) {
|
||||||
Promise.all([
|
Promise.all([
|
||||||
this.props.dispatch(
|
this.props.dispatch(
|
||||||
fetchRemoteSchema(nextProps.params.remoteSchemaName)
|
fetchRemoteSchema(this.props.params.remoteSchemaName)
|
||||||
),
|
),
|
||||||
this.props.dispatch({
|
this.props.dispatch({
|
||||||
type: VIEW_REMOTE_SCHEMA,
|
type: VIEW_REMOTE_SCHEMA,
|
||||||
data: nextProps.params.remoteSchemaName,
|
data: this.props.params.remoteSchemaName,
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@ -120,11 +121,23 @@ class Edit extends React.Component {
|
|||||||
throw new NotFoundError();
|
throw new NotFoundError();
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = require('../RemoteSchema.scss');
|
const {
|
||||||
|
isFetching,
|
||||||
const { isFetching, isRequesting, editState } = this.props;
|
isRequesting,
|
||||||
|
editState,
|
||||||
|
inconsistentObjects,
|
||||||
|
} = this.props;
|
||||||
const { remoteSchemaName } = this.props.params;
|
const { remoteSchemaName } = this.props.params;
|
||||||
|
|
||||||
|
const inconsistencyDetails = inconsistentObjects.find(
|
||||||
|
inconObj =>
|
||||||
|
inconObj.type === 'remote_schema' &&
|
||||||
|
inconObj?.definition?.name === remoteSchemaName
|
||||||
|
);
|
||||||
|
|
||||||
|
const fixInconsistencyMsg =
|
||||||
|
'This remote schema is in an inconsistent state. Please fix inconsistencies and reload metadata first';
|
||||||
|
|
||||||
const generateMigrateBtns = () => {
|
const generateMigrateBtns = () => {
|
||||||
return 'isModify' in editState && !editState.isModify ? (
|
return 'isModify' in editState && !editState.isModify ? (
|
||||||
<div className={styles.commonBtn}>
|
<div className={styles.commonBtn}>
|
||||||
@ -137,7 +150,8 @@ class Edit extends React.Component {
|
|||||||
this.modifyClick();
|
this.modifyClick();
|
||||||
}}
|
}}
|
||||||
data-test={'remote-schema-edit-modify-btn'}
|
data-test={'remote-schema-edit-modify-btn'}
|
||||||
disabled={isRequesting}
|
disabled={isRequesting || inconsistencyDetails}
|
||||||
|
title={inconsistencyDetails ? fixInconsistencyMsg : ''}
|
||||||
>
|
>
|
||||||
Modify
|
Modify
|
||||||
</Button>
|
</Button>
|
||||||
@ -148,7 +162,8 @@ class Edit extends React.Component {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.handleDeleteRemoteSchema(e);
|
this.handleDeleteRemoteSchema(e);
|
||||||
}}
|
}}
|
||||||
disabled={isRequesting}
|
disabled={isRequesting || inconsistencyDetails}
|
||||||
|
title={inconsistencyDetails ? fixInconsistencyMsg : ''}
|
||||||
data-test={'remote-schema-edit-delete-btn'}
|
data-test={'remote-schema-edit-delete-btn'}
|
||||||
>
|
>
|
||||||
{isRequesting ? 'Deleting ...' : 'Delete'}
|
{isRequesting ? 'Deleting ...' : 'Delete'}
|
||||||
@ -254,6 +269,7 @@ const mapStateToProps = state => {
|
|||||||
...state.remoteSchemas.headerData,
|
...state.remoteSchemas.headerData,
|
||||||
allRemoteSchemas: state.remoteSchemas.listData.remoteSchemas,
|
allRemoteSchemas: state.remoteSchemas.listData.remoteSchemas,
|
||||||
dataHeaders: { ...state.tables.dataHeaders },
|
dataHeaders: { ...state.tables.dataHeaders },
|
||||||
|
inconsistentObjects: state.metadata.inconsistentObjects,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,28 +1,56 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import CommonTabLayout from '../../../Common/Layout/CommonTabLayout/CommonTabLayout';
|
import CommonTabLayout from '../../../Common/Layout/CommonTabLayout/CommonTabLayout';
|
||||||
import tabInfo from './tabInfo';
|
import tabInfo from './tabInfo';
|
||||||
import Tooltip from 'react-bootstrap/lib/Tooltip';
|
|
||||||
import OverlayTrigger from 'react-bootstrap/lib/OverlayTrigger';
|
|
||||||
import { push } from 'react-router-redux';
|
import { push } from 'react-router-redux';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
fetchRemoteSchema,
|
fetchRemoteSchema,
|
||||||
RESET,
|
RESET,
|
||||||
getHeaderEvents,
|
getHeaderEvents,
|
||||||
} from '../Add/addRemoteSchemaReducer';
|
} from '../Add/addRemoteSchemaReducer';
|
||||||
|
|
||||||
import { VIEW_REMOTE_SCHEMA } from '../Actions';
|
import { VIEW_REMOTE_SCHEMA } from '../Actions';
|
||||||
import ReloadRemoteSchema from '../../Settings/MetadataOptions/ReloadRemoteSchema';
|
import ReloadRemoteSchema from '../../Settings/MetadataOptions/ReloadRemoteSchema';
|
||||||
|
|
||||||
import { appPrefix } from '../constants';
|
import { appPrefix } from '../constants';
|
||||||
|
|
||||||
import { NotFoundError } from '../../../Error/PageNotFound';
|
|
||||||
|
|
||||||
import globals from '../../../../Globals';
|
import globals from '../../../../Globals';
|
||||||
|
import styles from '../RemoteSchema.scss';
|
||||||
|
import ToolTip from '../../../Common/Tooltip/Tooltip';
|
||||||
|
import WarningSymbol from '../../../Common/WarningSymbol/WarningSymbol';
|
||||||
|
|
||||||
const prefixUrl = globals.urlPrefix + appPrefix;
|
const prefixUrl = globals.urlPrefix + appPrefix;
|
||||||
|
|
||||||
|
const RSHeadersDisplay = ({ data }) =>
|
||||||
|
data.length > 0 ? (
|
||||||
|
<tr>
|
||||||
|
<td>Headers</td>
|
||||||
|
<td>
|
||||||
|
{data &&
|
||||||
|
data
|
||||||
|
.filter(header => !!header.name)
|
||||||
|
.map((header, index) => [
|
||||||
|
<tr key={header}>
|
||||||
|
<td>
|
||||||
|
{`${header.name}: `}
|
||||||
|
{header.type === 'static'
|
||||||
|
? header.value
|
||||||
|
: '<' + header.value + '>'}
|
||||||
|
</td>
|
||||||
|
</tr>,
|
||||||
|
index !== data.length - 1 ? <hr /> : null,
|
||||||
|
])}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
) : null;
|
||||||
|
|
||||||
|
const RSReloadSchema = ({ readOnlyMode, remoteSchemaName, ...props }) =>
|
||||||
|
!readOnlyMode && remoteSchemaName && remoteSchemaName.length > 0 ? (
|
||||||
|
<div className={`${styles.commonBtn} ${styles.detailsRefreshButton}`}>
|
||||||
|
<ReloadRemoteSchema {...props} remoteSchemaName={remoteSchemaName} />
|
||||||
|
<ToolTip
|
||||||
|
placement="right"
|
||||||
|
message="If your remote schema has changed, you need to refresh the GraphQL Engine metadata to query the modified schema"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : null;
|
||||||
|
|
||||||
class ViewStitchedSchema extends React.Component {
|
class ViewStitchedSchema extends React.Component {
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const { remoteSchemaName } = this.props.params;
|
const { remoteSchemaName } = this.props.params;
|
||||||
@ -35,17 +63,17 @@ class ViewStitchedSchema extends React.Component {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
UNSAFE_componentWillReceiveProps(nextProps) {
|
componentDidUpdate(prevProps) {
|
||||||
if (
|
if (
|
||||||
nextProps.params.remoteSchemaName !== this.props.params.remoteSchemaName
|
prevProps.params.remoteSchemaName !== this.props.params.remoteSchemaName
|
||||||
) {
|
) {
|
||||||
Promise.all([
|
Promise.all([
|
||||||
this.props.dispatch(
|
this.props.dispatch(
|
||||||
fetchRemoteSchema(nextProps.params.remoteSchemaName)
|
fetchRemoteSchema(this.props.params.remoteSchemaName)
|
||||||
),
|
),
|
||||||
this.props.dispatch({
|
this.props.dispatch({
|
||||||
type: VIEW_REMOTE_SCHEMA,
|
type: VIEW_REMOTE_SCHEMA,
|
||||||
data: nextProps.params.remoteSchemaName,
|
data: this.props.params.remoteSchemaName,
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@ -69,19 +97,14 @@ class ViewStitchedSchema extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const currentRemoteSchema = this.props.allRemoteSchemas.find(
|
|
||||||
r => r.name === this.props.params.remoteSchemaName
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!currentRemoteSchema) {
|
|
||||||
// throw a 404 exception
|
|
||||||
throw new NotFoundError();
|
|
||||||
}
|
|
||||||
|
|
||||||
const styles = require('../RemoteSchema.scss');
|
|
||||||
|
|
||||||
const { remoteSchemaName } = this.props.params;
|
const { remoteSchemaName } = this.props.params;
|
||||||
const { manualUrl, envName, headers, readOnlyMode } = this.props;
|
const {
|
||||||
|
manualUrl,
|
||||||
|
envName,
|
||||||
|
headers,
|
||||||
|
readOnlyMode,
|
||||||
|
inconsistentObjects,
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
const filterHeaders = headers.filter(h => !!h.name);
|
const filterHeaders = headers.filter(h => !!h.name);
|
||||||
|
|
||||||
@ -92,21 +115,14 @@ class ViewStitchedSchema extends React.Component {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Manage',
|
title: 'Manage',
|
||||||
url: appPrefix + '/' + 'manage',
|
url: `${appPrefix}/manage`,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
if (remoteSchemaName) {
|
if (remoteSchemaName) {
|
||||||
breadCrumbs.push({
|
breadCrumbs.push({
|
||||||
title: remoteSchemaName.trim(),
|
title: remoteSchemaName.trim(),
|
||||||
url:
|
url: `${appPrefix}/manage/${remoteSchemaName.trim()}/details`,
|
||||||
appPrefix +
|
|
||||||
'/' +
|
|
||||||
'manage' +
|
|
||||||
'/' +
|
|
||||||
remoteSchemaName.trim() +
|
|
||||||
'/' +
|
|
||||||
'details',
|
|
||||||
});
|
});
|
||||||
breadCrumbs.push({
|
breadCrumbs.push({
|
||||||
title: 'details',
|
title: 'details',
|
||||||
@ -114,33 +130,18 @@ class ViewStitchedSchema extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const refresh = (
|
let tabInfoCopy = tabInfo;
|
||||||
<Tooltip id="tooltip-cascade">
|
|
||||||
If your remote schema has changed, you need to refresh the GraphQL
|
|
||||||
Engine metadata to query the modified schema
|
|
||||||
</Tooltip>
|
|
||||||
);
|
|
||||||
|
|
||||||
if (readOnlyMode) {
|
if (readOnlyMode) {
|
||||||
delete tabInfo.modify;
|
const { modify, ...rest } = tabInfoCopy;
|
||||||
|
tabInfoCopy = rest;
|
||||||
}
|
}
|
||||||
|
|
||||||
const showReloadRemoteSchema =
|
const inconsistencyDetails = inconsistentObjects.find(
|
||||||
!readOnlyMode && remoteSchemaName && remoteSchemaName.length > 0 ? (
|
inconObj =>
|
||||||
<div className={styles.commonBtn + ' ' + styles.detailsRefreshButton}>
|
inconObj.type === 'remote_schema' &&
|
||||||
<span>
|
inconObj?.definition?.name === remoteSchemaName
|
||||||
<ReloadRemoteSchema
|
);
|
||||||
{...this.props}
|
|
||||||
remoteSchemaName={remoteSchemaName}
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
<span>
|
|
||||||
<OverlayTrigger placement="right" overlay={refresh}>
|
|
||||||
<i className="fa fa-question-circle" aria-hidden="true" />
|
|
||||||
</OverlayTrigger>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
) : null;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@ -150,7 +151,7 @@ class ViewStitchedSchema extends React.Component {
|
|||||||
appPrefix={appPrefix}
|
appPrefix={appPrefix}
|
||||||
currentTab="details"
|
currentTab="details"
|
||||||
heading={remoteSchemaName}
|
heading={remoteSchemaName}
|
||||||
tabsInfo={tabInfo}
|
tabsInfo={tabInfoCopy}
|
||||||
breadCrumbs={breadCrumbs}
|
breadCrumbs={breadCrumbs}
|
||||||
baseUrl={`${appPrefix}/manage/${remoteSchemaName}`}
|
baseUrl={`${appPrefix}/manage/${remoteSchemaName}`}
|
||||||
/>
|
/>
|
||||||
@ -164,37 +165,35 @@ class ViewStitchedSchema extends React.Component {
|
|||||||
<td>GraphQL Server URL</td>
|
<td>GraphQL Server URL</td>
|
||||||
<td>{manualUrl || `<${envName}>`}</td>
|
<td>{manualUrl || `<${envName}>`}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{filterHeaders.length > 0 ? (
|
<RSHeadersDisplay data={filterHeaders} />
|
||||||
<tr>
|
|
||||||
<td>Headers</td>
|
|
||||||
<td>
|
|
||||||
{filterHeaders &&
|
|
||||||
filterHeaders
|
|
||||||
.filter(k => !!k.name)
|
|
||||||
.map((h, i) => [
|
|
||||||
<tr key={i}>
|
|
||||||
<td>
|
|
||||||
{h.name} :{' '}
|
|
||||||
{h.type === 'static'
|
|
||||||
? h.value
|
|
||||||
: '<' + h.value + '>'}
|
|
||||||
</td>
|
|
||||||
</tr>,
|
|
||||||
i !== filterHeaders.length - 1 ? <hr /> : null,
|
|
||||||
])}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
) : null}
|
|
||||||
{/*
|
|
||||||
<tr>
|
|
||||||
<td>Webhook</td>
|
|
||||||
<td>in-use/bypassed</td>
|
|
||||||
</tr>
|
|
||||||
*/}
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
{showReloadRemoteSchema}
|
{inconsistencyDetails && (
|
||||||
|
<div className={styles.add_mar_bottom}>
|
||||||
|
<div className={styles.subheading_text}>
|
||||||
|
<WarningSymbol tooltipText={'Inconsistent schema'} />
|
||||||
|
<span className={styles.add_mar_left_mid}>
|
||||||
|
This remote schema is in an inconsistent state.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<b>Reason:</b> {inconsistencyDetails.reason}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<i>
|
||||||
|
(Please resolve the inconsistencies and reload the remote
|
||||||
|
schema. Fields from this remote schema are currently not
|
||||||
|
exposed over the GraphQL API)
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<RSReloadSchema
|
||||||
|
readOnlyMode={readOnlyMode}
|
||||||
|
remoteSchemaName={remoteSchemaName}
|
||||||
|
{...this.props}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
@ -210,6 +209,7 @@ const mapStateToProps = state => {
|
|||||||
allRemoteSchemas: state.remoteSchemas.listData.remoteSchemas,
|
allRemoteSchemas: state.remoteSchemas.listData.remoteSchemas,
|
||||||
dataHeaders: { ...state.tables.dataHeaders },
|
dataHeaders: { ...state.tables.dataHeaders },
|
||||||
readOnlyMode: state.main.readOnlyMode,
|
readOnlyMode: state.main.readOnlyMode,
|
||||||
|
inconsistentObjects: state.metadata.inconsistentObjects,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
@import "../../Common/Common.scss";
|
@import '../../Common/Common.scss';
|
||||||
|
|
||||||
.addPaddCommom {
|
.addPaddCommom {
|
||||||
padding: 10px 0;
|
padding: 10px 0;
|
||||||
@ -42,17 +42,12 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
// height: 200px;
|
|
||||||
// border: 1px solid #000;
|
|
||||||
|
|
||||||
img {
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.commonBtn {
|
.commonBtn {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 20px 0;
|
padding: 20px 0;
|
||||||
padding-bottom: 10px
|
padding-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.readMore {
|
.readMore {
|
||||||
@ -61,7 +56,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.iconWrapper{
|
.iconWrapper {
|
||||||
padding: 20px 0;
|
padding: 20px 0;
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
@ -140,7 +135,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.red_button {
|
.red_button {
|
||||||
color: #FFF;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
@ -166,7 +161,6 @@
|
|||||||
.set_line_height {
|
.set_line_height {
|
||||||
line-height: 26px;
|
line-height: 26px;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.remoteSchemaImg {
|
.remoteSchemaImg {
|
||||||
@ -193,7 +187,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
a{
|
a {
|
||||||
color: #909090;
|
color: #909090;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -260,24 +254,24 @@
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.instructionsWrapper, .instructionsWrapperPos {
|
.instructionsWrapper,
|
||||||
|
.instructionsWrapperPos {
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
border-top: 1px solid #DEDEDE;
|
border-top: 1px solid #dedede;
|
||||||
.instructions {
|
.instructions {
|
||||||
padding: 12px 0;
|
padding: 12px 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.instructionsWrapper
|
.instructionsWrapper {
|
||||||
{
|
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
.instructionsWrapperPos
|
.instructionsWrapperPos {
|
||||||
{
|
|
||||||
position: static;
|
position: static;
|
||||||
}
|
}
|
||||||
.instructionsWrapper:hover, .instructionsWrapperPos:hover {
|
.instructionsWrapper:hover,
|
||||||
|
.instructionsWrapperPos:hover {
|
||||||
.instructions {
|
.instructions {
|
||||||
color: #505050
|
color: #505050;
|
||||||
}
|
}
|
||||||
|
|
||||||
.rightArrow {
|
.rightArrow {
|
||||||
|
@ -5,10 +5,10 @@ import PropTypes from 'prop-types';
|
|||||||
import LeftContainer from '../../Common/Layout/LeftContainer/LeftContainer';
|
import LeftContainer from '../../Common/Layout/LeftContainer/LeftContainer';
|
||||||
import PageContainer from '../../Common/Layout/PageContainer/PageContainer';
|
import PageContainer from '../../Common/Layout/PageContainer/PageContainer';
|
||||||
import RemoteSchemaSubSidebar from './RemoteSchemaSubSidebar';
|
import RemoteSchemaSubSidebar from './RemoteSchemaSubSidebar';
|
||||||
|
import styles from '../../Common/TableCommon/Table.scss';
|
||||||
|
|
||||||
class RemoteSchemaPageContainer extends React.Component {
|
class RemoteSchemaPageContainer extends React.Component {
|
||||||
render() {
|
render() {
|
||||||
const styles = require('../../Common/TableCommon/Table.scss');
|
|
||||||
const { appPrefix, children } = this.props;
|
const { appPrefix, children } = this.props;
|
||||||
|
|
||||||
const currentLocation = location.pathname;
|
const currentLocation = location.pathname;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { Route, IndexRedirect } from 'react-router';
|
import { Route, IndexRedirect } from 'react-router';
|
||||||
import { rightContainerConnector } from '../../Common/Layout';
|
import { RightContainer } from '../../Common/Layout/RightContainer';
|
||||||
import globals from '../../../Globals';
|
import globals from '../../../Globals';
|
||||||
import {
|
import {
|
||||||
remoteSchemaPageConnector,
|
remoteSchemaPageConnector,
|
||||||
@ -95,7 +95,7 @@ const getRemoteSchemaRouter = (connect, store, composeOnEnterHooks) => {
|
|||||||
onChange={fetchInitialData(store)}
|
onChange={fetchInitialData(store)}
|
||||||
>
|
>
|
||||||
<IndexRedirect to="manage" />
|
<IndexRedirect to="manage" />
|
||||||
<Route path="manage" component={rightContainerConnector(connect)}>
|
<Route path="manage" component={RightContainer}>
|
||||||
<IndexRedirect to="schemas" />
|
<IndexRedirect to="schemas" />
|
||||||
<Route path="schemas" component={landingConnector(connect)} />
|
<Route path="schemas" component={landingConnector(connect)} />
|
||||||
<Route path="add" component={addConnector(connect)} />
|
<Route path="add" component={addConnector(connect)} />
|
||||||
|
@ -2,6 +2,8 @@ import React from 'react';
|
|||||||
import { Link } from 'react-router';
|
import { Link } from 'react-router';
|
||||||
|
|
||||||
import LeftSubSidebar from '../../Common/Layout/LeftSubSidebar/LeftSubSidebar';
|
import LeftSubSidebar from '../../Common/Layout/LeftSubSidebar/LeftSubSidebar';
|
||||||
|
import styles from '../../Common/Layout/LeftSubSidebar/LeftSubSidebar.scss';
|
||||||
|
import WarningSymbol from '../../Common/WarningSymbol/WarningSymbol';
|
||||||
|
|
||||||
const RemoteSchemaSubSidebar = ({
|
const RemoteSchemaSubSidebar = ({
|
||||||
appPrefix,
|
appPrefix,
|
||||||
@ -12,8 +14,12 @@ const RemoteSchemaSubSidebar = ({
|
|||||||
filterItem,
|
filterItem,
|
||||||
viewRemoteSchema,
|
viewRemoteSchema,
|
||||||
main,
|
main,
|
||||||
|
...props
|
||||||
}) => {
|
}) => {
|
||||||
const styles = require('../../Common/Layout/LeftSubSidebar/LeftSubSidebar.scss');
|
const { inconsistentObjects } = props.metadata;
|
||||||
|
const inconsistentRemoteSchemas = inconsistentObjects.filter(
|
||||||
|
inconObject => inconObject.type === 'remote_schema'
|
||||||
|
);
|
||||||
|
|
||||||
function tableSearch(e) {
|
function tableSearch(e) {
|
||||||
const searchTerm = e.target.value;
|
const searchTerm = e.target.value;
|
||||||
@ -35,7 +41,7 @@ const RemoteSchemaSubSidebar = ({
|
|||||||
const getChildList = () => {
|
const getChildList = () => {
|
||||||
const _dataList = searchQuery ? filtered : dataList;
|
const _dataList = searchQuery ? filtered : dataList;
|
||||||
|
|
||||||
let childList;
|
let childList = [];
|
||||||
if (_dataList.length === 0) {
|
if (_dataList.length === 0) {
|
||||||
childList = (
|
childList = (
|
||||||
<li
|
<li
|
||||||
@ -46,8 +52,10 @@ const RemoteSchemaSubSidebar = ({
|
|||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
if (_dataList.length > 0) {
|
||||||
childList = _dataList.map((d, i) => {
|
childList = _dataList.map((d, i) => {
|
||||||
let activeTableClass = '';
|
let activeTableClass = '';
|
||||||
|
|
||||||
if (
|
if (
|
||||||
d.name === viewRemoteSchema &&
|
d.name === viewRemoteSchema &&
|
||||||
location.pathname.includes(viewRemoteSchema)
|
location.pathname.includes(viewRemoteSchema)
|
||||||
@ -55,6 +63,10 @@ const RemoteSchemaSubSidebar = ({
|
|||||||
activeTableClass = styles.activeLink;
|
activeTableClass = styles.activeLink;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const inconsistentCurrentSchema = inconsistentRemoteSchemas.find(
|
||||||
|
elem => elem.definition.name === d.name
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li
|
<li
|
||||||
className={activeTableClass}
|
className={activeTableClass}
|
||||||
@ -62,19 +74,30 @@ const RemoteSchemaSubSidebar = ({
|
|||||||
data-test={`remote-schema-sidebar-links-${i + 1}`}
|
data-test={`remote-schema-sidebar-links-${i + 1}`}
|
||||||
>
|
>
|
||||||
<Link
|
<Link
|
||||||
to={appPrefix + '/manage/' + d.name + '/details'}
|
to={`${appPrefix}/manage/${d.name}/details`}
|
||||||
data-test={d.name}
|
data-test={d.name}
|
||||||
>
|
>
|
||||||
<i
|
<i
|
||||||
className={styles.tableIcon + ' fa fa-code-fork'}
|
className={`${styles.tableIcon} fa fa-code-fork`}
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
{d.name}
|
{d.name}
|
||||||
|
{inconsistentCurrentSchema ? (
|
||||||
|
<WarningSymbol
|
||||||
|
customStyle={styles.padLeft4}
|
||||||
|
tooltipText={
|
||||||
|
'This remote schema is in an inconsistent state. ' +
|
||||||
|
'Fields from this remote schema are currently not exposed over the GraphQL API'
|
||||||
|
}
|
||||||
|
tooltipPlacement="right"
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return childList;
|
return childList;
|
||||||
};
|
};
|
||||||
|
@ -208,11 +208,6 @@ const handleInconsistentObjects = inconsistentObjects => {
|
|||||||
inconsistentObjects,
|
inconsistentObjects,
|
||||||
'functions'
|
'functions'
|
||||||
);
|
);
|
||||||
const filteredRemoteSchemas = filterInconsistentMetadataObjects(
|
|
||||||
remoteSchemas,
|
|
||||||
inconsistentObjects,
|
|
||||||
'remote_schemas'
|
|
||||||
);
|
|
||||||
const filteredActions = filterInconsistentMetadataObjects(
|
const filteredActions = filterInconsistentMetadataObjects(
|
||||||
actions,
|
actions,
|
||||||
inconsistentObjects,
|
inconsistentObjects,
|
||||||
@ -221,7 +216,7 @@ const handleInconsistentObjects = inconsistentObjects => {
|
|||||||
|
|
||||||
dispatch(setConsistentSchema(filteredSchema));
|
dispatch(setConsistentSchema(filteredSchema));
|
||||||
dispatch(setConsistentFunctions(filteredFunctions));
|
dispatch(setConsistentFunctions(filteredFunctions));
|
||||||
dispatch(setConsistentRemoteSchemas(filteredRemoteSchemas));
|
dispatch(setConsistentRemoteSchemas(remoteSchemas));
|
||||||
dispatch(setActions(filteredActions));
|
dispatch(setActions(filteredActions));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -234,9 +229,7 @@ export const loadInconsistentObjects = (reloadConfig, successCb, failureCb) => {
|
|||||||
const { shouldReloadMetadata, shouldReloadRemoteSchemas } = reloadConfig;
|
const { shouldReloadMetadata, shouldReloadRemoteSchemas } = reloadConfig;
|
||||||
|
|
||||||
const loadQuery = shouldReloadMetadata
|
const loadQuery = shouldReloadMetadata
|
||||||
? getReloadCacheAndGetInconsistentObjectsQuery(
|
? getReloadCacheAndGetInconsistentObjectsQuery(shouldReloadRemoteSchemas)
|
||||||
shouldReloadRemoteSchemas === false ? false : true
|
|
||||||
)
|
|
||||||
: inconsistentObjectsQuery;
|
: inconsistentObjectsQuery;
|
||||||
|
|
||||||
dispatch({ type: LOADING_METADATA });
|
dispatch({ type: LOADING_METADATA });
|
||||||
|
@ -5,13 +5,13 @@ import Sidebar from './Sidebar';
|
|||||||
import PageContainer from '../../Common/Layout/PageContainer/PageContainer';
|
import PageContainer from '../../Common/Layout/PageContainer/PageContainer';
|
||||||
|
|
||||||
type Metadata = {
|
type Metadata = {
|
||||||
inconsistentObjects: object[];
|
inconsistentObjects: Record<string, unknown>[];
|
||||||
ongoingRequest: boolean;
|
ongoingRequest: boolean;
|
||||||
allowedQueries: object[];
|
allowedQueries: Record<string, any>[];
|
||||||
};
|
};
|
||||||
|
|
||||||
type ExternalProps = {
|
type ExternalProps = {
|
||||||
location: RouteComponentProps<{}, {}>['location'];
|
location: RouteComponentProps<unknown, unknown>['location'];
|
||||||
children: JSX.Element;
|
children: JSX.Element;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -55,6 +55,8 @@ const mapStateToProps = (state: DerivedState) => {
|
|||||||
type StateProps = ReturnType<typeof mapStateToProps>;
|
type StateProps = ReturnType<typeof mapStateToProps>;
|
||||||
|
|
||||||
const connector = (connect: Connect) =>
|
const connector = (connect: Connect) =>
|
||||||
connect<StateProps, {}, {}, DerivedState>(mapStateToProps)(Container);
|
connect<StateProps, unknown, unknown, DerivedState>(mapStateToProps)(
|
||||||
|
Container
|
||||||
|
);
|
||||||
|
|
||||||
export default connector;
|
export default connector;
|
||||||
|
@ -2,6 +2,7 @@ import React, { Component } from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import Button from '../../../Common/Button/Button';
|
import Button from '../../../Common/Button/Button';
|
||||||
import { reloadRemoteSchema } from '../Actions';
|
import { reloadRemoteSchema } from '../Actions';
|
||||||
|
import metaDataStyles from '../Settings.scss';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
showSuccessNotification,
|
showSuccessNotification,
|
||||||
@ -17,7 +18,6 @@ class ReloadRemoteSchema extends Component {
|
|||||||
render() {
|
render() {
|
||||||
const { dispatch, remoteSchemaName } = this.props;
|
const { dispatch, remoteSchemaName } = this.props;
|
||||||
const { isReloading } = this.state;
|
const { isReloading } = this.state;
|
||||||
const metaDataStyles = require('../Settings.scss');
|
|
||||||
const reloadRemoteMetadataHandler = () => {
|
const reloadRemoteMetadataHandler = () => {
|
||||||
this.setState({ isReloading: true });
|
this.setState({ isReloading: true });
|
||||||
dispatch(
|
dispatch(
|
||||||
|
@ -133,7 +133,7 @@ const MetadataStatus = ({ dispatch, metadata }) => {
|
|||||||
of the metadata
|
of the metadata
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.add_mar_top_small}>
|
<div className={styles.add_mar_top_small}>
|
||||||
The console will also not be able to display these inconsistent
|
The console might also not be able to display these inconsistent
|
||||||
objects
|
objects
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -10,11 +10,11 @@ import { getAdminSecret } from '../ApiExplorer/ApiRequest/utils';
|
|||||||
import styles from '../../Common/TableCommon/Table.scss';
|
import styles from '../../Common/TableCommon/Table.scss';
|
||||||
|
|
||||||
interface Metadata {
|
interface Metadata {
|
||||||
inconsistentObjects: object[];
|
inconsistentObjects: Record<string, unknown>[];
|
||||||
}
|
}
|
||||||
|
|
||||||
type SidebarProps = {
|
type SidebarProps = {
|
||||||
location: RouteComponentProps<{}, {}>['location'];
|
location: RouteComponentProps<unknown, unknown>['location'];
|
||||||
metadata: Metadata;
|
metadata: Metadata;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
109
console/src/components/Services/Support/HelpPage.tsx
Normal file
109
console/src/components/Services/Support/HelpPage.tsx
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import styles from './Support.scss';
|
||||||
|
import discord from './images/discord.svg';
|
||||||
|
import docs from './images/docs.svg';
|
||||||
|
import stackOverflow from './images/stack-overflow.svg';
|
||||||
|
import github from './images/github.svg';
|
||||||
|
|
||||||
|
const CHECK_FORUMS = `
|
||||||
|
If you need any help with developing on Hasura, you can check out
|
||||||
|
these various Hasura forums. Our community members include some very
|
||||||
|
experienced engineers from some of the world’s most exciting
|
||||||
|
companies, and many of them have been using Hasura in production for a
|
||||||
|
long time.`;
|
||||||
|
|
||||||
|
const supportListState = [
|
||||||
|
{
|
||||||
|
brand: discord,
|
||||||
|
title: 'Discord',
|
||||||
|
description:
|
||||||
|
'Our community hangs out here. Join discord to ask/help folks in the community.',
|
||||||
|
link: 'https://discord.com/invite/hasura',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
brand: docs,
|
||||||
|
title: 'Docs',
|
||||||
|
description: 'Head to docs to search for what you’re looking for.',
|
||||||
|
link: 'https://hasura.io/docs/',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
brand: stackOverflow,
|
||||||
|
title: 'StackOverflow',
|
||||||
|
description: 'Ask your Hasura questions here and tag as ‘hasura’',
|
||||||
|
link: 'https://stackoverflow.com/questions/tagged/hasura',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
brand: github,
|
||||||
|
title: 'GitHub',
|
||||||
|
description:
|
||||||
|
'Create an issue on GitHub to report bugs, suggest improvements or give us a star!',
|
||||||
|
link: 'https://github.com/hasura/graphql-engine/',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const HelpPage = () => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`${styles.padd_left_remove} ${styles.supportForumWrapper} container-fluid ${styles.padd_top}`}
|
||||||
|
>
|
||||||
|
<div className={styles.padd_left}>
|
||||||
|
<h2 className={`${styles.headerText} ${styles.inline_block}`}>
|
||||||
|
Support Forums
|
||||||
|
</h2>
|
||||||
|
<div className={`${styles.descriptionText} ${styles.wd60}`}>
|
||||||
|
{CHECK_FORUMS}
|
||||||
|
</div>
|
||||||
|
<div className={styles.supportWrapper}>
|
||||||
|
{supportListState.map((list, index) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`col-md-6 col-sm-6 col-xs-12 ${styles.padd_remove} ${styles.supportDisplay}`}
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
key={index}
|
||||||
|
href={list.link}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className={styles.supportFlex}
|
||||||
|
>
|
||||||
|
<div className={styles.supportList}>
|
||||||
|
<div className={styles.supportBrand}>
|
||||||
|
<img src={list.brand} alt={list.title} />
|
||||||
|
</div>
|
||||||
|
<div className={styles.supportContainer}>
|
||||||
|
<div className={styles.title}>{list.title}</div>
|
||||||
|
<div className={styles.descriptionText}>
|
||||||
|
{list.description}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<div className={`${styles.descriptionText} ${styles.wd60}`}>
|
||||||
|
If you would like to talk to our Product Specialists, head to our{' '}
|
||||||
|
<a
|
||||||
|
href="https://hasura.io/help"
|
||||||
|
// eslint-disable-next-line react/jsx-no-target-blank
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
help page
|
||||||
|
</a>{' '}
|
||||||
|
to chat with us or{' '}
|
||||||
|
<a
|
||||||
|
href="https://calendly.com/hasura/prod-expert-call"
|
||||||
|
// eslint-disable-next-line react/jsx-no-target-blank
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
set up a call
|
||||||
|
</a>
|
||||||
|
.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default HelpPage;
|
60
console/src/components/Services/Support/Support.scss
Normal file
60
console/src/components/Services/Support/Support.scss
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
@import '../../Common/Common.scss';
|
||||||
|
|
||||||
|
.supportForumWrapper {
|
||||||
|
.headerText {
|
||||||
|
padding-bottom: 16px;
|
||||||
|
}
|
||||||
|
.supportWrapper {
|
||||||
|
padding-top: 40px;
|
||||||
|
padding-bottom: 32px;
|
||||||
|
width: 75%;
|
||||||
|
display: flex;
|
||||||
|
list-style: none;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
clear: both;
|
||||||
|
.supportDisplay {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.supportFlex {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
&:hover {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.supportList {
|
||||||
|
padding: 22px;
|
||||||
|
width: calc(100% - 15px);
|
||||||
|
margin-right: 15px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: solid 1px #bbbbbb;
|
||||||
|
display: flex;
|
||||||
|
flex: 1 0 auto;
|
||||||
|
.supportBrand {
|
||||||
|
padding-right: 10px;
|
||||||
|
}
|
||||||
|
.supportContainer {
|
||||||
|
.title {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #303030;
|
||||||
|
border-bottom: 1px solid transparent;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
}
|
||||||
|
.descriptionText {
|
||||||
|
font-weight: normal;
|
||||||
|
color: #303030;
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
background-color: #ecf0f2;
|
||||||
|
.title {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
29
console/src/components/Services/Support/SupportContainer.tsx
Normal file
29
console/src/components/Services/Support/SupportContainer.tsx
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Link } from 'react-router';
|
||||||
|
import { RightContainer } from '../../Common/Layout/RightContainer';
|
||||||
|
import LeftContainer from '../../Common/Layout/LeftContainer/LeftContainer';
|
||||||
|
import PageContainer from '../../Common/Layout/PageContainer/PageContainer';
|
||||||
|
|
||||||
|
import styles from '../../Common/TableCommon/Table.scss';
|
||||||
|
|
||||||
|
const helmetTitle = 'Support Forums | Hasura';
|
||||||
|
|
||||||
|
const LeftBar = () => (
|
||||||
|
<LeftContainer>
|
||||||
|
<ul>
|
||||||
|
<li role="presentation" className={styles.active}>
|
||||||
|
<Link className={styles.linkBorder} to="/support/forums/">
|
||||||
|
Support Forums
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</LeftContainer>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const SupportContainer: React.FC = ({ children }) => {
|
||||||
|
return (
|
||||||
|
<PageContainer helmet={helmetTitle} leftContainer={<LeftBar />}>
|
||||||
|
<RightContainer>{children}</RightContainer>
|
||||||
|
</PageContainer>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,6 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||||
|
<g id="prefix__Group_8285" data-name="Group 8285" transform="translate(.334)">
|
||||||
|
<path id="prefix__discord-brands" d="M13.474 11.025a1.188 1.188 0 1 1-1.184-1.288 1.236 1.236 0 0 1 1.184 1.288zm-5.42-1.288a1.293 1.293 0 0 0 0 2.576 1.236 1.236 0 0 0 1.184-1.288 1.229 1.229 0 0 0-1.184-1.288zm12.255-7.346V23.21c-2.924-2.584-1.989-1.728-5.385-4.886l.615 2.147H2.379A2.385 2.385 0 0 1 0 18.081V2.391A2.385 2.385 0 0 1 2.379 0H17.93a2.385 2.385 0 0 1 2.379 2.391zM17 13.392a15.541 15.541 0 0 0-1.67-6.765 5.739 5.739 0 0 0-3.261-1.219l-.162.186A7.724 7.724 0 0 1 14.8 7.068a9.861 9.861 0 0 0-8.669-.337c-.429.2-.685.337-.685.337a7.829 7.829 0 0 1 3.052-1.52l-.116-.139a5.739 5.739 0 0 0-3.264 1.218 15.541 15.541 0 0 0-1.671 6.766 4.208 4.208 0 0 0 3.54 1.764s.429-.522.778-.963a3.609 3.609 0 0 1-2.031-1.369c.171.119.452.274.476.29a8.459 8.459 0 0 0 7.242.406 6.641 6.641 0 0 0 1.335-.685 3.662 3.662 0 0 1-2.1 1.381c.348.441.766.94.766.94A4.242 4.242 0 0 0 17 13.392z" transform="translate(1.762 .146)" style="fill:#505050"/>
|
||||||
|
<path id="prefix__Rectangle_4799" d="M0 0H24V24H0z" data-name="Rectangle 4799" transform="translate(-.334)" style="fill:none"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
14
console/src/components/Services/Support/images/docs.svg
Normal file
14
console/src/components/Services/Support/images/docs.svg
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||||
|
<defs>
|
||||||
|
<style>
|
||||||
|
.prefix__cls-1{fill:none;stroke:#505050;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5px}
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
<g id="prefix__streamline-icon-paginate-filter-text_24x24" data-name="streamline-icon-paginate-filter-text@24x24" transform="translate(0 .004)">
|
||||||
|
<path id="prefix__Path_5887" d="M3.75.746h19.5v19.5H3.75z" class="prefix__cls-1" data-name="Path 5887"/>
|
||||||
|
<path id="prefix__Path_5888" d="M20.25 23.246h-18a1.5 1.5 0 0 1-1.5-1.5v-18" class="prefix__cls-1" data-name="Path 5888"/>
|
||||||
|
<path id="prefix__Path_5889" d="M8.25 6.746h10.5" class="prefix__cls-1" data-name="Path 5889"/>
|
||||||
|
<path id="prefix__Path_5890" d="M8.25 9.746h10.5" class="prefix__cls-1" data-name="Path 5890"/>
|
||||||
|
<path id="prefix__Path_5891" d="M8.25 12.746h7.5" class="prefix__cls-1" data-name="Path 5891"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 976 B |
@ -0,0 +1,5 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24.021" height="23.585" viewBox="0 0 24.021 23.585">
|
||||||
|
<g id="prefix__streamline-icon-developer-community-github-1_24x24_1_" data-name="streamline-icon-developer-community-github-1@24x24 (1)" transform="translate(.08 -.021)">
|
||||||
|
<path id="prefix__Path_5872" d="M14.41 22.35a.5.5 0 0 0 .64.48 11.25 11.25 0 1 0-6.24 0 .5.5 0 0 0 .64-.48V20A2.81 2.81 0 0 1 6 18.28 6.07 6.07 0 0 0 4.64 16c2.85.69 2.9 2.54 4.84 1.67a4 4 0 0 1 .63-1.82c-2.2-.25-4.52-.6-4.52-4.4a3.84 3.84 0 0 1 1-2.66 3.56 3.56 0 0 1 .1-2.62s.83-.27 2.73 1a9.39 9.39 0 0 1 5 0c1.89-1.28 2.72-1 2.72-1a3.56 3.56 0 0 1 .1 2.62 3.83 3.83 0 0 1 1 2.66c0 3.81-2.32 4.15-4.53 4.39a3.83 3.83 0 0 1 .68 2.33z" data-name="Path 5872" style="fill:none;stroke:#505050;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5px"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 855 B |
@ -0,0 +1,16 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="20.075" height="23.735" viewBox="0 0 20.075 23.735">
|
||||||
|
<defs>
|
||||||
|
<style>
|
||||||
|
.prefix__cls-1{fill:none;stroke:#505050;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5px}
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
<g id="prefix__streamline-icon-developer-community-stack-overflow_24x24" data-name="streamline-icon-developer-community-stack-overflow@24x24" transform="translate(-2.1 -.015)">
|
||||||
|
<path id="prefix__Path_5873" d="M17.85 14.46V21a2 2 0 0 1-2 2h-11a2 2 0 0 1-2-2v-6.5" class="prefix__cls-1" data-name="Path 5873"/>
|
||||||
|
<path id="prefix__Path_5874" d="M14.25 18.96h-8" class="prefix__cls-1" data-name="Path 5874"/>
|
||||||
|
<path id="prefix__Path_5875" d="M14.63 16.27l-7.82-1.66" class="prefix__cls-1" data-name="Path 5875"/>
|
||||||
|
<path id="prefix__Path_5876" d="M15.57 13.72l-7.31-3.24" class="prefix__cls-1" data-name="Path 5876"/>
|
||||||
|
<path id="prefix__Path_5877" d="M17.01 11.42l-6.48-4.69" class="prefix__cls-1" data-name="Path 5877"/>
|
||||||
|
<path id="prefix__Path_5878" d="M18.9 9.48l-5.36-5.94" class="prefix__cls-1" data-name="Path 5878"/>
|
||||||
|
<path id="prefix__Path_5879" d="M21.15 7.96l-4.01-6.92" class="prefix__cls-1" data-name="Path 5879"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
@ -1,25 +0,0 @@
|
|||||||
import styled from 'styled-components';
|
|
||||||
import {
|
|
||||||
flexbox,
|
|
||||||
color,
|
|
||||||
border,
|
|
||||||
typography,
|
|
||||||
layout,
|
|
||||||
space,
|
|
||||||
shadow,
|
|
||||||
} from 'styled-system';
|
|
||||||
|
|
||||||
export const StyledAlertBox = styled.div`
|
|
||||||
${flexbox};
|
|
||||||
${color}
|
|
||||||
${border}
|
|
||||||
${typography}
|
|
||||||
${layout}
|
|
||||||
${space}
|
|
||||||
${shadow}
|
|
||||||
|
|
||||||
/* Alert type text */
|
|
||||||
span {
|
|
||||||
text-transform: capitalize;
|
|
||||||
}
|
|
||||||
`;
|
|
45
console/src/components/UIKit/atoms/AlertBox/AlertBox.ts
Normal file
45
console/src/components/UIKit/atoms/AlertBox/AlertBox.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
import {
|
||||||
|
flexbox,
|
||||||
|
color,
|
||||||
|
border,
|
||||||
|
typography,
|
||||||
|
layout,
|
||||||
|
space,
|
||||||
|
shadow,
|
||||||
|
FlexboxProps,
|
||||||
|
ColorProps,
|
||||||
|
BorderProps,
|
||||||
|
TypographyProps,
|
||||||
|
LayoutProps,
|
||||||
|
SpaceProps,
|
||||||
|
ShadowProps,
|
||||||
|
} from 'styled-system';
|
||||||
|
|
||||||
|
import { BoxProps, Box } from '../Box';
|
||||||
|
|
||||||
|
interface StyledAlertBoxOwnProps
|
||||||
|
extends FlexboxProps,
|
||||||
|
ColorProps,
|
||||||
|
BorderProps,
|
||||||
|
TypographyProps,
|
||||||
|
LayoutProps,
|
||||||
|
SpaceProps,
|
||||||
|
ShadowProps {}
|
||||||
|
|
||||||
|
export interface StyledAlertBoxProps extends StyledAlertBoxOwnProps, BoxProps {}
|
||||||
|
|
||||||
|
export const StyledAlertBox = styled(Box)<StyledAlertBoxProps>`
|
||||||
|
${flexbox};
|
||||||
|
${color}
|
||||||
|
${border}
|
||||||
|
${typography}
|
||||||
|
${layout}
|
||||||
|
${space}
|
||||||
|
${shadow}
|
||||||
|
|
||||||
|
/* Alert type text */
|
||||||
|
span {
|
||||||
|
text-transform: capitalize;
|
||||||
|
}
|
||||||
|
`;
|
@ -1,13 +1,19 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { theme } from '../../theme';
|
import { theme, Theme } from '../../theme';
|
||||||
import { Icon } from '../Icon';
|
import { Icon, IconProps } from '../Icon';
|
||||||
import { StyledAlertBox } from './AlertBox';
|
import { StyledAlertBox, StyledAlertBoxProps } from './AlertBox';
|
||||||
import { Text } from '../Typography';
|
import { Text } from '../Typography';
|
||||||
|
|
||||||
const alertBoxWidth = 866;
|
const alertBoxWidth = 866;
|
||||||
|
|
||||||
export const AlertBox = props => {
|
export interface AlertBoxProps
|
||||||
|
extends IconProps,
|
||||||
|
Omit<StyledAlertBoxProps, 'size'> {
|
||||||
|
type: keyof Theme['alertBox'];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AlertBox: React.FC<AlertBoxProps> = props => {
|
||||||
const { children, type } = props;
|
const { children, type } = props;
|
||||||
|
|
||||||
const backgroundColorValue = theme.alertBox[type]
|
const backgroundColorValue = theme.alertBox[type]
|
@ -112,8 +112,8 @@ const iconReferenceMap = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type IconProps = {
|
export type IconProps = {
|
||||||
pointer: boolean;
|
pointer?: boolean;
|
||||||
size: number;
|
size?: number;
|
||||||
type: keyof Theme['icon'];
|
type: keyof Theme['icon'];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -16,14 +16,17 @@ Heading.defaultProps = {
|
|||||||
* fontSize: 'explain'
|
* fontSize: 'explain'
|
||||||
* fontWeight: 'bold'
|
* fontWeight: 'bold'
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export type TextProps = {
|
export type TextProps = {
|
||||||
type: keyof Theme['lineHeights'];
|
type?: keyof Theme['lineHeights'];
|
||||||
fontWeight: keyof Theme['fontWeights'];
|
fontWeight?: keyof Theme['fontWeights'];
|
||||||
fontSize: keyof Theme['fontSizes'];
|
fontSize?: keyof Theme['fontSizes'];
|
||||||
mb: keyof Theme['space'];
|
mb?: keyof Theme['space'];
|
||||||
mt: keyof Theme['space'];
|
mt?: keyof Theme['space'];
|
||||||
mr: keyof Theme['space'];
|
mr?: keyof Theme['space'];
|
||||||
ml: keyof Theme['space'];
|
ml?: keyof Theme['space'];
|
||||||
|
pl?: keyof Theme['space'];
|
||||||
|
as?: keyof JSX.IntrinsicElements | React.ComponentType<any>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Text: React.FC<TextProps> = props => {
|
export const Text: React.FC<TextProps> = props => {
|
||||||
@ -64,6 +67,9 @@ Text.defaultProps = {
|
|||||||
mt: 'zero',
|
mt: 'zero',
|
||||||
mr: 'zero',
|
mr: 'zero',
|
||||||
ml: 'zero',
|
ml: 'zero',
|
||||||
|
pl: 'zero',
|
||||||
|
fontWeight: 'normal',
|
||||||
|
fontSize: 'p',
|
||||||
};
|
};
|
||||||
|
|
||||||
type TextLinkProps = {
|
type TextLinkProps = {
|
||||||
|
@ -81,10 +81,6 @@ const Html: React.FC<HtmlProps> = props => {
|
|||||||
|
|
||||||
<div id="content" className="content" />
|
<div id="content" className="content" />
|
||||||
<script src={`${assets.javascript.main}`} charSet="UTF-8" />
|
<script src={`${assets.javascript.main}`} charSet="UTF-8" />
|
||||||
{/*
|
|
||||||
<script src="//cdn.jsdelivr.net/gh/highlightjs/cdn-release@9.13.1/build/highlight.min.js" />
|
|
||||||
<script type="text/javascript" src="https://unpkg.com/sql-formatter@latest/dist/sql-formatter.min.js" />
|
|
||||||
*/}
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
|
31
console/src/hooks/useOnClickOutside.ts
Normal file
31
console/src/hooks/useOnClickOutside.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
|
const MOUSEDOWN = 'mousedown';
|
||||||
|
const TOUCHSTART = 'touchstart';
|
||||||
|
|
||||||
|
type HandledEvents = [typeof MOUSEDOWN, typeof TOUCHSTART];
|
||||||
|
type HandledEventsType = HandledEvents[number];
|
||||||
|
type PossibleEvent = {
|
||||||
|
[Type in HandledEventsType]: HTMLElementEventMap[Type];
|
||||||
|
}[HandledEventsType];
|
||||||
|
type Handler = (event: PossibleEvent) => void;
|
||||||
|
|
||||||
|
export const useOnClickOutside = (
|
||||||
|
ref: React.RefObject<HTMLElement>,
|
||||||
|
handler: Handler
|
||||||
|
) => {
|
||||||
|
useEffect(() => {
|
||||||
|
const listener = (event: PossibleEvent) => {
|
||||||
|
if (!ref.current || ref.current.contains(event.target as Node)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
handler(event);
|
||||||
|
};
|
||||||
|
document.addEventListener('mousedown', listener);
|
||||||
|
document.addEventListener('touchstart', listener);
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('mousedown', listener);
|
||||||
|
document.removeEventListener('touchstart', listener);
|
||||||
|
};
|
||||||
|
}, [ref, handler]);
|
||||||
|
};
|
10
console/src/hooks/useToggle.ts
Normal file
10
console/src/hooks/useToggle.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
|
export const useToggle = (
|
||||||
|
initialValue: boolean
|
||||||
|
): [boolean, () => void, React.Dispatch<React.SetStateAction<boolean>>] => {
|
||||||
|
const [value, setValue] = useState(initialValue);
|
||||||
|
const toggleValue = () => setValue(prev => !prev);
|
||||||
|
|
||||||
|
return [value, toggleValue, setValue];
|
||||||
|
};
|
@ -40,6 +40,8 @@ import { showErrorNotification } from './components/Services/Common/Notification
|
|||||||
import { CLI_CONSOLE_MODE } from './constants';
|
import { CLI_CONSOLE_MODE } from './constants';
|
||||||
import UIKit from './components/UIKit/';
|
import UIKit from './components/UIKit/';
|
||||||
import { Heading } from './components/UIKit/atoms';
|
import { Heading } from './components/UIKit/atoms';
|
||||||
|
import { SupportContainer } from './components/Services/Support/SupportContainer';
|
||||||
|
import HelpPage from './components/Services/Support/HelpPage';
|
||||||
|
|
||||||
const routes = store => {
|
const routes = store => {
|
||||||
// load hasuractl migration status
|
// load hasuractl migration status
|
||||||
@ -71,7 +73,6 @@ const routes = store => {
|
|||||||
|
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
const _dataRouterUtils = dataRouterUtils(connect, store, composeOnEnterHooks);
|
const _dataRouterUtils = dataRouterUtils(connect, store, composeOnEnterHooks);
|
||||||
const requireSchema = _dataRouterUtils.requireSchema;
|
const requireSchema = _dataRouterUtils.requireSchema;
|
||||||
const dataRouter = _dataRouterUtils.makeDataRouter;
|
const dataRouter = _dataRouterUtils.makeDataRouter;
|
||||||
@ -146,6 +147,9 @@ const routes = store => {
|
|||||||
{actionsRouter}
|
{actionsRouter}
|
||||||
{eventsRouter}
|
{eventsRouter}
|
||||||
{uiKitRouter}
|
{uiKitRouter}
|
||||||
|
<Route path="support" component={SupportContainer}>
|
||||||
|
<Route path="forums" component={HelpPage} />
|
||||||
|
</Route>
|
||||||
</Route>
|
</Route>
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="404" component={PageNotFound} status="404" />
|
<Route path="404" component={PageNotFound} status="404" />
|
||||||
|
@ -26,10 +26,10 @@ type Telemetry = {
|
|||||||
|
|
||||||
const setConsoleOptsInDB = (
|
const setConsoleOptsInDB = (
|
||||||
opts: TelemetryState['console_opts'],
|
opts: TelemetryState['console_opts'],
|
||||||
successCb: (arg: object) => void,
|
successCb: (arg: Record<string, any>) => void,
|
||||||
errorCb: (arg: Error) => void
|
errorCb: (arg: Error) => void
|
||||||
) => (
|
) => (
|
||||||
dispatch: ThunkDispatch<ReduxState, {}, AnyAction>,
|
dispatch: ThunkDispatch<ReduxState, unknown, AnyAction>,
|
||||||
getState: GetReduxState
|
getState: GetReduxState
|
||||||
) => {
|
) => {
|
||||||
const url = Endpoints.getSchema;
|
const url = Endpoints.getSchema;
|
||||||
@ -66,7 +66,7 @@ const setConsoleOptsInDB = (
|
|||||||
|
|
||||||
// eslint-disable-next-line consistent-return
|
// eslint-disable-next-line consistent-return
|
||||||
return dispatch(requestAction(url, options)).then(
|
return dispatch(requestAction(url, options)).then(
|
||||||
(data: object) => {
|
(data: Record<string, unknown>) => {
|
||||||
if (successCb) {
|
if (successCb) {
|
||||||
successCb(data);
|
successCb(data);
|
||||||
}
|
}
|
||||||
@ -86,7 +86,7 @@ const telemetryNotificationShown = () => (
|
|||||||
};
|
};
|
||||||
|
|
||||||
const setTelemetryNotificationShownInDB = () => {
|
const setTelemetryNotificationShownInDB = () => {
|
||||||
const successCb = (data: object) => {
|
const successCb = (data: Record<string, unknown>) => {
|
||||||
console.log(
|
console.log(
|
||||||
`Updated telemetry notification status in db ${JSON.stringify(data)}`
|
`Updated telemetry notification status in db ${JSON.stringify(data)}`
|
||||||
);
|
);
|
||||||
@ -108,7 +108,7 @@ const setTelemetryNotificationShownInDB = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const setPreReleaseNotificationOptOutInDB = () => (
|
const setPreReleaseNotificationOptOutInDB = () => (
|
||||||
dispatch: ThunkDispatch<ReduxState, {}, AnyAction>
|
dispatch: ThunkDispatch<ReduxState, unknown, AnyAction>
|
||||||
) => {
|
) => {
|
||||||
const successCb = () => {
|
const successCb = () => {
|
||||||
dispatch(
|
dispatch(
|
||||||
@ -132,7 +132,7 @@ const setPreReleaseNotificationOptOutInDB = () => (
|
|||||||
|
|
||||||
const loadConsoleOpts = () => {
|
const loadConsoleOpts = () => {
|
||||||
return (
|
return (
|
||||||
dispatch: ThunkDispatch<ReduxState, {}, AnyAction>,
|
dispatch: ThunkDispatch<ReduxState, unknown, AnyAction>,
|
||||||
getState: GetReduxState
|
getState: GetReduxState
|
||||||
) => {
|
) => {
|
||||||
const url = Endpoints.getSchema;
|
const url = Endpoints.getSchema;
|
||||||
@ -172,12 +172,12 @@ const loadConsoleOpts = () => {
|
|||||||
|
|
||||||
interface SetConsoleOptsAction {
|
interface SetConsoleOptsAction {
|
||||||
type: typeof SET_CONSOLE_OPTS;
|
type: typeof SET_CONSOLE_OPTS;
|
||||||
data: object;
|
data: Record<string, unknown>;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SetNotificationShowAction {
|
interface SetNotificationShowAction {
|
||||||
type: typeof SET_NOTIFICATION_SHOWN;
|
type: typeof SET_NOTIFICATION_SHOWN;
|
||||||
data?: object;
|
data?: Record<string, unknown>;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SetHasuraUuid {
|
interface SetHasuraUuid {
|
||||||
@ -193,7 +193,7 @@ type TelemetryActionTypes =
|
|||||||
export const requireConsoleOpts = ({
|
export const requireConsoleOpts = ({
|
||||||
dispatch,
|
dispatch,
|
||||||
}: {
|
}: {
|
||||||
dispatch: ThunkDispatch<ReduxState, {}, AnyAction>;
|
dispatch: ThunkDispatch<ReduxState, unknown, AnyAction>;
|
||||||
}) => (nextState: ReduxState, replaceState: ReduxState, callback: any) => {
|
}) => (nextState: ReduxState, replaceState: ReduxState, callback: any) => {
|
||||||
dispatch(loadConsoleOpts()).finally(callback);
|
dispatch(loadConsoleOpts()).finally(callback);
|
||||||
};
|
};
|
||||||
|
@ -6,13 +6,13 @@ import { setTelemetryNotificationShownInDB } from './Actions';
|
|||||||
import { ReduxState } from '../types';
|
import { ReduxState } from '../types';
|
||||||
|
|
||||||
const onRemove = () => {
|
const onRemove = () => {
|
||||||
return (dispatch: ThunkDispatch<ReduxState, {}, AnyAction>) => {
|
return (dispatch: ThunkDispatch<ReduxState, unknown, AnyAction>) => {
|
||||||
dispatch(setTelemetryNotificationShownInDB());
|
dispatch(setTelemetryNotificationShownInDB());
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const showTelemetryNotification = () => {
|
const showTelemetryNotification = () => {
|
||||||
return (dispatch: ThunkDispatch<ReduxState, {}, AnyAction>) => {
|
return (dispatch: ThunkDispatch<ReduxState, unknown, AnyAction>) => {
|
||||||
dispatch(
|
dispatch(
|
||||||
Notifications.show({
|
Notifications.show({
|
||||||
position: 'tr',
|
position: 'tr',
|
||||||
|
94
console/src/utils/postgresColumnTypes.ts
Normal file
94
console/src/utils/postgresColumnTypes.ts
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
/**
|
||||||
|
* THIS IS A WORK IN PROGRESS
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface IntegerColumnType {
|
||||||
|
int2: number;
|
||||||
|
int4: number;
|
||||||
|
int8: number;
|
||||||
|
smallint: number;
|
||||||
|
int: number;
|
||||||
|
bigint: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PrecisionColumnType {
|
||||||
|
real: number;
|
||||||
|
float4: number;
|
||||||
|
float: number;
|
||||||
|
float8: number;
|
||||||
|
numeric: number;
|
||||||
|
decimal: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SerialColumnType {
|
||||||
|
smallserial: number;
|
||||||
|
serial: number;
|
||||||
|
bigserial: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StringColumnType {
|
||||||
|
uuid: string;
|
||||||
|
text: string;
|
||||||
|
varchar: string;
|
||||||
|
char: string;
|
||||||
|
citext: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BooleanColumnType {
|
||||||
|
bit: boolean;
|
||||||
|
bool: boolean;
|
||||||
|
boolean: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DateTimeColumnType {
|
||||||
|
date: string;
|
||||||
|
timestamp: string;
|
||||||
|
timestamptz: string;
|
||||||
|
time: string;
|
||||||
|
timetz: string;
|
||||||
|
interval: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NetworkAddressColumnType {
|
||||||
|
inet: string;
|
||||||
|
cidr: string;
|
||||||
|
macaddr: string;
|
||||||
|
macaddr8: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MiscellaneousColumnType {
|
||||||
|
money: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface JSONColumnType {
|
||||||
|
json: Record<string, unknown>;
|
||||||
|
jsonb: Record<string, unknown>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type JSONColumn = keyof JSONColumnType;
|
||||||
|
|
||||||
|
export interface ByteColumnType {
|
||||||
|
bytea: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type IntegerColumn = keyof IntegerColumnType;
|
||||||
|
export type PrecisionColumn = keyof PrecisionColumnType;
|
||||||
|
export type SerialColumn = keyof SerialColumnType;
|
||||||
|
export type StringColumn = keyof StringColumnType;
|
||||||
|
export type BooleanColumn = keyof BooleanColumnType;
|
||||||
|
export type DateTimeColumn = keyof DateTimeColumnType;
|
||||||
|
export type NetworkAddressColumn = keyof NetworkAddressColumnType;
|
||||||
|
export type MiscellaneousColumn = keyof MiscellaneousColumnType;
|
||||||
|
export type ByteColumn = keyof ByteColumnType;
|
||||||
|
|
||||||
|
export type Column =
|
||||||
|
| IntegerColumn
|
||||||
|
| PrecisionColumn
|
||||||
|
| SerialColumn
|
||||||
|
| StringColumn
|
||||||
|
| BooleanColumn
|
||||||
|
| DateTimeColumn
|
||||||
|
| NetworkAddressColumn
|
||||||
|
| MiscellaneousColumn
|
||||||
|
| JSONColumn
|
||||||
|
| ByteColumn;
|
@ -1,5 +1,7 @@
|
|||||||
.. title:: 404 - Page Not Found
|
.. title:: 404 - Page Not Found
|
||||||
|
|
||||||
|
:orphan:
|
||||||
|
|
||||||
404 - Page Not Found
|
404 - Page Not Found
|
||||||
---------------------
|
---------------------
|
||||||
|
|
||||||
|
25
docs/_static/scripts/hdocs.js
vendored
25
docs/_static/scripts/hdocs.js
vendored
@ -14,8 +14,8 @@ window.hdocs = (function () {
|
|||||||
|
|
||||||
docsearch({
|
docsearch({
|
||||||
appId: 'WCBB1VVLRC',
|
appId: 'WCBB1VVLRC',
|
||||||
apiKey: '298d448cd9d7ed93fbab395658da19e8',
|
apiKey: HDOCS_ALGOLIA_API_KEY,
|
||||||
indexName: 'graphql-docs-prod',
|
indexName: HDOCS_ALGOLIA_INDEX,
|
||||||
inputSelector: '#search_element',
|
inputSelector: '#search_element',
|
||||||
transformData: hdocs.transformSearchData,
|
transformData: hdocs.transformSearchData,
|
||||||
debug: false
|
debug: false
|
||||||
@ -116,13 +116,22 @@ window.hdocs = (function () {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const searchField = document.getElementById('search_element');
|
||||||
|
const searchHelp = document.getElementById('search_help');
|
||||||
|
const hideHelp = function () { searchHelp.classList.add('hide'); }
|
||||||
|
|
||||||
if (suggestions.length === 0) {
|
if (suggestions.length === 0) {
|
||||||
setTimeout(function () { document.getElementById('search_help').classList.remove('hide'); }, 100);
|
setTimeout(function () { searchHelp.classList.remove('hide'); }, 100);
|
||||||
document.getElementById('search_element').addEventListener('blur', function () {
|
searchField.addEventListener('blur', function () {
|
||||||
setTimeout(function () { document.getElementById('search_help').classList.add('hide'); }, 100);
|
setTimeout(hideHelp, 100);
|
||||||
});
|
}, { once: true });
|
||||||
} else if (!document.getElementById('search_help').classList.contains('hide')) {
|
searchField.addEventListener('input', function () {
|
||||||
document.getElementById('search_help').classList.add('hide');
|
if (searchField.value === '') {
|
||||||
|
setTimeout(hideHelp, 100);
|
||||||
|
}
|
||||||
|
}, { once: true });
|
||||||
|
} else if (!searchHelp.classList.contains('hide')) {
|
||||||
|
hideHelp();
|
||||||
}
|
}
|
||||||
|
|
||||||
return suggestions;
|
return suggestions;
|
||||||
|
119
docs/_static/scripts/mysql-subscribe.js
vendored
Normal file
119
docs/_static/scripts/mysql-subscribe.js
vendored
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
const mysql_email_input = document.getElementById('mysql-EMAIL');
|
||||||
|
const mysql_submit_btn = document.getElementById('mysql-embedded-subscribe');
|
||||||
|
const mysql_success_status = document.getElementById('mysql-success-response');
|
||||||
|
const mysql_status_error = document.getElementById('mysql-error-response');
|
||||||
|
|
||||||
|
mysql_email_input.addEventListener('input', function() {
|
||||||
|
mysql_submit_btn.value = 'Subscribe';
|
||||||
|
mysql_submit_btn.disabled = false;
|
||||||
|
|
||||||
|
mysql_status_error.innerHTML = '';
|
||||||
|
mysql_status_error.classList.add('hide');
|
||||||
|
});
|
||||||
|
|
||||||
|
const showMySQLErrorMsg = () => {
|
||||||
|
mysql_submit_btn.value = 'Subscribe';
|
||||||
|
mysql_submit_btn.disabled = false;
|
||||||
|
mysql_status_error.innerHTML = 'Please enter a valid email';
|
||||||
|
mysql_status_error.classList.remove('hide');
|
||||||
|
clearMySQLMsg();
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearMySQLMsg = () => {
|
||||||
|
setTimeout(function(){
|
||||||
|
mysql_success_status.innerHTML = '';
|
||||||
|
mysql_status_error.innerHTML = '';
|
||||||
|
|
||||||
|
mysql_success_status.classList.add('hide');
|
||||||
|
mysql_status_error.classList.add('hide');
|
||||||
|
mysql_submit_btn.disabled = true;
|
||||||
|
//reset input
|
||||||
|
mysql_email_input.value = '';
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
const submitMySQLForm = function (form) {
|
||||||
|
|
||||||
|
let gqlEndpoint = 'https://graphql-engine-website.hasura.io/v1/graphql';
|
||||||
|
if(window.location.host !== "hasura.io") {
|
||||||
|
gqlEndpoint = 'https://graphql-engine-website.hasura-stg.hasura-app.io/v1/graphql';
|
||||||
|
}
|
||||||
|
// change button state
|
||||||
|
mysql_submit_btn.value = 'Subscribing...';
|
||||||
|
mysql_submit_btn.disabled = true;
|
||||||
|
|
||||||
|
const email = form.elements["EMAIL"].value;
|
||||||
|
|
||||||
|
const hbs_context = {
|
||||||
|
"hutk": readCookie("hubspotutk"), // include this parameter and set it to the hubspotutk cookie value to enable cookie tracking on your submission
|
||||||
|
"pageUri": window.location.host + window.location.pathname,
|
||||||
|
"pageName": document.title,
|
||||||
|
};
|
||||||
|
|
||||||
|
const gqlMutation = `mutation notifyDatabaseSupport($object: databaseSupportInput!) {
|
||||||
|
notifyDatabaseSupport(object: $object) {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
|
||||||
|
const object = {
|
||||||
|
"category": "docs",
|
||||||
|
"database": "MySQL",
|
||||||
|
"email": email,
|
||||||
|
"hbs_context": hbs_context
|
||||||
|
};
|
||||||
|
|
||||||
|
fetch(gqlEndpoint, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
query: gqlMutation,
|
||||||
|
variables: { object: object }
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
// change button state
|
||||||
|
mysql_submit_btn.value = 'Subscribe';
|
||||||
|
mysql_submit_btn.disabled = false;
|
||||||
|
if (data && data.data && data.data.notifyDatabaseSupport.id) {
|
||||||
|
mysql_success_status.innerHTML = 'Subscribed!';
|
||||||
|
mysql_success_status.classList.remove('hide');
|
||||||
|
} else {
|
||||||
|
if(data.errors && data.errors[0].message.includes('Uniqueness violation')) {
|
||||||
|
mysql_status_error.innerHTML = 'You have already subscribed';
|
||||||
|
} else {
|
||||||
|
mysql_status_error.innerHTML = 'Something went wrong';
|
||||||
|
}
|
||||||
|
mysql_status_error.classList.remove('hide');
|
||||||
|
}
|
||||||
|
clearMySQLMsg();
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Error:', error);
|
||||||
|
mysql_submit_btn.value = 'Subscribe';
|
||||||
|
mysql_submit_btn.disabled = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener('submit', function (event) {
|
||||||
|
|
||||||
|
// Only run on forms flagged for mysql-subscribe-form validation
|
||||||
|
if (!event.target.classList.contains('mysql-subscribe-form')) return;
|
||||||
|
|
||||||
|
// Prevent form from submitting
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
// email validation
|
||||||
|
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||||
|
|
||||||
|
if(!emailPattern.test(mysql_email_input.value)) {
|
||||||
|
showMySQLErrorMsg();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, let the form submit normally
|
||||||
|
submitMySQLForm(event.target);
|
||||||
|
|
||||||
|
|
||||||
|
}, false);
|
9
docs/_static/scripts/newsletter.js
vendored
9
docs/_static/scripts/newsletter.js
vendored
@ -1,7 +1,7 @@
|
|||||||
const email_input = document.getElementById('mce-EMAIL');
|
const email_input = document.getElementById('mce-EMAIL');
|
||||||
const submit_btn = document.getElementById('mc-embedded-subscribe');
|
const submit_btn = document.getElementById('mc-embedded-subscribe');
|
||||||
const mcStatusSuccess = document.querySelector('.mce-success-response');
|
const mcStatusSuccess = document.getElementById('mce-success-response');
|
||||||
const mcStatusError = document.querySelector('.mce-error-response');
|
const mcStatusError = document.getElementById('mce-error-response');
|
||||||
|
|
||||||
email_input.addEventListener('input', function() {
|
email_input.addEventListener('input', function() {
|
||||||
submit_btn.value = 'Subscribe';
|
submit_btn.value = 'Subscribe';
|
||||||
@ -60,16 +60,19 @@ const submitNewsletterForm = function (form) {
|
|||||||
"pageUri": window.location.host + window.location.pathname,
|
"pageUri": window.location.host + window.location.pathname,
|
||||||
"pageName": document.title,
|
"pageName": document.title,
|
||||||
};
|
};
|
||||||
|
|
||||||
const gqlMutation = `mutation docsNewsletterSignup($objects: [newsletterSignupInput!]! ) {
|
const gqlMutation = `mutation docsNewsletterSignup($objects: [newsletterSignupInput!]! ) {
|
||||||
signupNewsletter(objects: $objects) {
|
signupNewsletter(objects: $objects) {
|
||||||
affected_rows
|
affected_rows
|
||||||
}
|
}
|
||||||
}`;
|
}`;
|
||||||
|
|
||||||
const objects = [{
|
const objects = [{
|
||||||
"email": email,
|
"email": email,
|
||||||
"hbs_context": hbs_context,
|
"hbs_context": hbs_context,
|
||||||
"category": "docs"
|
"category": "docs"
|
||||||
}]
|
}];
|
||||||
|
|
||||||
fetch(gqlEndpoint, {
|
fetch(gqlEndpoint, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
|
6
docs/_static/styles/landing.css
vendored
6
docs/_static/styles/landing.css
vendored
@ -15,13 +15,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.body_content {
|
.body_content {
|
||||||
font-family: 'Gudea';
|
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
color: #333;
|
color: #333;
|
||||||
}
|
}
|
||||||
|
|
||||||
.small_content {
|
.small_content {
|
||||||
font-family: 'Gudea';
|
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: #333;
|
color: #333;
|
||||||
}
|
}
|
||||||
@ -101,6 +99,10 @@
|
|||||||
padding-bottom: 0;
|
padding-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
.text_left {
|
.text_left {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
54
docs/_static/styles/main.css
vendored
54
docs/_static/styles/main.css
vendored
@ -99,16 +99,16 @@ p {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Newsletter */
|
/* Newsletter */
|
||||||
#mc_embed_signup {
|
.mc_embed_signup {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
#mc_embed_signup .subscribe-form-content {
|
.mc_embed_signup .subscribe-form-content {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: #001934;
|
color: #001934;
|
||||||
padding-right: 40px;
|
padding-right: 40px;
|
||||||
}
|
}
|
||||||
#mc_embed_signup form {
|
.mc_embed_signup form {
|
||||||
display: flex !important;
|
display: flex !important;
|
||||||
padding: 0 0 0 0 !important;
|
padding: 0 0 0 0 !important;
|
||||||
flex: 1 !important;
|
flex: 1 !important;
|
||||||
@ -116,17 +116,17 @@ p {
|
|||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
/* todo: redesign & clean up */
|
/* todo: redesign & clean up */
|
||||||
#mc_embed_signup input:focus {border-color:#333;}
|
.mc_embed_signup input:focus {border-color:#333;}
|
||||||
#mc_embed_signup .button {clear:both; background-color: #aaa; border: 0 none; border-radius:4px; transition: all 0.23s ease-in-out 0s; color: #FFFFFF; cursor: pointer; display: inline-block; font-size:15px; font-weight: normal; height: 32px; line-height: 32px; margin: 0 5px 10px 0; padding: 0 22px; text-align: center; text-decoration: none; vertical-align: top; white-space: nowrap; width: auto;}
|
.mc_embed_signup .button {clear:both; background-color: #aaa; border: 0 none; border-radius:4px; transition: all 0.23s ease-in-out 0s; color: #FFFFFF; cursor: pointer; display: inline-block; font-size:15px; font-weight: normal; height: 32px; line-height: 32px; margin: 0 5px 10px 0; padding: 0 22px; text-align: center; text-decoration: none; vertical-align: top; white-space: nowrap; width: auto;}
|
||||||
#mc_embed_signup .button:hover {background-color:#777;}
|
.mc_embed_signup .button:hover {background-color:#777;}
|
||||||
#mc_embed_signup .clear {clear:both;}
|
.mc_embed_signup .clear {clear:both;}
|
||||||
|
|
||||||
#mc_embed_signup div#mce-responses {float:left; top:-1.4em; padding:0em .5em 0em .5em; overflow:hidden; width:90%; margin: 0 5%; clear: both;}
|
.mc_embed_signup div.mce-responses {float:left; top:-1.4em; padding:0em .5em 0em .5em; overflow:hidden; width:90%; margin: 0 5%; clear: both;}
|
||||||
#mc_embed_signup div.response {margin:1em 0; padding:1em .5em .5em 0; font-weight:bold; float:left; top:-1.5em; z-index:1; width:80%;}
|
.mc_embed_signup div.response {margin:1em 0; padding:1em .5em .5em 0; font-weight:bold; float:left; top:-1.5em; z-index:1; width:80%;}
|
||||||
#mc_embed_signup #mce-error-response {display:none;}
|
/*.mc_embed_signup .mce-error-response {display:none;}*/
|
||||||
#mc_embed_signup #mce-success-response {color:#529214; display:none;}
|
.mc_embed_signup .mce-success-response {color:#529214;}
|
||||||
#mc_embed_signup label.error {display:block; float:none; width:auto; margin-left:1.05em; text-align:left; padding:.5em 0;}
|
.mc_embed_signup label.error {display:block; float:none; width:auto; margin-left:1.05em; text-align:left; padding:.5em 0;}
|
||||||
#mc-embedded-subscribe {clear:both; width:auto; display:block; margin:1em 0 1em 5%;}
|
.mc-embedded-subscribe {clear:both; width:auto; display:block; margin:1em 0 1em 5%;}
|
||||||
|
|
||||||
.newsletter-link,
|
.newsletter-link,
|
||||||
.newsletter-link:hover,
|
.newsletter-link:hover,
|
||||||
@ -204,7 +204,7 @@ p {
|
|||||||
.submit-box input:focus {
|
.submit-box input:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
#mce-responses .error-message, #mce-responses .success-message {
|
.mce-responses .error-message, .mce-responses .success-message {
|
||||||
background-color: #001934;
|
background-color: #001934;
|
||||||
padding: 8px 12px !important;
|
padding: 8px 12px !important;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
@ -216,13 +216,13 @@ p {
|
|||||||
width: auto !important;
|
width: auto !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
#mce-responses .error-message a, #mce-responses .success-message a {
|
.mce-responses .error-message a, .mce-responses .success-message a {
|
||||||
color: #fff !important;
|
color: #fff !important;
|
||||||
}
|
}
|
||||||
#mce-responses .error-message {
|
.mce-responses .error-message {
|
||||||
color: #efc371 !important;
|
color: #efc371 !important;
|
||||||
}
|
}
|
||||||
#mce-responses .success-message {
|
.mce-responses .success-message {
|
||||||
color: #1cd3c6 !important;
|
color: #1cd3c6 !important;
|
||||||
}
|
}
|
||||||
#content_inner_wrapper {
|
#content_inner_wrapper {
|
||||||
@ -640,6 +640,10 @@ article ol ol {
|
|||||||
margin: 15px;
|
margin: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sphinxsidebarwrapper > ul:not(.current) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
#sidebar {
|
#sidebar {
|
||||||
background-color: #001934;
|
background-color: #001934;
|
||||||
width: 24% !important;
|
width: 24% !important;
|
||||||
@ -1010,17 +1014,17 @@ article ol ol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
@media (min-width: 768px) and (max-width: 1385px) {
|
@media (min-width: 768px) and (max-width: 1385px) {
|
||||||
#mc_embed_signup form {
|
.mc_embed_signup form {
|
||||||
padding-top: 10px !important;
|
padding-top: 10px !important;
|
||||||
}
|
}
|
||||||
#mc_embed_signup {
|
.mc_embed_signup {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
#mc_embed_signup .subscribe-form-content {
|
.mc_embed_signup .subscribe-form-content {
|
||||||
padding-right: 0;
|
padding-right: 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
#mce-responses .error-message, #mce-responses .success-message {
|
.mce-responses .error-message, .mce-responses .success-message {
|
||||||
top: 34px !important;
|
top: 34px !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1030,20 +1034,20 @@ article ol ol {
|
|||||||
top: 170px;
|
top: 170px;
|
||||||
height: calc(100% - 170px);
|
height: calc(100% - 170px);
|
||||||
}
|
}
|
||||||
#mc_embed_signup {
|
.mc_embed_signup {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
#mc_embed_signup .subscribe-form-content {
|
.mc_embed_signup .subscribe-form-content {
|
||||||
padding-right: 0;
|
padding-right: 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
#mc_embed_signup form {
|
.mc_embed_signup form {
|
||||||
padding-top: 10px !important;
|
padding-top: 10px !important;
|
||||||
}
|
}
|
||||||
.submit-box input {
|
.submit-box input {
|
||||||
padding: 0 4px !important;
|
padding: 0 4px !important;
|
||||||
}
|
}
|
||||||
#mce-responses .error-message, #mce-responses .success-message {
|
.mce-responses .error-message, .mce-responses .success-message {
|
||||||
top: 34px !important;
|
top: 34px !important;
|
||||||
}
|
}
|
||||||
#input_search_box {
|
#input_search_box {
|
||||||
|
5
docs/_theme/djangodocs/basic/layout.html
vendored
5
docs/_theme/djangodocs/basic/layout.html
vendored
@ -140,6 +140,11 @@
|
|||||||
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
|
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
|
||||||
})(window,document,'script','dataLayer','GTM-PF5MQ2Z');</script>
|
})(window,document,'script','dataLayer','GTM-PF5MQ2Z');</script>
|
||||||
<!-- End Google Tag Manager -->
|
<!-- End Google Tag Manager -->
|
||||||
|
<!-- Posthog -->
|
||||||
|
<script>
|
||||||
|
!function(t,e){var o,n,p,r;e.__SV||(window.posthog=e,e._i=[],e.init=function(i,s,a){function g(t,e){var o=e.split(".");2==o.length&&(t=t[o[0]],e=o[1]),t[e]=function(){t.push([e].concat(Array.prototype.slice.call(arguments,0)))}}(p=t.createElement("script")).type="text/javascript",p.async=!0,p.src=s.api_host+"/static/array.js",(r=t.getElementsByTagName("script")[0]).parentNode.insertBefore(p,r);var u=e;for(void 0!==a?u=e[a]=[]:a="posthog",u.people=u.people||[],u.toString=function(t){var e="posthog";return"posthog"!==a&&(e+="."+a),t||(e+=" (stub)"),e},u.people.toString=function(){return u.toString(1)+".people (stub)"},o="capture identify alias people.set people.set_once set_config register register_once unregister opt_out_capturing has_opted_out_capturing opt_in_capturing reset isFeatureEnabled onFeatureFlags".split(" "),n=0;n<o.length;n++)g(u,o[n]);e._i.push([i,s,a])},e.__SV=1)}(document,window.posthog||[]);
|
||||||
|
posthog.init('a1dops3FFe8KioWsry6W6AVqCG_j-FXmw1LY2d6TrYU', {api_host: 'https://cloud-posthog.hasura-app.io'})
|
||||||
|
</script>
|
||||||
{%- if use_opensearch %}
|
{%- if use_opensearch %}
|
||||||
<link rel="search" type="application/opensearchdescription+xml"
|
<link rel="search" type="application/opensearchdescription+xml"
|
||||||
title="{% trans docstitle=docstitle|e %}Search within {{ docstitle }}{% endtrans %}"
|
title="{% trans docstitle=docstitle|e %}Search within {{ docstitle }}{% endtrans %}"
|
||||||
|
40
docs/_theme/djangodocs/layout.html
vendored
40
docs/_theme/djangodocs/layout.html
vendored
@ -4,6 +4,16 @@
|
|||||||
{% set is_landing_page = true %}
|
{% set is_landing_page = true %}
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
|
||||||
|
{%- if pagename.startswith('graphql/core') %}
|
||||||
|
{% set is_core = true %}
|
||||||
|
{% set ALGOLIA_INDEX = 'graphql-docs-prod' %}
|
||||||
|
{% set ALGOLIA_API_KEY = '298d448cd9d7ed93fbab395658da19e8' %}
|
||||||
|
{%- elif pagename.startswith('graphql/cloud') %}
|
||||||
|
{% set is_cloud = true %}
|
||||||
|
{% set ALGOLIA_INDEX = 'cloud-docs-prod' %}
|
||||||
|
{% set ALGOLIA_API_KEY = 'cf84f05a225bedb72ce472dada63d29f' %}
|
||||||
|
{%- endif %}
|
||||||
|
|
||||||
{% set css_files = css_files + ['_static/graphiql/graphiql.css', '_static/styles/main.css'] %}
|
{% set css_files = css_files + ['_static/graphiql/graphiql.css', '_static/styles/main.css'] %}
|
||||||
|
|
||||||
{%- if is_landing_page %}
|
{%- if is_landing_page %}
|
||||||
@ -19,6 +29,10 @@
|
|||||||
|
|
||||||
{% set deferred_script_files = ['_static/react/react.min.js', '_static/react/react-dom.min.js', '_static/graphiql/graphiql.min.js', '_static/scripts/hdocs.js', '_static/scripts/newsletter.js'] %}
|
{% set deferred_script_files = ['_static/react/react.min.js', '_static/react/react-dom.min.js', '_static/graphiql/graphiql.min.js', '_static/scripts/hdocs.js', '_static/scripts/newsletter.js'] %}
|
||||||
|
|
||||||
|
{%- if pagename.endswith('mysql-preview') %}
|
||||||
|
{% set deferred_script_files = deferred_script_files + ['_static/scripts/mysql-subscribe.js'] %}
|
||||||
|
{%- endif %}
|
||||||
|
|
||||||
{%- macro secondnav() %}
|
{%- macro secondnav() %}
|
||||||
{%- if prev %}
|
{%- if prev %}
|
||||||
« <a href="{{ prev.link|e }}" title="{{ prev.title|e }}">previous</a>
|
« <a href="{{ prev.link|e }}" title="{{ prev.title|e }}">previous</a>
|
||||||
@ -105,7 +119,7 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href='https://hasura.io/docs/1.0/graphql/manual/getting-started/index.html'>
|
<a href='https://hasura.io/docs/1.0/graphql/core/getting-started/index.html'>
|
||||||
<button class="commonBtn navBtnHome">
|
<button class="commonBtn navBtnHome">
|
||||||
Get Started
|
Get Started
|
||||||
</button>
|
</button>
|
||||||
@ -162,7 +176,7 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a class="menuLink" href='https://hasura.io/docs/1.0/graphql/manual/getting-started/index.html'>
|
<a class="menuLink" href='https://hasura.io/docs/1.0/graphql/core/getting-started/index.html'>
|
||||||
<button class='commonBtn navBtnHome'>
|
<button class='commonBtn navBtnHome'>
|
||||||
Get Started
|
Get Started
|
||||||
</button>
|
</button>
|
||||||
@ -187,12 +201,12 @@
|
|||||||
<img src="{{ pathto('_images/layout/close-icon.svg', 1) }}" alt="Close"/>
|
<img src="{{ pathto('_images/layout/close-icon.svg', 1) }}" alt="Close"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="tabbarContainerWrapper blueBgColor boderBottom">
|
<div class="tabbarContainerWrapper blueBgColor boderBottom">
|
||||||
<a href="" class="tabbarTabActive">
|
<a href="{{ pathto('graphql/core/index.html', 1) }}" {%- if is_core %} class="tabbarTabActive" {%- endif %}>
|
||||||
<span>
|
<span>
|
||||||
Hasura Core
|
Hasura Core
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
<a href="https://hasura.io/docs/cloud/1.0/manual/index.html">
|
<a href="{{ pathto('graphql/cloud/index.html', 1) }}" {%- if is_cloud %} class="tabbarTabActive" {%- endif %}>
|
||||||
<span>
|
<span>
|
||||||
Hasura Cloud
|
Hasura Cloud
|
||||||
</span>
|
</span>
|
||||||
@ -280,29 +294,27 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="subscribe-form-wrapper">
|
<div class="subscribe-form-wrapper">
|
||||||
<div id="mc_embed_signup">
|
<div id="mc_embed_signup" class="mc_embed_signup">
|
||||||
<div class="subscribe-form-content">
|
<div class="subscribe-form-content">
|
||||||
Stay up to date with product & security news
|
Stay up to date with product & security news
|
||||||
<a target="_blank" class="newsletter-link" href="https://us13.campaign-archive.com/home/?u=9b63e92a98ecdc99732456b0e&id=f5c4f66bcf" rel="noopener">See past editions</a>
|
|
||||||
</div>
|
</div>
|
||||||
<form id="mc-embedded-subscribe-form" name="mc-embedded-subscribe-form" class="validate post-subscription-form newsletter-form" target="_blank" rel="noopener" novalidate>
|
<form id="mc-embedded-subscribe-form" name="mc-embedded-subscribe-form" class="validate post-subscription-form mc-embedded-subscribe-form newsletter-form" target="_blank" rel="noopener" novalidate>
|
||||||
<div class="full-width">
|
<div class="full-width">
|
||||||
<div class="input-box">
|
<div class="input-box">
|
||||||
<input type="email" name="EMAIL" id="mce-EMAIL" aria-label="Email" placeholder="Your Email Address" pattern="^([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x22([^\x0d\x22\x5c\x80-\xff]|\x5c[\x00-\x7f])*\x22)(\x2e([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x22([^\x0d\x22\x5c\x80-\xff]|\x5c[\x00-\x7f])*\x22))*\x40([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x5b([^\x0d\x5b-\x5d\x80-\xff]|\x5c[\x00-\x7f])*\x5d)(\x2e([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x5b([^\x0d\x5b-\x5d\x80-\xff]|\x5c[\x00-\x7f])*\x5d))*(\.\w{2,})+$" required>
|
<input type="email" name="EMAIL" id="mce-EMAIL" class="mce-EMAIL" aria-label="Email" placeholder="Your Email Address" pattern="^([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x22([^\x0d\x22\x5c\x80-\xff]|\x5c[\x00-\x7f])*\x22)(\x2e([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x22([^\x0d\x22\x5c\x80-\xff]|\x5c[\x00-\x7f])*\x22))*\x40([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x5b([^\x0d\x5b-\x5d\x80-\xff]|\x5c[\x00-\x7f])*\x5d)(\x2e([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x5b([^\x0d\x5b-\x5d\x80-\xff]|\x5c[\x00-\x7f])*\x5d))*(\.\w{2,})+$" required>
|
||||||
</div>
|
</div>
|
||||||
<div id="mce-responses" class="clear display-inline">
|
<div id="mce-responses" class="clear display-inline mce-responses">
|
||||||
<div class="mce-error-response response error-message hide">
|
<div id="mce-error-response" class="mce-error-response response error-message hide">
|
||||||
</div>
|
</div>
|
||||||
<div class="mce-success-response response success-message hide">
|
<div id="mce-success-response" class="mce-success-response response success-message hide">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style="position: absolute; left: -5000px;" aria-hidden="true"><input type="text" name="b_9b63e92a98ecdc99732456b0e_f5c4f66bcf" tabindex="-1" value=""></div>
|
<div style="position: absolute; left: -5000px;" aria-hidden="true"><input type="text" name="b_9b63e92a98ecdc99732456b0e_f5c4f66bcf" tabindex="-1" value=""></div>
|
||||||
<div class="clear submit-box">
|
<div class="clear submit-box">
|
||||||
<input type="submit" disabled="true" value="Subscribe" name="subscribe" id="mc-embedded-subscribe" class="button">
|
<input type="submit" disabled="true" value="Subscribe" name="subscribe" id="mc-embedded-subscribe" class="button mc-embedded-subscribe">
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="footer-hasura-custom">
|
<div class="footer-hasura-custom">
|
||||||
@ -344,6 +356,8 @@
|
|||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
const HDOCS_BASE_DOMAIN = "{{ BASE_DOMAIN }}";
|
const HDOCS_BASE_DOMAIN = "{{ BASE_DOMAIN }}";
|
||||||
const HDOCS_GRAPHIQL_DEFAULT_ENDPOINT = "{{ GRAPHIQL_DEFAULT_ENDPOINT }}";
|
const HDOCS_GRAPHIQL_DEFAULT_ENDPOINT = "{{ GRAPHIQL_DEFAULT_ENDPOINT }}";
|
||||||
|
const HDOCS_ALGOLIA_INDEX = "{{ALGOLIA_INDEX}}";
|
||||||
|
const HDOCS_ALGOLIA_API_KEY = "{{ALGOLIA_API_KEY}}";
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', function () {
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
hdocs && hdocs.setup();
|
hdocs && hdocs.setup();
|
||||||
|
21
docs/_theme/djangodocs/localtoc.html
vendored
21
docs/_theme/djangodocs/localtoc.html
vendored
@ -1,24 +1,3 @@
|
|||||||
{%- if display_toc %}
|
{%- if display_toc %}
|
||||||
<!--
|
|
||||||
<div class="header_main_logo inline-block mobile-hide">
|
|
||||||
<a href="https://{{ BASE_DOMAIN }}/" target="_blank" rel="noopener">
|
|
||||||
<div class="img_wrapper inline-block">
|
|
||||||
<img class="responsive logo_img" src="{{ pathto('_images/layout/logo-lite.svg', 1) }}" alt="Hasura Logo Light" />
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
<a class="docs_label" href="{{ pathto('', 1) }}">
|
|
||||||
<div class="inline-block hero"> docs </div>
|
|
||||||
</a>
|
|
||||||
<a class="version_txt">
|
|
||||||
<select value="{{ version }}" onchange="location = this.value;" class="selected" aria-label="Select Version">
|
|
||||||
{%- if version == '1.0' %}
|
|
||||||
<option class="option_val" value="https://{{ BASE_DOMAIN }}/docs/1.0/graphql/manual/index.html" selected="selected">v1.x</option>
|
|
||||||
{%- else -%}
|
|
||||||
<option class="option_val" value="https://{{ BASE_DOMAIN }}/docs/1.0/graphql/manual/index.html">v1.x</option>
|
|
||||||
{% endif %}
|
|
||||||
</select>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
-->
|
|
||||||
{{ toc_full }}
|
{{ toc_full }}
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
30
docs/_theme/djangodocs/pages/landing.html
vendored
30
docs/_theme/djangodocs/pages/landing.html
vendored
@ -5,25 +5,23 @@
|
|||||||
<div class="box_head_wrapper">
|
<div class="box_head_wrapper">
|
||||||
<div class="box_head">
|
<div class="box_head">
|
||||||
<img src="{{ pathto('_images/landing/graphql.svg', 1) }}" alt="GraphQL engine"/>
|
<img src="{{ pathto('_images/landing/graphql.svg', 1) }}" alt="GraphQL engine"/>
|
||||||
<h3 class="head_wrapper">1. The Hasura GraphQL engine</h3>
|
<h3 class="head_wrapper">Hasura docs</h3>
|
||||||
</div>
|
|
||||||
<div class="view_all_wrapper small_content">
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="small_content space_wrapper text_left">
|
<div class="space_wrapper text_left">
|
||||||
This guide covers all Hasura GraphQL engine concepts and features.
|
<p class="description">This guide covers all Hasura concepts and features.</p>
|
||||||
<br/> <br/>
|
<ul>
|
||||||
</div>
|
<li>
|
||||||
<div class="sign_in_wrapper space_wrapper">
|
<a class="docs_link" href="{{ pathto('graphql/core/index.html', 1) }}">
|
||||||
<div class="get_start">
|
Core docs
|
||||||
<img src="{{ pathto('_images/landing/manual.svg', 1) }}" alt="Manual"/>
|
|
||||||
</div>
|
|
||||||
<a href="{{ pathto('graphql/manual/index.html', 1) }}">
|
|
||||||
<div class="docs_link body_content">
|
|
||||||
Open manual
|
|
||||||
<img src="{{ pathto('_images/landing/right-arrow.svg', 1) }}" alt="Right Arrow" />
|
|
||||||
</div>
|
|
||||||
</a>
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a class="docs_link" href="{{ pathto('graphql/cloud/index.html', 1) }}">
|
||||||
|
Cloud docs
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user