mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-15 01:12:56 +03:00
Customization of table GraphQL schema descriptions
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/3570 GitOrigin-RevId: f642a4d9efdd26a344951fcab6c1bbbc1253dfa3
This commit is contained in:
parent
773870f443
commit
dd403f92e2
@ -21,6 +21,7 @@ The optimization can be enabled using the
|
||||
(Add entries below in the order of server, console, cli, docs, others)
|
||||
- console: add support for remote database relationships
|
||||
- console: enable support for update permissions for mssql #3591
|
||||
- server: add support for customization of table GraphQL schema descriptions (#7496)
|
||||
- cli: skip tls verfication for all API requests when `insecure-skip-tls-verify` flag is set (#4926)
|
||||
|
||||
- server: classify MSSQL exceptions and improve API error responses
|
||||
|
@ -59,7 +59,8 @@ Add a table/view ``author``:
|
||||
},
|
||||
"custom_column_names": {
|
||||
"id": "authorId"
|
||||
}
|
||||
},
|
||||
"comment": "Authors of books"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -512,6 +512,12 @@ Table Config
|
||||
- false
|
||||
- :ref:`CustomColumnNames`
|
||||
- Customise the column fields
|
||||
* - comment
|
||||
- false
|
||||
- ``String``
|
||||
- Customise the description shown in GraphQL introspection. If null or omitted then
|
||||
if a comment exists on the database table, it is used as the description
|
||||
(Postgres-only), and if not, an autogenerated description is used instead.
|
||||
|
||||
.. _custom_root_fields:
|
||||
|
||||
|
@ -365,10 +365,7 @@ tableSelectionSet sourceName tableInfo selectPermissions = memoizeOn 'tableSelec
|
||||
let xRelay = relayExtension @b
|
||||
tableFields = Map.elems $ _tciFieldInfoMap tableCoreInfo
|
||||
tablePkeyColumns = _pkColumns <$> _tciPrimaryKey tableCoreInfo
|
||||
description =
|
||||
Just $
|
||||
mkDescriptionWith (_tciDescription tableCoreInfo) $
|
||||
"columns and relationships of " <>> tableName
|
||||
description = G.Description . PG.getPGDescription <$> _tciDescription tableCoreInfo
|
||||
fieldParsers <-
|
||||
concat <$> for tableFields \fieldInfo ->
|
||||
fieldSelection sourceName tableName tablePkeyColumns fieldInfo selectPermissions
|
||||
|
@ -404,11 +404,11 @@ alterCustomColumnNamesInMetadata ::
|
||||
TableCoreInfo b ->
|
||||
m ()
|
||||
alterCustomColumnNamesInMetadata source droppedCols ti = do
|
||||
let TableConfig customFields customColumnNames customName = _tciCustomConfig ti
|
||||
tn = _tciName ti
|
||||
modifiedCustomColumnNames = foldl' (flip M.delete) customColumnNames droppedCols
|
||||
when (modifiedCustomColumnNames /= customColumnNames) $
|
||||
let tableConfig@TableConfig {..} = _tciCustomConfig ti
|
||||
tableName = _tciName ti
|
||||
modifiedCustomColumnNames = foldl' (flip M.delete) _tcCustomColumnNames droppedCols
|
||||
when (modifiedCustomColumnNames /= _tcCustomColumnNames) $
|
||||
tell $
|
||||
MetadataModifier $
|
||||
tableMetadataSetter @b source tn . tmConfiguration
|
||||
.~ TableConfig @b customFields modifiedCustomColumnNames customName
|
||||
tableMetadataSetter @b source tableName . tmConfiguration
|
||||
.~ tableConfig {_tcCustomColumnNames = modifiedCustomColumnNames}
|
||||
|
@ -31,7 +31,7 @@ import Data.HashMap.Strict.InsOrd qualified as OMap
|
||||
import Data.HashSet qualified as S
|
||||
import Data.Text.Extended
|
||||
import Data.These (These (..))
|
||||
import Hasura.Backends.Postgres.SQL.Types (QualifiedTable)
|
||||
import Hasura.Backends.Postgres.SQL.Types (PGDescription (..), QualifiedTable)
|
||||
import Hasura.Base.Error
|
||||
import Hasura.EncJSON
|
||||
import Hasura.GraphQL.Context
|
||||
@ -294,7 +294,7 @@ runSetTableCustomFieldsQV2 ::
|
||||
(QErrM m, CacheRWM m, MetadataM m) => SetTableCustomFields -> m EncJSON
|
||||
runSetTableCustomFieldsQV2 (SetTableCustomFields source tableName rootFields columnNames) = do
|
||||
void $ askTabInfo @('Postgres 'Vanilla) source tableName -- assert that table is tracked
|
||||
let tableConfig = TableConfig @('Postgres 'Vanilla) rootFields columnNames Nothing
|
||||
let tableConfig = TableConfig @('Postgres 'Vanilla) rootFields columnNames Nothing Automatic
|
||||
buildSchemaCacheFor
|
||||
(MOSourceObjId source $ AB.mkAnyBackend $ SMOTable @('Postgres 'Vanilla) tableName)
|
||||
$ MetadataModifier $
|
||||
@ -464,6 +464,7 @@ buildTableCache = Inc.cache proc (source, sourceConfig, dbTablesMeta, tableBuild
|
||||
let columns :: [RawColumnInfo b] = _ptmiColumns metadataTable
|
||||
columnMap = mapFromL (FieldName . toTxt . rciName) columns
|
||||
primaryKey = _ptmiPrimaryKey metadataTable
|
||||
description = buildDescription name config metadataTable
|
||||
rawPrimaryKey <- liftEitherA -< traverse (resolvePrimaryKeyColumns columnMap) primaryKey
|
||||
enumValues <-
|
||||
if isEnum
|
||||
@ -487,7 +488,7 @@ buildTableCache = Inc.cache proc (source, sourceConfig, dbTablesMeta, tableBuild
|
||||
_tciViewInfo = _ptmiViewInfo metadataTable,
|
||||
_tciEnumValues = enumValues,
|
||||
_tciCustomConfig = config,
|
||||
_tciDescription = _ptmiDescription metadataTable,
|
||||
_tciDescription = description,
|
||||
_tciExtraTableMetadata = _ptmiExtraTableMetadata metadataTable
|
||||
}
|
||||
|
||||
@ -583,3 +584,12 @@ buildTableCache = Inc.cache proc (source, sourceConfig, dbTablesMeta, tableBuild
|
||||
<> englishList "and" (dquote . ciColumn <$> (one :| two : more))
|
||||
<> " are in conflict: they are mapped to the same field name, " <>> name
|
||||
_ -> pure ()
|
||||
|
||||
buildDescription :: TableName b -> TableConfig b -> DBTableMetadata b -> Maybe PGDescription
|
||||
buildDescription tableName tableConfig tableMetadata =
|
||||
case _tcComment tableConfig of
|
||||
Automatic -> _ptmiDescription tableMetadata <|> Just autogeneratedDescription
|
||||
Explicit description -> PGDescription . toTxt <$> description
|
||||
where
|
||||
autogeneratedDescription =
|
||||
PGDescription $ "columns and relationships of " <>> tableName
|
||||
|
@ -7,6 +7,7 @@ module Hasura.RQL.Types.Table
|
||||
DBTableMetadata (..),
|
||||
DBTablesMetadata,
|
||||
DelPermInfo (..),
|
||||
Comment (..),
|
||||
FieldInfo (..),
|
||||
FieldInfoMap,
|
||||
ForeignKey (..),
|
||||
@ -55,6 +56,7 @@ module Hasura.RQL.Types.Table
|
||||
tcCustomColumnNames,
|
||||
tcCustomName,
|
||||
tcCustomRootFields,
|
||||
tcComment,
|
||||
tciCustomConfig,
|
||||
tciDescription,
|
||||
tciEnumValues,
|
||||
@ -83,6 +85,7 @@ import Control.Lens hiding ((.=))
|
||||
import Data.Aeson.Casing
|
||||
import Data.Aeson.Extended
|
||||
import Data.Aeson.TH
|
||||
import Data.Aeson.Types (prependFailure, typeMismatch)
|
||||
import Data.HashMap.Strict qualified as M
|
||||
import Data.HashMap.Strict.Extended qualified as M
|
||||
import Data.HashSet qualified as HS
|
||||
@ -91,6 +94,7 @@ import Data.List.NonEmpty qualified as NE
|
||||
import Data.Semigroup (Any (..), Max (..))
|
||||
import Data.Text qualified as T
|
||||
import Data.Text.Extended
|
||||
import Data.Text.NonEmpty (NonEmptyText, mkNonEmptyText)
|
||||
import Hasura.Backends.Postgres.SQL.Types qualified as PG (PGDescription)
|
||||
import Hasura.Base.Error
|
||||
import Hasura.Incremental (Cacheable)
|
||||
@ -577,10 +581,33 @@ isMutable f (Just vi) = f vi
|
||||
|
||||
type CustomColumnNames b = HashMap (Column b) G.Name
|
||||
|
||||
data Comment
|
||||
= -- | Automatically generate a comment (derive it from DB comments, or a sensible default describing the source of the data)
|
||||
Automatic
|
||||
| -- | The user's explicitly provided comment, no explicitly no comment (ie. leave it blank, do not autogenerate one)
|
||||
Explicit (Maybe NonEmptyText)
|
||||
deriving (Eq, Show, Generic)
|
||||
|
||||
instance NFData Comment
|
||||
|
||||
instance Cacheable Comment
|
||||
|
||||
instance FromJSON Comment where
|
||||
parseJSON = \case
|
||||
Null -> pure Automatic
|
||||
String text -> pure . Explicit $ mkNonEmptyText text
|
||||
val -> prependFailure "parsing Comment failed, " (typeMismatch "String or Null" val)
|
||||
|
||||
instance ToJSON Comment where
|
||||
toJSON Automatic = Null
|
||||
toJSON (Explicit (Just value)) = String (toTxt value)
|
||||
toJSON (Explicit Nothing) = String ""
|
||||
|
||||
data TableConfig b = TableConfig
|
||||
{ _tcCustomRootFields :: !TableCustomRootFields,
|
||||
_tcCustomColumnNames :: !(CustomColumnNames b),
|
||||
_tcCustomName :: !(Maybe G.Name)
|
||||
_tcCustomName :: !(Maybe G.Name),
|
||||
_tcComment :: !Comment
|
||||
}
|
||||
deriving (Generic)
|
||||
|
||||
@ -599,7 +626,7 @@ $(makeLenses ''TableConfig)
|
||||
|
||||
emptyTableConfig :: (TableConfig b)
|
||||
emptyTableConfig =
|
||||
TableConfig emptyCustomRootFields M.empty Nothing
|
||||
TableConfig emptyCustomRootFields M.empty Nothing Automatic
|
||||
|
||||
instance (Backend b) => FromJSON (TableConfig b) where
|
||||
parseJSON = withObject "TableConfig" $ \obj ->
|
||||
@ -607,6 +634,7 @@ instance (Backend b) => FromJSON (TableConfig b) where
|
||||
<$> obj .:? "custom_root_fields" .!= emptyCustomRootFields
|
||||
<*> obj .:? "custom_column_names" .!= M.empty
|
||||
<*> obj .:? "custom_name"
|
||||
<*> obj .:? "comment" .!= Automatic
|
||||
|
||||
data Constraint (b :: BackendType) = Constraint
|
||||
{ _cName :: !(ConstraintName b),
|
||||
|
@ -0,0 +1,18 @@
|
||||
description: GraphQL introspection query
|
||||
url: /v1/graphql
|
||||
status: 200
|
||||
query:
|
||||
query: |
|
||||
query IntrospectionQuery {
|
||||
__type(name: "automatic_comment_in_db") {
|
||||
name
|
||||
description
|
||||
kind
|
||||
}
|
||||
}
|
||||
response:
|
||||
data:
|
||||
__type:
|
||||
name: automatic_comment_in_db
|
||||
description: What a great comment in the DB
|
||||
kind: OBJECT
|
@ -0,0 +1,18 @@
|
||||
description: GraphQL introspection query
|
||||
url: /v1/graphql
|
||||
status: 200
|
||||
query:
|
||||
query: |
|
||||
query IntrospectionQuery {
|
||||
__type(name: "automatic_no_comment_in_db") {
|
||||
name
|
||||
description
|
||||
kind
|
||||
}
|
||||
}
|
||||
response:
|
||||
data:
|
||||
__type:
|
||||
name: automatic_no_comment_in_db
|
||||
description: columns and relationships of "automatic_no_comment_in_db"
|
||||
kind: OBJECT
|
@ -0,0 +1,18 @@
|
||||
description: GraphQL introspection query
|
||||
url: /v1/graphql
|
||||
status: 200
|
||||
query:
|
||||
query: |
|
||||
query IntrospectionQuery {
|
||||
__type(name: "explicit_comment_in_metadata") {
|
||||
name
|
||||
description
|
||||
kind
|
||||
}
|
||||
}
|
||||
response:
|
||||
data:
|
||||
__type:
|
||||
name: explicit_comment_in_metadata
|
||||
description: Such an explicit comment, wow
|
||||
kind: OBJECT
|
@ -0,0 +1,18 @@
|
||||
description: GraphQL introspection query
|
||||
url: /v1/graphql
|
||||
status: 200
|
||||
query:
|
||||
query: |
|
||||
query IntrospectionQuery {
|
||||
__type(name: "explicit_no_comment_in_metadata") {
|
||||
name
|
||||
description
|
||||
kind
|
||||
}
|
||||
}
|
||||
response:
|
||||
data:
|
||||
__type:
|
||||
name: explicit_no_comment_in_metadata
|
||||
description: null
|
||||
kind: OBJECT
|
@ -0,0 +1,33 @@
|
||||
type: bulk
|
||||
args:
|
||||
|
||||
- type: run_sql
|
||||
args:
|
||||
sql: |
|
||||
CREATE TABLE "automatic_comment_in_db" (
|
||||
id serial primary key
|
||||
);
|
||||
COMMENT ON TABLE "automatic_comment_in_db" IS 'What a great comment in the DB';
|
||||
|
||||
- type: run_sql
|
||||
args:
|
||||
sql: |
|
||||
CREATE TABLE "automatic_no_comment_in_db" (
|
||||
id serial primary key
|
||||
);
|
||||
|
||||
- type: run_sql
|
||||
args:
|
||||
sql: |
|
||||
CREATE TABLE "explicit_comment_in_metadata" (
|
||||
id serial primary key
|
||||
);
|
||||
COMMENT ON TABLE "explicit_comment_in_metadata" IS 'Fantastic comment, so good, so hidden';
|
||||
|
||||
- type: run_sql
|
||||
args:
|
||||
sql: |
|
||||
CREATE TABLE "explicit_no_comment_in_metadata" (
|
||||
id serial primary key
|
||||
);
|
||||
COMMENT ON TABLE "explicit_no_comment_in_metadata" IS 'This would be a great comment, but you can''t see it';
|
@ -0,0 +1,7 @@
|
||||
type: run_sql
|
||||
args:
|
||||
sql: |
|
||||
DROP TABLE "automatic_comment_in_db" cascade;
|
||||
DROP TABLE "automatic_no_comment_in_db" cascade;
|
||||
DROP TABLE "explicit_comment_in_metadata" cascade;
|
||||
DROP TABLE "explicit_no_comment_in_metadata" cascade;
|
@ -0,0 +1,21 @@
|
||||
type: bulk
|
||||
args:
|
||||
- type: pg_track_table
|
||||
args:
|
||||
table: automatic_comment_in_db
|
||||
|
||||
- type: pg_track_table
|
||||
args:
|
||||
table: automatic_no_comment_in_db
|
||||
|
||||
- type: pg_track_table
|
||||
args:
|
||||
table: explicit_comment_in_metadata
|
||||
configuration:
|
||||
comment: Such an explicit comment, wow
|
||||
|
||||
- type: pg_track_table
|
||||
args:
|
||||
table: explicit_no_comment_in_metadata
|
||||
configuration:
|
||||
comment: ""
|
@ -0,0 +1,17 @@
|
||||
type: bulk
|
||||
args:
|
||||
- type: pg_untrack_table
|
||||
args:
|
||||
table: automatic_comment_in_db
|
||||
|
||||
- type: pg_untrack_table
|
||||
args:
|
||||
table: automatic_no_comment_in_db
|
||||
|
||||
- type: pg_untrack_table
|
||||
args:
|
||||
table: explicit_comment_in_metadata
|
||||
|
||||
- type: pg_untrack_table
|
||||
args:
|
||||
table: explicit_no_comment_in_metadata
|
@ -139,3 +139,24 @@ class TestDisableGraphQLIntrospection:
|
||||
|
||||
def test_disable_introspection(self, hge_ctx):
|
||||
check_query_f(hge_ctx, self.dir() + "/disable_introspection.yaml")
|
||||
|
||||
@pytest.mark.usefixtures('per_class_tests_db_state')
|
||||
class TestGraphQlIntrospectionDescriptions:
|
||||
|
||||
setup_metadata_api_version = "v2"
|
||||
|
||||
@classmethod
|
||||
def dir(cls):
|
||||
return "queries/graphql_introspection/descriptions"
|
||||
|
||||
def test_automatic_comment_in_db(self, hge_ctx):
|
||||
check_query_f(hge_ctx, self.dir() + "/automatic_comment_in_db.yaml")
|
||||
|
||||
def test_automatic_no_comment_in_db(self, hge_ctx):
|
||||
check_query_f(hge_ctx, self.dir() + "/automatic_no_comment_in_db.yaml")
|
||||
|
||||
def test_explicit_comment_in_metadata(self, hge_ctx):
|
||||
check_query_f(hge_ctx, self.dir() + "/explicit_comment_in_metadata.yaml")
|
||||
|
||||
def test_explicit_no_comment_in_metadata(self, hge_ctx):
|
||||
check_query_f(hge_ctx, self.dir() + "/explicit_no_comment_in_metadata.yaml")
|
||||
|
Loading…
Reference in New Issue
Block a user