mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-11-11 05:10:51 +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
|
||||
refs:
|
||||
constants:
|
||||
# TODO upload to hasura docker hub:
|
||||
# - &server_builder_image hasura/graphql-engine-server-builder:2020-01-14
|
||||
- &server_builder_image jberryman/graphql-engine-server-builder-8.10:2020-04-29
|
||||
- &server_builder_image hasura/graphql-engine-server-builder:2020-08-26
|
||||
skip_job_on_ciignore: &skip_job_on_ciignore
|
||||
run:
|
||||
name: checking if job should be terminated or not
|
||||
@ -165,10 +163,10 @@ jobs:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
keys:
|
||||
- cabal-store-v2-{{ 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-v2-{{ checksum "server/cabal.project" }}-
|
||||
- cabal-store-v2-
|
||||
- cabal-store-v3-{{ 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" }}-
|
||||
- cabal-store-v3-{{ checksum "server/cabal.project" }}-
|
||||
- cabal-store-v3-
|
||||
- run:
|
||||
name: Install latest postgresql client tools
|
||||
command: |
|
||||
@ -187,7 +185,7 @@ jobs:
|
||||
make enable_coverage=true ci-build
|
||||
fi
|
||||
- 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:
|
||||
- ~/.cabal/packages
|
||||
- ~/.cabal/store
|
||||
|
@ -1,8 +1,6 @@
|
||||
# Don't update this without updating the
|
||||
# packager imager of graphql-engine
|
||||
FROM phadej/ghc:8.10.1-stretch
|
||||
# TODO https://github.com/haskell/docker-haskell/issues/17
|
||||
#FROM haskell:8.10.1
|
||||
FROM haskell:8.10.2-stretch
|
||||
|
||||
ARG docker_ver="17.09.0-ce"
|
||||
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 \
|
||||
&& curl -s https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - \
|
||||
&& 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 - \
|
||||
&& 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 \
|
||||
|
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,
|
||||
# before executing run.sh
|
||||
set -euo pipefail
|
||||
ROOT="${BASH_SOURCE[0]%/*}"
|
||||
cd "${BASH_SOURCE[0]%/*}"
|
||||
ROOT="${PWD}"
|
||||
cd - > /dev/null
|
||||
|
||||
SERVER_DIR="$ROOT/../../server"
|
||||
|
||||
@ -18,8 +20,8 @@ echo "server binary: $SERVER_BINARY"
|
||||
cd -
|
||||
set +x
|
||||
|
||||
export SERVER_OUTPUT_DIR="server-output"
|
||||
export LATEST_SERVER_BINARY="./graphql-engine-latest"
|
||||
export SERVER_OUTPUT_DIR="$ROOT/server-output"
|
||||
export LATEST_SERVER_BINARY="$ROOT/graphql-engine-latest"
|
||||
|
||||
# Create Python virtualenv
|
||||
if ! [ -f ".venv/bin/activate" ] ; then
|
||||
@ -40,7 +42,8 @@ log_duration=on
|
||||
port=$PG_PORT
|
||||
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"
|
||||
|
||||
|
@ -12,7 +12,9 @@ set -euo pipefail
|
||||
# # echo an error message before exiting
|
||||
# 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() {
|
||||
URL="$1"
|
||||
@ -119,6 +121,17 @@ trap rm_worktree ERR
|
||||
|
||||
make_latest_release_worktree() {
|
||||
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() {
|
||||
@ -148,7 +161,18 @@ get_server_upgrade_tests() {
|
||||
cd $RELEASE_PYTEST_DIR
|
||||
tmpfile="$(mktemp --dry-run)"
|
||||
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
|
||||
cat "$tmpfile"
|
||||
cd - >/dev/null
|
||||
@ -174,11 +198,12 @@ run_server_upgrade_pytest() {
|
||||
set -x
|
||||
|
||||
# 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" \
|
||||
--avoid-error-message-checks "$@" \
|
||||
-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
|
||||
set +x
|
||||
cd -
|
||||
|
@ -191,9 +191,7 @@ pip3 install -r requirements.txt
|
||||
# node js deps
|
||||
curl -sL https://deb.nodesource.com/setup_8.x | bash -
|
||||
apt-get install -y nodejs
|
||||
npm_config_loglevel=error npm install $PYTEST_ROOT/remote_schemas/nodejs/
|
||||
|
||||
npm install apollo-server graphql
|
||||
(cd remote_schemas/nodejs && npm_config_loglevel=error npm ci)
|
||||
|
||||
mkdir -p "$OUTPUT_FOLDER/hpc"
|
||||
|
||||
@ -385,6 +383,51 @@ kill_hge_servers
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
### 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
|
||||
|
||||
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: update sidebar icons for different action and trigger types (#5445)
|
||||
- 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: 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)
|
||||
@ -66,7 +136,6 @@ If you do have such headers configured, then you must update the header configur
|
||||
|
||||
## `v1.3.0-beta.3`
|
||||
|
||||
|
||||
### Bug fixes and improvements
|
||||
|
||||
(Add entries here in the order of: server, console, cli, docs, others)
|
||||
|
@ -12,7 +12,7 @@ import (
|
||||
const completionCmdExample = `# Bash
|
||||
# Linux
|
||||
# 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
|
||||
# Install bash-completion using homebrew:
|
||||
$ brew install bash-completion
|
||||
|
@ -2,6 +2,7 @@ package commands
|
||||
|
||||
import (
|
||||
"github.com/hasura/graphql-engine/cli"
|
||||
"github.com/hasura/graphql-engine/cli/util"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
@ -28,14 +29,21 @@ func NewSeedCmd(ec *cli.ExecutionContext) *cobra.Command {
|
||||
newSeedCreateCmd(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"))
|
||||
v.BindPFlag("admin_secret", seedCmd.PersistentFlags().Lookup("admin-secret"))
|
||||
v.BindPFlag("access_key", seedCmd.PersistentFlags().Lookup("access-key"))
|
||||
f := seedCmd.PersistentFlags()
|
||||
|
||||
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
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ func newSeedCreateCmd(ec *cli.ExecutionContext) *cobra.Command {
|
||||
}
|
||||
cmd := &cobra.Command{
|
||||
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:
|
||||
hasura seed create new_table_seed
|
||||
|
||||
|
@ -1,22 +1,19 @@
|
||||
# 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)
|
||||
|
||||
## 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
|
||||
heroku](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/hasura/graphql-engine-heroku)
|
||||
Please check out our [docs](https://hasura.io/docs/cloud/1.0/manual/getting-started/index.html) for the detailed steps.
|
||||
|
||||
Please checkout our [docs](https://hasura.io/docs/1.0/graphql/manual/deployment/index.html) for other deployment methods
|
||||
|
||||
- Get the Heroku app URL (say `my-app.herokuapp.com`)
|
||||
- Get the app URL (something like `https://<my-project-name>.hasura.app`)
|
||||
- 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:
|
||||
|
||||
![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):
|
||||
|
||||
@ -99,4 +96,3 @@ now
|
||||
```
|
||||
|
||||
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('primary-key-select-1')).select('1');
|
||||
cy.get(getElementFromAlias('modify-table-pks-save')).click();
|
||||
cy.get(getElementFromAlias('pk-config-text')).within(() => {
|
||||
cy.get('b').contains(getColName(0));
|
||||
cy.get('b').contains('id');
|
||||
});
|
||||
cy.wait(5000);
|
||||
// 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('modify-table-pks-save')).click();
|
||||
cy.get(getElementFromAlias('pk-config-text')).within(() => {
|
||||
cy.get('b').contains('id');
|
||||
});
|
||||
cy.get(getElementFromAlias('pk-config-text')).within(() => {
|
||||
cy.get('b').should('not.contain', getColName(0));
|
||||
});
|
||||
cy.get(getElementFromAlias('modify-table-close-pks')).click();
|
||||
cy.wait(3000);
|
||||
};
|
||||
|
86
console/package-lock.json
generated
86
console/package-lock.json
generated
@ -3627,22 +3627,67 @@
|
||||
}
|
||||
},
|
||||
"@typescript-eslint/eslint-plugin": {
|
||||
"version": "2.24.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.24.0.tgz",
|
||||
"integrity": "sha512-wJRBeaMeT7RLQ27UQkDFOu25MqFOBus8PtOa9KaT5ZuxC1kAsd7JEHqWt4YXuY9eancX0GK9C68i5OROnlIzBA==",
|
||||
"version": "3.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-3.8.0.tgz",
|
||||
"integrity": "sha512-lFb4VCDleFSR+eo4Ew+HvrJ37ZH1Y9ZyE+qyP7EiwBpcCVxwmUc5PAqhShCQ8N8U5vqYydm74nss+a0wrrCErw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@typescript-eslint/experimental-utils": "2.24.0",
|
||||
"eslint-utils": "^1.4.3",
|
||||
"@typescript-eslint/experimental-utils": "3.8.0",
|
||||
"debug": "^4.1.1",
|
||||
"functional-red-black-tree": "^1.0.1",
|
||||
"regexpp": "^3.0.0",
|
||||
"semver": "^7.3.2",
|
||||
"tsutils": "^3.17.1"
|
||||
},
|
||||
"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": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.0.0.tgz",
|
||||
"integrity": "sha512-Z+hNr7RAVWxznLPuA7DIh8UNX1j9CDrUQxskw9IrBE1Dxue2lyXT+shqEIeLUjrokxIP8CMy1WkjgG3rTsd5/g==",
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz",
|
||||
"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
|
||||
}
|
||||
}
|
||||
@ -3682,6 +3727,12 @@
|
||||
"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": {
|
||||
"version": "2.24.0",
|
||||
"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": {
|
||||
"version": "1.8.5",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.8.5.tgz",
|
||||
@ -7678,6 +7738,16 @@
|
||||
"integrity": "sha1-NfDVy8ZMLj7WLpO0saevBbp+1Nc=",
|
||||
"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": {
|
||||
"version": "1.4.3",
|
||||
"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",
|
||||
"server-build": "make server-build",
|
||||
"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",
|
||||
"test": "cypress run --spec 'cypress/integration/**/**/test.ts' --key $CYPRESS_KEY --parallel --record",
|
||||
"lint": "eslint -c .eslintrc src --ext .js,.ts,.tsx",
|
||||
@ -147,7 +146,7 @@
|
||||
"@types/webpack-dev-middleware": "3.7.0",
|
||||
"@types/webpack-hot-middleware": "2.25.0",
|
||||
"@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",
|
||||
"autoprefixer": "9.7.6",
|
||||
"babel-eslint": "10.1.0",
|
||||
|
@ -28,7 +28,7 @@ export const requireAsyncGlobals = ({ dispatch }) => {
|
||||
return (nextState, finalState, callback) => {
|
||||
Promise.all([
|
||||
dispatch(loadConsoleOpts()),
|
||||
dispatch(fetchServerConfig()),
|
||||
dispatch(fetchServerConfig),
|
||||
]).finally(callback);
|
||||
};
|
||||
};
|
||||
|
@ -11,7 +11,7 @@ type DropDownButtonProps = {
|
||||
}[];
|
||||
dataKey: string;
|
||||
dataIndex?: string;
|
||||
onButtonChange: (e: React.MouseEvent<{}>) => void;
|
||||
onButtonChange: (e: React.MouseEvent<unknown>) => void;
|
||||
onInputChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
value?: string;
|
||||
inputVal: string;
|
||||
|
@ -1,5 +1,5 @@
|
||||
@import "~bootstrap-sass/assets/stylesheets/bootstrap/variables";
|
||||
@import "../../Common.scss";
|
||||
@import '~bootstrap-sass/assets/stylesheets/bootstrap/variables';
|
||||
@import '../../Common.scss';
|
||||
|
||||
.container {
|
||||
}
|
||||
@ -28,8 +28,8 @@
|
||||
// background: #444;
|
||||
// color: $navbar-inverse-color;
|
||||
color: #333;
|
||||
border: 1px solid #E5E5E5;
|
||||
background-color: #F8F8F8;
|
||||
border: 1px solid #e5e5e5;
|
||||
background-color: #f8f8f8;
|
||||
|
||||
/*
|
||||
a,a:visited {
|
||||
@ -66,7 +66,7 @@
|
||||
*/
|
||||
|
||||
a {
|
||||
color: #767E93;
|
||||
color: #767e93;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
}
|
||||
@ -75,7 +75,7 @@
|
||||
padding: 7px 0;
|
||||
// color: $navbar-inverse-link-hover-color;
|
||||
transition: color 0.5s;
|
||||
pointer: cursor;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -113,7 +113,7 @@
|
||||
.sidebarHeading {
|
||||
font-weight: bold;
|
||||
display: inline-block;
|
||||
color: #767E93;
|
||||
color: #767e93;
|
||||
font-size: 15px;
|
||||
}
|
||||
}
|
||||
@ -148,19 +148,23 @@
|
||||
padding-left: 5px !important;
|
||||
display: initial !important;
|
||||
|
||||
.tableIcon, .functionIcon {
|
||||
//display: inline;
|
||||
.tableIcon,
|
||||
.functionIcon {
|
||||
margin-right: 5px;
|
||||
font-size: 12px;
|
||||
width: 12px;
|
||||
}
|
||||
|
||||
.icon_mar_left {
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.noChildren {
|
||||
font-weight: 400 !important;
|
||||
padding-bottom: 10px !important;
|
||||
color: #767E93 !important;
|
||||
color: #767e93 !important;
|
||||
}
|
||||
|
||||
li:first-child {
|
||||
@ -170,11 +174,16 @@
|
||||
|
||||
.activeLink {
|
||||
a {
|
||||
// border-left: 4px solid #FFC627;
|
||||
color: #FD9540!important;
|
||||
color: #fd9540 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.padLeft4 {
|
||||
margin-left: 8px;
|
||||
top: 12px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.floatRight {
|
||||
float: right;
|
||||
margin-right: 20px;
|
||||
|
@ -1,5 +1,4 @@
|
||||
import React from 'react';
|
||||
import { Connect } from 'react-redux';
|
||||
import styles from './RightContainer.scss';
|
||||
|
||||
const RightContainer: React.FC = ({ children }) => {
|
||||
@ -16,6 +15,4 @@ const RightContainer: React.FC = ({ children }) => {
|
||||
);
|
||||
};
|
||||
|
||||
const rightContainerConnector = (connect: Connect) => connect()(RightContainer);
|
||||
|
||||
export default rightContainerConnector;
|
||||
export default RightContainer;
|
||||
|
@ -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> = {};
|
||||
if (styleOverrides) {
|
||||
Object.keys(styleOverrides).forEach(comp => {
|
||||
customStyles[comp] = (provided: object) => ({
|
||||
customStyles[comp] = (provided: Record<string, unknown>) => ({
|
||||
...provided,
|
||||
...styleOverrides[comp],
|
||||
});
|
||||
|
@ -11,3 +11,6 @@
|
||||
width: 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>;
|
||||
|
||||
export type Json =
|
||||
|
@ -11,9 +11,9 @@ import RuntimeError from './RuntimeError';
|
||||
import { registerRunTimeError } from '../Main/Actions';
|
||||
|
||||
export interface Metadata {
|
||||
inconsistentObjects: object[];
|
||||
inconsistentObjects: Record<string, unknown>[];
|
||||
ongoingRequest: boolean;
|
||||
allowedQueries: object[];
|
||||
allowedQueries: Record<string, unknown>[];
|
||||
}
|
||||
|
||||
export interface ErrorBoundaryProps {
|
||||
|
@ -3,17 +3,26 @@ import { UPDATE_DATA_HEADERS } from '../Services/Data/DataActions';
|
||||
import { saveAdminSecretState } from '../AppState';
|
||||
import { ADMIN_SECRET_HEADER_KEY, CLI_CONSOLE_MODE } from '../../constants';
|
||||
import requestAction from '../../utils/requestAction';
|
||||
import { Dispatch } from '../../types';
|
||||
import globals from '../../Globals';
|
||||
|
||||
type VerifyLoginOptions = {
|
||||
adminSecret: string;
|
||||
shouldPersist: boolean;
|
||||
successCallback: () => void;
|
||||
errorCallback: (err: Error) => void;
|
||||
dispatch: Dispatch;
|
||||
};
|
||||
|
||||
export const verifyLogin = ({
|
||||
adminSecret,
|
||||
shouldPersist,
|
||||
successCallback,
|
||||
errorCallback,
|
||||
dispatch,
|
||||
}) => {
|
||||
}: VerifyLoginOptions) => {
|
||||
const url = Endpoints.getSchema;
|
||||
const requestOptions = {
|
||||
const requestOptions: RequestInit = {
|
||||
credentials: globalCookiePolicy,
|
||||
method: 'POST',
|
||||
headers: {
|
@ -4,6 +4,7 @@ import requestAction from '../../utils/requestAction';
|
||||
import requestActionPlain from '../../utils/requestActionPlain';
|
||||
import Endpoints, { globalCookiePolicy } from '../../Endpoints';
|
||||
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_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 LOGIN_IN_PROGRESS = 'Main/LOGIN_IN_PROGRESS';
|
||||
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 registerRunTimeError = data => ({
|
||||
@ -52,6 +55,29 @@ const setReadOnlyMode = 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 = () => {
|
||||
return (dispatch, getState) => {
|
||||
const { serverVersion } = getState().main;
|
||||
@ -110,7 +136,7 @@ const loadServerVersion = () => dispatch => {
|
||||
);
|
||||
};
|
||||
|
||||
const fetchServerConfig = () => (dispatch, getState) => {
|
||||
const fetchServerConfig = (dispatch, getState) => {
|
||||
const url = Endpoints.serverConfig;
|
||||
const options = {
|
||||
method: 'GET',
|
||||
@ -318,6 +344,16 @@ const mainReducer = (state = defaultState, action) => {
|
||||
...state,
|
||||
featuresCompatibility: { ...action.data },
|
||||
};
|
||||
case POSTGRES_VERSION_SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
postgresVersion: action.payload,
|
||||
};
|
||||
case POSTGRES_VERSION_ERROR:
|
||||
return {
|
||||
...state,
|
||||
postgresVersion: null,
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
@ -7,7 +7,6 @@ import Tooltip from 'react-bootstrap/lib/Tooltip';
|
||||
import * as tooltips from './Tooltips';
|
||||
import globals from '../../Globals';
|
||||
import { getPathRoot } from '../Common/utils/urlUtils';
|
||||
|
||||
import Spinner from '../Common/Spinner/Spinner';
|
||||
import WarningSymbol from '../Common/WarningSymbol/WarningSymbol';
|
||||
import logo from './images/white-logo.svg';
|
||||
@ -21,6 +20,7 @@ import {
|
||||
loadLatestServerVersion,
|
||||
featureCompatibilityInit,
|
||||
emitProClickedEvent,
|
||||
fetchPostgresVersion,
|
||||
} from './Actions';
|
||||
|
||||
import {
|
||||
@ -45,7 +45,7 @@ import {
|
||||
import ToolTip from '../Common/Tooltip/Tooltip';
|
||||
import { setPreReleaseNotificationOptOutInDB } from '../../telemetry/Actions';
|
||||
import { Icon } from '../UIKit/atoms/Icon';
|
||||
import { ProPopup } from './components/ProPopup';
|
||||
import { Help, ProPopup } from './components/';
|
||||
|
||||
class Main extends React.Component {
|
||||
constructor(props) {
|
||||
@ -82,7 +82,9 @@ class Main extends React.Component {
|
||||
});
|
||||
});
|
||||
|
||||
dispatch(fetchServerConfig());
|
||||
dispatch(fetchPostgresVersion);
|
||||
|
||||
dispatch(fetchServerConfig);
|
||||
}
|
||||
|
||||
toggleProPopup = () => {
|
||||
@ -655,14 +657,7 @@ class Main extends React.Component {
|
||||
{getSettingsSelectedMarker()}
|
||||
</div>
|
||||
</Link>
|
||||
<a
|
||||
id="help"
|
||||
href="https://hasura.io/help"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<div className={styles.headerRightNavbarBtn}>HELP</div>
|
||||
</a>
|
||||
<Help isSelected={currentActiveBlock === 'support'} />
|
||||
{getLoveSection()}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -897,18 +897,15 @@
|
||||
.headerRightNavbarBtn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
// background-color: #22283b;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
color: #fff;
|
||||
|
||||
&:hover {
|
||||
span {
|
||||
opacity: 0.7;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.selected {
|
||||
bottom: 0;
|
||||
|
@ -25,7 +25,8 @@ export interface MainState {
|
||||
error: Error | null;
|
||||
isFetching: boolean;
|
||||
};
|
||||
featuresCompatibility: object;
|
||||
featuresCompatibility: Record<string, unknown>;
|
||||
postgresVersion: string | null;
|
||||
}
|
||||
|
||||
const defaultState: MainState = {
|
||||
@ -56,6 +57,7 @@ const defaultState: MainState = {
|
||||
isFetching: false,
|
||||
},
|
||||
featuresCompatibility: {},
|
||||
postgresVersion: null,
|
||||
};
|
||||
|
||||
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 { Route, IndexRedirect } from 'react-router';
|
||||
import { rightContainerConnector } from '../../Common/Layout';
|
||||
import { RightContainer } from '../../Common/Layout/RightContainer';
|
||||
import Container from './Containers/Main';
|
||||
import { fetchActions } from './ServerIO';
|
||||
import globals from '../../../Globals';
|
||||
@ -36,7 +36,7 @@ const getActionsRouter = (connect, store, composeOnEnterHooks) => {
|
||||
onChange={actionsInit(store)}
|
||||
>
|
||||
<IndexRedirect to="manage" />
|
||||
<Route path="manage" component={rightContainerConnector(connect)}>
|
||||
<Route path="manage" component={RightContainer}>
|
||||
<IndexRedirect to="actions" />
|
||||
<Route path="actions" component={ActionsLandingPage(connect)} />
|
||||
<Route path="add" component={AddAction(connect)} />
|
||||
@ -51,7 +51,7 @@ const getActionsRouter = (connect, store, composeOnEnterHooks) => {
|
||||
component={ActionPermissions(connect)}
|
||||
/>
|
||||
</Route>
|
||||
<Route path="types" component={rightContainerConnector(connect)}>
|
||||
<Route path="types" component={RightContainer}>
|
||||
<IndexRedirect to="manage" />
|
||||
<Route path="manage" component={TypesManage(connect)} />
|
||||
<Route path="relationships" component={TypesRelationships(connect)} />
|
||||
|
@ -272,6 +272,8 @@ const analyzeFetcher = (headers, mode) => {
|
||||
|
||||
const changeRequestHeader = (index, key, newValue, isDisabled) => {
|
||||
return (dispatch, getState) => {
|
||||
websocketSubscriptionClient = null;
|
||||
|
||||
const currentState = getState().apiexplorer;
|
||||
|
||||
const updatedHeader = {
|
||||
|
@ -11,14 +11,14 @@ export type OperationData = {
|
||||
displayName: string;
|
||||
type: OperationTypeNode;
|
||||
variableName: string;
|
||||
variables: object;
|
||||
variables: Record<string, unknown>;
|
||||
operationDefinition: OperationDefinitionNode;
|
||||
};
|
||||
|
||||
export type GenerateOptions = {
|
||||
serverUrl: string;
|
||||
headers: { [name: string]: string };
|
||||
context: object;
|
||||
context: Record<string, unknown>;
|
||||
operationDataList: Array<OperationData>;
|
||||
options: OptionValues;
|
||||
};
|
||||
|
@ -110,7 +110,8 @@ export const getErrorMessage = (
|
||||
notificationMessage = error.code;
|
||||
}
|
||||
} 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) {
|
||||
notificationMessage = error.custom;
|
||||
} else if ('code' in error && 'error' in error && 'path' in error) {
|
||||
|
@ -443,6 +443,7 @@ class AddTable extends Component {
|
||||
columnDefaultFunctions,
|
||||
columnTypeCasts,
|
||||
checkConstraints,
|
||||
postgresVersion,
|
||||
} = this.props;
|
||||
|
||||
const getCreateBtnText = () => {
|
||||
@ -493,6 +494,7 @@ class AddTable extends Component {
|
||||
onSelect={setFreqUsedColumn}
|
||||
action={'add'}
|
||||
dispatch={dispatch}
|
||||
postgresVersion={postgresVersion}
|
||||
/>
|
||||
</div>
|
||||
<hr />
|
||||
@ -586,6 +588,7 @@ const mapStateToProps = state => ({
|
||||
columnTypeCasts: state.tables.columnTypeCasts,
|
||||
columnDataTypeFetchErr: state.tables.columnDataTypeFetchErr,
|
||||
schemaList: state.tables.schemaList,
|
||||
postgresVersion: state.main.postgresVersion,
|
||||
});
|
||||
|
||||
const addTableConnector = connect => connect(mapStateToProps)(AddTable);
|
||||
|
@ -1,8 +1,27 @@
|
||||
import React from 'react';
|
||||
import Dropdown from '../../../../Common/Dropdown/Dropdown';
|
||||
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',
|
||||
validFor: ['add'],
|
||||
@ -17,6 +36,14 @@ const frequentlyUsedColumns = [
|
||||
typeText: 'bigint (auto-increment)',
|
||||
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',
|
||||
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 typeText = c.typeText + '; ';
|
||||
const typeText = `${c.typeText}; `;
|
||||
const defaultText =
|
||||
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 = ({
|
||||
onSelect,
|
||||
action = null,
|
||||
dispatch = null,
|
||||
}) => {
|
||||
const frequentlyUsedColumnsOptions = () => {
|
||||
return frequentlyUsedColumns
|
||||
postgresVersion,
|
||||
}: FrequentlyUsedColumnSelectorProps) => {
|
||||
const frequentlyUsedColumnsOptions = frequentlyUsedColumns
|
||||
.filter(fuc => !action || fuc.validFor.includes(action))
|
||||
.filter(col =>
|
||||
postgresVersion && col.minPGVersion
|
||||
? parseFloat(postgresVersion) >= col.minPGVersion
|
||||
: true
|
||||
)
|
||||
.map(fuc => {
|
||||
const { title, subTitle } = getFreqUsedColDisplayInfo(fuc);
|
||||
return {
|
||||
@ -109,15 +148,14 @@ const FrequentlyUsedColumnSelector = ({
|
||||
onClick: () => (dispatch ? dispatch(onSelect(fuc)) : onSelect(fuc)),
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
testId={'frequently-used-columns'}
|
||||
options={frequentlyUsedColumnsOptions()}
|
||||
testId="frequently-used-columns"
|
||||
options={frequentlyUsedColumnsOptions}
|
||||
position="bottom"
|
||||
key={'frequently-used-columns'}
|
||||
keyPrefix={'frequently-used-columns'}
|
||||
key="frequently-used-columns"
|
||||
keyPrefix="frequently-used-columns"
|
||||
>
|
||||
<Button color="white" size="xs">
|
||||
+ Frequently used columns
|
@ -1,11 +1,10 @@
|
||||
import React from 'react';
|
||||
import { ThunkDispatch } from 'redux-thunk';
|
||||
import { AnyAction } from 'redux';
|
||||
import ReloadMetadata from '../../../Settings/MetadataOptions/ReloadMetadata';
|
||||
import { Dispatch } from '../../../../../types';
|
||||
|
||||
export interface ReloadEnumValuesButtonProps {
|
||||
isEnum: boolean;
|
||||
dispatch: ThunkDispatch<{}, {}, AnyAction>;
|
||||
dispatch: Dispatch;
|
||||
tooltipStyle?: string;
|
||||
}
|
||||
|
||||
|
@ -56,9 +56,9 @@ export interface TableRowProps {
|
||||
refName: 'valueNode' | 'nullNode' | 'defaultNode' | 'insertRadioNode',
|
||||
node: HTMLInputElement | null
|
||||
) => void;
|
||||
enumOptions: object;
|
||||
enumOptions: Record<string, unknown>;
|
||||
index: number;
|
||||
clone?: object;
|
||||
clone?: Record<string, unknown>;
|
||||
onChange?: (e: React.ChangeEvent<HTMLInputElement>, val: unknown) => void;
|
||||
onFocus?: (e: React.FocusEvent<HTMLInputElement>) => void;
|
||||
prevValue?: unknown;
|
||||
|
@ -659,7 +659,10 @@ const makeMigrationCall = (
|
||||
}
|
||||
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(
|
||||
showNotification(
|
||||
{
|
||||
@ -667,8 +670,9 @@ const makeMigrationCall = (
|
||||
level: 'error',
|
||||
message: (
|
||||
<p>
|
||||
{getErrorMessage('', err)}
|
||||
<br />
|
||||
{errorDetailsLines.map((m, i) => (
|
||||
<div key={i}>{m}</div>
|
||||
))}
|
||||
<br />
|
||||
Do you want to drop the dependent items as well?
|
||||
</p>
|
||||
@ -680,7 +684,7 @@ const makeMigrationCall = (
|
||||
makeMigrationCall(
|
||||
dispatch,
|
||||
getState,
|
||||
cascadeUpQueries(upQueries), // cascaded new up queries
|
||||
cascadeUpQueries(upQueries, isPgCascade), // cascaded new up queries
|
||||
downQueries,
|
||||
migrationName,
|
||||
customOnSuccess,
|
||||
@ -689,6 +693,7 @@ const makeMigrationCall = (
|
||||
successMsg,
|
||||
errorMsg,
|
||||
shouldSkipSchemaReload,
|
||||
false,
|
||||
true // prevent further retry
|
||||
),
|
||||
},
|
||||
@ -700,8 +705,10 @@ const makeMigrationCall = (
|
||||
|
||||
const onError = err => {
|
||||
if (!isRetry) {
|
||||
const dependecyError = getDependencyError(err);
|
||||
if (dependecyError) return retryMigration(dependecyError, errorMsg);
|
||||
const { dependencyError, pgDependencyError } = getDependencyError(err);
|
||||
if (dependencyError) return retryMigration(dependencyError, errorMsg);
|
||||
if (pgDependencyError)
|
||||
return retryMigration(pgDependencyError, errorMsg, true);
|
||||
}
|
||||
|
||||
dispatch(handleMigrationErrors(errorMsg, err));
|
||||
|
@ -27,7 +27,7 @@ import {
|
||||
// metadataConnector,
|
||||
} from '.';
|
||||
|
||||
import { rightContainerConnector } from '../../Common/Layout';
|
||||
import { RightContainer } from '../../Common/Layout/RightContainer';
|
||||
|
||||
import {
|
||||
fetchDataInit,
|
||||
@ -51,7 +51,7 @@ const makeDataRouter = (
|
||||
return (
|
||||
<Route path="data" component={dataPageConnector(connect)}>
|
||||
<IndexRedirect to="schema/public" />
|
||||
<Route path="schema" component={rightContainerConnector(connect)}>
|
||||
<Route path="schema" component={RightContainer}>
|
||||
<IndexRedirect to="public" />
|
||||
<Route path=":schema" 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 { migrationMode, readOnlyMode } = getState().main;
|
||||
const isStatementTimeout = statementTimeout && !isMigration;
|
||||
|
||||
const migrateUrl = returnMigrateUrl(migrationMode);
|
||||
|
||||
@ -49,7 +50,7 @@ const executeSQL = (isMigration, migrationName, statementTimeout) => (
|
||||
|
||||
const schemaChangesUp = [];
|
||||
|
||||
if (statementTimeout && !isMigration) {
|
||||
if (isStatementTimeout) {
|
||||
schemaChangesUp.push(
|
||||
getRunSqlQuery(
|
||||
getStatementTimeoutSql(statementTimeout),
|
||||
@ -111,7 +112,10 @@ const executeSQL = (isMigration, migrationName, statementTimeout) => (
|
||||
}
|
||||
dispatch(showSuccessNotification('SQL executed!'));
|
||||
dispatch(fetchDataInit()).then(() => {
|
||||
dispatch({ type: REQUEST_SUCCESS, data });
|
||||
dispatch({
|
||||
type: REQUEST_SUCCESS,
|
||||
data: data && (isStatementTimeout ? data[1] : data[0]),
|
||||
});
|
||||
});
|
||||
dispatch(fetchTrackedFunctions());
|
||||
},
|
||||
@ -168,11 +172,7 @@ const rawSQLReducer = (state = defaultState, action) => {
|
||||
lastSuccess: null,
|
||||
};
|
||||
case REQUEST_SUCCESS:
|
||||
if (
|
||||
action.data &&
|
||||
action.data[0] &&
|
||||
action.data[0].result_type === 'CommandOk'
|
||||
) {
|
||||
if (action.data && action.data.result_type === 'CommandOk') {
|
||||
return {
|
||||
...state,
|
||||
ongoingRequest: false,
|
||||
@ -188,8 +188,8 @@ const rawSQLReducer = (state = defaultState, action) => {
|
||||
lastError: null,
|
||||
lastSuccess: true,
|
||||
resultType: 'tuples',
|
||||
result: action.data[0].result.slice(1),
|
||||
resultHeaders: action.data[0].result[0],
|
||||
result: action.data.result.slice(1),
|
||||
resultHeaders: action.data.result[0],
|
||||
};
|
||||
case REQUEST_ERROR:
|
||||
return {
|
||||
|
@ -692,7 +692,7 @@ class Schema extends Component {
|
||||
className={styles.add_mar_top}
|
||||
key={'non-trackable-custom-functions'}
|
||||
>
|
||||
<CollapsibleToggle title={heading} isOpen>
|
||||
<CollapsibleToggle title={heading}>
|
||||
<div className={`${styles.padd_left_remove} col-xs-12`}>
|
||||
{getNonTrackableFuncList()}
|
||||
</div>
|
||||
|
@ -126,6 +126,7 @@ const ColumnCreator = ({
|
||||
dataTypes: restTypes = [],
|
||||
validTypeCasts,
|
||||
columnDefaultFunctions,
|
||||
postgresVersion,
|
||||
}) => {
|
||||
const {
|
||||
colName,
|
||||
@ -256,6 +257,7 @@ const ColumnCreator = ({
|
||||
<FrequentlyUsedColumnSelector
|
||||
onSelect={frequentlyUsedColumn.onSelect}
|
||||
action={'modify'}
|
||||
postgresVersion={postgresVersion}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@ -89,6 +89,7 @@ class ModifyTable extends React.Component {
|
||||
schemaList,
|
||||
tableEnum,
|
||||
rootFieldsEdit,
|
||||
postgresVersion,
|
||||
} = this.props;
|
||||
|
||||
const dataTypeIndexMap = getAllDataTypeMap(dataTypes);
|
||||
@ -249,6 +250,7 @@ class ModifyTable extends React.Component {
|
||||
dataTypes={dataTypes}
|
||||
validTypeCasts={validTypeCasts}
|
||||
columnDefaultFunctions={columnDefaultFunctions}
|
||||
postgresVersion={postgresVersion}
|
||||
/>
|
||||
<hr />
|
||||
{getComputedFieldsSection()}
|
||||
@ -352,6 +354,7 @@ const mapStateToProps = (state, ownProps) => ({
|
||||
validTypeCasts: state.tables.columnTypeCasts,
|
||||
columnDataTypeFetchErr: state.tables.columnDataTypeFetchErr,
|
||||
schemaList: state.tables.schemaList,
|
||||
postgresVersion: state.main.postgresVersion,
|
||||
...state.tables.modify,
|
||||
});
|
||||
|
||||
|
@ -55,7 +55,9 @@ const PrimaryKeyEditor = ({
|
||||
);
|
||||
|
||||
// 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
|
||||
const pkEditorExpanded = () => (
|
||||
@ -95,7 +97,9 @@ const PrimaryKeyEditor = ({
|
||||
);
|
||||
};
|
||||
|
||||
useEffect(setPkEditState, [columns]);
|
||||
useEffect(() => {
|
||||
setPkEditState();
|
||||
}, [columns.length]);
|
||||
|
||||
// remove
|
||||
const onRemove = () => {
|
||||
|
@ -5,7 +5,7 @@ import RawSqlButton from '../Common/Components/RawSqlButton';
|
||||
|
||||
export interface ViewDefinitionsProps {
|
||||
dispatch: () => void;
|
||||
sql: string | object;
|
||||
sql: string | Record<string, unknown>;
|
||||
}
|
||||
|
||||
const ViewDefinitions: React.FC<ViewDefinitionsProps> = ({ dispatch, sql }) => (
|
||||
|
@ -1766,7 +1766,7 @@ class Permissions extends Component {
|
||||
'Backend only',
|
||||
tooltip,
|
||||
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
|
||||
testId={'toggle-backend-only'}
|
||||
|
@ -112,11 +112,11 @@ class RelationshipEditor extends React.Component {
|
||||
<div className={styles.display_flex}>
|
||||
{getEditBtn()}
|
||||
<b className={styles.textNoNewLine}>{relName}</b>
|
||||
</div>
|
||||
<GqlCompatibilityWarning
|
||||
identifier={relName}
|
||||
className={styles.add_mar_left_small}
|
||||
/>
|
||||
</div>
|
||||
<div className={tableStyles.relationshipTopPadding}>
|
||||
<p className={styles.textNoNewLine}>{getRelDef(relConfig)}</p>
|
||||
</div>
|
||||
|
@ -791,13 +791,23 @@ WHERE
|
||||
export const isColTypeString = 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 = {}) => {
|
||||
if (i.type === 'run_sql' || i.type === 'untrack_table') {
|
||||
return {
|
||||
...i,
|
||||
args: {
|
||||
...i.args,
|
||||
...(isPgCascade && { sql: cascadePGSqlQuery(i.args.sql) }),
|
||||
cascade: true,
|
||||
},
|
||||
};
|
||||
@ -808,14 +818,27 @@ export const cascadeUpQueries = (upQueries = []) =>
|
||||
export const getDependencyError = (err = {}) => {
|
||||
if (err.code == ERROR_CODES.dependencyError.code) {
|
||||
// direct dependency error
|
||||
return 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 };
|
||||
return { dependencyError: err };
|
||||
}
|
||||
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
|
||||
manual
|
||||
showPagination={false}
|
||||
freezeWhenExpanded
|
||||
SubComponent={(logRow: any) => {
|
||||
const finalIndex = logRow.index;
|
||||
const finalRow = logs[finalIndex];
|
||||
|
@ -119,7 +119,7 @@ type ExternalProps = RouteComponentProps<
|
||||
{
|
||||
triggerName: string;
|
||||
},
|
||||
{}
|
||||
unknown
|
||||
>;
|
||||
|
||||
const mapStateToProps: MapStateToProps<PropsFromState, ExternalProps> = (
|
||||
|
@ -1,7 +1,6 @@
|
||||
import React from 'react';
|
||||
import { Connect } from 'react-redux';
|
||||
import { Route, IndexRedirect, EnterHook, RouterState } from 'react-router';
|
||||
import rightContainerConnector from '../../Common/Layout/RightContainer/RightContainer';
|
||||
import Container from './Container';
|
||||
import { fetchTriggers } from './ServerIO';
|
||||
import globals from '../../../Globals';
|
||||
@ -52,6 +51,7 @@ import {
|
||||
AdhocEventLogs,
|
||||
AdhocEventsInfo,
|
||||
} from './AdhocEvents';
|
||||
import { RightContainer } from '../../Common/Layout/RightContainer';
|
||||
|
||||
const triggersInit = (dispatch: Dispatch): EnterHook => {
|
||||
return (
|
||||
@ -87,10 +87,7 @@ const getTriggersRouter = (
|
||||
onEnter={composeOnEnterHooks([triggersInit(store.dispatch)])}
|
||||
>
|
||||
<IndexRedirect to={dataEventsPrefix} />
|
||||
<Route
|
||||
path={dataEventsPrefix}
|
||||
component={rightContainerConnector(connect)}
|
||||
>
|
||||
<Route path={dataEventsPrefix} component={RightContainer}>
|
||||
<IndexRedirect to={getDataEventsLandingRoute('relative')} />
|
||||
<Route path={getAddETRoute('relative')} component={AddEventTrigger} />
|
||||
<Route
|
||||
@ -114,10 +111,7 @@ const getTriggersRouter = (
|
||||
component={EventTriggerLanding}
|
||||
/>
|
||||
</Route>
|
||||
<Route
|
||||
path={scheduledEventsPrefix}
|
||||
component={rightContainerConnector(connect)}
|
||||
>
|
||||
<Route path={scheduledEventsPrefix} component={RightContainer}>
|
||||
<IndexRedirect to={getScheduledEventsLandingRoute('relative')} />
|
||||
<Route
|
||||
path={getAddSTRoute('relative')}
|
||||
@ -144,10 +138,7 @@ const getTriggersRouter = (
|
||||
component={ScheduledTriggeModify}
|
||||
/>
|
||||
</Route>
|
||||
<Route
|
||||
path={adhocEventsPrefix}
|
||||
component={rightContainerConnector(connect)}
|
||||
>
|
||||
<Route path={adhocEventsPrefix} component={RightContainer}>
|
||||
<IndexRedirect to={getAdhocEventsInfoRoute('relative')} />
|
||||
<Route
|
||||
path={getAddAdhocEventRoute('relative')}
|
||||
|
@ -26,7 +26,7 @@ export type RouterTriggerProps = RouteComponentProps<
|
||||
{
|
||||
triggerName: string;
|
||||
},
|
||||
{}
|
||||
unknown
|
||||
>;
|
||||
|
||||
export type TriggerKind = 'event' | 'cron';
|
||||
|
@ -1,7 +1,4 @@
|
||||
/* */
|
||||
import { listState } from './state';
|
||||
/* */
|
||||
|
||||
import Endpoints, { globalCookiePolicy } from '../../../Endpoints';
|
||||
import requestAction from '../../../utils/requestAction';
|
||||
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 { loadMigrationStatus } from '../../Main/Actions';
|
||||
import { handleMigrationErrors } from '../../../utils/migration';
|
||||
|
||||
import { showSuccessNotification } from '../Common/Notification';
|
||||
import { filterInconsistentMetadataObjects } from '../Settings/utils';
|
||||
|
||||
/* Action constants */
|
||||
|
||||
@ -51,20 +46,9 @@ const fetchRemoteSchemas = () => {
|
||||
dispatch({ type: FETCH_REMOTE_SCHEMAS });
|
||||
return dispatch(requestAction(url, options)).then(
|
||||
data => {
|
||||
let consistentRemoteSchemas = data;
|
||||
const { inconsistentObjects } = getState().metadata;
|
||||
|
||||
if (inconsistentObjects.length > 0) {
|
||||
consistentRemoteSchemas = filterInconsistentMetadataObjects(
|
||||
data,
|
||||
inconsistentObjects,
|
||||
'remote_schemas'
|
||||
);
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: REMOTE_SCHEMAS_FETCH_SUCCESS,
|
||||
data: consistentRemoteSchemas,
|
||||
data,
|
||||
});
|
||||
return Promise.resolve();
|
||||
},
|
||||
|
@ -318,7 +318,6 @@ const modifyRemoteSchema = () => {
|
||||
return (dispatch, getState) => {
|
||||
const currState = getState().remoteSchemas.addData;
|
||||
const remoteSchemaName = currState.name.trim().replace(/ +/g, '');
|
||||
// const url = Endpoints.getSchema;
|
||||
const upQueryArgs = [];
|
||||
const downQueryArgs = [];
|
||||
const migrationName = 'update_remote_schema_' + remoteSchemaName;
|
||||
@ -345,9 +344,10 @@ const modifyRemoteSchema = () => {
|
||||
},
|
||||
};
|
||||
|
||||
resolveObj.definition.headers = [
|
||||
...getReqHeader(getState().remoteSchemas.headerData.headers),
|
||||
];
|
||||
resolveObj.definition.headers = getReqHeader(
|
||||
getState().remoteSchemas.headerData.headers
|
||||
);
|
||||
|
||||
if (resolveObj.definition.url) {
|
||||
delete resolveObj.definition.url_from_env;
|
||||
} else {
|
||||
|
@ -22,6 +22,7 @@ import { NotFoundError } from '../../../Error/PageNotFound';
|
||||
|
||||
import globals from '../../../../Globals';
|
||||
import { getConfirmation } from '../../../Common/utils/jsUtils';
|
||||
import styles from '../RemoteSchema.scss';
|
||||
|
||||
const prefixUrl = globals.urlPrefix + appPrefix;
|
||||
|
||||
@ -49,17 +50,17 @@ class Edit extends React.Component {
|
||||
]);
|
||||
}
|
||||
|
||||
UNSAFE_componentWillReceiveProps(nextProps) {
|
||||
componentDidUpdate(prevProps) {
|
||||
if (
|
||||
nextProps.params.remoteSchemaName !== this.props.params.remoteSchemaName
|
||||
prevProps.params.remoteSchemaName !== this.props.params.remoteSchemaName
|
||||
) {
|
||||
Promise.all([
|
||||
this.props.dispatch(
|
||||
fetchRemoteSchema(nextProps.params.remoteSchemaName)
|
||||
fetchRemoteSchema(this.props.params.remoteSchemaName)
|
||||
),
|
||||
this.props.dispatch({
|
||||
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();
|
||||
}
|
||||
|
||||
const styles = require('../RemoteSchema.scss');
|
||||
|
||||
const { isFetching, isRequesting, editState } = this.props;
|
||||
const {
|
||||
isFetching,
|
||||
isRequesting,
|
||||
editState,
|
||||
inconsistentObjects,
|
||||
} = this.props;
|
||||
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 = () => {
|
||||
return 'isModify' in editState && !editState.isModify ? (
|
||||
<div className={styles.commonBtn}>
|
||||
@ -137,7 +150,8 @@ class Edit extends React.Component {
|
||||
this.modifyClick();
|
||||
}}
|
||||
data-test={'remote-schema-edit-modify-btn'}
|
||||
disabled={isRequesting}
|
||||
disabled={isRequesting || inconsistencyDetails}
|
||||
title={inconsistencyDetails ? fixInconsistencyMsg : ''}
|
||||
>
|
||||
Modify
|
||||
</Button>
|
||||
@ -148,7 +162,8 @@ class Edit extends React.Component {
|
||||
e.preventDefault();
|
||||
this.handleDeleteRemoteSchema(e);
|
||||
}}
|
||||
disabled={isRequesting}
|
||||
disabled={isRequesting || inconsistencyDetails}
|
||||
title={inconsistencyDetails ? fixInconsistencyMsg : ''}
|
||||
data-test={'remote-schema-edit-delete-btn'}
|
||||
>
|
||||
{isRequesting ? 'Deleting ...' : 'Delete'}
|
||||
@ -254,6 +269,7 @@ const mapStateToProps = state => {
|
||||
...state.remoteSchemas.headerData,
|
||||
allRemoteSchemas: state.remoteSchemas.listData.remoteSchemas,
|
||||
dataHeaders: { ...state.tables.dataHeaders },
|
||||
inconsistentObjects: state.metadata.inconsistentObjects,
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -1,28 +1,56 @@
|
||||
import React from 'react';
|
||||
|
||||
import CommonTabLayout from '../../../Common/Layout/CommonTabLayout/CommonTabLayout';
|
||||
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 {
|
||||
fetchRemoteSchema,
|
||||
RESET,
|
||||
getHeaderEvents,
|
||||
} from '../Add/addRemoteSchemaReducer';
|
||||
|
||||
import { VIEW_REMOTE_SCHEMA } from '../Actions';
|
||||
import ReloadRemoteSchema from '../../Settings/MetadataOptions/ReloadRemoteSchema';
|
||||
|
||||
import { appPrefix } from '../constants';
|
||||
|
||||
import { NotFoundError } from '../../../Error/PageNotFound';
|
||||
|
||||
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 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 {
|
||||
componentDidMount() {
|
||||
const { remoteSchemaName } = this.props.params;
|
||||
@ -35,17 +63,17 @@ class ViewStitchedSchema extends React.Component {
|
||||
]);
|
||||
}
|
||||
|
||||
UNSAFE_componentWillReceiveProps(nextProps) {
|
||||
componentDidUpdate(prevProps) {
|
||||
if (
|
||||
nextProps.params.remoteSchemaName !== this.props.params.remoteSchemaName
|
||||
prevProps.params.remoteSchemaName !== this.props.params.remoteSchemaName
|
||||
) {
|
||||
Promise.all([
|
||||
this.props.dispatch(
|
||||
fetchRemoteSchema(nextProps.params.remoteSchemaName)
|
||||
fetchRemoteSchema(this.props.params.remoteSchemaName)
|
||||
),
|
||||
this.props.dispatch({
|
||||
type: VIEW_REMOTE_SCHEMA,
|
||||
data: nextProps.params.remoteSchemaName,
|
||||
data: this.props.params.remoteSchemaName,
|
||||
}),
|
||||
]);
|
||||
}
|
||||
@ -69,19 +97,14 @@ class ViewStitchedSchema extends React.Component {
|
||||
}
|
||||
|
||||
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 { manualUrl, envName, headers, readOnlyMode } = this.props;
|
||||
const {
|
||||
manualUrl,
|
||||
envName,
|
||||
headers,
|
||||
readOnlyMode,
|
||||
inconsistentObjects,
|
||||
} = this.props;
|
||||
|
||||
const filterHeaders = headers.filter(h => !!h.name);
|
||||
|
||||
@ -92,21 +115,14 @@ class ViewStitchedSchema extends React.Component {
|
||||
},
|
||||
{
|
||||
title: 'Manage',
|
||||
url: appPrefix + '/' + 'manage',
|
||||
url: `${appPrefix}/manage`,
|
||||
},
|
||||
];
|
||||
|
||||
if (remoteSchemaName) {
|
||||
breadCrumbs.push({
|
||||
title: remoteSchemaName.trim(),
|
||||
url:
|
||||
appPrefix +
|
||||
'/' +
|
||||
'manage' +
|
||||
'/' +
|
||||
remoteSchemaName.trim() +
|
||||
'/' +
|
||||
'details',
|
||||
url: `${appPrefix}/manage/${remoteSchemaName.trim()}/details`,
|
||||
});
|
||||
breadCrumbs.push({
|
||||
title: 'details',
|
||||
@ -114,33 +130,18 @@ class ViewStitchedSchema extends React.Component {
|
||||
});
|
||||
}
|
||||
|
||||
const refresh = (
|
||||
<Tooltip id="tooltip-cascade">
|
||||
If your remote schema has changed, you need to refresh the GraphQL
|
||||
Engine metadata to query the modified schema
|
||||
</Tooltip>
|
||||
);
|
||||
let tabInfoCopy = tabInfo;
|
||||
|
||||
if (readOnlyMode) {
|
||||
delete tabInfo.modify;
|
||||
const { modify, ...rest } = tabInfoCopy;
|
||||
tabInfoCopy = rest;
|
||||
}
|
||||
|
||||
const showReloadRemoteSchema =
|
||||
!readOnlyMode && remoteSchemaName && remoteSchemaName.length > 0 ? (
|
||||
<div className={styles.commonBtn + ' ' + styles.detailsRefreshButton}>
|
||||
<span>
|
||||
<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;
|
||||
const inconsistencyDetails = inconsistentObjects.find(
|
||||
inconObj =>
|
||||
inconObj.type === 'remote_schema' &&
|
||||
inconObj?.definition?.name === remoteSchemaName
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
@ -150,7 +151,7 @@ class ViewStitchedSchema extends React.Component {
|
||||
appPrefix={appPrefix}
|
||||
currentTab="details"
|
||||
heading={remoteSchemaName}
|
||||
tabsInfo={tabInfo}
|
||||
tabsInfo={tabInfoCopy}
|
||||
breadCrumbs={breadCrumbs}
|
||||
baseUrl={`${appPrefix}/manage/${remoteSchemaName}`}
|
||||
/>
|
||||
@ -164,37 +165,35 @@ class ViewStitchedSchema extends React.Component {
|
||||
<td>GraphQL Server URL</td>
|
||||
<td>{manualUrl || `<${envName}>`}</td>
|
||||
</tr>
|
||||
{filterHeaders.length > 0 ? (
|
||||
<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>
|
||||
*/}
|
||||
<RSHeadersDisplay data={filterHeaders} />
|
||||
</tbody>
|
||||
</table>
|
||||
</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>
|
||||
<br />
|
||||
<br />
|
||||
@ -210,6 +209,7 @@ const mapStateToProps = state => {
|
||||
allRemoteSchemas: state.remoteSchemas.listData.remoteSchemas,
|
||||
dataHeaders: { ...state.tables.dataHeaders },
|
||||
readOnlyMode: state.main.readOnlyMode,
|
||||
inconsistentObjects: state.metadata.inconsistentObjects,
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
@import "../../Common/Common.scss";
|
||||
@import '../../Common/Common.scss';
|
||||
|
||||
.addPaddCommom {
|
||||
padding: 10px 0;
|
||||
@ -42,17 +42,12 @@
|
||||
width: 100%;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
// height: 200px;
|
||||
// border: 1px solid #000;
|
||||
|
||||
img {
|
||||
}
|
||||
}
|
||||
|
||||
.commonBtn {
|
||||
text-align: center;
|
||||
padding: 20px 0;
|
||||
padding-bottom: 10px
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.readMore {
|
||||
@ -140,7 +135,7 @@
|
||||
}
|
||||
|
||||
.red_button {
|
||||
color: #FFF;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
a {
|
||||
@ -166,7 +161,6 @@
|
||||
.set_line_height {
|
||||
line-height: 26px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.remoteSchemaImg {
|
||||
@ -260,24 +254,24 @@
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.instructionsWrapper, .instructionsWrapperPos {
|
||||
.instructionsWrapper,
|
||||
.instructionsWrapperPos {
|
||||
margin-top: 20px;
|
||||
border-top: 1px solid #DEDEDE;
|
||||
border-top: 1px solid #dedede;
|
||||
.instructions {
|
||||
padding: 12px 0;
|
||||
}
|
||||
}
|
||||
.instructionsWrapper
|
||||
{
|
||||
.instructionsWrapper {
|
||||
position: relative;
|
||||
}
|
||||
.instructionsWrapperPos
|
||||
{
|
||||
.instructionsWrapperPos {
|
||||
position: static;
|
||||
}
|
||||
.instructionsWrapper:hover, .instructionsWrapperPos:hover {
|
||||
.instructionsWrapper:hover,
|
||||
.instructionsWrapperPos:hover {
|
||||
.instructions {
|
||||
color: #505050
|
||||
color: #505050;
|
||||
}
|
||||
|
||||
.rightArrow {
|
||||
|
@ -5,10 +5,10 @@ import PropTypes from 'prop-types';
|
||||
import LeftContainer from '../../Common/Layout/LeftContainer/LeftContainer';
|
||||
import PageContainer from '../../Common/Layout/PageContainer/PageContainer';
|
||||
import RemoteSchemaSubSidebar from './RemoteSchemaSubSidebar';
|
||||
import styles from '../../Common/TableCommon/Table.scss';
|
||||
|
||||
class RemoteSchemaPageContainer extends React.Component {
|
||||
render() {
|
||||
const styles = require('../../Common/TableCommon/Table.scss');
|
||||
const { appPrefix, children } = this.props;
|
||||
|
||||
const currentLocation = location.pathname;
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Route, IndexRedirect } from 'react-router';
|
||||
import { rightContainerConnector } from '../../Common/Layout';
|
||||
import { RightContainer } from '../../Common/Layout/RightContainer';
|
||||
import globals from '../../../Globals';
|
||||
import {
|
||||
remoteSchemaPageConnector,
|
||||
@ -95,7 +95,7 @@ const getRemoteSchemaRouter = (connect, store, composeOnEnterHooks) => {
|
||||
onChange={fetchInitialData(store)}
|
||||
>
|
||||
<IndexRedirect to="manage" />
|
||||
<Route path="manage" component={rightContainerConnector(connect)}>
|
||||
<Route path="manage" component={RightContainer}>
|
||||
<IndexRedirect to="schemas" />
|
||||
<Route path="schemas" component={landingConnector(connect)} />
|
||||
<Route path="add" component={addConnector(connect)} />
|
||||
|
@ -2,6 +2,8 @@ import React from 'react';
|
||||
import { Link } from 'react-router';
|
||||
|
||||
import LeftSubSidebar from '../../Common/Layout/LeftSubSidebar/LeftSubSidebar';
|
||||
import styles from '../../Common/Layout/LeftSubSidebar/LeftSubSidebar.scss';
|
||||
import WarningSymbol from '../../Common/WarningSymbol/WarningSymbol';
|
||||
|
||||
const RemoteSchemaSubSidebar = ({
|
||||
appPrefix,
|
||||
@ -12,8 +14,12 @@ const RemoteSchemaSubSidebar = ({
|
||||
filterItem,
|
||||
viewRemoteSchema,
|
||||
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) {
|
||||
const searchTerm = e.target.value;
|
||||
@ -35,7 +41,7 @@ const RemoteSchemaSubSidebar = ({
|
||||
const getChildList = () => {
|
||||
const _dataList = searchQuery ? filtered : dataList;
|
||||
|
||||
let childList;
|
||||
let childList = [];
|
||||
if (_dataList.length === 0) {
|
||||
childList = (
|
||||
<li
|
||||
@ -46,8 +52,10 @@ const RemoteSchemaSubSidebar = ({
|
||||
</li>
|
||||
);
|
||||
} else {
|
||||
if (_dataList.length > 0) {
|
||||
childList = _dataList.map((d, i) => {
|
||||
let activeTableClass = '';
|
||||
|
||||
if (
|
||||
d.name === viewRemoteSchema &&
|
||||
location.pathname.includes(viewRemoteSchema)
|
||||
@ -55,6 +63,10 @@ const RemoteSchemaSubSidebar = ({
|
||||
activeTableClass = styles.activeLink;
|
||||
}
|
||||
|
||||
const inconsistentCurrentSchema = inconsistentRemoteSchemas.find(
|
||||
elem => elem.definition.name === d.name
|
||||
);
|
||||
|
||||
return (
|
||||
<li
|
||||
className={activeTableClass}
|
||||
@ -62,19 +74,30 @@ const RemoteSchemaSubSidebar = ({
|
||||
data-test={`remote-schema-sidebar-links-${i + 1}`}
|
||||
>
|
||||
<Link
|
||||
to={appPrefix + '/manage/' + d.name + '/details'}
|
||||
to={`${appPrefix}/manage/${d.name}/details`}
|
||||
data-test={d.name}
|
||||
>
|
||||
<i
|
||||
className={styles.tableIcon + ' fa fa-code-fork'}
|
||||
className={`${styles.tableIcon} fa fa-code-fork`}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
{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>
|
||||
</li>
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return childList;
|
||||
};
|
||||
|
@ -208,11 +208,6 @@ const handleInconsistentObjects = inconsistentObjects => {
|
||||
inconsistentObjects,
|
||||
'functions'
|
||||
);
|
||||
const filteredRemoteSchemas = filterInconsistentMetadataObjects(
|
||||
remoteSchemas,
|
||||
inconsistentObjects,
|
||||
'remote_schemas'
|
||||
);
|
||||
const filteredActions = filterInconsistentMetadataObjects(
|
||||
actions,
|
||||
inconsistentObjects,
|
||||
@ -221,7 +216,7 @@ const handleInconsistentObjects = inconsistentObjects => {
|
||||
|
||||
dispatch(setConsistentSchema(filteredSchema));
|
||||
dispatch(setConsistentFunctions(filteredFunctions));
|
||||
dispatch(setConsistentRemoteSchemas(filteredRemoteSchemas));
|
||||
dispatch(setConsistentRemoteSchemas(remoteSchemas));
|
||||
dispatch(setActions(filteredActions));
|
||||
}
|
||||
};
|
||||
@ -234,9 +229,7 @@ export const loadInconsistentObjects = (reloadConfig, successCb, failureCb) => {
|
||||
const { shouldReloadMetadata, shouldReloadRemoteSchemas } = reloadConfig;
|
||||
|
||||
const loadQuery = shouldReloadMetadata
|
||||
? getReloadCacheAndGetInconsistentObjectsQuery(
|
||||
shouldReloadRemoteSchemas === false ? false : true
|
||||
)
|
||||
? getReloadCacheAndGetInconsistentObjectsQuery(shouldReloadRemoteSchemas)
|
||||
: inconsistentObjectsQuery;
|
||||
|
||||
dispatch({ type: LOADING_METADATA });
|
||||
|
@ -5,13 +5,13 @@ import Sidebar from './Sidebar';
|
||||
import PageContainer from '../../Common/Layout/PageContainer/PageContainer';
|
||||
|
||||
type Metadata = {
|
||||
inconsistentObjects: object[];
|
||||
inconsistentObjects: Record<string, unknown>[];
|
||||
ongoingRequest: boolean;
|
||||
allowedQueries: object[];
|
||||
allowedQueries: Record<string, any>[];
|
||||
};
|
||||
|
||||
type ExternalProps = {
|
||||
location: RouteComponentProps<{}, {}>['location'];
|
||||
location: RouteComponentProps<unknown, unknown>['location'];
|
||||
children: JSX.Element;
|
||||
};
|
||||
|
||||
@ -55,6 +55,8 @@ const mapStateToProps = (state: DerivedState) => {
|
||||
type StateProps = ReturnType<typeof mapStateToProps>;
|
||||
|
||||
const connector = (connect: Connect) =>
|
||||
connect<StateProps, {}, {}, DerivedState>(mapStateToProps)(Container);
|
||||
connect<StateProps, unknown, unknown, DerivedState>(mapStateToProps)(
|
||||
Container
|
||||
);
|
||||
|
||||
export default connector;
|
||||
|
@ -2,6 +2,7 @@ import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Button from '../../../Common/Button/Button';
|
||||
import { reloadRemoteSchema } from '../Actions';
|
||||
import metaDataStyles from '../Settings.scss';
|
||||
|
||||
import {
|
||||
showSuccessNotification,
|
||||
@ -17,7 +18,6 @@ class ReloadRemoteSchema extends Component {
|
||||
render() {
|
||||
const { dispatch, remoteSchemaName } = this.props;
|
||||
const { isReloading } = this.state;
|
||||
const metaDataStyles = require('../Settings.scss');
|
||||
const reloadRemoteMetadataHandler = () => {
|
||||
this.setState({ isReloading: true });
|
||||
dispatch(
|
||||
|
@ -133,7 +133,7 @@ const MetadataStatus = ({ dispatch, metadata }) => {
|
||||
of the metadata
|
||||
</div>
|
||||
<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
|
||||
</div>
|
||||
</div>
|
||||
|
@ -10,11 +10,11 @@ import { getAdminSecret } from '../ApiExplorer/ApiRequest/utils';
|
||||
import styles from '../../Common/TableCommon/Table.scss';
|
||||
|
||||
interface Metadata {
|
||||
inconsistentObjects: object[];
|
||||
inconsistentObjects: Record<string, unknown>[];
|
||||
}
|
||||
|
||||
type SidebarProps = {
|
||||
location: RouteComponentProps<{}, {}>['location'];
|
||||
location: RouteComponentProps<unknown, unknown>['location'];
|
||||
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 { theme } from '../../theme';
|
||||
import { Icon } from '../Icon';
|
||||
import { StyledAlertBox } from './AlertBox';
|
||||
import { theme, Theme } from '../../theme';
|
||||
import { Icon, IconProps } from '../Icon';
|
||||
import { StyledAlertBox, StyledAlertBoxProps } from './AlertBox';
|
||||
import { Text } from '../Typography';
|
||||
|
||||
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 backgroundColorValue = theme.alertBox[type]
|
@ -112,8 +112,8 @@ const iconReferenceMap = {
|
||||
};
|
||||
|
||||
export type IconProps = {
|
||||
pointer: boolean;
|
||||
size: number;
|
||||
pointer?: boolean;
|
||||
size?: number;
|
||||
type: keyof Theme['icon'];
|
||||
};
|
||||
|
||||
|
@ -16,14 +16,17 @@ Heading.defaultProps = {
|
||||
* fontSize: 'explain'
|
||||
* fontWeight: 'bold'
|
||||
*/
|
||||
|
||||
export type TextProps = {
|
||||
type: keyof Theme['lineHeights'];
|
||||
fontWeight: keyof Theme['fontWeights'];
|
||||
fontSize: keyof Theme['fontSizes'];
|
||||
mb: keyof Theme['space'];
|
||||
mt: keyof Theme['space'];
|
||||
mr: keyof Theme['space'];
|
||||
ml: keyof Theme['space'];
|
||||
type?: keyof Theme['lineHeights'];
|
||||
fontWeight?: keyof Theme['fontWeights'];
|
||||
fontSize?: keyof Theme['fontSizes'];
|
||||
mb?: keyof Theme['space'];
|
||||
mt?: keyof Theme['space'];
|
||||
mr?: 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 => {
|
||||
@ -64,6 +67,9 @@ Text.defaultProps = {
|
||||
mt: 'zero',
|
||||
mr: 'zero',
|
||||
ml: 'zero',
|
||||
pl: 'zero',
|
||||
fontWeight: 'normal',
|
||||
fontSize: 'p',
|
||||
};
|
||||
|
||||
type TextLinkProps = {
|
||||
|
@ -81,10 +81,6 @@ const Html: React.FC<HtmlProps> = props => {
|
||||
|
||||
<div id="content" className="content" />
|
||||
<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>
|
||||
</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 UIKit from './components/UIKit/';
|
||||
import { Heading } from './components/UIKit/atoms';
|
||||
import { SupportContainer } from './components/Services/Support/SupportContainer';
|
||||
import HelpPage from './components/Services/Support/HelpPage';
|
||||
|
||||
const routes = store => {
|
||||
// load hasuractl migration status
|
||||
@ -71,7 +73,6 @@ const routes = store => {
|
||||
|
||||
return;
|
||||
};
|
||||
|
||||
const _dataRouterUtils = dataRouterUtils(connect, store, composeOnEnterHooks);
|
||||
const requireSchema = _dataRouterUtils.requireSchema;
|
||||
const dataRouter = _dataRouterUtils.makeDataRouter;
|
||||
@ -146,6 +147,9 @@ const routes = store => {
|
||||
{actionsRouter}
|
||||
{eventsRouter}
|
||||
{uiKitRouter}
|
||||
<Route path="support" component={SupportContainer}>
|
||||
<Route path="forums" component={HelpPage} />
|
||||
</Route>
|
||||
</Route>
|
||||
</Route>
|
||||
<Route path="404" component={PageNotFound} status="404" />
|
||||
|
@ -26,10 +26,10 @@ type Telemetry = {
|
||||
|
||||
const setConsoleOptsInDB = (
|
||||
opts: TelemetryState['console_opts'],
|
||||
successCb: (arg: object) => void,
|
||||
successCb: (arg: Record<string, any>) => void,
|
||||
errorCb: (arg: Error) => void
|
||||
) => (
|
||||
dispatch: ThunkDispatch<ReduxState, {}, AnyAction>,
|
||||
dispatch: ThunkDispatch<ReduxState, unknown, AnyAction>,
|
||||
getState: GetReduxState
|
||||
) => {
|
||||
const url = Endpoints.getSchema;
|
||||
@ -66,7 +66,7 @@ const setConsoleOptsInDB = (
|
||||
|
||||
// eslint-disable-next-line consistent-return
|
||||
return dispatch(requestAction(url, options)).then(
|
||||
(data: object) => {
|
||||
(data: Record<string, unknown>) => {
|
||||
if (successCb) {
|
||||
successCb(data);
|
||||
}
|
||||
@ -86,7 +86,7 @@ const telemetryNotificationShown = () => (
|
||||
};
|
||||
|
||||
const setTelemetryNotificationShownInDB = () => {
|
||||
const successCb = (data: object) => {
|
||||
const successCb = (data: Record<string, unknown>) => {
|
||||
console.log(
|
||||
`Updated telemetry notification status in db ${JSON.stringify(data)}`
|
||||
);
|
||||
@ -108,7 +108,7 @@ const setTelemetryNotificationShownInDB = () => {
|
||||
};
|
||||
|
||||
const setPreReleaseNotificationOptOutInDB = () => (
|
||||
dispatch: ThunkDispatch<ReduxState, {}, AnyAction>
|
||||
dispatch: ThunkDispatch<ReduxState, unknown, AnyAction>
|
||||
) => {
|
||||
const successCb = () => {
|
||||
dispatch(
|
||||
@ -132,7 +132,7 @@ const setPreReleaseNotificationOptOutInDB = () => (
|
||||
|
||||
const loadConsoleOpts = () => {
|
||||
return (
|
||||
dispatch: ThunkDispatch<ReduxState, {}, AnyAction>,
|
||||
dispatch: ThunkDispatch<ReduxState, unknown, AnyAction>,
|
||||
getState: GetReduxState
|
||||
) => {
|
||||
const url = Endpoints.getSchema;
|
||||
@ -172,12 +172,12 @@ const loadConsoleOpts = () => {
|
||||
|
||||
interface SetConsoleOptsAction {
|
||||
type: typeof SET_CONSOLE_OPTS;
|
||||
data: object;
|
||||
data: Record<string, unknown>;
|
||||
}
|
||||
|
||||
interface SetNotificationShowAction {
|
||||
type: typeof SET_NOTIFICATION_SHOWN;
|
||||
data?: object;
|
||||
data?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
interface SetHasuraUuid {
|
||||
@ -193,7 +193,7 @@ type TelemetryActionTypes =
|
||||
export const requireConsoleOpts = ({
|
||||
dispatch,
|
||||
}: {
|
||||
dispatch: ThunkDispatch<ReduxState, {}, AnyAction>;
|
||||
dispatch: ThunkDispatch<ReduxState, unknown, AnyAction>;
|
||||
}) => (nextState: ReduxState, replaceState: ReduxState, callback: any) => {
|
||||
dispatch(loadConsoleOpts()).finally(callback);
|
||||
};
|
||||
|
@ -6,13 +6,13 @@ import { setTelemetryNotificationShownInDB } from './Actions';
|
||||
import { ReduxState } from '../types';
|
||||
|
||||
const onRemove = () => {
|
||||
return (dispatch: ThunkDispatch<ReduxState, {}, AnyAction>) => {
|
||||
return (dispatch: ThunkDispatch<ReduxState, unknown, AnyAction>) => {
|
||||
dispatch(setTelemetryNotificationShownInDB());
|
||||
};
|
||||
};
|
||||
|
||||
const showTelemetryNotification = () => {
|
||||
return (dispatch: ThunkDispatch<ReduxState, {}, AnyAction>) => {
|
||||
return (dispatch: ThunkDispatch<ReduxState, unknown, AnyAction>) => {
|
||||
dispatch(
|
||||
Notifications.show({
|
||||
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
|
||||
|
||||
:orphan:
|
||||
|
||||
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({
|
||||
appId: 'WCBB1VVLRC',
|
||||
apiKey: '298d448cd9d7ed93fbab395658da19e8',
|
||||
indexName: 'graphql-docs-prod',
|
||||
apiKey: HDOCS_ALGOLIA_API_KEY,
|
||||
indexName: HDOCS_ALGOLIA_INDEX,
|
||||
inputSelector: '#search_element',
|
||||
transformData: hdocs.transformSearchData,
|
||||
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) {
|
||||
setTimeout(function () { document.getElementById('search_help').classList.remove('hide'); }, 100);
|
||||
document.getElementById('search_element').addEventListener('blur', function () {
|
||||
setTimeout(function () { document.getElementById('search_help').classList.add('hide'); }, 100);
|
||||
});
|
||||
} else if (!document.getElementById('search_help').classList.contains('hide')) {
|
||||
document.getElementById('search_help').classList.add('hide');
|
||||
setTimeout(function () { searchHelp.classList.remove('hide'); }, 100);
|
||||
searchField.addEventListener('blur', function () {
|
||||
setTimeout(hideHelp, 100);
|
||||
}, { once: true });
|
||||
searchField.addEventListener('input', function () {
|
||||
if (searchField.value === '') {
|
||||
setTimeout(hideHelp, 100);
|
||||
}
|
||||
}, { once: true });
|
||||
} else if (!searchHelp.classList.contains('hide')) {
|
||||
hideHelp();
|
||||
}
|
||||
|
||||
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 submit_btn = document.getElementById('mc-embedded-subscribe');
|
||||
const mcStatusSuccess = document.querySelector('.mce-success-response');
|
||||
const mcStatusError = document.querySelector('.mce-error-response');
|
||||
const mcStatusSuccess = document.getElementById('mce-success-response');
|
||||
const mcStatusError = document.getElementById('mce-error-response');
|
||||
|
||||
email_input.addEventListener('input', function() {
|
||||
submit_btn.value = 'Subscribe';
|
||||
@ -60,16 +60,19 @@ const submitNewsletterForm = function (form) {
|
||||
"pageUri": window.location.host + window.location.pathname,
|
||||
"pageName": document.title,
|
||||
};
|
||||
|
||||
const gqlMutation = `mutation docsNewsletterSignup($objects: [newsletterSignupInput!]! ) {
|
||||
signupNewsletter(objects: $objects) {
|
||||
affected_rows
|
||||
}
|
||||
}`;
|
||||
|
||||
const objects = [{
|
||||
"email": email,
|
||||
"hbs_context": hbs_context,
|
||||
"category": "docs"
|
||||
}]
|
||||
}];
|
||||
|
||||
fetch(gqlEndpoint, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
|
6
docs/_static/styles/landing.css
vendored
6
docs/_static/styles/landing.css
vendored
@ -15,13 +15,11 @@
|
||||
}
|
||||
|
||||
.body_content {
|
||||
font-family: 'Gudea';
|
||||
font-size: 15px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.small_content {
|
||||
font-family: 'Gudea';
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
}
|
||||
@ -101,6 +99,10 @@
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.description {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.text_left {
|
||||
text-align: left;
|
||||
}
|
||||
|
54
docs/_static/styles/main.css
vendored
54
docs/_static/styles/main.css
vendored
@ -99,16 +99,16 @@ p {
|
||||
}
|
||||
|
||||
/* Newsletter */
|
||||
#mc_embed_signup {
|
||||
.mc_embed_signup {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
#mc_embed_signup .subscribe-form-content {
|
||||
.mc_embed_signup .subscribe-form-content {
|
||||
font-weight: bold;
|
||||
color: #001934;
|
||||
padding-right: 40px;
|
||||
}
|
||||
#mc_embed_signup form {
|
||||
.mc_embed_signup form {
|
||||
display: flex !important;
|
||||
padding: 0 0 0 0 !important;
|
||||
flex: 1 !important;
|
||||
@ -116,17 +116,17 @@ p {
|
||||
font-size: 16px;
|
||||
}
|
||||
/* todo: redesign & clean up */
|
||||
#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:hover {background-color:#777;}
|
||||
#mc_embed_signup .clear {clear:both;}
|
||||
.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:hover {background-color:#777;}
|
||||
.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.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-success-response {color:#529214; display:none;}
|
||||
#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_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 .mce-error-response {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-embedded-subscribe {clear:both; width:auto; display:block; margin:1em 0 1em 5%;}
|
||||
|
||||
.newsletter-link,
|
||||
.newsletter-link:hover,
|
||||
@ -204,7 +204,7 @@ p {
|
||||
.submit-box input:focus {
|
||||
outline: none;
|
||||
}
|
||||
#mce-responses .error-message, #mce-responses .success-message {
|
||||
.mce-responses .error-message, .mce-responses .success-message {
|
||||
background-color: #001934;
|
||||
padding: 8px 12px !important;
|
||||
border-radius: 4px;
|
||||
@ -216,13 +216,13 @@ p {
|
||||
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;
|
||||
}
|
||||
#mce-responses .error-message {
|
||||
.mce-responses .error-message {
|
||||
color: #efc371 !important;
|
||||
}
|
||||
#mce-responses .success-message {
|
||||
.mce-responses .success-message {
|
||||
color: #1cd3c6 !important;
|
||||
}
|
||||
#content_inner_wrapper {
|
||||
@ -640,6 +640,10 @@ article ol ol {
|
||||
margin: 15px;
|
||||
}
|
||||
|
||||
.sphinxsidebarwrapper > ul:not(.current) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#sidebar {
|
||||
background-color: #001934;
|
||||
width: 24% !important;
|
||||
@ -1010,17 +1014,17 @@ article ol ol {
|
||||
}
|
||||
}
|
||||
@media (min-width: 768px) and (max-width: 1385px) {
|
||||
#mc_embed_signup form {
|
||||
.mc_embed_signup form {
|
||||
padding-top: 10px !important;
|
||||
}
|
||||
#mc_embed_signup {
|
||||
.mc_embed_signup {
|
||||
display: block;
|
||||
}
|
||||
#mc_embed_signup .subscribe-form-content {
|
||||
.mc_embed_signup .subscribe-form-content {
|
||||
padding-right: 0;
|
||||
text-align: center;
|
||||
}
|
||||
#mce-responses .error-message, #mce-responses .success-message {
|
||||
.mce-responses .error-message, .mce-responses .success-message {
|
||||
top: 34px !important;
|
||||
}
|
||||
}
|
||||
@ -1030,20 +1034,20 @@ article ol ol {
|
||||
top: 170px;
|
||||
height: calc(100% - 170px);
|
||||
}
|
||||
#mc_embed_signup {
|
||||
.mc_embed_signup {
|
||||
display: block;
|
||||
}
|
||||
#mc_embed_signup .subscribe-form-content {
|
||||
.mc_embed_signup .subscribe-form-content {
|
||||
padding-right: 0;
|
||||
text-align: center;
|
||||
}
|
||||
#mc_embed_signup form {
|
||||
.mc_embed_signup form {
|
||||
padding-top: 10px !important;
|
||||
}
|
||||
.submit-box input {
|
||||
padding: 0 4px !important;
|
||||
}
|
||||
#mce-responses .error-message, #mce-responses .success-message {
|
||||
.mce-responses .error-message, .mce-responses .success-message {
|
||||
top: 34px !important;
|
||||
}
|
||||
#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);
|
||||
})(window,document,'script','dataLayer','GTM-PF5MQ2Z');</script>
|
||||
<!-- 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 %}
|
||||
<link rel="search" type="application/opensearchdescription+xml"
|
||||
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 %}
|
||||
{%- 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'] %}
|
||||
|
||||
{%- 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'] %}
|
||||
|
||||
{%- if pagename.endswith('mysql-preview') %}
|
||||
{% set deferred_script_files = deferred_script_files + ['_static/scripts/mysql-subscribe.js'] %}
|
||||
{%- endif %}
|
||||
|
||||
{%- macro secondnav() %}
|
||||
{%- if prev %}
|
||||
« <a href="{{ prev.link|e }}" title="{{ prev.title|e }}">previous</a>
|
||||
@ -105,7 +119,7 @@
|
||||
</a>
|
||||
</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">
|
||||
Get Started
|
||||
</button>
|
||||
@ -162,7 +176,7 @@
|
||||
</a>
|
||||
</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'>
|
||||
Get Started
|
||||
</button>
|
||||
@ -187,12 +201,12 @@
|
||||
<img src="{{ pathto('_images/layout/close-icon.svg', 1) }}" alt="Close"/>
|
||||
</div>
|
||||
<div class="tabbarContainerWrapper blueBgColor boderBottom">
|
||||
<a href="" class="tabbarTabActive">
|
||||
<a href="{{ pathto('graphql/core/index.html', 1) }}" {%- if is_core %} class="tabbarTabActive" {%- endif %}>
|
||||
<span>
|
||||
Hasura Core
|
||||
</span>
|
||||
</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>
|
||||
Hasura Cloud
|
||||
</span>
|
||||
@ -280,29 +294,27 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="subscribe-form-wrapper">
|
||||
<div id="mc_embed_signup">
|
||||
<div id="mc_embed_signup" class="mc_embed_signup">
|
||||
<div class="subscribe-form-content">
|
||||
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>
|
||||
<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="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 id="mce-responses" class="clear display-inline">
|
||||
<div class="mce-error-response response error-message hide">
|
||||
<div id="mce-responses" class="clear display-inline mce-responses">
|
||||
<div id="mce-error-response" class="mce-error-response response error-message hide">
|
||||
</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 style="position: absolute; left: -5000px;" aria-hidden="true"><input type="text" name="b_9b63e92a98ecdc99732456b0e_f5c4f66bcf" tabindex="-1" value=""></div>
|
||||
<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>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-hasura-custom">
|
||||
@ -344,6 +356,8 @@
|
||||
<script type="text/javascript">
|
||||
const HDOCS_BASE_DOMAIN = "{{ BASE_DOMAIN }}";
|
||||
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 () {
|
||||
hdocs && hdocs.setup();
|
||||
|
21
docs/_theme/djangodocs/localtoc.html
vendored
21
docs/_theme/djangodocs/localtoc.html
vendored
@ -1,24 +1,3 @@
|
||||
{%- 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 }}
|
||||
{%- 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">
|
||||
<img src="{{ pathto('_images/landing/graphql.svg', 1) }}" alt="GraphQL engine"/>
|
||||
<h3 class="head_wrapper">1. The Hasura GraphQL engine</h3>
|
||||
</div>
|
||||
<div class="view_all_wrapper small_content">
|
||||
<h3 class="head_wrapper">Hasura docs</h3>
|
||||
</div>
|
||||
</div>
|
||||
<div class="small_content space_wrapper text_left">
|
||||
This guide covers all Hasura GraphQL engine concepts and features.
|
||||
<br/> <br/>
|
||||
</div>
|
||||
<div class="sign_in_wrapper space_wrapper">
|
||||
<div class="get_start">
|
||||
<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>
|
||||
<div class="space_wrapper text_left">
|
||||
<p class="description">This guide covers all Hasura concepts and features.</p>
|
||||
<ul>
|
||||
<li>
|
||||
<a class="docs_link" href="{{ pathto('graphql/core/index.html', 1) }}">
|
||||
Core docs
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="docs_link" href="{{ pathto('graphql/cloud/index.html', 1) }}">
|
||||
Cloud docs
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</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