From 58c44f55dd98468bcc71f99cf7d93727edb06b06 Mon Sep 17 00:00:00 2001 From: Vishnu Bharathi P Date: Thu, 12 Nov 2020 14:55:48 +0530 Subject: [PATCH] Merge oss/master onto mono/main GitOrigin-RevId: 1c8c4d60e033c8a0bc8b2beed24c5bceb7d4bcc8 --- .circleci/cli-builder.dockerfile | 4 +- .circleci/config.yml | 4 +- CHANGELOG.md | 5 + cli/commands/scripts_update_config_v2.go | 4 +- cli/migrate/database/hasuradb/metadata.go | 15 +- cli/plugins/scanner.go | 10 + cli/plugins/types.go | 2 +- console/src/Endpoints.ts | 5 +- .../components/Common/utils/v1QueryUtils.ts | 65 +- console/src/components/Main/Actions.js | 195 +++--- .../components/Main/ConsoleNotification.ts | 1 - console/src/components/Main/Main.js | 44 ++ console/src/components/Main/Main.scss | 6 +- .../components/Main/NotificationSection.tsx | 18 +- console/src/telemetry/Actions.ts | 251 +++++--- console/src/types.ts | 35 ++ contrib/metadata-types/README.md | 114 ++-- contrib/metadata-types/package.json | 5 +- docs/graphql/core/schema/data-validations.rst | 8 +- scripts/cli-migrations/Makefile | 6 +- scripts/cli-migrations/v2/Dockerfile | 8 +- .../cli-migrations/v2/docker-entrypoint.sh | 7 +- .../v2/prepare_docker_context.sh | 29 + scripts/edit-pg-dump/edit-pg-dump.sh | 2 +- server/.gitignore | 3 + server/.hlint.yaml | 8 + server/CONTRIBUTING.md | 14 + server/graphql-engine.cabal | 25 +- server/sample.hie.yaml | 10 + server/src-exec/Main.hs | 12 +- server/src-lib/Hasura/App.hs | 4 +- .../Backends/Postgres/Execute/Mutation.hs | 88 ++- .../Backends/Postgres/Execute/RemoteJoin.hs | 116 ++-- .../Hasura/Backends/Postgres/SQL/DML.hs | 27 +- .../Backends/Postgres/Translate/BoolExp.hs | 10 +- .../Backends/Postgres/Translate/Delete.hs | 11 +- .../Backends/Postgres/Translate/Insert.hs | 131 +--- .../Backends/Postgres/Translate/Mutation.hs | 25 +- .../Backends/Postgres/Translate/Returning.hs | 65 +- .../Backends/Postgres/Translate/Select.hs | 44 +- .../Backends/Postgres/Translate/Update.hs | 9 +- .../src-lib/Hasura/Eventing/EventTrigger.hs | 12 +- server/src-lib/Hasura/GraphQL/Context.hs | 32 +- .../src-lib/Hasura/GraphQL/Execute/Insert.hs | 156 +++-- .../Hasura/GraphQL/Execute/Mutation.hs | 37 +- .../src-lib/Hasura/GraphQL/Execute/Query.hs | 2 +- server/src-lib/Hasura/GraphQL/Parser/Class.hs | 87 +-- .../Hasura/GraphQL/Parser/Class.hs-boot | 5 - .../Hasura/GraphQL/Parser/Class/Parse.hs | 46 ++ .../src-lib/Hasura/GraphQL/Parser/Collect.hs | 33 +- .../Hasura/GraphQL/Parser/Internal/Parser.hs | 166 +---- .../GraphQL/Parser/Internal/Parser.hs-boot | 23 - .../Hasura/GraphQL/Parser/Internal/Types.hs | 142 +++++ server/src-lib/Hasura/GraphQL/Schema.hs | 72 ++- .../src-lib/Hasura/GraphQL/Schema/Insert.hs | 77 --- .../src-lib/Hasura/GraphQL/Schema/Mutation.hs | 156 ++--- .../src-lib/Hasura/GraphQL/Schema/OrderBy.hs | 56 +- .../src-lib/Hasura/GraphQL/Schema/Select.hs | 433 +++++++------- .../Hasura/GraphQL/Transport/WebSocket.hs | 63 +- .../Hasura/Incremental/Internal/Dependency.hs | 10 +- server/src-lib/Hasura/Prelude.hs | 20 +- .../src-lib/Hasura/RQL/DDL/ComputedField.hs | 12 +- server/src-lib/Hasura/RQL/DDL/EventTrigger.hs | 79 ++- server/src-lib/Hasura/RQL/DDL/Metadata.hs | 206 +++---- .../Hasura/RQL/DDL/Metadata/Generator.hs | 96 +-- .../src-lib/Hasura/RQL/DDL/Metadata/Types.hs | 516 +--------------- server/src-lib/Hasura/RQL/DDL/Permission.hs | 128 ++-- .../Hasura/RQL/DDL/Permission/Internal.hs | 93 --- server/src-lib/Hasura/RQL/DDL/Relationship.hs | 6 +- .../Hasura/RQL/DDL/Relationship/Rename.hs | 1 - server/src-lib/Hasura/RQL/DDL/Schema/Cache.hs | 20 +- server/src-lib/Hasura/RQL/DDL/Schema/Diff.hs | 7 +- .../src-lib/Hasura/RQL/DDL/Schema/Function.hs | 45 -- .../src-lib/Hasura/RQL/DDL/Schema/Rename.hs | 3 +- server/src-lib/Hasura/RQL/DDL/Schema/Table.hs | 2 +- server/src-lib/Hasura/RQL/DDL/Utils.hs | 20 - server/src-lib/Hasura/RQL/DML/Count.hs | 2 + server/src-lib/Hasura/RQL/DML/Delete.hs | 1 + server/src-lib/Hasura/RQL/DML/Insert.hs | 67 ++- server/src-lib/Hasura/RQL/DML/Internal.hs | 4 +- server/src-lib/Hasura/RQL/DML/Select.hs | 4 +- .../Hasura/RQL/{Types/DML.hs => DML/Types.hs} | 202 ++----- server/src-lib/Hasura/RQL/DML/Update.hs | 2 + server/src-lib/Hasura/RQL/IR/BoolExp.hs | 93 ++- server/src-lib/Hasura/RQL/IR/Delete.hs | 10 +- server/src-lib/Hasura/RQL/IR/Insert.hs | 75 ++- server/src-lib/Hasura/RQL/IR/OrderBy.hs | 82 +++ server/src-lib/Hasura/RQL/IR/RemoteJoin.hs | 2 +- server/src-lib/Hasura/RQL/IR/Returning.hs | 17 +- server/src-lib/Hasura/RQL/IR/Select.hs | 204 ++++--- server/src-lib/Hasura/RQL/IR/Update.hs | 9 +- server/src-lib/Hasura/RQL/Types.hs | 4 +- server/src-lib/Hasura/RQL/Types/Action.hs | 11 +- server/src-lib/Hasura/RQL/Types/Catalog.hs | 4 +- server/src-lib/Hasura/RQL/Types/Column.hs | 6 +- server/src-lib/Hasura/RQL/Types/Common.hs | 147 ++++- .../src-lib/Hasura/RQL/Types/ComputedField.hs | 16 +- .../src-lib/Hasura/RQL/Types/CustomTypes.hs | 2 +- .../src-lib/Hasura/RQL/Types/EventTrigger.hs | 3 +- server/src-lib/Hasura/RQL/Types/Function.hs | 48 ++ server/src-lib/Hasura/RQL/Types/Metadata.hs | 565 +++++++++++++++++- server/src-lib/Hasura/RQL/Types/Permission.hs | 123 +++- .../Hasura/RQL/Types/QueryCollection.hs | 2 +- .../Types.hs => Types/Relationship.hs} | 57 +- .../Hasura/RQL/Types/RemoteRelationship.hs | 11 +- .../src-lib/Hasura/RQL/Types/SchemaCache.hs | 1 - .../Hasura/RQL/Types/SchemaCache/Build.hs | 6 +- .../Hasura/RQL/Types/SchemaCacheTypes.hs | 2 +- server/src-lib/Hasura/RQL/Types/Table.hs | 14 +- server/src-lib/Hasura/SQL/Backend.hs | 2 +- server/src-lib/Hasura/Server/API/PGDump.hs | 12 +- server/src-lib/Hasura/Server/API/Query.hs | 3 +- server/src-lib/Hasura/Server/App.hs | 18 +- server/src-lib/Hasura/Server/Init.hs | 22 +- server/src-lib/Hasura/Server/Init/Config.hs | 9 +- server/src-lib/Hasura/Server/Migrate.hs | 11 +- server/src-rsr/catalog_version.txt | 2 +- server/src-rsr/initialise.sql | 25 +- server/src-rsr/migrations/37_to_38.sql | 14 +- server/src-rsr/migrations/38_to_37.sql | 14 +- server/src-rsr/migrations/38_to_39.sql | 57 +- server/src-rsr/migrations/39_to_38.sql | 48 +- server/src-rsr/migrations/39_to_40.sql | 72 +-- server/src-rsr/migrations/40_to_39.sql | 46 +- server/src-rsr/migrations/40_to_41.sql | 37 +- server/src-rsr/migrations/41_to_40.sql | 36 +- server/src-rsr/migrations/41_to_42.sql | 34 ++ server/src-rsr/migrations/42_to_41.sql | 33 + server/src-rsr/trigger.sql.shakespeare | 4 +- server/src-test/Hasura/RQL/MetadataSpec.hs | 14 +- server/tests-py/context.py | 2 +- .../create-delete/create_and_reset.yaml | 68 ++- .../developer_insert_has_keys_any_fail.yaml | 2 +- ...s_var_editors_err_not_allowed_user_id.yaml | 2 +- ...ler_insert_computer_has_keys_all_fail.yaml | 6 +- .../permissions/user_insert_account_fail.yaml | 4 +- .../permissions/user_cannot_publish.yaml | 2 +- ...or_student_role_insert_check_bio_fail.yaml | 8 +- ..._role_insert_check_is_registered_fail.yaml | 4 +- ...r_user_role_insert_check_user_id_fail.yaml | 4 +- server/tests-py/test_actions.py | 6 +- 141 files changed, 3662 insertions(+), 3062 deletions(-) create mode 100755 scripts/cli-migrations/v2/prepare_docker_context.sh create mode 100644 server/sample.hie.yaml delete mode 100644 server/src-lib/Hasura/GraphQL/Parser/Class.hs-boot create mode 100644 server/src-lib/Hasura/GraphQL/Parser/Class/Parse.hs delete mode 100644 server/src-lib/Hasura/GraphQL/Parser/Internal/Parser.hs-boot create mode 100644 server/src-lib/Hasura/GraphQL/Parser/Internal/Types.hs delete mode 100644 server/src-lib/Hasura/GraphQL/Schema/Insert.hs delete mode 100644 server/src-lib/Hasura/RQL/DDL/Utils.hs rename server/src-lib/Hasura/RQL/{Types/DML.hs => DML/Types.hs} (60%) create mode 100644 server/src-lib/Hasura/RQL/IR/OrderBy.hs rename server/src-lib/Hasura/RQL/{DDL/Relationship/Types.hs => Types/Relationship.hs} (67%) create mode 100644 server/src-rsr/migrations/41_to_42.sql create mode 100644 server/src-rsr/migrations/42_to_41.sql diff --git a/.circleci/cli-builder.dockerfile b/.circleci/cli-builder.dockerfile index 9cf3ffd0e5137..b0bbe6f0fc262 100644 --- a/.circleci/cli-builder.dockerfile +++ b/.circleci/cli-builder.dockerfile @@ -1,6 +1,6 @@ -FROM golang:1.13 +FROM golang:1.14 -ARG upx_version="3.94" +ARG upx_version="3.96" # install go dependencies RUN go get github.com/mitchellh/gox \ diff --git a/.circleci/config.yml b/.circleci/config.yml index 7cf15c62efbbb..c95cdfcedc67a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -374,7 +374,7 @@ jobs: # test and build cli test_and_build_cli: docker: - - image: hasura/graphql-engine-cli-builder:20191205 + - image: hasura/graphql-engine-cli-builder:20201105 - image: circleci/postgres:10-alpine environment: POSTGRES_USER: gql_test @@ -669,4 +669,4 @@ workflows: - build_image - test_console - test_and_build_cli_migrations - - all_server_tests_pass + - all_server_tests_pass \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index c8f9d7a89725c..9e70f79b12546 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -104,6 +104,7 @@ This release contains the [PDV refactor (#4111)](https://github.com/hasura/graph ### Bug fixes and improvements (Add entries here in the order of: server, console, cli, docs, others) +- cli: fix cli-migrations-v2 image failing to run as a non root user (close #4651, close #5333) - server: Fix fine-grained incremental cache invalidation (fix #3759) @@ -120,11 +121,14 @@ This release contains the [PDV refactor (#4111)](https://github.com/hasura/graph - server: accept only non-negative integers for batch size and refetch interval (close #5653) (#5759) - server: fix bug which arised when renaming a table which had a manual relationship defined (close #4158) - server: limit the length of event trigger names (close #5786) +- server: Configurable websocket keep-alive interval. Add `--websocket-keepalive` command-line flag + and handle `HASURA_GRAPHQL_WEBSOCKET_KEEPALIVE` env variable (fix #3539) **NOTE:** If you have event triggers with names greater than 42 chars, then you should update their names to avoid running into Postgres identifier limit bug (#5786) - server: validate remote schema queries (fixes #4143) - server: fix issue with tracking custom functions that return `SETOF` materialized view (close #5294) (#5945) - server: introduce optional custom table name in table configuration to track the table according to the custom name. The `set_table_custom_fields` API has been deprecated, A new API `set_table_customization` has been added to set the configuration. (#3811) - server: allow remote relationships with union, interface and enum type fields as well (fixes #5875) (#6080) +- server: fix event trigger cleanup on deletion via replace_metadata (fix #5461) (#6137) - 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) - console: remove ONLY as default for ALTER TABLE in column alter operations (close #5512) #5706 @@ -133,6 +137,7 @@ This release contains the [PDV refactor (#4111)](https://github.com/hasura/graph - console: down migrations improvements (close #3503, #4988) (#4790) - cli: add missing global flags for seed command (#5565) - cli: allow seeds as alias for seed command (#5693) +- cli: fix bug in metadata apply which made the server aquire some redundant and unnecessary locks (close #6115) - docs: add docs page on networking with docker (close #4346) (#4811) - docs: add tabs for console / cli / api workflows (close #3593) (#4948) - docs: add postgres concepts page to docs (close #4440) (#4471) diff --git a/cli/commands/scripts_update_config_v2.go b/cli/commands/scripts_update_config_v2.go index 581dc375c611f..1ee9c7a00361d 100644 --- a/cli/commands/scripts_update_config_v2.go +++ b/cli/commands/scripts_update_config_v2.go @@ -133,7 +133,7 @@ func newScriptsUpdateConfigV2Cmd(ec *cli.ExecutionContext) *cobra.Command { } } // check if up.sql file exists - if string(sqlUp.Bytes()) != "" { + if sqlUp.String() != "" { upMigration, ok := fileCfg.Migrations.Migrations[version][source.Up] if !ok { // if up.sql doesn't exists, create a up.sql file and upMigration @@ -207,7 +207,7 @@ func newScriptsUpdateConfigV2Cmd(ec *cli.ExecutionContext) *cobra.Command { } } // check if up.sql file exists - if string(sqlDown.Bytes()) != "" { + if sqlDown.String() != "" { downMigration, ok := fileCfg.Migrations.Migrations[version][source.Down] if !ok { // if up.sql doesn't exists, create a up.sql file and upMigration diff --git a/cli/migrate/database/hasuradb/metadata.go b/cli/migrate/database/hasuradb/metadata.go index 01e48632a4b76..1d8ad654ef06d 100644 --- a/cli/migrate/database/hasuradb/metadata.go +++ b/cli/migrate/database/hasuradb/metadata.go @@ -180,18 +180,9 @@ func (h *HasuraDB) ApplyMetadata() error { if err != nil { return err } - query := HasuraInterfaceBulk{ - Type: "bulk", - Args: []interface{}{ - HasuraInterfaceQuery{ - Type: "clear_metadata", - Args: HasuraArgs{}, - }, - HasuraInterfaceQuery{ - Type: "replace_metadata", - Args: obj, - }, - }, + query := HasuraInterfaceQuery{ + Type: "replace_metadata", + Args: obj, } resp, body, err := h.sendv1Query(query) if err != nil { diff --git a/cli/plugins/scanner.go b/cli/plugins/scanner.go index 255d928375af7..d807d415fac91 100644 --- a/cli/plugins/scanner.go +++ b/cli/plugins/scanner.go @@ -19,16 +19,24 @@ import ( ) func (c *Config) findPluginManifestFiles(indexDir string) ([]string, error) { + c.Logger.Debugf("finding plugin manifest files in directory %v", indexDir) var out []string fs := afero.Afero{ Fs: afero.NewOsFs(), } fs.Walk(indexDir, func(path string, info os.FileInfo, err error) error { + if info == nil { + if err != nil { + return err + } + return nil + } if info.Mode().IsRegular() && filepath.Ext(info.Name()) == paths.ManifestExtension { out = append(out, path) } return nil }) + return out, nil } @@ -50,6 +58,7 @@ func (c *Config) LoadPluginListFromFS(indexDir string) (Plugins, error) { // LoadPluginByName loads a plugins index file by its name. When plugin // file not found, it returns an error that can be checked with os.IsNotExist. func (c *Config) LoadPluginByName(pluginName string) (*PluginVersions, error) { + c.Logger.Debugf("loading plugin %s", pluginName) if !IsSafePluginName(pluginName) { return nil, errors.Errorf("plugin name %q not allowed", pluginName) } @@ -68,6 +77,7 @@ func (c *Config) LoadPluginByName(pluginName string) (*PluginVersions, error) { } func (c *Config) LoadPlugins(files []string, pluginName ...string) Plugins { + c.Logger.Debugf("loading plugins") ps := Plugins{} for _, file := range files { p, err := c.ReadPluginFromFile(file) diff --git a/cli/plugins/types.go b/cli/plugins/types.go index 3571177d3c3df..f116311cf2d1c 100644 --- a/cli/plugins/types.go +++ b/cli/plugins/types.go @@ -99,7 +99,7 @@ type Plugin struct { func (p *Plugin) ParseVersion() { v, err := semver.NewVersion(p.Version) if err != nil { - p.ParsedVersion = nil + p.ParsedVersion = semver.MustParse("0.0.0-dev") return } p.ParsedVersion = v diff --git a/console/src/Endpoints.ts b/console/src/Endpoints.ts index bcce3d404df19..b38b23a49be8f 100644 --- a/console/src/Endpoints.ts +++ b/console/src/Endpoints.ts @@ -20,8 +20,9 @@ const Endpoints = { hasuractlMetadata: `${hasuractlUrl}/apis/metadata`, hasuractlMigrateSettings: `${hasuractlUrl}/apis/migrate/settings`, telemetryServer: 'wss://telemetry.hasura.io/v1/ws', - consoleNotificationsStg: 'https://data.hasura-stg.hasura-app.io/v1/query', - consoleNotificationsProd: 'https://data.hasura.io/v1/query', + consoleNotificationsStg: + 'https://notifications.hasura-stg.hasura-app.io/v1/graphql', + consoleNotificationsProd: 'https://notifications.hasura.io/v1/graphql', }; const globalCookiePolicy = 'same-origin'; diff --git a/console/src/components/Common/utils/v1QueryUtils.ts b/console/src/components/Common/utils/v1QueryUtils.ts index ceb99750bf99a..11044dca96d30 100644 --- a/console/src/components/Common/utils/v1QueryUtils.ts +++ b/console/src/components/Common/utils/v1QueryUtils.ts @@ -795,47 +795,34 @@ export const getConsoleNotificationQuery = ( time: Date | string | number, userType?: Nullable ) => { - let consoleUserScope = { - $ilike: `%${userType}%`, - }; + let consoleUserScopeVar = `%${userType}%`; if (!userType) { - consoleUserScope = { - $ilike: '%OSS%', - }; + consoleUserScopeVar = '%OSS%'; } - return { - args: { - table: 'console_notification', - columns: ['*'], - where: { - $or: [ - { - expiry_date: { - $gte: time, - }, - }, - { - expiry_date: { - $eq: null, - }, - }, - ], - scope: consoleUserScope, - start_date: { $lte: time }, - }, - order_by: [ - { - type: 'asc', - nulls: 'last', - column: 'priority', - }, - { - type: 'desc', - column: 'start_date', - }, - ], - }, - type: 'select', + const query = `query fetchNotifications($currentTime: timestamptz, $userScope: String) { + console_notifications( + where: {start_date: {_lte: $currentTime}, scope: {_ilike: $userScope}, _or: [{expiry_date: {_gte: $currentTime}}, {expiry_date: {_eq: null}}]}, + order_by: {priority: asc_nulls_last, start_date: desc} + ) { + content + created_at + external_link + expiry_date + id + is_active + priority + scope + start_date + subject + type + } + }`; + + const variables = { + userScope: consoleUserScopeVar, + currentTime: time, }; + + return { query, variables }; }; diff --git a/console/src/components/Main/Actions.js b/console/src/components/Main/Actions.js index bffb7f1522bbc..775b86fb969fa 100644 --- a/console/src/components/Main/Actions.js +++ b/console/src/components/Main/Actions.js @@ -79,8 +79,12 @@ const fetchConsoleNotifications = () => (dispatch, getState) => { const consoleId = window.__env.consoleId; const consoleScope = getConsoleScope(serverVersion, consoleId); let userType = 'admin'; - if (headers.hasOwnProperty(HASURA_COLLABORATOR_TOKEN)) { - const collabToken = headers[HASURA_COLLABORATOR_TOKEN]; + const headerHasCollabToken = Object.keys(headers).find( + header => header.toLowerCase() === HASURA_COLLABORATOR_TOKEN + ); + + if (headerHasCollabToken) { + const collabToken = headers[headerHasCollabToken]; userType = getUserType(collabToken); } @@ -94,12 +98,14 @@ const fetchConsoleNotifications = () => (dispatch, getState) => { } const now = new Date().toISOString(); - const query = getConsoleNotificationQuery(now, consoleScope); + const payload = getConsoleNotificationQuery(now, consoleScope); const options = { - body: JSON.stringify(query), + body: JSON.stringify(payload), method: 'POST', headers: { 'content-type': 'application/json', + // temp. change until Auth is added + 'x-hasura-role': 'user', }, }; @@ -108,94 +114,131 @@ const fetchConsoleNotifications = () => (dispatch, getState) => { const lastSeenNotifications = JSON.parse( window.localStorage.getItem('notifications:lastSeen') ); - if (!data.length) { - dispatch({ type: FETCH_CONSOLE_NOTIFICATIONS_SET_DEFAULT }); - dispatch( - updateConsoleNotificationsState({ - read: 'default', - date: now, - showBadge: false, - }) - ); - if (!lastSeenNotifications) { - window.localStorage.setItem( - 'notifications:lastSeen', - JSON.stringify(0) - ); - } - return; - } + if (data.data.console_notifications) { + const fetchedData = data.data.console_notifications; - const uppercaseScopedData = makeUppercaseScopes(data); - let filteredData = filterScope(uppercaseScopedData, consoleScope); - - if ( - !lastSeenNotifications || - lastSeenNotifications !== filteredData.length - ) { - window.localStorage.setItem( - 'notifications:lastSeen', - JSON.stringify(filteredData.length) - ); - } - - if (previousRead) { - if (!consoleStateDB.console_notifications) { + if (!fetchedData.length) { + dispatch({ type: FETCH_CONSOLE_NOTIFICATIONS_SET_DEFAULT }); dispatch( updateConsoleNotificationsState({ - read: [], + read: 'default', date: now, - showBadge: true, + showBadge: false, }) ); - } else { - let newReadValue; - - if (previousRead === 'default' || previousRead === 'error') { - newReadValue = []; - toShowBadge = false; - } else if (previousRead === 'all') { - const previousList = JSON.parse( - localStorage.getItem('notifications:data') + if (!lastSeenNotifications) { + window.localStorage.setItem( + 'notifications:lastSeen', + JSON.stringify(0) + ); + } + return; + } + + // NOTE: these 2 steps may not be required if the table in the DB + // enforces the usage of `enums` and we're sure that the notification scope + // is only from the allowed permutations of scope. We aren't doing that yet + // because within the GQL query, I can't be using the `_ilike` operator during + // filtering. Hence I'm keeping it here since this is a new feature and + // mistakes can happen while adding data into the DB. + + // TODO: is to remove these once things are more streamlined + const uppercaseScopedData = makeUppercaseScopes(fetchedData); + let filteredData = filterScope(uppercaseScopedData, consoleScope); + + if ( + lastSeenNotifications && + lastSeenNotifications > filteredData.length + ) { + window.localStorage.setItem( + 'notifications:lastSeen', + JSON.stringify(filteredData.length) + ); + } + + if (previousRead) { + if (!consoleStateDB.console_notifications) { + dispatch( + updateConsoleNotificationsState({ + read: [], + date: now, + showBadge: true, + }) ); - if (previousList.length) { - const resDiff = filteredData.filter( - newNotif => - !previousList.find(oldNotif => oldNotif.id === newNotif.id) + } else { + let newReadValue; + if (previousRead === 'default' || previousRead === 'error') { + newReadValue = []; + toShowBadge = false; + } else if (previousRead === 'all') { + const previousList = JSON.parse( + localStorage.getItem('notifications:data') ); - if (!resDiff.length) { - // since the data hasn't changed since the last call - newReadValue = previousRead; + if (!previousList) { + // we don't have a record of the IDs that were marked as read previously + newReadValue = []; + toShowBadge = true; + } else if (previousList.length) { + const readNotificationsDiff = filteredData.filter( + newNotif => + !previousList.find(oldNotif => oldNotif.id === newNotif.id) + ); + if (!readNotificationsDiff.length) { + // since the data hasn't changed since the last call + newReadValue = previousRead; + toShowBadge = false; + } else { + newReadValue = [...previousList.map(notif => `${notif.id}`)]; + toShowBadge = true; + filteredData = [...readNotificationsDiff, ...previousList]; + } + } + } else { + newReadValue = previousRead; + if ( + previousRead.length && + lastSeenNotifications >= filteredData.length + ) { toShowBadge = false; - } else { - newReadValue = [...previousList.map(notif => `${notif.id}`)]; + } else if (lastSeenNotifications < filteredData.length) { toShowBadge = true; - filteredData = [...resDiff, ...previousList]; } } - } else { - newReadValue = previousRead; - if ( - previousRead.length && - lastSeenNotifications === filteredData.length - ) { - toShowBadge = false; - } + dispatch( + updateConsoleNotificationsState({ + read: newReadValue, + date: consoleStateDB.console_notifications[userType].date, + showBadge: toShowBadge, + }) + ); } - dispatch( - updateConsoleNotificationsState({ - read: newReadValue, - date: consoleStateDB.console_notifications[userType].date, - showBadge: toShowBadge, - }) + } + + dispatch({ + type: FETCH_CONSOLE_NOTIFICATIONS_SUCCESS, + data: filteredData, + }); + + // update/set the lastSeen value upon data is set + if ( + !lastSeenNotifications || + lastSeenNotifications !== filteredData.length + ) { + window.localStorage.setItem( + 'notifications:lastSeen', + JSON.stringify(filteredData.length) ); } + return; } - - dispatch({ - type: FETCH_CONSOLE_NOTIFICATIONS_SUCCESS, - data: filteredData, - }); + dispatch({ type: FETCH_CONSOLE_NOTIFICATIONS_ERROR }); + dispatch( + updateConsoleNotificationsState({ + read: 'error', + date: now, + showBadge: false, + }) + ); }) .catch(err => { console.error(err); diff --git a/console/src/components/Main/ConsoleNotification.ts b/console/src/components/Main/ConsoleNotification.ts index 26b0c851c05c1..ee2341eb0e0a7 100644 --- a/console/src/components/Main/ConsoleNotification.ts +++ b/console/src/components/Main/ConsoleNotification.ts @@ -31,7 +31,6 @@ export type ConsoleNotification = { scope?: NotificationScope; }; -// FIXME? : we may have to remove this export const defaultNotification: ConsoleNotification = { subject: 'No updates available at the moment', created_at: Date.now(), diff --git a/console/src/components/Main/Main.js b/console/src/components/Main/Main.js index 9eb1d8269ab8c..40533658f2cc5 100644 --- a/console/src/components/Main/Main.js +++ b/console/src/components/Main/Main.js @@ -35,10 +35,40 @@ import { setProClickState, getLoveConsentState, setLoveConsentState, + getUserType, } from './utils'; import { getSchemaBaseRoute } from '../Common/utils/routesUtils'; import LoveSection from './LoveSection'; import { Help, ProPopup } from './components/'; +import { HASURA_COLLABORATOR_TOKEN } from '../../constants'; +import { UPDATE_CONSOLE_NOTIFICATIONS } from '../../telemetry/Actions'; + +const updateRequestHeaders = props => { + const { requestHeaders, dispatch } = props; + + const collabTokenKey = Object.keys(requestHeaders).find( + hdr => hdr.toLowerCase() === HASURA_COLLABORATOR_TOKEN + ); + + if (collabTokenKey) { + const userID = getUserType(requestHeaders[collabTokenKey]); + if (props.console_opts && props.console_opts.console_notifications) { + if (!props.console_opts.console_notifications[userID]) { + dispatch({ + type: UPDATE_CONSOLE_NOTIFICATIONS, + data: { + ...props.console_opts.console_notifications, + [userID]: { + read: [], + date: null, + showBadge: true, + }, + }, + }); + } + } + } +}; class Main extends React.Component { constructor(props) { @@ -57,6 +87,7 @@ class Main extends React.Component { componentDidMount() { const { dispatch } = this.props; + updateRequestHeaders(this.props); dispatch(loadServerVersion()).then(() => { dispatch(featureCompatibilityInit()); @@ -74,6 +105,18 @@ class Main extends React.Component { dispatch(fetchServerConfig); } + componentDidUpdate(prevProps) { + const prevHeaders = Object.keys(prevProps.requestHeaders); + const currHeaders = Object.keys(this.props.requestHeaders); + + if ( + prevHeaders.length !== currHeaders.length || + prevHeaders.filter(hdr => !currHeaders.includes(hdr)).length + ) { + updateRequestHeaders(this.props); + } + } + toggleProPopup = () => { const { dispatch } = this.props; dispatch(emitProClickedEvent({ open: !this.state.isPopUpOpen })); @@ -368,6 +411,7 @@ const mapStateToProps = (state, ownProps) => { currentSchema: state.tables.currentSchema, metadata: state.metadata, console_opts: state.telemetry.console_opts, + requestHeaders: state.tables.dataHeaders, }; }; diff --git a/console/src/components/Main/Main.scss b/console/src/components/Main/Main.scss index c3a1579e6af53..ca79ae413397d 100644 --- a/console/src/components/Main/Main.scss +++ b/console/src/components/Main/Main.scss @@ -1367,7 +1367,7 @@ position: absolute; width: 17px; top: 16px; - right: 8px; + right: 0.8rem; border-radius: 50%; user-select: none; visibility: visible; @@ -1536,10 +1536,6 @@ .secureSectionText { display: none; } - - .shareSection { - display: none; - } } @media (max-width: 1050px) { diff --git a/console/src/components/Main/NotificationSection.tsx b/console/src/components/Main/NotificationSection.tsx index 7e1744d6c95d6..26122e76df9ca 100644 --- a/console/src/components/Main/NotificationSection.tsx +++ b/console/src/components/Main/NotificationSection.tsx @@ -511,15 +511,19 @@ const HasuraNotifications: React.FC< let userType = 'admin'; - if (dataHeaders?.[HASURA_COLLABORATOR_TOKEN]) { - const collabToken = dataHeaders[HASURA_COLLABORATOR_TOKEN]; + const headerHasCollabToken = Object.keys(dataHeaders).find( + header => header.toLowerCase() === HASURA_COLLABORATOR_TOKEN + ); + + if (headerHasCollabToken) { + const collabToken = dataHeaders[headerHasCollabToken]; userType = getUserType(collabToken); } const previouslyReadState = React.useMemo( () => console_opts?.console_notifications && - console_opts?.console_notifications[userType].read, + console_opts?.console_notifications[userType]?.read, [console_opts?.console_notifications, userType] ); const showBadge = React.useMemo( @@ -639,7 +643,7 @@ const HasuraNotifications: React.FC< useOnClickOutside([dropDownRef, wrapperRef], onClickOutside); - const onClickShareSection = () => { + const onClickNotificationButton = () => { if (showBadge) { if (console_opts?.console_notifications) { let updatedState = {}; @@ -718,11 +722,11 @@ const HasuraNotifications: React.FC< return ( <>