Merge branch 'master' into feature/add-identity-frequent-column

This commit is contained in:
Aleksandra Sikora 2020-08-03 18:21:52 +02:00 committed by GitHub
commit decdcc34ab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
273 changed files with 5763 additions and 11910 deletions

View File

@ -6,8 +6,24 @@
(Add entries here in the order of: server, console, cli, docs, others)
- server: bugfix to allow HASURA_GRAPHQL_QUERY_PLAN_CACHE_SIZE of 0 (#5363)
- server: support only a bounded plan cache, with a default size of 4000 (closes #5363)
- server: add logs for action handlers
- server: add request/response sizes in event triggers (and scheduled trigger) logs
- 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)
## `v1.3.0`
### Bug fixes and improvements
(Add entries here in the order of: server, console, cli, docs, others)
- server: adjustments to idle GC to try to free memory more eagerly (related to #3388)
- server: process events generated by the event triggers asynchronously (close #5189) (#5352)
- console: display line number that error originated from in GraphQL editor (close #4849) (#4942)
- docs: add page on created_at / updated_at timestamps (close #2880) (#5223)
## `v1.3.0-beta.4`

View File

@ -44,7 +44,8 @@ Read more at [hasura.io](https://hasura.io) and the [docs](https://hasura.io/doc
**Table of Contents**
- [Quickstart:](#quickstart)
- [One-click deployment on Heroku](#one-click-deployment-on-heroku)
- [One-click deployment on Hasura Cloud](#one-click-deployment-on-hasura-cloud)
- [Other one-click deployment options](#other-one-click-deployment-options)
- [Other deployment methods](#other-deployment-methods)
- [Architecture](#architecture)
- [Client-side tooling](#client-side-tooling)
@ -86,7 +87,7 @@ Check out the instructions for the following one-click deployment options:
| **Infra provider** | **One-click link** | **Additional information** |
|:------------------:|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|:-------------------------------------------------------------------------------------------------------------------------------------------------:|
| Heroku | [![Deploy to Heroku](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/hasura/graphql-engine-heroku) | [docs](https://hasura.io/docs/1.0/graphql/manual/guides/deployment/heroku-quickstart.html) |
| Heroku | [![Deploy to Heroku](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/hasura/graphql-engine-heroku) | [docs](https://hasura.io/docs/1.0/graphql/manual/guides/deployment/heroku-one-click.html) |
| DigitalOcean | [![Deploy to DigitalOcean](https://graphql-engine-cdn.hasura.io/img/create_hasura_droplet_200px.png)](https://marketplace.digitalocean.com/apps/hasura?action=deploy&refcode=c4d9092d2c48&utm_source=hasura&utm_campaign=readme) | [docs](https://hasura.io/docs/1.0/graphql/manual/guides/deployment/digital-ocean-one-click.html#hasura-graphql-engine-digitalocean-one-click-app) |
| Azure | [![Deploy to Azure](http://azuredeploy.net/deploybutton.png)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3a%2f%2fraw.githubusercontent.com%2fhasura%2fgraphql-engine%2fmaster%2finstall-manifests%2fazure-container-with-pg%2fazuredeploy.json) | [docs](https://hasura.io/docs/1.0/graphql/manual/guides/deployment/azure-container-instances-postgres.html) |
| Render | [![Deploy to Render](https://render.com/images/deploy-to-render-button.svg)](https://render.com/deploy?repo=https://github.com/render-examples/hasura-graphql) | [docs](https://hasura.io/docs/1.0/graphql/manual/guides/deployment/render-one-click.html) |

View File

@ -17,6 +17,212 @@ import (
type CustomQuery linq.Query
func (q CustomQuery) MergeCustomTypes(squashList *database.CustomList) error {
actionPermissionsTransition := transition.New(&cronTriggerConfig{})
actionPermissionsTransition.Initial("new")
actionPermissionsTransition.State("created")
actionPermissionsTransition.Event(setCustomTypes).To("created").From("new", "created")
next := q.Iterate()
for item, ok := next(); ok; item, ok = next() {
g := item.(linq.Group)
if g.Key == "" {
continue
}
var first *list.Element
for ind, val := range g.Group {
element := val.(*list.Element)
switch obj := element.Value.(type) {
case *setCustomTypesInput:
if ind == 0 {
first = element
continue
}
first.Value = obj
squashList.Remove(element)
}
}
}
return nil
}
func (q CustomQuery) MergeActionPermissions(squashList *database.CustomList) error {
actionPermissionsTransition := transition.New(&actionPermissionConfig{})
actionPermissionsTransition.Initial("new")
actionPermissionsTransition.State("created")
actionPermissionsTransition.State("deleted")
actionPermissionsTransition.Event(createActionPermission).To("created").From("new", "deleted")
actionPermissionsTransition.Event(dropActionPermission).To("deleted").From("new", "created")
next := q.Iterate()
for item, ok := next(); ok; item, ok = next() {
g := item.(linq.Group)
if g.Key == "" {
continue
}
key := g.Key.(string)
cfg := actionPermissionConfig{
action: key,
}
prevElems := make([]*list.Element, 0)
for _, val := range g.Group {
element := val.(*list.Element)
switch element.Value.(type) {
case *createActionPermissionInput:
err := actionPermissionsTransition.Trigger(createActionPermission, &cfg, nil)
if err != nil {
return err
}
prevElems = append(prevElems, element)
case *dropActionPermissionInput:
if cfg.GetState() == "created" {
prevElems = append(prevElems, element)
}
err := actionPermissionsTransition.Trigger(dropActionPermission, &cfg, nil)
if err != nil {
return err
}
for _, e := range prevElems {
squashList.Remove(e)
}
}
}
}
return nil
}
func (q CustomQuery) MergeActions(squashList *database.CustomList) error {
actionTransition := transition.New(&actionConfig{})
actionTransition.Initial("new")
actionTransition.State("created")
actionTransition.State("updated")
actionTransition.State("deleted")
actionTransition.Event(createAction).To("created").From("new", "deleted")
actionTransition.Event(updateAction).To("updated").From("new", "created", "updated", "deleted")
actionTransition.Event(dropAction).To("deleted").From("new", "created", "updated")
next := q.Iterate()
for item, ok := next(); ok; item, ok = next() {
g := item.(linq.Group)
if g.Key == "" {
continue
}
key, ok := g.Key.(string)
if !ok {
continue
}
cfg := actionConfig{
name: key,
}
prevElems := make([]*list.Element, 0)
for _, val := range g.Group {
element := val.(*list.Element)
switch obj := element.Value.(type) {
case *createActionInput:
err := actionTransition.Trigger(createAction, &cfg, nil)
if err != nil {
return errors.Wrapf(err, "error squashin Action: %v", obj.Name)
}
prevElems = append(prevElems, element)
case *updateActionInput:
if len(prevElems) != 0 {
if _, ok := prevElems[0].Value.(*createActionInput); ok {
prevElems[0].Value = &createActionInput{
actionDefinition: obj.actionDefinition,
}
prevElems = prevElems[:1]
err := actionTransition.Trigger(dropAction, &cfg, nil)
if err != nil {
return errors.Wrapf(err, "error squashing action: %v", obj.Name)
}
squashList.Remove(element)
err = actionTransition.Trigger(createAction, &cfg, nil)
if err != nil {
return errors.Wrapf(err, "error squashing action: %v", obj.Name)
}
continue
}
for _, e := range prevElems {
squashList.Remove(e)
}
prevElems = prevElems[:0]
err := actionTransition.Trigger(dropAction, &cfg, nil)
if err != nil {
return errors.Wrapf(err, "error squashing action: %v", obj.Name)
}
}
prevElems = append(prevElems, element)
err := actionTransition.Trigger(updateAction, &cfg, nil)
if err != nil {
return errors.Wrapf(err, "error squashing: %v", obj.Name)
}
case *dropActionInput:
if cfg.GetState() == "created" {
prevElems = append(prevElems, element)
// drop action permissions as well
actionPermissionGroup := CustomQuery(linq.FromIterable(squashList).GroupByT(
func(element *list.Element) string {
switch args := element.Value.(type) {
case *createActionPermissionInput:
if v, ok := args.Action.(string); ok {
return v
}
case *dropActionPermissionInput:
if v, ok := args.Action.(string); ok {
return v
}
}
return ""
}, func(element *list.Element) *list.Element {
return element
},
))
next := actionPermissionGroup.Iterate()
for item, ok := next(); ok; item, ok = next() {
g := item.(linq.Group)
if g.Key == "" {
continue
}
key, ok := g.Key.(string)
if !ok {
continue
}
if key == obj.Name {
for _, val := range g.Group {
element := val.(*list.Element)
squashList.Remove(element)
}
}
}
}
err := actionTransition.Trigger(dropAction, &cfg, nil)
if err != nil {
return err
}
for _, e := range prevElems {
squashList.Remove(e)
}
prevElems = prevElems[:0]
}
}
}
return nil
}
func (q CustomQuery) MergeCronTriggers(squashList *database.CustomList) error {
cronTriggersTransition := transition.New(&cronTriggerConfig{})
cronTriggersTransition.Initial("new")
@ -975,6 +1181,21 @@ func (h *HasuraDB) PushToList(migration io.Reader, fileType string, l *database.
}
l.PushBack(o)
}
case *createActionInput, *updateActionInput:
if v.Type == updateAction {
o, ok := v.Args.(*updateActionInput)
if !ok {
break
}
l.PushBack(o)
}
if v.Type == createAction {
o, ok := v.Args.(*createActionInput)
if !ok {
break
}
l.PushBack(o)
}
default:
l.PushBack(actionType)
}
@ -1437,6 +1658,70 @@ func (h *HasuraDB) Squash(l *database.CustomList, ret chan<- interface{}) {
ret <- err
}
customTypesGroup := CustomQuery(linq.FromIterable(l).GroupByT(
func(element *list.Element) string {
switch element.Value.(type) {
case *setCustomTypesInput:
return setCustomTypes
}
return ""
}, func(element *list.Element) *list.Element {
return element
},
))
err = customTypesGroup.MergeCustomTypes(l)
if err != nil {
ret <- err
}
actionGroup := CustomQuery(linq.FromIterable(l).GroupByT(
func(element *list.Element) string {
switch args := element.Value.(type) {
case *createActionInput:
if v, ok := args.Name.(string); ok {
return v
}
case *updateActionInput:
if v, ok := args.Name.(string); ok {
return v
}
case *dropActionInput:
if v, ok := args.Name.(string); ok {
return v
}
}
return ""
}, func(element *list.Element) *list.Element {
return element
},
))
err = actionGroup.MergeActions(l)
if err != nil {
ret <- err
}
actionPermissionGroup := CustomQuery(linq.FromIterable(l).GroupByT(
func(element *list.Element) string {
switch args := element.Value.(type) {
case *createActionPermissionInput:
if v, ok := args.Action.(string); ok {
return v
}
case *dropActionPermissionInput:
if v, ok := args.Action.(string); ok {
return v
}
}
return ""
}, func(element *list.Element) *list.Element {
return element
},
))
err = actionPermissionGroup.MergeActionPermissions(l)
if err != nil {
ret <- err
}
for e := l.Front(); e != nil; e = e.Next() {
q := HasuraInterfaceQuery{
Args: e.Value,
@ -1520,10 +1805,23 @@ func (h *HasuraDB) Squash(l *database.CustomList, ret chan<- interface{}) {
q.Type = createCronTrigger
case *deleteCronTriggerInput:
q.Type = deleteCronTrigger
case *createActionInput:
q.Type = createAction
case *updateActionInput:
q.Type = updateAction
case *dropActionInput:
q.Type = dropAction
case *createActionPermissionInput:
q.Type = createActionPermission
case *dropActionPermissionInput:
q.Type = dropActionPermission
case *setCustomTypesInput:
q.Type = setCustomTypes
case *RunSQLInput:
ret <- []byte(args.SQL)
continue
default:
h.logger.Debug("cannot find metadata type for:", args)
ret <- fmt.Errorf("invalid metadata action")
return
}

View File

@ -80,6 +80,51 @@ type deleteCronTriggerInput struct {
Name string `json:"name" yaml:"name"`
}
type actionDefinition struct {
Name interface{} `json:"name,omitempty" yaml:"name,omitempty"`
Definition interface{} `json:"definition,omitempty" yaml:"definition,omitempty"`
}
type createActionInput struct {
actionDefinition
Comment string `json:"comment,omitempty" yaml:"comment,omitempty"`
}
type actionAndPermission struct {
actionDefinition
Permissions []PermissionDefinition `json:"permissions" yaml:"permissions"`
}
type dropActionInput struct {
Name interface{} `json:"name,omitempty" yaml:"name,omitempty"`
ClearData bool `json:"clear_data,omitempty" yaml:"clear_data,omitempty"`
}
type updateActionInput struct {
actionDefinition
}
type PermissionDefinition struct {
Role interface{} `json:"role,omitempty" yaml:"role,omitempty"`
Comment string `json:"comment,omitempty" yaml:"comment,omitempty"`
}
type createActionPermissionInput struct {
Action interface{} `json:"action,omitempty" yaml:"action,omitempty"`
PermissionDefinition
}
type dropActionPermissionInput struct {
Action interface{} `json:"action,omitempty" yaml:"action,omitempty"`
PermissionDefinition
}
type setCustomTypesInput struct {
InputObjects interface{} `json:"input_objects,omitempty" yaml:"input_objects,omitempty"`
Objects interface{} `json:"objects,omitempty" yaml:"objects,omitempty"`
Scalars interface{} `json:"scalars,omitempty" yaml:"scalars,omitempty"`
Enums interface{} `json:"enums,omitempty" yaml:"enums,omitempty"`
}
func (h *newHasuraIntefaceQuery) UnmarshalJSON(b []byte) error {
type t newHasuraIntefaceQuery
var q t
@ -175,6 +220,18 @@ func (h *newHasuraIntefaceQuery) UnmarshalJSON(b []byte) error {
q.Args = &createCronTriggerInput{}
case deleteCronTrigger:
q.Args = &deleteCronTriggerInput{}
case createAction:
q.Args = &createActionInput{}
case dropAction:
q.Args = &dropActionInput{}
case updateAction:
q.Args = &updateActionInput{}
case createActionPermission:
q.Args = &createActionPermissionInput{}
case dropActionPermission:
q.Args = &dropActionPermissionInput{}
case setCustomTypes:
q.Args = &setCustomTypesInput{}
default:
return fmt.Errorf("cannot squash type %s", q.Type)
}
@ -357,6 +414,12 @@ const (
deleteRemoteRelationship = "delete_remote_relationship"
createCronTrigger = "create_cron_trigger"
deleteCronTrigger = "delete_cron_trigger"
createAction = "create_action"
dropAction = "drop_action"
updateAction = "update_action"
createActionPermission = "create_action_permission"
dropActionPermission = "drop_action_permission"
setCustomTypes = "set_custom_types"
)
type tableMap struct {
@ -700,6 +763,8 @@ type replaceMetadataInput struct {
AllowList []*addCollectionToAllowListInput `json:"allowlist" yaml:"allowlist"`
RemoteSchemas []*addRemoteSchemaInput `json:"remote_schemas" yaml:"remote_schemas"`
CronTriggers []*createCronTriggerInput `json:"cron_triggers" yaml:"cron_triggers"`
Actions []*actionAndPermission `json:"actions" yaml:"actions"`
CustomTypes *setCustomTypesInput `json:"custom_types" yaml:"custom_types"`
}
func (rmi *replaceMetadataInput) convertToMetadataActions(l *database.CustomList) {
@ -806,15 +871,17 @@ func (rmi *replaceMetadataInput) convertToMetadataActions(l *database.CustomList
}
for _, table := range rmi.Tables {
for _, remoteRelationship := range *table.RemoteRelationships {
r := createRemoteRelationshipInput{
remoteRelationshipDefinition: remoteRelationship.Definiton,
Table: tableSchema{
Name: table.Table.Name,
Schema: table.Table.Schema,
},
if table.RemoteRelationships != nil {
for _, remoteRelationship := range *table.RemoteRelationships {
r := createRemoteRelationshipInput{
remoteRelationshipDefinition: remoteRelationship.Definiton,
Table: tableSchema{
Name: table.Table.Name,
Schema: table.Table.Schema,
},
}
l.PushBack(r)
}
l.PushBack(r)
}
}
@ -842,6 +909,26 @@ func (rmi *replaceMetadataInput) convertToMetadataActions(l *database.CustomList
for _, ct := range rmi.CronTriggers {
l.PushBack(ct)
}
// track actions
for _, action := range rmi.Actions {
// action definition
a := &createActionInput{
actionDefinition: action.actionDefinition,
}
l.PushBack(a)
// permission
for _, permission := range action.Permissions {
p := &createActionPermissionInput{
Action: action.Name,
PermissionDefinition: permission,
}
l.PushBack(p)
}
}
if rmi.CustomTypes != nil {
l.PushBack(rmi.CustomTypes)
}
}
type InconsistentMetadata struct {
@ -1022,7 +1109,17 @@ type remoteRelationshipConfig struct {
tableName, schemaName, name string
transition.Transition
}
type cronTriggerConfig struct {
name string
transition.Transition
}
type actionConfig struct {
name string
transition.Transition
}
type actionPermissionConfig struct {
action string
transition.Transition
}

View File

@ -38,6 +38,9 @@ func getLatestVersion() (*semver.Version, *semver.Version, error) {
if err != nil {
return nil, nil, errors.Wrap(err, "decoding update check response")
}
if response.Latest == nil && response.PreRelease == nil {
return nil,nil, fmt.Errorf("expected version info not found at %s", updateCheckURL)
}
return response.Latest, response.PreRelease, nil
}

View File

@ -47,7 +47,6 @@ export const unTrackFunction = () => {
};
export const trackFunction = () => {
cy.get(getElementFromAlias('toggle-trackable-functions')).click();
cy.get(
getElementFromAlias(`add-track-function-${getCustomFunctionName(1)}`)
).should('exist');

View File

@ -490,13 +490,14 @@ export const passArrayDataType = () => {
// create new column
cy.get(getElementFromAlias('table-modify')).click();
cy.wait(1000);
cy.get(getElementFromAlias('modify-table-edit-add-new-column')).click();
cy.get(getElementFromAlias('column-name')).type('array_column');
cy.get(getElementFromAlias('col-type-0'))
.children('div')
.click()
.find('input')
.type('text[]', { force: true });
cy.get(getElementFromAlias('add-column-button')).click();
cy.get(getElementFromAlias('modify-table-add-new-column-save')).click();
// insert new row
cy.get(getElementFromAlias('table-insert-rows')).click();

View File

@ -65,9 +65,7 @@ export const passMTRenameTable = () => {
export const passMTRenameColumn = () => {
cy.wait(10000);
cy.get(getElementFromAlias('modify-table-edit-column-0')).click();
cy.get(getElementFromAlias('edit-col-name'))
.clear()
.type(getColName(3));
cy.get(getElementFromAlias('edit-col-name')).clear().type(getColName(3));
cy.get(getElementFromAlias('modify-table-column-0-save')).click();
cy.wait(15000);
validateColumn(
@ -76,9 +74,7 @@ export const passMTRenameColumn = () => {
ResultType.SUCCESS
);
cy.get(getElementFromAlias('modify-table-edit-column-0')).click();
cy.get(getElementFromAlias('edit-col-name'))
.clear()
.type('id');
cy.get(getElementFromAlias('edit-col-name')).clear().type('id');
cy.get(getElementFromAlias('modify-table-column-0-save')).click();
cy.wait(15000);
validateColumn(getTableName(0, testName), ['id'], ResultType.SUCCESS);
@ -87,9 +83,7 @@ export const passMTRenameColumn = () => {
export const passMTChangeDefaultValueForPKey = () => {
cy.wait(10000);
cy.get(getElementFromAlias('modify-table-edit-column-0')).click();
cy.get(getElementFromAlias('edit-col-default'))
.clear()
.type('1234');
cy.get(getElementFromAlias('edit-col-default')).clear().type('1234');
cy.get(getElementFromAlias('modify-table-column-0-save')).click();
cy.wait(15000);
};
@ -103,7 +97,8 @@ export const passMTMoveToTable = () => {
};
export const failMTWithoutColName = () => {
cy.get(getElementFromAlias('add-column-button')).click();
cy.get(getElementFromAlias('modify-table-edit-add-new-column')).click();
cy.get(getElementFromAlias('modify-table-add-new-column-save')).click();
cy.url().should(
'eq',
`${baseUrl}/data/schema/public/tables/${getTableName(0, testName)}/modify`
@ -118,7 +113,7 @@ export const failMTWithoutColName = () => {
export const failMTWithoutColType = () => {
cy.get(getElementFromAlias('column-name')).type(getColName(2));
cy.get(getElementFromAlias('add-column-button')).click();
cy.get(getElementFromAlias('modify-table-add-new-column-save')).click();
cy.url().should(
'eq',
`${baseUrl}/data/schema/public/tables/${getTableName(0, testName)}/modify`
@ -138,7 +133,7 @@ export const Addcolumnnullable = () => {
.first()
.click();
cy.get(getElementFromAlias('nullable-checkbox')).uncheck({ force: true });
cy.get(getElementFromAlias('add-column-button')).click();
cy.get(getElementFromAlias('modify-table-add-new-column-save')).click();
cy.wait(2500);
cy.url().should(
'eq',
@ -160,7 +155,7 @@ export const Addcolumnname = (name: string) => {
.first()
.click();
cy.get(getElementFromAlias('add-column-button')).click();
cy.get(getElementFromAlias('modify-table-add-new-column-save')).click();
cy.wait(5000);
validateColumn(getTableName(0, testName), [name], ResultType.SUCCESS);
};
@ -172,7 +167,7 @@ export const passMTAddColumn = () => {
cy.get(getElementFromAlias('data_test_column_type_value_integer'))
.first()
.click();
cy.get(getElementFromAlias('add-column-button')).click();
cy.get(getElementFromAlias('modify-table-add-new-column-save')).click();
cy.wait(5000);
validateColumn(
getTableName(0, testName),
@ -193,9 +188,7 @@ export const failMCWithWrongDefaultValue = () => {
};
export const passMCWithRightDefaultValue = () => {
cy.get(getElementFromAlias('edit-col-default'))
.clear()
.type('1234');
cy.get(getElementFromAlias('edit-col-default')).clear().type('1234');
cy.get(getElementFromAlias('modify-table-column-1-save')).click();
cy.wait(10000);
};
@ -261,9 +254,7 @@ export const passMTDeleteCol = () => {
setPromptValue(getColName(0));
cy.get(getElementFromAlias('modify-table-edit-column-1')).click();
cy.get(getElementFromAlias('modify-table-column-1-remove')).click();
cy.window()
.its('prompt')
.should('be.called');
cy.window().its('prompt').should('be.called');
cy.wait(5000);
cy.url().should(
'eq',
@ -279,9 +270,7 @@ export const passMTDeleteCol = () => {
export const passMTDeleteTableCancel = () => {
setPromptValue(null);
cy.get(getElementFromAlias('delete-table')).click();
cy.window()
.its('prompt')
.should('be.called');
cy.window().its('prompt').should('be.called');
cy.url().should(
'eq',
`${baseUrl}/data/schema/public/tables/${getTableName(0, testName)}/modify`
@ -293,9 +282,7 @@ export const passMTDeleteTableCancel = () => {
export const passMTDeleteTable = () => {
setPromptValue(getTableName(0, testName));
cy.get(getElementFromAlias('delete-table')).click();
cy.window()
.its('prompt')
.should('be.called');
cy.window().its('prompt').should('be.called');
cy.wait(5000);
cy.url().should('eq', `${baseUrl}/data/schema/public`);
validateCT(getTableName(0, testName), ResultType.FAILURE);
@ -313,9 +300,7 @@ export const createTable = (name: string, dict: { [key: string]: any }) => {
const keys = Object.keys(dict).map(k => k);
const values = Object.keys(dict).map(k => dict[k]);
for (let i = 0; i < keys.length; i += 1) {
cy.get('input[placeholder="column_name"]')
.last()
.type(keys[i]);
cy.get('input[placeholder="column_name"]').last().type(keys[i]);
cy.get('select')
.find('option')
.contains('-- type --')
@ -324,9 +309,7 @@ export const createTable = (name: string, dict: { [key: string]: any }) => {
.select(values[i]);
}
cy.get('select')
.last()
.select('id');
cy.get('select').last().select('id');
cy.get(getElementFromAlias('table-create')).click();
cy.wait(7000);
cy.url().should(
@ -381,16 +364,12 @@ export const Checkviewtabledelete = () => {
cy.get(getElementFromAlias('table-modify')).click();
setPromptValue('author_average_rating_mod');
cy.get(getElementFromAlias('delete-view')).click();
cy.window()
.its('prompt')
.should('be.called');
cy.window().its('prompt').should('be.called');
cy.wait(7000);
validateCT('author_average_rating_mod', ResultType.FAILURE);
};
export const Issue = () => {
cy.get('.ace_text-input')
.first()
.type('#include');
cy.get('.ace_text-input').first().type('#include');
};

View File

@ -3055,6 +3055,11 @@
"fork-ts-checker-webpack-plugin": "*"
}
},
"@types/highlight.js": {
"version": "9.12.4",
"resolved": "https://registry.npmjs.org/@types/highlight.js/-/highlight.js-9.12.4.tgz",
"integrity": "sha512-t2szdkwmg2JJyuCM20e8kR2X59WCE5Zkl4bzm1u1Oukjm79zpbiAv+QjnwLnuuV0WHEcX2NgUItu0pAMKuOPww=="
},
"@types/history": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/@types/history/-/history-3.2.4.tgz",
@ -3472,6 +3477,11 @@
"integrity": "sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA==",
"dev": true
},
"@types/sql-formatter": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@types/sql-formatter/-/sql-formatter-2.3.0.tgz",
"integrity": "sha512-Xh9kEOaKWhm3vYD5lUjYFFiSfpN4y3/iQCJUAVwFaQ1rVvHs4WXTa5C8E7gyF3kxwsMS8KgttW7WBAPtFlsvAg=="
},
"@types/styled-components": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/@types/styled-components/-/styled-components-5.1.0.tgz",
@ -9646,6 +9656,11 @@
"integrity": "sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==",
"dev": true
},
"highlight.js": {
"version": "9.15.8",
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-9.15.8.tgz",
"integrity": "sha512-RrapkKQWwE+wKdF73VsOa2RQdIoO3mxwJ4P8mhbI6KYJUraUHRKM5w5zQQKXNk0xNL4UVRdulV9SBJcmzJNzVA=="
},
"history": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/history/-/history-3.3.0.tgz",
@ -16916,6 +16931,14 @@
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
},
"sql-formatter": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/sql-formatter/-/sql-formatter-2.3.3.tgz",
"integrity": "sha512-m6pqVXwsm9GkCHC/+gdPvNowI7PNoVTT6OZMWKwXJoP2MvfntfhcfyliIf4/QX6t+DirSJ6XDSiSS70YvZ87Lw==",
"requires": {
"lodash": "^4.16.0"
}
},
"sshpk": {
"version": "1.16.1",
"resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz",

View File

@ -50,6 +50,8 @@
"dependencies": {
"@graphql-codegen/core": "1.13.5",
"@graphql-codegen/typescript": "1.13.5",
"@types/highlight.js": "9.12.4",
"@types/sql-formatter": "2.3.0",
"ace-builds": "^1.4.11",
"apollo-link": "1.2.14",
"apollo-link-ws": "1.0.20",
@ -59,6 +61,7 @@
"graphiql-explorer": "0.6.2",
"graphql": "14.5.8",
"graphql-voyager": "1.0.0-rc.29",
"highlight.js": "9.15.8",
"history": "3.3.0",
"inflection": "1.12.0",
"isomorphic-fetch": "2.2.1",
@ -90,6 +93,7 @@
"redux-thunk": "2.3.0",
"sanitize-filename": "1.6.3",
"semver": "5.5.1",
"sql-formatter": "2.3.3",
"styled-components": "5.0.1",
"styled-system": "5.1.5",
"subscriptions-transport-ws": "0.9.16"

View File

@ -5,10 +5,6 @@ import { stripTrailingSlash } from './components/Common/utils/urlUtils';
import { isEmpty } from './components/Common/utils/jsUtils';
// TODO: move this section to a more appropriate location
/* set helper tools into window */
import sqlFormatter from './helpers/sql-formatter.min';
const hljs = require('./helpers/highlight.min');
declare global {
interface Window {
@ -27,22 +23,10 @@ declare global {
consolePath: string;
cliUUID: string;
};
sqlFormatter: unknown;
hljs: unknown;
}
const CONSOLE_ASSET_VERSION: string;
}
if (
(window as Window) &&
typeof window === 'object' &&
!window.sqlFormatter &&
!window.hljs
) {
window.sqlFormatter = sqlFormatter;
window.hljs = hljs;
}
/* initialize globals */
const isProduction = window.__env.nodeEnv !== 'development';

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

View File

@ -11,7 +11,7 @@ type DropDownButtonProps = {
}[];
dataKey: string;
dataIndex?: string;
onButtonChange: (e: React.MouseEvent) => void;
onButtonChange: (e: React.MouseEvent<{}>) => void;
onInputChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
value?: string;
inputVal: string;

View File

@ -11,12 +11,14 @@ interface LeftSidebarSectionProps extends React.ComponentProps<'div'> {
currentItem?: LeftSidebarItem;
getServiceEntityLink: (s: string) => string;
service: string;
sidebarIcon?: string;
}
const LeftSidebarSection = ({
items = [],
currentItem,
service,
sidebarIcon,
getServiceEntityLink,
}: LeftSidebarSectionProps) => {
// TODO needs refactor to accomodate other services
@ -76,7 +78,9 @@ const LeftSidebarSection = ({
>
<Link to={getServiceEntityLink(a.name)} data-test={a.name}>
<i
className={`${styles.tableIcon} fa fa-wrench`}
className={`${styles.tableIcon} fa ${
sidebarIcon || 'fa-wrench'
}`}
aria-hidden="true"
/>
{a.name}

View File

@ -151,18 +151,23 @@
outline: 0;
}
/* Event Trigger */
.ReactTable .rt-table .rt-thead .rt-tr .rt-th:first-child,
.ReactTable .rt-table .rt-tbody .rt-tr-group .rt-tr.-odd .rt-td:first-child,
.ReactTable .rt-table .rt-tbody .rt-tr-group .rt-tr.-even .rt-td:first-child {
min-width: 75px !important;
}
.dataTable.ReactTable .rt-tbody .rt-th,
.dataTable.ReactTable .rt-tbody .rt-td,
.dataTable.ReactTable .rt-thead .rt-resizable-header-content {
justify-content: left;
}
.ReactTable .rt-tbody .rt-th,
.ReactTable .rt-tbody .rt-td {
display: flex;
justify-content: center;
align-items: center;
}
.ReactTable .rt-thead .rt-resizable-header-content {

View File

@ -35,11 +35,14 @@
border-left: 1px solid #ddd;
}
.tableHeaderCell i {
font-size: 12px;
position: absolute;
top: calc(50% - 5px);
right: 8px;
.tableHeaderCell {
text-align: left;
i {
font-size: 12px;
position: absolute;
top: calc(50% - 5px);
right: 8px;
}
}
.insertContainer {
@ -150,9 +153,6 @@ a.expanded {
text-overflow: ellipsis;
}
.sqlBody {
}
.tablesBody {
padding-top: 20px;
}
@ -170,11 +170,6 @@ a.expanded {
padding-bottom: 10px;
}
.tableCellCenterAligned {
text-align: center;
overflow: unset !important;
}
.tableCellCenterAlignedOverflow {
text-align: center;
overflow: hidden !important;
@ -186,10 +181,15 @@ a.expanded {
.tableCenterContent {
display: flex;
width: 100%;
justify-content: center;
align-items: center;
}
.overflowUnset {
overflow: unset !important;
}
.bulkDeleteButton {
margin: 10px;
font-size: 16px;
@ -199,4 +199,5 @@ a.expanded {
.headerInputCheckbox {
height: 16px;
margin-bottom: 2px;
margin-right: 3px !important;
}

View File

@ -1,5 +1,6 @@
import React from 'react';
import sqlFormatter from 'sql-formatter';
import hljs from 'highlight.js';
import PropTypes from 'prop-types';
class TextAreaWithCopy extends React.Component {
@ -12,9 +13,7 @@ class TextAreaWithCopy extends React.Component {
if (copyText.length > 0) {
switch (textLanguage) {
case 'sql':
text = window.sqlFormatter
? window.sqlFormatter.format(copyText, { language: textLanguage })
: copyText;
text = sqlFormatter.format(copyText, { language: textLanguage });
break;
default:
text = copyText;
@ -35,7 +34,6 @@ class TextAreaWithCopy extends React.Component {
try {
const successful = document.execCommand('copy');
// const msg = successful ? 'successful' : 'unsuccessful';
const tooltip = document.getElementById(id);
if (!successful) {
tooltip.innerHTML = 'Error copying';
@ -75,18 +73,14 @@ class TextAreaWithCopy extends React.Component {
};
const renderSQLValue = () => {
if (!window || !window.hljs || !window.sqlFormatter) {
return renderSimpleValue();
}
return (
<pre>
<code
className={style.formattedCode}
dangerouslySetInnerHTML={{
__html: window.hljs.highlight(
__html: hljs.highlight(
'sql',
window.sqlFormatter.format(copyText, { language: textLanguage })
sqlFormatter.format(copyText, { language: textLanguage })
).value,
}}
/>
@ -95,16 +89,12 @@ class TextAreaWithCopy extends React.Component {
};
const renderJSONValue = () => {
if (!window || !window.hljs) {
return renderSimpleValue();
}
return (
<pre>
<code
className={style.formattedCode}
dangerouslySetInnerHTML={{
__html: window.hljs.highlight(
__html: hljs.highlight(
'json',
JSON.stringify(JSON.parse(copyText), null, 4)
).value,

View File

@ -5,6 +5,8 @@ import {
} from 'graphql';
import React from 'react';
import endpoints from '../../../Endpoints';
import { Dispatch } from '../../../types';
import requestAction from '../../../utils/requestAction';
export const getGraphQLQueryPayload = (
query: string,
@ -14,7 +16,7 @@ export const getGraphQLQueryPayload = (
variables,
});
export const useIntrospectionSchema = (headers = {}) => {
export const useIntrospectionSchema = (headers = {}, dispatch: Dispatch) => {
const [schema, setSchema] = React.useState<GraphQLSchema | null>(null);
const [loading, setLoading] = React.useState(true);
const [error, setError] = React.useState(null);
@ -22,12 +24,22 @@ export const useIntrospectionSchema = (headers = {}) => {
const introspect = () => {
setLoading(true);
fetch(endpoints.graphQLUrl, {
method: 'POST',
headers,
body: JSON.stringify(getGraphQLQueryPayload(getIntrospectionQuery(), {})),
})
.then(r => r.json())
dispatch(
requestAction(
endpoints.graphQLUrl,
{
method: 'POST',
headers,
body: JSON.stringify(
getGraphQLQueryPayload(getIntrospectionQuery(), {})
),
},
undefined,
undefined,
true,
true
)
)
.then(response => {
if (response.data) {
setSchema(buildClientSchema(response.data));
@ -45,7 +57,7 @@ export const useIntrospectionSchema = (headers = {}) => {
return () => setSchema(null);
};
React.useEffect(introspect, []);
React.useEffect(introspect, [dispatch, headers]);
return {
schema,

View File

@ -10,24 +10,17 @@ export const sqlEscapeText = (rawText: string) => {
// detect DDL statements in SQL
export const checkSchemaModification = (sql: string) => {
let isSchemaModification = false;
const sqlStatements = sql
.toLowerCase()
.split(';')
.map(s => s.trim());
.map(sqlStr => sqlStr.trim());
sqlStatements.forEach(statement => {
if (
return sqlStatements.some(
statement =>
statement.startsWith('create ') ||
statement.startsWith('alter ') ||
statement.startsWith('drop ')
) {
isSchemaModification = true;
}
});
return isSchemaModification;
);
};
export const getCheckConstraintBoolExp = (check: string) => {

View File

@ -496,9 +496,6 @@ export const generateCreateEventTriggerQuery = (
columns: state.operationColumns
.filter(c => !!c.enabled)
.map(c => c.name),
payload: state.operationColumns
.filter(c => !!c.enabled)
.map(c => c.name),
}
: null,
delete: state.operations.delete

View File

@ -13,6 +13,7 @@ const CodeTabs = ({
currentAction,
parentMutation,
shouldDerive,
dispatch,
}) => {
const [loading, setLoading] = React.useState(true);
const [error, setError] = React.useState(null);
@ -25,7 +26,8 @@ const CodeTabs = ({
framework,
currentAction.action_name,
actionsSdl,
shouldDerive ? parentMutation : null
shouldDerive ? parentMutation : null,
dispatch
)
.then(codeFiles => {
setCodegenFiles(codeFiles);

View File

@ -14,7 +14,7 @@ import { Icon } from '../../../UIKit/atoms';
import CodeTabs from './CodeTabs';
import DerivedFrom from './DerivedFrom';
const Codegen = ({ allActions, allTypes, currentAction }) => {
const Codegen = ({ dispatch, allActions, allTypes, currentAction }) => {
const [allFrameworks, setAllFrameworks] = React.useState([]);
const [selectedFramework, selectFramework] = React.useState('');
const [loading, setLoading] = React.useState(true);
@ -33,7 +33,7 @@ const Codegen = ({ allActions, allTypes, currentAction }) => {
const init = () => {
setLoading(true);
setError(null);
getAllCodegenFrameworks()
getAllCodegenFrameworks(dispatch)
.then(frameworks => {
setAllFrameworks(frameworks);
selectFramework(frameworks[0].name);
@ -188,6 +188,7 @@ const Codegen = ({ allActions, allTypes, currentAction }) => {
currentAction={currentAction}
shouldDerive={shouldDerive}
parentMutation={parentMutation}
dispatch={dispatch}
/>
</div>
<hr />

View File

@ -19,6 +19,8 @@ const {
} = require('graphql');
const { camelize } = require('inflection');
import { getPersistedDerivedAction } from '../lsUtils';
import requestAction from '../../../../utils/requestAction';
import requestActionPlain from '../../../../utils/requestActionPlain';
export const getCodegenFilePath = framework => {
return `${BASE_CODEGEN_PATH}/${framework}/actions-codegen.js`;
@ -37,18 +39,12 @@ export const getGlitchProjectURL = () => {
export const GLITCH_PROJECT_URL = '';
export const getAllCodegenFrameworks = () => {
return fetch(ALL_FRAMEWORKS_FILE_PATH)
.then(r => r.json())
.catch(e => {
console.error('could not fetch the latest codegen file');
throw e;
});
export const getAllCodegenFrameworks = dispatch => {
return dispatch(requestAction(ALL_FRAMEWORKS_FILE_PATH, {}));
};
export const getCodegenFunc = framework => {
return fetch(getCodegenFilePath(framework))
.then(r => r.text())
export const getCodegenFunc = (framework, dispatch) => {
return dispatch(requestActionPlain(getCodegenFilePath(framework)))
.then(rawJsString => {
const { codegen } = require('@graphql-codegen/core');
const typescriptPlugin = require('@graphql-codegen/typescript');
@ -65,9 +61,10 @@ export const getFrameworkCodegen = (
framework,
actionName,
actionsSdl,
parentOperation
parentOperation,
dispatch
) => {
return getCodegenFunc(framework)
return getCodegenFunc(framework, dispatch)
.then(codegenerator => {
const derive = {
operation: parentOperation,

View File

@ -7,12 +7,13 @@ import Tooltip from './Tooltip';
import styles from './Styles.scss';
import { useIntrospectionSchema } from '../../../../Common/utils/graphqlUtils';
const CloneType = ({ headers, toggleModal, handleClonedTypes }) => {
const CloneType = ({ headers, toggleModal, handleClonedTypes, dispatch }) => {
const [prefix, setPrefix] = React.useState('_');
const prefixOnChange = e => setPrefix(e.target.value);
const { schema, loading, error, introspect } = useIntrospectionSchema(
headers
headers,
dispatch
);
if (loading) return <Spinner />;

View File

@ -10,8 +10,6 @@ import styles from '../Actions.scss';
// import TryItOut from '../../Common/Landing/TryItOut';
const actionsArchDiagram = `${globals.assetsPath}/common/img/actions.png`;
class Landing extends React.Component {
render() {
const { readOnlyMode } = this.props;
@ -22,10 +20,10 @@ class Landing extends React.Component {
<div>
<TopicDescription
title="What are Actions?"
// imgUrl={`${globals.assetsPath}/common/img/remote_schema.png`} // TODO: update image & description
imgUrl={actionsArchDiagram}
imgUrl={`${globals.assetsPath}/common/img/actions.png`}
imgAlt="Actions"
description="Actions are custom queries or mutations that are resolved via HTTP handlers. Actions can be used to carry out complex data validations, data enrichment from external sources or execute just about any custom business logic."
knowMoreHref="https://hasura.io/docs/1.0/graphql/manual/actions/index.html"
/>
<hr className={styles.clear_fix} />
</div>

View File

@ -138,7 +138,7 @@ export const createAction = () => (dispatch, getState) => {
}
const state = {
handler: rawState.handler,
handler: rawState.handler.trim(),
kind: rawState.kind,
types,
actionType,
@ -258,7 +258,7 @@ export const saveAction = currentAction => (dispatch, getState) => {
}
const state = {
handler: rawState.handler,
handler: rawState.handler.trim(),
kind: rawState.kind,
types,
actionType,

View File

@ -41,6 +41,17 @@ const LeftSidebar = ({
actionsList = [...actions];
}
const getActionIcon = action => {
switch (action.action_defn.type) {
case 'mutation':
return 'fa-pencil-square-o';
case 'query':
return 'fa-book';
default:
return 'fa-wrench';
}
};
const getChildList = () => {
let childList;
if (actionsList.length === 0) {
@ -59,6 +70,8 @@ const LeftSidebar = ({
activeTableClass = styles.activeLink;
}
const actionIcon = getActionIcon(a);
return (
<li
className={activeTableClass}
@ -70,7 +83,7 @@ const LeftSidebar = ({
data-test={a.action_name}
>
<i
className={styles.tableIcon + ' fa fa-wrench'}
className={styles.tableIcon + ' fa ' + actionIcon}
aria-hidden="true"
/>
{a.action_name}

View File

@ -12,6 +12,7 @@ import { execute } from 'apollo-link';
import { getHeadersAsJSON, getGraphQLEndpoint } from './utils';
import { saveAppState, clearState } from '../../AppState.js';
import { ADMIN_SECRET_HEADER_KEY } from '../../../constants';
import requestActionPlain from '../../../utils/requestActionPlain';
const CHANGE_TAB = 'ApiExplorer/CHANGE_TAB';
const CHANGE_API_SELECTION = 'ApiExplorer/CHANGE_API_SELECTION';
@ -214,20 +215,22 @@ const isSubscription = graphQlParams => {
return false;
};
const graphQLFetcherFinal = (graphQLParams, url, headers) => {
const graphQLFetcherFinal = (graphQLParams, url, headers, dispatch) => {
if (isSubscription(graphQLParams)) {
return graphqlSubscriber(graphQLParams, url, headers);
}
return fetch(url, {
method: 'POST',
headers: getHeadersAsJSON(headers),
body: JSON.stringify(graphQLParams),
}).then(response => response.json());
return dispatch(
requestAction(url, {
method: 'POST',
headers: getHeadersAsJSON(headers),
body: JSON.stringify(graphQLParams),
})
);
};
/* Analyse Fetcher */
const analyzeFetcher = (headers, mode) => {
return query => {
return (query, dispatch) => {
const editedQuery = {
query,
is_relay: mode === 'relay',
@ -255,12 +258,14 @@ const analyzeFetcher = (headers, mode) => {
editedQuery.user = user;
return fetch(`${Endpoints.graphQLUrl}/explain`, {
method: 'post',
headers: reqHeaders,
body: JSON.stringify(editedQuery),
credentials: 'include',
});
return dispatch(
requestAction(`${Endpoints.graphQLUrl}/explain`, {
method: 'post',
headers: reqHeaders,
body: JSON.stringify(editedQuery),
credentials: 'include',
})
);
};
};
/* End of it */
@ -436,9 +441,9 @@ const getStateAfterClearingHistory = state => {
};
};
const getRemoteQueries = (queryUrl, cb) => {
fetch(queryUrl)
.then(resp => resp.text().then(cb))
const getRemoteQueries = (queryUrl, cb, dispatch) => {
dispatch(requestActionPlain(queryUrl))
.then(cb)
.catch(e => console.error('Invalid query file URL: ', e));
};

View File

@ -6,7 +6,7 @@ import { print, parse } from 'graphql';
import { isValidGraphQLOperation } from '../utils';
import { getGraphQLQueryPayload } from '../../../Common/utils/graphqlUtils';
export default class AnalyseButton extends React.Component {
export default class AnalyzeButton extends React.Component {
constructor(props) {
super(props);
// Ensure props are correct
@ -71,6 +71,7 @@ export default class AnalyseButton extends React.Component {
mode={mode}
analyseQuery={this.state.analyseQuery}
clearAnalyse={this.clearAnalyse.bind(this)}
dispatch={this.props.dispatch}
{...this.props}
/>
)}
@ -172,7 +173,7 @@ export default class AnalyseButton extends React.Component {
};
}
AnalyseButton.propTypes = {
AnalyzeButton.propTypes = {
operations: PropTypes.array,
query: PropTypes.string,
variables: PropTypes.string,

View File

@ -1,6 +1,8 @@
import React from 'react';
import PropTypes from 'prop-types';
import Modal from 'react-modal';
import sqlFormatter from 'sql-formatter';
import hljs from 'highlight.js';
import RootFields from './RootFields';
export default class QueryAnalyser extends React.Component {
@ -14,14 +16,9 @@ export default class QueryAnalyser extends React.Component {
}
componentDidMount() {
const { dispatch, analyseQuery } = this.props;
this.props
.analyzeFetcher(this.props.analyseQuery.query)
.then(r => {
if (r.ok) {
return r.json();
}
return r.text().then(rText => Promise.reject(new Error(rText)));
})
.analyzeFetcher(analyseQuery.query, dispatch)
.then(data => {
this.setState({
analyseData: Array.isArray(data) ? data : [data],
@ -77,32 +74,22 @@ export default class QueryAnalyser extends React.Component {
/>
</div>
</div>
{window.hljs && window.sqlFormatter ? (
<pre>
<code
dangerouslySetInnerHTML={{
__html:
this.state.activeNode >= 0 &&
this.state.analyseData.length > 0 &&
window.hljs.highlight(
'sql',
window.sqlFormatter.format(
this.state.analyseData[this.state.activeNode]
.sql,
{ language: 'sql' }
)
).value,
}}
/>
</pre>
) : (
<code>
{this.state.activeNode >= 0 &&
this.state.analyseData.length > 0
? this.state.analyseData[this.state.activeNode].sql
: ''}
</code>
)}
<pre>
<code
dangerouslySetInnerHTML={{
__html:
this.state.activeNode >= 0 &&
this.state.analyseData.length > 0 &&
hljs.highlight(
'sql',
sqlFormatter.format(
this.state.analyseData[this.state.activeNode].sql,
{ language: 'sql' }
)
).value,
}}
/>
</pre>
</div>
</div>
<div className="plansWrapper">
@ -120,18 +107,6 @@ export default class QueryAnalyser extends React.Component {
/>
</div>
</div>
{/*
<pre>
<code>
{this.state.activeNode >= 0
&& this.state.analyseData.length > 0
? this.state.analyseData[
this.state.activeNode
].plan.map((k, i) => <div key={ i }>{k}</div> )
: ''}
</code>
</pre>
*/}
<pre>
<code>
{this.state.activeNode >= 0 &&
@ -150,20 +125,6 @@ export default class QueryAnalyser extends React.Component {
</Modal>
);
}
/*
fetchAnalyse() {
const options = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
credentials: 'include',
};
options.body = JSON.stringify(this.props.analyseQuery);
return fetch('http://localhost:8080/v1alpha1/graphql/explain', options);
}
*/
handleAnalyseNodeChange = e => {
const nodeKey = e.target.getAttribute('data-key');
if (nodeKey) {
@ -174,12 +135,10 @@ export default class QueryAnalyser extends React.Component {
let text = '';
if (this.state.analyseData.length > 0) {
if (type === 'sql') {
text = window.sqlFormatter
? window.sqlFormatter.format(
this.state.analyseData[this.state.activeNode].sql,
{ language: 'sql' }
)
: this.state.analyseData[this.state.activeNode].sql;
text = sqlFormatter.format(
this.state.analyseData[this.state.activeNode].sql,
{ language: 'sql' }
);
} else {
text = this.state.analyseData[this.state.activeNode].plan.join('\n');
}

View File

@ -831,3 +831,10 @@ label {
.graphiqlModeToggle {
float: right;
}
.headerInfoIcon {
cursor: pointer;
padding-right: 8px;
font-size: 14px;
display: inline-block;
}

View File

@ -39,12 +39,26 @@ import {
import { getGraphQLEndpoint } from '../utils';
import styles from '../ApiExplorer.scss';
import { ADMIN_SECRET_HEADER_KEY } from '../../../../constants';
import {
ADMIN_SECRET_HEADER_KEY,
HASURA_CLIENT_NAME,
HASURA_COLLABORATOR_TOKEN,
} from '../../../../constants';
/* When the page is loaded for the first time, hydrate the header state from the localStorage
* Keep syncing the localStorage state when user modifies.
* */
const ActionIcon = ({ message, dataHeaderID }) => (
<Tooltip placement="left" message={message}>
<i
className={`${styles.headerInfoIcon} fa fa-question-circle`}
data-header-id={dataHeaderID}
aria-hidden="true"
/>
</Tooltip>
);
class ApiRequest extends Component {
constructor(props) {
super(props);
@ -332,6 +346,14 @@ class ApiRequest extends Component {
const isAdminSecret =
header.key.toLowerCase() === ADMIN_SECRET_HEADER_KEY;
const consoleId = window.__env.consoleId;
const isClientName =
header.key.toLowerCase() === HASURA_CLIENT_NAME && consoleId;
const isCollaboratorToken =
header.key.toLowerCase() === HASURA_COLLABORATOR_TOKEN && consoleId;
const getHeaderActiveCheckBox = () => {
let headerActiveCheckbox = null;
@ -523,6 +545,18 @@ class ApiRequest extends Component {
{getHeaderAdminVal()}
{getJWTInspectorIcon()}
{getHeaderRemoveBtn()}
{isClientName && (
<ActionIcon
message="Hasura client name is a header that indicates where the request is being made from. This is used by GraphQL Engine for providing detailed metrics."
dataHeaderID={i}
/>
)}
{isCollaboratorToken && (
<ActionIcon
message="Hasura collaborator token is an admin-secret alternative when you login using Hasura. This is used by GraphQL Engine to authorise your requests."
dataHeaderID={i}
/>
)}
</td>
);
}

View File

@ -93,7 +93,8 @@ class GraphiQLWrapper extends Component {
return graphQLFetcherFinal(
graphQLParams,
getGraphQLEndpoint(mode),
graphqlNetworkData.headers
graphqlNetworkData.headers,
dispatch
);
};
@ -235,6 +236,7 @@ class GraphiQLWrapper extends Component {
<AnalyzeButton
operations={graphiqlContext && graphiqlContext.state.operations}
analyzeFetcher={analyzeFetcherInstance}
dispatch={dispatch}
{...analyzerProps}
/>
</GraphiQL.Toolbar>

View File

@ -18,6 +18,7 @@ import { getHeadersAsJSON } from '../utils';
import '../GraphiQLWrapper/GraphiQL.css';
import './OneGraphExplorer.css';
import { showErrorNotification } from '../../Common/Notification';
import requestAction from '../../../../utils/requestAction';
class OneGraphExplorer extends React.Component {
state = {
@ -53,13 +54,15 @@ class OneGraphExplorer extends React.Component {
}
setPersistedQuery() {
const { urlParams, numberOfTables } = this.props;
const { urlParams, numberOfTables, dispatch } = this.props;
const queryFile = urlParams ? urlParams.query_file : null;
if (queryFile) {
getRemoteQueries(queryFile, remoteQuery =>
this.setState({ query: remoteQuery })
getRemoteQueries(
queryFile,
remoteQuery => this.setState({ query: remoteQuery }),
dispatch
);
} else if (numberOfTables === 0) {
const NO_TABLES_MESSAGE = `# Looks like you do not have any tables.
@ -96,14 +99,15 @@ class OneGraphExplorer extends React.Component {
const headers = JSON.parse(JSON.stringify(headers_));
dispatch(setLoading(true));
this.setState({ schema: null });
fetch(endpoint, {
method: 'POST',
headers: getHeadersAsJSON(headers || []),
body: JSON.stringify({
query: getIntrospectionQuery(),
}),
})
.then(response => response.json())
dispatch(
requestAction(endpoint, {
method: 'POST',
headers: getHeadersAsJSON(headers || []),
body: JSON.stringify({
query: getIntrospectionQuery(),
}),
})
)
.then(result => {
if (result.errors && result.errors.length > 0) {
const errorMessage = result.errors[0].message;
@ -191,19 +195,6 @@ class OneGraphExplorer extends React.Component {
const { renderGraphiql } = this.props;
const explorer = (
<GraphiQLExplorer
schema={schema}
query={query}
onEdit={this.editQuery}
explorerIsOpen={explorerOpen}
onToggleExplorer={this.handleToggle}
getDefaultScalarArgValue={getDefaultScalarArgValue}
makeDefaultArg={makeDefaultArg}
width={explorerWidth}
/>
);
let explorerSeparator;
if (explorerOpen) {
explorerSeparator = (
@ -230,7 +221,16 @@ class OneGraphExplorer extends React.Component {
onMouseUp={this.handleExplorerResizeStop}
>
<div className="gqlexplorer">
{explorer}
<GraphiQLExplorer
schema={schema}
query={query}
onEdit={this.editQuery}
explorerIsOpen={explorerOpen}
onToggleExplorer={this.handleToggle}
getDefaultScalarArgValue={getDefaultScalarArgValue}
makeDefaultArg={makeDefaultArg}
width={explorerWidth}
/>
{explorerSeparator}
</div>
{graphiql}

View File

@ -1,5 +1,6 @@
import React from 'react';
import styles from '../../RemoteSchema/RemoteSchema.scss';
import KnowMoreLink from '../../../Common/KnowMoreLink/KnowMoreLink';
const Rectangle = require('./images/Rectangle.svg');
@ -7,11 +8,12 @@ type TopicDescriptionProps = {
title: string;
imgUrl: string;
imgAlt: string;
knowMoreHref?: string;
description: React.ReactNode;
};
const TopicDescription = (props: TopicDescriptionProps) => {
const { title, imgUrl, imgAlt, description } = props;
const { title, imgUrl, imgAlt, description, knowMoreHref } = props;
return (
<div>
<div className={styles.subHeaderText}>
@ -22,7 +24,7 @@ const TopicDescription = (props: TopicDescriptionProps) => {
<img className="img-responsive" src={imgUrl} alt={imgAlt} />
</div>
<div className={`${styles.descriptionText} ${styles.wd60}`}>
{description}
{description} {knowMoreHref && <KnowMoreLink href={knowMoreHref} />}
</div>
</div>
);

View File

@ -70,7 +70,7 @@ const PrimaryKeySelector = ({ primaryKeys, columns, setPk, dispatch }) => {
-- select --
</option>
) : (
<option value={pk}>{columns[pk].name}</option>
<option value={pk}>{columns[pk]?.name}</option>
)}
{nonPkColumns.map(({ name, index }, j) => (
<option key={j} value={index}>

View File

@ -33,6 +33,9 @@ const RootFieldEditor = ({
if (rfType.includes('select')) {
return rfType.replace('select', tableName);
}
if (rfType.includes('one')) {
return rfType.replace('one', tableName) + '_one';
}
return `${rfType}_${tableName}`;
};

View File

@ -17,6 +17,7 @@ import { getStatementTimeoutSql, parseCreateSQL } from './utils';
import dataHeaders from '../Common/Headers';
import returnMigrateUrl from '../Common/getMigrateUrl';
import { getRunSqlQuery } from '../../../Common/utils/v1QueryUtils';
import requestAction from '../../../../utils/requestAction';
const MAKING_REQUEST = 'RawSQL/MAKING_REQUEST';
const SET_SQL = 'RawSQL/SET_SQL';
@ -97,76 +98,47 @@ const executeSQL = (isMigration, migrationName, statementTimeout) => (
headers: dataHeaders(getState),
body: JSON.stringify(requestBody),
};
fetch(url, options).then(
response => {
if (response.ok) {
response.json().then(
data => {
if (isMigration) {
dispatch(loadMigrationStatus());
}
dispatch(showSuccessNotification('SQL executed!'));
dispatch(fetchDataInit()).then(() => {
dispatch({ type: REQUEST_SUCCESS, data });
});
dispatch(fetchTrackedFunctions());
},
err => {
const parsedErrorMsg = err;
parsedErrorMsg.message = JSON.parse(err.message);
dispatch({ type: UPDATE_MIGRATION_STATUS_ERROR, data: err });
dispatch(
showErrorNotification(
'SQL execution failed!',
'Something is wrong. Received an invalid response json.',
parsedErrorMsg
)
);
dispatch({
type: REQUEST_ERROR,
data: 'Something is wrong. Received an invalid response json.',
});
console.err('RunSQL error: ', err);
}
);
return;
}
response.json().then(
errorMsg => {
const title = 'SQL Execution Failed';
dispatch({ type: UPDATE_MIGRATION_STATUS_ERROR, data: errorMsg });
dispatch({ type: REQUEST_ERROR, data: errorMsg });
if (isMigration) {
dispatch(handleMigrationErrors(title, errorMsg));
} else {
dispatch(showErrorNotification(title, errorMsg.code, errorMsg));
}
},
() => {
dispatch(
showErrorNotification(
'SQL execution failed!',
'Something is wrong. Please check your configuration again'
)
);
dispatch({
type: REQUEST_ERROR,
data: 'Something is wrong. Please check your configuration again',
});
return dispatch(requestAction(url, options))
.then(
data => {
if (isMigration) {
dispatch(loadMigrationStatus());
}
);
},
error => {
console.error(error);
dispatch(showSuccessNotification('SQL executed!'));
dispatch(fetchDataInit()).then(() => {
dispatch({ type: REQUEST_SUCCESS, data });
});
dispatch(fetchTrackedFunctions());
},
err => {
const title = 'SQL Execution Failed';
dispatch({ type: UPDATE_MIGRATION_STATUS_ERROR, data: err });
dispatch({ type: REQUEST_ERROR, data: err });
if (isMigration) {
dispatch(handleMigrationErrors(title, err));
} else {
dispatch(showErrorNotification(title, err.code, err));
}
}
)
.catch(errorMsg => {
const parsedErrorMsg = errorMsg;
parsedErrorMsg.message = JSON.parse(errorMsg.message);
dispatch({ type: UPDATE_MIGRATION_STATUS_ERROR, data: errorMsg });
dispatch(
showErrorNotification(
'SQL execution failed',
'Cannot connect to server'
'SQL execution failed!',
'Something is wrong. Received an invalid response json.',
parsedErrorMsg
)
);
dispatch({ type: REQUEST_ERROR, data: 'server-connection-failed' });
}
);
dispatch({
type: REQUEST_ERROR,
data: 'Something is wrong. Received an invalid response json.',
});
console.err('RunSQL error: ', errorMsg);
});
};
const rawSQLReducer = (state = defaultState, action) => {

View File

@ -1,4 +1,4 @@
import React, { useEffect, useRef, useState } from 'react';
import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import Helmet from 'react-helmet';
import AceEditor from 'react-ace';
@ -34,7 +34,7 @@ import {
} from '../../../Common/AceEditor/utils';
import { CLI_CONSOLE_MODE } from '../../../../constants';
import NotesSection from './molecules/NotesSection';
import styles from '../../../Common/TableCommon/Table.scss';
/**
* # RawSQL React FC
* ## renders raw SQL page on route `/data/sql`
@ -73,41 +73,35 @@ const RawSQL = ({
migrationMode,
allSchemas,
}) => {
const styles = require('../../../Common/TableCommon/Table.scss');
// local storage key for SQL
const LS_RAW_SQL_SQL = 'rawSql:sql';
/* hooks */
// set up sqlRef to use in unmount
const sqlRef = useRef(sql);
const [statementTimeout, setStatementTimeout] = useState(
Number(getLocalStorageItem(LS_RAW_SQL_STATEMENT_TIMEOUT)) || 10
);
const [sqlText, onChangeSQLText] = useState(sql);
useEffect(() => {
if (!sql) {
const sqlFromLocalStorage = localStorage.getItem(LS_RAW_SQL_SQL);
if (sqlFromLocalStorage) {
dispatch({ type: SET_SQL, data: sqlFromLocalStorage });
onChangeSQLText(sqlFromLocalStorage);
}
}
return () => {
localStorage.setItem(LS_RAW_SQL_SQL, sqlRef.current);
localStorage.setItem(LS_RAW_SQL_SQL, sqlText);
};
}, [dispatch, sql]);
// set SQL to sqlRef
useEffect(() => {
sqlRef.current = sql;
}, [sql]);
/* hooks - end */
}, [dispatch, sql, sqlText]);
const submitSQL = () => {
if (!sqlText) {
localStorage.setItem(LS_RAW_SQL_SQL, '');
return;
}
// set SQL to LS
localStorage.setItem(LS_RAW_SQL_SQL, sql);
localStorage.setItem(LS_RAW_SQL_SQL, sqlText);
// check migration mode global
if (migrationMode) {
@ -120,21 +114,15 @@ const RawSQL = ({
}
if (!isMigration && globals.consoleMode === CLI_CONSOLE_MODE) {
// if migration is not checked, check if is schema modification
if (checkSchemaModification(sql)) {
if (checkSchemaModification(sqlText)) {
dispatch(modalOpen());
const confirmation = false;
if (confirmation) {
dispatch(executeSQL(isMigration, migrationName, statementTimeout));
}
} else {
dispatch(executeSQL(isMigration, migrationName, statementTimeout));
return;
}
} else {
dispatch(executeSQL(isMigration, migrationName, statementTimeout));
}
} else {
dispatch(executeSQL(false, '', statementTimeout));
dispatch(executeSQL(isMigration, migrationName, statementTimeout));
return;
}
dispatch(executeSQL(false, '', statementTimeout));
};
const getMigrationWarningModal = () => {
@ -171,6 +159,7 @@ const RawSQL = ({
const getSQLSection = () => {
const handleSQLChange = val => {
onChangeSQLText(val);
dispatch({ type: SET_SQL, data: val });
// set migration checkbox true
@ -189,21 +178,19 @@ const RawSQL = ({
return [schema.table_schema, schema.table_name].join('.');
});
for (let i = 0; i < objects.length; i++) {
const object = objects[i];
allObjectsTrackable = objects.every(object => {
if (object.type === 'function') {
allObjectsTrackable = false;
break;
} else {
const objectName = [object.schema, object.name].join('.');
if (trackedObjectNames.includes(objectName)) {
allObjectsTrackable = false;
break;
}
return false;
}
}
const objectName = [object.schema, object.name].join('.');
if (trackedObjectNames.includes(objectName)) {
return false;
}
return true;
});
if (allObjectsTrackable) {
dispatch({ type: SET_TRACK_TABLE_CHECKED, data: true });
@ -223,7 +210,7 @@ const RawSQL = ({
theme={ACE_EDITOR_THEME}
fontSize={ACE_EDITOR_FONT_SIZE}
name="raw_sql"
value={sql}
value={sqlText}
minLines={15}
maxLines={100}
width="100%"
@ -233,7 +220,9 @@ const RawSQL = ({
name: 'submit',
bindKey: { win: 'Ctrl-Enter', mac: 'Command-Enter' },
exec: () => {
submitSQL();
if (sqlText) {
submitSQL();
}
},
},
]}
@ -475,6 +464,7 @@ const RawSQL = ({
color="yellow"
size="sm"
data-test="run-sql"
disabled={!sqlText.length}
>
Run!
</Button>

View File

@ -532,9 +532,12 @@ class Schema extends Component {
};
const heading = getSectionHeading(
'Untracked foreign-key relations',
'Untracked foreign-key relationships',
'Relationships inferred via foreign-keys that are not exposed over the GraphQL API',
getTrackAllBtn()
<>
<KnowMoreLink href="https://hasura.io/docs/1.0/graphql/manual/schema/table-relationships/index.html" />
<span className={styles.add_mar_left}>{getTrackAllBtn()}</span>
</>
);
return (
@ -621,14 +624,14 @@ class Schema extends Component {
const heading = getSectionHeading(
'Untracked custom functions',
'Custom functions that are not exposed over the GraphQL API',
<KnowMoreLink href="https://hasura.io/docs/1.0/graphql/manual/queries/custom-functions.html" />
<KnowMoreLink href="https://hasura.io/docs/1.0/graphql/manual/schema/custom-functions.html" />
);
return (
<div className={styles.add_mar_top} key={'custom-functions-content'}>
<CollapsibleToggle
title={heading}
isOpen={!noTrackableFunctions}
isOpen
testId={'toggle-trackable-functions'}
>
<div className={`${styles.padd_left_remove} col-xs-12`}>
@ -689,7 +692,7 @@ class Schema extends Component {
className={styles.add_mar_top}
key={'non-trackable-custom-functions'}
>
<CollapsibleToggle title={heading} isOpen={false}>
<CollapsibleToggle title={heading} isOpen>
<div className={`${styles.padd_left_remove} col-xs-12`}>
{getNonTrackableFuncList()}
</div>

View File

@ -464,7 +464,10 @@ const ViewRows = ({
manualTriggersButton = getManualTriggersButton();
return (
<div key={rowIndex} className={styles.tableCellCenterAligned}>
<div
key={rowIndex}
className={`${styles.tableCenterContent} ${styles.overflowUnset}`}
>
{cloneButton}
{editButton}
{deleteButton}
@ -923,7 +926,7 @@ const ViewRows = ({
return (
<DragFoldTable
className="-highlight -fit-content"
className="dataTable -highlight -fit-content"
data={_gridRows}
columns={_gridHeadings}
headerTitle={'Click to sort / Drag to rearrange'}

View File

@ -2,7 +2,7 @@ import React, { useState } from 'react';
import { showErrorNotification } from '../../Common/Notification';
import gqlPattern, { gqlColumnErrorNotif } from '../Common/GraphQLValidation';
import { commonDataTypes } from '../utils';
import ExpandableEditor from '../../../Common/Layout/ExpandableEditor/Editor';
import CustomInputAutoSuggest from '../../../Common/CustomInputAutoSuggest/CustomInputAutoSuggest';
import {
@ -11,7 +11,6 @@ import {
inferDefaultValues,
} from '../Common/utils';
import Button from '../../../Common/Button/Button';
import { addColSql } from '../TableModify/ModifyActions';
import styles from './ModifyTable.scss';
@ -38,9 +37,7 @@ const useColumnEditor = (dispatch, tableName) => {
colDependentSQLGenerator,
} = columnState;
const onSubmit = e => {
e.preventDefault();
const onSubmit = () => {
// auto-trim column name
const trimmedColName = colName.trim();
@ -254,19 +251,6 @@ const ColumnCreator = ({
);
};
const getSubmitButton = () => {
return (
<Button
type="submit"
color="yellow"
size="sm"
data-test="add-column-button"
>
+ Add column
</Button>
);
};
const getFrequentlyUsedColumnSelector = () => {
return (
<FrequentlyUsedColumnSelector
@ -276,25 +260,32 @@ const ColumnCreator = ({
);
};
return (
<div className={styles.activeEdit}>
<form
className={`form-inline ${styles.display_flex}`}
onSubmit={onSubmit}
>
const expandedContent = () => (
<div>
<form className={`form-inline ${styles.display_flex}`}>
{getColumnNameInput()}
{getColumnTypeInput()}
{getColumnNullableInput()}
{getColumnUniqueInput()}
{getColumnDefaultInput()}
{getSubmitButton()}
</form>
<div className={styles.add_mar_top}>
<div className={styles.add_mar_top_small}>
{getFrequentlyUsedColumnSelector()}
</div>
</div>
);
return (
<ExpandableEditor
key={'new-col'}
editorExpanded={expandedContent}
property={'add-new-column'}
service={'modify-table'}
expandButtonText={'Add a new column'}
saveFunc={onSubmit}
isCollapsable
/>
);
};
export default ColumnCreator;

View File

@ -70,7 +70,6 @@ const ColumnEditorList = ({
}
return col.data_type;
};
const getType = () =>
isArrayDataType ? col.udt_name.replace('_', '') + '[]' : col.udt_name;
@ -107,7 +106,6 @@ const ColumnEditorList = ({
dispatch(deleteColumnSql(col, tableSchema));
}
};
const gqlCompatibilityWarning = () => {
return (
<GqlCompatibilityWarning
@ -121,7 +119,6 @@ const ColumnEditorList = ({
const propertiesDisplay = [];
const propertiesList = [];
propertiesList.push(columnProperties.display_type_name);
if (columnProperties.pkConstraint) {

View File

@ -243,8 +243,6 @@ class ModifyTable extends React.Component {
columnDefaultFunctions={columnDefaultFunctions}
customColumnNames={getTableCustomColumnNames(table)}
/>
<hr />
<h4 className={styles.subheading_text}>Add a new column</h4>
<ColumnCreator
dispatch={dispatch}
tableName={tableName}

View File

@ -1,4 +1,4 @@
import React from 'react';
import React, { useEffect } from 'react';
import {
DELETE_PK_WARNING,
setPrimaryKeys,
@ -95,6 +95,8 @@ const PrimaryKeyEditor = ({
);
};
useEffect(setPkEditState, [columns]);
// remove
const onRemove = () => {
if (pkConstraintName) {

View File

@ -68,7 +68,7 @@ const AddManualRelationship = ({
return (
<div key="add_manual_relationship">
<div className={styles.add_mar_bottom}>
<label> Add a new relationship manually </label>
Add a new relationship <b>manually</b>
</div>
<ExpandableEditor
editorExpanded={expandedContent}

View File

@ -17,6 +17,8 @@ import gqlPattern, { gqlRelErrorNotif } from '../Common/GraphQLValidation';
import { getRelDef, getObjArrRelList } from './utils';
import Button from '../../../Common/Button/Button';
import ToolTip from '../../../Common/Tooltip/Tooltip';
import KnowMoreLink from '../../../Common/KnowMoreLink/KnowMoreLink';
import AddManualRelationship from './AddManualRelationship';
import RemoteRelationships from './RemoteRelationships/RemoteRelationships';
import suggestedRelationshipsRaw from './autoRelations';
@ -141,10 +143,9 @@ const AddRelationship = ({
suggestedRelationshipsData.arrayRel.length < 1
) {
return (
<div className={`${styles.remove_margin_bottom} form-group`}>
<label>
You have no new relationships that can be added via foreign-keys
</label>
<div className={styles.add_mar_bottom}>
You have <b>no new relationships</b> that can be added{' '}
<b>via foreign-keys</b>
</div>
);
}
@ -250,7 +251,7 @@ const AddRelationship = ({
return (
<div>
<div>
<label> Add new relationships via foreign-keys </label>
Add new relationships <b>via foreign-keys</b>
</div>
<div className={tableStyles.tableContainer}>
<table
@ -428,7 +429,6 @@ const Relationships = ({
cachedRelationshipData={relAdd}
dispatch={dispatch}
/>
<hr />
<AddManualRelationship
tableSchema={tableSchema}
allSchemas={allSchemas}
@ -471,13 +471,16 @@ const Relationships = ({
<div
className={`${styles.padd_left_remove} col-xs-10 col-md-10 ${styles.add_mar_bottom}`}
>
<h4 className={styles.subheading_text}>Table Relationships</h4>
<h4 className={styles.subheading_text}>
Table Relationships
<ToolTip message={'Relationships to tables / views'} />
&nbsp;
<KnowMoreLink href="https://hasura.io/docs/1.0/graphql/manual/schema/table-relationships/index.html" />
</h4>
{addedRelationshipsView}
<br />
{getAddRelSection()}
</div>
<div className={`${styles.padd_left_remove} col-xs-10 col-md-10`}>
<h4 className={styles.subheading_text}>Remote Relationships</h4>
<RemoteRelationships
relationships={existingRemoteRelationships}
reduxDispatch={dispatch}

View File

@ -9,6 +9,8 @@ import RelationshipEditor from './RelationshipEditor';
import { NotFoundError } from '../../../Error/PageNotFound';
import RemoteRelationships from './RemoteRelationships/RemoteRelationships';
import { fetchRemoteSchemas } from '../../RemoteSchema/Actions';
import ToolTip from '../../../Common/Tooltip/Tooltip';
import KnowMoreLink from '../../../Common/KnowMoreLink/KnowMoreLink';
class RelationshipsView extends Component {
componentDidMount() {
@ -138,7 +140,6 @@ class RelationshipsView extends Component {
const remoteRelationshipsSection = () => {
return (
<div className={`${styles.padd_left_remove} col-xs-10 col-md-10`}>
<h4 className={styles.subheading_text}>Remote Relationships</h4>
<RemoteRelationships
relationships={tableSchema.remote_relationships}
reduxDispatch={dispatch}
@ -160,18 +161,25 @@ class RelationshipsView extends Component {
/>
<br />
<div className={`${styles.padd_left_remove} container-fluid`}>
<div className={`${styles.padd_left_remove} col-xs-10 col-md-10`}>
<h4 className={styles.subheading_text}>Relationships</h4>
<div
className={`${styles.padd_left_remove} ${styles.add_mar_bottom} col-xs-10 col-md-10`}
>
<h4 className={styles.subheading_text}>
Table Relationships
<ToolTip message={'Relationships to tables / views'} />
&nbsp;
<KnowMoreLink href="https://hasura.io/docs/1.0/graphql/manual/schema/table-relationships/index.html" />
</h4>
{addedRelationshipsView}
<br />
<AddManualRelationship
tableSchema={tableSchema}
allSchemas={allSchemas}
schemaList={schemaList}
relAdd={manualRelAdd}
dispatch={dispatch}
/>
<hr />
<div className={styles.activeEdit}>
<AddManualRelationship
tableSchema={tableSchema}
allSchemas={allSchemas}
schemaList={schemaList}
relAdd={manualRelAdd}
dispatch={dispatch}
/>
</div>
</div>
{remoteRelationshipsSection()}
</div>

View File

@ -4,6 +4,8 @@ import { RemoteRelationshipServer } from './utils';
import RemoteRelationshipList from './components/RemoteRelationshipList';
import { fetchRemoteSchemas } from '../../../RemoteSchema/Actions';
import { Table } from '../../../../Common/utils/pgUtils';
import ToolTip from '../../../../Common/Tooltip/Tooltip';
import KnowMoreLink from '../../../../Common/KnowMoreLink/KnowMoreLink';
import { Dispatch } from '../../../../../types';
type Props = {
@ -24,17 +26,22 @@ const RemoteRelationships: React.FC<Props> = ({
}, []);
return (
<div>
<div className={styles.add_mar_bottom}>
Relationships to remote schemas
<>
<h4 className={styles.subheading_text}>
Remote Schema Relationships
<ToolTip message="Relationships to remote schemas" />
&nbsp;
<KnowMoreLink href="https://hasura.io/docs/1.0/graphql/manual/schema/remote-relationships/remote-schema-relationships.html" />
</h4>
<div className={styles.activeEdit}>
<RemoteRelationshipList
relationships={relationships}
table={table}
remoteSchemas={remoteSchemas}
reduxDispatch={reduxDispatch}
/>
</div>
<RemoteRelationshipList
relationships={relationships}
table={table}
remoteSchemas={remoteSchemas}
reduxDispatch={reduxDispatch}
/>
</div>
</>
);
};

View File

@ -1,4 +1,5 @@
import React from 'react';
import { ThunkAction } from 'redux-thunk';
import globals from '../../../../../../Globals';
import { useIntrospectionSchemaRemote } from '../../../../RemoteSchema/graphqlUtils';
import {
@ -12,6 +13,7 @@ import { LoadingSkeleton, NoRemoteSchemaPlaceholder } from './PlaceHolder';
import ArgElement from './ArgElement';
import FieldElement from './FieldElement';
import styles from '../SchemaExplorer.scss';
import { ReduxState, ReduxAction } from '../../../../../../types';
type Props = {
relationship: RemoteRelationship;
@ -21,6 +23,7 @@ type Props = {
handleArgValueChange: (a: TreeArgElement, value: string) => void;
remoteSchemaName: string;
columns: string[];
reduxDispatch: ThunkAction<void, ReduxState, unknown, ReduxAction>;
};
const Explorer: React.FC<Props> = ({
@ -31,12 +34,14 @@ const Explorer: React.FC<Props> = ({
handleArgValueKindChange,
remoteSchemaName,
columns,
reduxDispatch,
}) => {
const { loading, error, schema, introspect } = useIntrospectionSchemaRemote(
remoteSchemaName,
{
'x-hasura-admin-secret': globals.adminSecret,
}
},
reduxDispatch
);
if (!remoteSchemaName) {

View File

@ -1,4 +1,5 @@
import React from 'react';
import { ThunkAction } from 'redux-thunk';
import {
RemoteRelationship,
TreeArgElement,
@ -23,6 +24,7 @@ import {
} from '../Tooltips';
import Explorer from './Explorer';
import { Table } from '../../../../../Common/utils/pgUtils';
import { ReduxState, ReduxAction } from '../../../../../../types';
type Props = {
table: Table;
@ -30,6 +32,7 @@ type Props = {
isLast: boolean;
state: RemoteRelationship;
dispatch: React.Dispatch<RemoteRelAction>;
reduxDispatch: ThunkAction<void, ReduxState, unknown, ReduxAction>;
};
const RemoteRelEditor: React.FC<Props> = ({
@ -38,6 +41,7 @@ const RemoteRelEditor: React.FC<Props> = ({
remoteSchemas,
state,
dispatch,
reduxDispatch,
}) => {
const handleNameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
e.persist();
@ -149,6 +153,7 @@ const RemoteRelEditor: React.FC<Props> = ({
handleArgValueChange={handleArgValueChange}
remoteSchemaName={state.remoteSchema}
columns={tableColumns}
reduxDispatch={reduxDispatch}
/>
</div>
</div>

View File

@ -32,6 +32,7 @@ const EditorWrapper: React.FC<Props> = ({
isLast={isLast}
state={state}
dispatch={dispatch}
reduxDispatch={reduxDispatch}
/>
);
@ -49,7 +50,7 @@ const EditorWrapper: React.FC<Props> = ({
}
: null;
const expandButtonText = isLast ? 'Add a remote relationship' : 'Edit';
const expandButtonText = isLast ? 'Add a remote schema relationship' : 'Edit';
const collapseButtonText = isLast ? 'Cancel' : 'Close';
return (
<ExpandableEditor

View File

@ -22,6 +22,19 @@ interface Props {
}
const STContainer: React.FC<Props> = ({ children, tabName }) => {
let activeTab = tabName as string;
if (tabName === 'processed') {
activeTab = 'Processed';
} else if (tabName === 'pending') {
activeTab = 'Pending';
} else if (tabName === 'add') {
activeTab = 'Create';
} else if (tabName === 'logs') {
activeTab = 'Invocation Logs';
} else if (tabName === 'info') {
activeTab = 'Info';
}
const breadCrumbs = [
{
title: 'Events',
@ -32,7 +45,7 @@ const STContainer: React.FC<Props> = ({ children, tabName }) => {
url: getAdhocEventsRoute(),
},
{
title: tabName,
title: activeTab,
url: '',
},
];

View File

@ -37,7 +37,7 @@ const Info: React.FC = () => {
<div className={styles.padd_left}>
<TopicDescription
title="What are Scheduled events?"
imgUrl={`${globals.assetsPath}/common/img/event-trigger.png`}
imgUrl={`${globals.assetsPath}/common/img/scheduled-event.png`}
imgAlt={ADHOC_EVENTS_HEADING}
description={topicDescription}
/>

View File

@ -66,7 +66,7 @@ const Form: React.FC<Props> = props => {
</FormSection>
<FormSection
id="trigger-schedule"
tooltip="Schedule for your cron"
tooltip="Schedule for your cron (events are created based on the UTC timezone)"
heading="Cron Schedule"
>
<div className={`${styles.add_mar_bottom_mid} ${styles.display_flex}`}>

View File

@ -175,6 +175,7 @@ const InvocationLogsTable: React.FC<Props> = props => {
return (
<ReactTable
className="-highlight"
data={rowsFormatted}
columns={gridHeadings}
minRows={0}

View File

@ -14,24 +14,20 @@ type Props = {
};
const RedeliverEvent: React.FC<Props> = ({ dispatch, eventId }) => {
// const [loading, setLoading] = React.useState(false);
const [error, setError] = React.useState<any>(null);
const [error, setError] = React.useState<null | Error>(null);
const [logs, setLogs] = React.useState<InvocationLog[]>([]);
React.useEffect(() => {
const intervalId = setInterval(() => {
// setLoading(true);
dispatch(
getEventLogs(
eventId,
'data',
l => {
setLogs(l);
// setLoading(false);
},
e => {
setError(e);
// setLoading(false);
}
)
);
@ -68,7 +64,7 @@ const RedeliverEvent: React.FC<Props> = ({ dispatch, eventId }) => {
name="event_payload"
value={JSON.stringify(latestLog.request, null, 4)}
minLines={10}
maxLines={10}
maxLines={30}
width="100%"
showPrintMargin={false}
showGutter={false}
@ -81,39 +77,25 @@ const RedeliverEvent: React.FC<Props> = ({ dispatch, eventId }) => {
Latest Invocation Response
</div>
</div>
{error === null ? (
<AceEditor
mode="json"
theme="github"
name="event_payload"
value={JSON.stringify(latestLog.response, null, 4)}
minLines={10}
maxLines={10}
width="100%"
showPrintMargin={false}
showGutter={false}
style={{
backgroundColor: '#fdf9ed',
marginTop: '10px',
}}
/>
) : (
<AceEditor
mode="json"
theme="github"
name="event_payload"
value={JSON.stringify(error, null, 4)}
minLines={8}
maxLines={10}
width="100%"
showPrintMargin={false}
showGutter={false}
style={{
backgroundColor: '#fdf9ed',
marginTop: '10px',
}}
/>
)}
<AceEditor
mode="json"
theme="github"
name="event_payload"
value={JSON.stringify(
error === null ? latestLog.response : error,
null,
4
)}
minLines={8}
maxLines={30}
width="100%"
showPrintMargin={false}
showGutter={false}
style={{
backgroundColor: '#fdf9ed',
marginTop: '10px',
}}
/>
</div>
</div>
<div

View File

@ -7,36 +7,26 @@ import Skull from '../../../../Common/Icons/Invalid';
export const getEventStatusIcon = (status: string) => {
switch (status) {
case 'scheduled':
return <ClockIcon className="" title="This event has been scheduled" />;
break;
return <ClockIcon title="This event has been scheduled" />;
case 'dead':
return (
<Skull
className=""
title="This event is dead and will never be delivered"
/>
);
break;
return <Skull title="This event is dead and will never be delivered" />;
case 'delivered':
return <CheckIcon className="" title="This event has been delivered" />;
break;
return <CheckIcon title="This event has been delivered" />;
case 'error':
return <CrossIcon className="" title="This event failed with an error" />;
break;
return <CrossIcon title="This event failed with an error" />;
default:
return null;
break;
}
};
export const getEventDeliveryIcon = (delivered: boolean) => {
return delivered ? (
<CheckIcon className="" title="This event has been delivered" />
<CheckIcon title="This event has been delivered" />
) : (
<CrossIcon className="" title="This event has not been delivered" />
<CrossIcon title="This event has not been delivered" />
);
};
export const getInvocationLogStatus = (status: number) => {
return status < 300 ? <CheckIcon className="" /> : <CrossIcon className="" />;
return status < 300 ? <CheckIcon /> : <CrossIcon />;
};

View File

@ -73,13 +73,24 @@ const STContainer: React.FC<Props> = ({
throw new NotFoundError();
}
let activeTab = tabName as string;
if (tabName === 'processed') {
activeTab = 'Processed';
} else if (tabName === 'pending') {
activeTab = 'Pending';
} else if (tabName === 'modify') {
activeTab = 'Modify';
} else if (tabName === 'logs') {
activeTab = 'Invocation Logs';
}
const breadCrumbs = [
{
title: 'Events',
url: getDataEventsLandingRoute(),
},
{
title: 'Scheduled',
title: 'Cron Triggers',
url: getScheduledEventsLandingRoute(),
},
{
@ -87,7 +98,7 @@ const STContainer: React.FC<Props> = ({
url: tabInfo[tabName].getRoute(triggerName),
},
{
title: tabName,
title: activeTab,
url: '',
},
];

View File

@ -50,7 +50,7 @@ const Landing: React.FC<Props> = props => {
<div>
<TopicDescription
title="What are Cron Triggers?"
imgUrl={`${globals.assetsPath}/common/img/event-trigger.png`}
imgUrl={`${globals.assetsPath}/common/img/cron-trigger.png`}
imgAlt={CRON_TRIGGER}
description={topicDescription}
/>

View File

@ -46,7 +46,7 @@ export const parseServerScheduledTrigger = (
name: trigger.name,
webhook: trigger.webhook_conf,
schedule: trigger.cron_schedule,
payload: JSON.stringify(trigger.payload),
payload: JSON.stringify(trigger.payload, null, 2),
headers: parseServerHeaders(trigger.header_conf),
loading: {
modify: false,

View File

@ -32,10 +32,11 @@ insert_user(objects: [{name: "testuser"}] ){
return (
<div>
<TopicDescription
title="What are Data Triggers?"
title={`What are ${EVENT_TRIGGER}s?`}
imgUrl={`${globals.assetsPath}/common/img/event-trigger.png`}
imgAlt="Data Triggers"
description="A Data Trigger atomically captures events (insert, update, delete) on a specified table and then reliably calls a HTTP webhook to run some custom business logic."
imgAlt={`${EVENT_TRIGGER}s`}
description={`An ${EVENT_TRIGGER} atomically captures events (insert, update, delete) on a specified table and then reliably calls a HTTP webhook to run some custom business logic.`}
knowMoreHref="https://hasura.io/docs/1.0/graphql/manual/event-triggers/index.html"
/>
<hr className={styles.clear_fix} />
</div>

View File

@ -36,11 +36,7 @@ const TableHeader = ({ triggerName, tabName, count, readOnlyMode }) => {
url: '',
},
{
title: 'Manage',
url: getDataEventsLandingRoute(),
},
{
title: 'Data Events',
title: 'Data Triggers',
url: getDataEventsLandingRoute(),
},
{

View File

@ -50,7 +50,6 @@
width: 15px;
}
.invocationsSection {
li {
display: block;
padding: 5px 10px;
@ -144,6 +143,7 @@
.redeliverModal {
width: 75%;
margin-bottom: 0;
}
.redeliverEventSection {
@ -199,10 +199,6 @@
clear: both;
}
.redeliverModal {
width: 75%;
}
.redeliverEventSection {
clear: both;
margin: 0;

View File

@ -409,9 +409,6 @@ export const modifyEventTrigger = (
columns: state.operationColumns
.filter(c => !!c.enabled)
.map(c => c.name),
payload: state.operationColumns
.filter(c => !!c.enabled)
.map(c => c.name),
}
: null,
delete: state.operations.delete ? { columns: '*' } : null,

View File

@ -18,6 +18,18 @@ interface Props {
const LeftSidebar: React.FC<Props> = props => {
const { triggers, currentTrigger, service } = props;
const getSidebarIcon = () => {
switch (service) {
case 'cron':
return 'fa-calendar';
case 'data':
return 'fa-database';
default:
return 'fa-wrench';
}
};
const {
getChildList: getTriggersList,
getSearchInput,
@ -38,7 +50,8 @@ const LeftSidebar: React.FC<Props> = props => {
},
items: triggers,
currentItem: currentTrigger,
service,
service: 'triggers',
sidebarIcon: getSidebarIcon(),
});
// TODO, move to common utils

View File

@ -14,8 +14,8 @@ export const EVENTS_SERVICE_HEADING = 'Events';
export const ADHOC_EVENTS_HEADING = 'One-off Scheduled Events';
export const CRON_EVENTS_HEADING = 'Cron Triggers';
export const CRON_TRIGGER = 'Cron Trigger';
export const EVENT_TRIGGER = 'Data Trigger';
export const DATA_EVENTS_HEADING = 'Data Triggers';
export const EVENT_TRIGGER = 'Event Trigger';
export const DATA_EVENTS_HEADING = 'Event Triggers';
export const getSubserviceHeadings = (subservice: EventKind) => {
switch (subservice) {
case 'data':

View File

@ -57,6 +57,7 @@ class RemoteSchema extends React.Component {
imgUrl={`${globals.assetsPath}/common/img/remote_schema.png`}
imgAlt="Remote Schema"
description="Remote schemas are external GraphQL services which can be merged with Hasura to provide a unified GraphQL API. Think of it like automated schema stitching. All you need to do is build a GraphQL service and then provide its HTTP endpoint to Hasura. Your GraphQL service can be written in any language or framework."
knowMoreHref="https://hasura.io/docs/1.0/graphql/manual/remote-schemas/index.html"
/>
<hr className={styles.clear_fix} />

View File

@ -2,6 +2,7 @@ import { useEffect, useState } from 'react';
import endpoints from '../../../Endpoints';
import { getRemoteSchemaIntrospectionQuery } from '../../Common/utils/v1QueryUtils';
import { buildClientSchema, isWrappingType, isObjectType } from 'graphql';
import requestAction from '../../../utils/requestAction';
// local cache where introspection schema is cached
let introspectionSchemaCache = {};
@ -14,7 +15,11 @@ export const clearIntrospectionSchemaCache = remoteSchemaName => {
};
// custom hook for introspecting remote schema
export const useIntrospectionSchemaRemote = (remoteSchemaName, headers) => {
export const useIntrospectionSchemaRemote = (
remoteSchemaName,
headers,
dispatch
) => {
const [schema, setSchema] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
@ -33,14 +38,24 @@ export const useIntrospectionSchemaRemote = (remoteSchemaName, headers) => {
// perform introspection
setLoading(true);
setError(null);
fetch(endpoints.query, {
method: 'POST',
headers: {
...headers,
},
body: JSON.stringify(getRemoteSchemaIntrospectionQuery(remoteSchemaName)),
})
.then(r => r.json())
dispatch(
requestAction(
endpoints.query,
{
method: 'POST',
headers: {
...headers,
},
body: JSON.stringify(
getRemoteSchemaIntrospectionQuery(remoteSchemaName)
),
},
undefined,
undefined,
true,
true
)
)
.then(response => {
const clientSchema = buildClientSchema(response.data);
setSchema(clientSchema);
@ -54,7 +69,7 @@ export const useIntrospectionSchemaRemote = (remoteSchemaName, headers) => {
});
};
useEffect(introspectSchema, [remoteSchemaName]);
useEffect(introspectSchema, [remoteSchemaName, dispatch]);
return {
schema,

View File

@ -14,22 +14,25 @@ export const readFile = (file, callback) => {
reader.readAsText(file);
};
const getQueryFragments = queryDef => {
const fragments = [];
function recurQueryDef(queryDef, fragments, definitionHash) {
visit(queryDef, {
FragmentSpread(node) {
fragments.push(node.name.value);
fragments.add(node.name.value);
recurQueryDef(definitionHash[node.name.value], fragments, definitionHash);
},
});
}
return fragments;
const getQueryFragments = (queryDef, definitionHash = {}) => {
const fragments = new Set();
recurQueryDef(queryDef, fragments, definitionHash);
return [...fragments];
};
const getQueryString = (queryDef, fragmentDefs) => {
const getQueryString = (queryDef, fragmentDefs, definitionHash = {}) => {
let queryString = print(queryDef);
const queryFragments = getQueryFragments(queryDef);
const queryFragments = getQueryFragments(queryDef, definitionHash);
queryFragments.forEach(qf => {
const fragmentDef = fragmentDefs.find(fd => fd.name.value === qf);
@ -53,6 +56,14 @@ export const parseQueryString = queryString => {
throw new Error('Parsing operation failed');
}
const definitionHash = (parsedQueryString.definitions || []).reduce(
(defObj, queryObj) => {
defObj[queryObj.name.value] = queryObj;
return defObj;
},
{}
);
const queryDefs = parsedQueryString.definitions.filter(
def => def.kind === 'OperationDefinition'
);
@ -68,7 +79,7 @@ export const parseQueryString = queryString => {
const query = {
name: queryDef.name.value,
query: getQueryString(queryDef, fragmentDefs),
query: getQueryString(queryDef, fragmentDefs, definitionHash),
};
queries.push(query);

View File

@ -27,6 +27,8 @@ class ImportMetadata extends Component {
this.setState({ isImporting: false });
};
this.setState({ isImporting: true });
dispatch(replaceMetadataFromFile(fileContent, successCb, errorCb));
}
@ -38,8 +40,6 @@ class ImportMetadata extends Component {
const handleImport = e => {
e.preventDefault();
this.setState({ isImporting: true });
uploadFile(this.importMetadata, 'json');
};

View File

@ -1,20 +1,28 @@
import React, { Component } from 'react';
import { Connect } from 'react-redux';
import { GraphQLVoyager } from 'graphql-voyager';
import fetch from 'isomorphic-fetch';
import Endpoints from '../../../Endpoints';
import '../../../../node_modules/graphql-voyager/dist/voyager.css';
import './voyagerView.css';
import requestAction from '../../../utils/requestAction';
import { Dispatch } from '../../../types';
interface VoyagerViewProps {
headers: Headers;
}
interface StateProps {
headers: Headers;
}
const mapStateToProps = (state: State) => {
return {
headers: state.tables.dataHeaders,
};
};
const mapDispatchToProps = (dispatch: Dispatch) => {
return {
requestAction: (url: string, options: RequestInit) =>
dispatch(requestAction(url, options)),
};
};
// TODO: replace by redux State when it's defined
interface State {
tables: {
@ -22,16 +30,17 @@ interface State {
};
}
type Props = VoyagerViewProps & StateProps;
type Props = VoyagerViewProps &
ReturnType<typeof mapStateToProps> &
ReturnType<typeof mapDispatchToProps>;
class VoyagerView extends Component<Props, State> {
introspectionProvider = (query: string) => {
return fetch(Endpoints.graphQLUrl, {
introspectionProvider = (query: string) =>
this.props.requestAction(Endpoints.graphQLUrl, {
method: 'POST',
headers: this.props.headers,
body: JSON.stringify({ query }),
}).then(response => response.json());
};
});
render() {
return (
@ -44,12 +53,7 @@ class VoyagerView extends Component<Props, State> {
}
const generatedVoyagerConnector = (connect: Connect) => {
const mapStateToProps = (state: State) => {
return {
headers: state.tables.dataHeaders,
};
};
return connect(mapStateToProps)(VoyagerView);
return connect(mapStateToProps, mapDispatchToProps)(VoyagerView);
};
export default generatedVoyagerConnector;

View File

@ -2,4 +2,6 @@ export const SERVER_CONSOLE_MODE = 'server';
export const CLI_CONSOLE_MODE = 'cli';
export const ADMIN_SECRET_HEADER_KEY = 'x-hasura-admin-secret';
export const HASURA_COLLABORATOR_TOKEN = 'hasura-collaborator-token';
export const HASURA_CLIENT_NAME = 'hasura-client-name';
export const REDUX_LOCATION_CHANGE_ACTION_TYPE = '@@router/LOCATION_CHANGE';

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -11,60 +11,48 @@ const filterEventsBlockList = [
];
const DATA_PATH = '/data';
const API_EXPLORER_PATH = '/api-explorer';
const REMOTE_SCHEMAS_PATH = '/remote-schemas';
const EVENTS_PATH = '/events';
const ACTIONS_PATH = '/actions';
const dataHandler = (path: string) => {
return (
DATA_PATH +
path
.replace(/\/schema\/([^/]*)(\/)?/, '/schema/SCHEMA_NAME$2')
.replace(
/(\/schema\/.*)\/tables\/([^/]*)(\/.*)?/,
'$1/tables/TABLE_NAME$3'
)
.replace(/(\/schema\/.*)\/views\/([^/]*)(\/.*)?/, '$1/views/VIEW_NAME$3')
.replace(
/(\/schema\/.*)\/functions\/([^/]*)(\/.*)?/,
'$1/functions/FUNCTION_NAME$3'
)
);
};
const apiExplorerHandler = () => {
return API_EXPLORER_PATH;
return path
.replace(/(\/schema\/)[^/]*(\/)?/, '$1SCHEMA_NAME$2')
.replace(/(\/schema\/.*\/tables\/)[^/]*(\/.*)?/, '$1TABLE_NAME$2')
.replace(/(\/schema\/.*\/views\/)[^/]*(\/.*)?/, '$1VIEW_NAME$2')
.replace(/(\/schema\/.*\/functions\/)[^/]*(\/.*)?/, '$1FUNCTION_NAME$2');
};
const remoteSchemasHandler = (path: string) => {
return (
REMOTE_SCHEMAS_PATH +
path.replace(/(\/manage\/)[^/]*(\/\w+.*)$/, '$1REMOTE_SCHEMA_NAME$2')
);
return path.replace(/(\/manage\/)[^/]*(\/\w+.*)$/, '$1REMOTE_SCHEMA_NAME$2');
};
const eventsHandler = (path: string) => {
return (
EVENTS_PATH +
path.replace(/(\/manage\/triggers\/)[^/]*(\/\w+.*)$/, '$1TRIGGER_NAME$2')
);
return path
.replace(/(\/manage\/triggers\/)[^/]*(\/\w+.*)$/, '$1TRIGGER_NAME$2')
.replace(/(\/data\/)[^/]*\/(.*)+$/, '$1DATA_TRIGGER_NAME/$2')
.replace(/(\/cron\/)[^/]*\/(.*)+$/, '$1CRON_TRIGGER_NAME/$2');
};
const actionsHandler = (path: string) => {
return path.replace(/(\/manage\/)[^/]*\/(.*)+$/, '$1ACTION_NAME/$2');
};
const sanitiseUrl = (rawPath: string) => {
const path = rawPath.replace(new RegExp(globals.urlPrefix, 'g'), '');
if (path.indexOf(DATA_PATH) === 0) {
return dataHandler(path.slice(DATA_PATH.length));
if (path.startsWith(DATA_PATH)) {
return dataHandler(path);
}
if (path.indexOf(API_EXPLORER_PATH) === 0) {
return apiExplorerHandler();
if (path.startsWith(REMOTE_SCHEMAS_PATH)) {
return remoteSchemasHandler(path);
}
if (path.indexOf(REMOTE_SCHEMAS_PATH) === 0) {
return remoteSchemasHandler(path.slice(REMOTE_SCHEMAS_PATH.length));
if (path.startsWith(EVENTS_PATH)) {
return eventsHandler(path);
}
if (path.indexOf(EVENTS_PATH) === 0) {
return eventsHandler(path.slice(EVENTS_PATH.length));
if (path.startsWith(ACTIONS_PATH)) {
return actionsHandler(path);
}
return '/';
return path;
};
export { filterEventsBlockList, sanitiseUrl };

View File

@ -14,7 +14,7 @@ import { globalCookiePolicy } from '../Endpoints';
const requestAction = (
url: string,
options: RequestInit,
options: RequestInit = {},
SUCCESS?: string,
ERROR?: string,
includeCredentials = true,

View File

@ -11,7 +11,7 @@ BUILDVERSION ?= "x.y"
# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -j 4 -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
# the i18n builder cannot share the environment and doctrees with the others
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .

View File

@ -55,7 +55,7 @@ def on_finish_building(app, exception):
for link in indexObjs:
url = ET.SubElement(root, "url")
ET.SubElement(url, "loc").text = base_domain + str(current_version) + "/" + link["url"]
ET.SubElement(url, "loc").text = "https://" + base_domain + "/" + str(current_version) + "/" + link["url"]
ET.SubElement(url, "changefreq").text = "daily"
ET.SubElement(url, "priority").text = "1" if (current_version == latest_version) else "0.5"

View File

@ -1,109 +0,0 @@
import fett
from docutils import statemachine
from docutils.utils.error_reporting import ErrorString
from sphinx.util.compat import Directive
import yaml
# List of tabs ( ID, Display Name)
TABS_RAW = [
('linux', 'Linux'),
('mac', 'Mac'),
('windows', 'Windows'),
]
TABS_IDS = [tab[0] for tab in TABS_RAW]
TABS_DISPLAY = [tab[1] for tab in TABS_RAW]
class GlobalTabsDirective(Directive):
has_content = True
required_arguments = 0
optional_arguments = 0
final_argument_whitespace = True
TABS_TEMPLATE = '''
.. raw:: html
<div class="global-tabs">
<ul class="tab-strip tab-strip--singleton" role="tablist">
{{ for tab in tabs sortTabs }}
<li class="tab-strip__element" data-tabid="{{ tab.id }}" role="tab" aria-selected="{{ if i zero }}true{{ else }}false{{ end }}">{{ tab.name }}</li>
{{ end }}
</ul>
<div class="tabs__content" role="tabpanel">
{{ for tab in tabs sortTabs}}
<div class="tabpanel-{{ tab.id }}">
{{ tab.content }}
.. raw:: html
</div>
{{ end }}
</div>
</div>
'''
def run(self):
contents = '\n'.join(self.content)
try:
data = yaml.safe_load(contents)
except yaml.YAMLError as error:
raise self.severe(u'Error parsing YAML:\n{}.'.format(ErrorString(error)))
raw_template = fett.Template(self.TABS_TEMPLATE)
try:
rendered_template = raw_template.render(data)
except Exception as error:
raise self.severe('Failed to render template: {}'.format(ErrorString(error)))
rendered_lines = statemachine.string2lines(rendered_template, 4, convert_whitespace=1)
self.state_machine.insert_input(rendered_lines, '')
return []
def setup(app):
app.add_directive('global-tabs', GlobalTabsDirective)
return {'parallel_read_safe': True,
'parallel_write_safe': True}
def numberOfTabs(tabData):
return len(TABS_RAW)
fett.Template.FILTERS['numberOfTabs'] = numberOfTabs
def getTabNames(tabData):
for tab in tabData:
index = TABS_IDS.index(tab['id'])
tab['name'] = TABS_DISPLAY[index]
return tabData
fett.Template.FILTERS['getTabNames'] = getTabNames
def sortTabs(tabData):
# Create a list for the sorted data
sorted_tabs = [None] * len(TABS_RAW)
for tab in tabData:
index = TABS_IDS.index(tab['id'])
tab['name'] = TABS_DISPLAY[index]
sorted_tabs[index] = tab
# Fill in any missing tabs with empty content
for index in range(len(sorted_tabs)):
if sorted_tabs[index] is None:
sorted_tabs[index] = {'id': TABS_IDS[index], 'name': TABS_DISPLAY[index], 'content': ''}
return sorted_tabs
fett.Template.FILTERS['sortTabs'] = sortTabs

File diff suppressed because one or more lines are too long

View File

@ -1,68 +0,0 @@
/*Display and hide the appropriate tab content*/
.tab {
display: none;
}
.tab-active {
display: block;
}
/*Clear the padding above the tabs*/
.tab-strip__element {
padding-top: 0;
}
.tab-strip {
list-style: none;
padding-left: 0;
margin-left: 0 !important;
}
/*Styles for inactive & unhovered tabs*/
.tab-strip__element {
background-color: #f4f6f6;
border-radius: 4px 4px 0 0;
border: 0.5px solid #babdbe;
color: #babdbe;
display: table-cell;
font-weight: bold;
height: 34px;
padding: 0 !important;
text-align: center;
vertical-align: middle;
width: 1%;
}
/*Styles for active tabs*/
.tab-strip__element[aria-selected=true],
.tab-strip__element[aria-selected=true]:focus,
.tab-strip__element[aria-selected=true]:hover {
color: #6ba442;
font-weight: bold;
background-color: #fff;
border: 0.5px solid #babdbe;
border-bottom-color: transparent;
}
/*White background on hover*/
.tab-strip__element:hover {
background: #fff;
}
/*Caret styling*/
.tab-strip__element[aria-selected=true] > .caret,
.tab-strip__element[aria-selected=true]:focus > .caret,
.tab-strip__element[aria-selected=true]:hover > .caret {
border-top-color: #6ba442;
border-bottom-color: #6ba442;
}
.tab-strip__element[aria-selected=true] > .caret,
.tab-strip__element[aria-selected=true]:hover > .caret {
border-top-color: #babdbe;
border-bottom-color: #babdbe;
}
.tab-strip [aria-selected]:hover {
cursor: pointer;
}

View File

@ -1,112 +0,0 @@
/**
* Show the appropriate tab content and hide other tab's content
* @param {string} currentAttrValue The currently selected tab ID.
* @returns {void}
*/
function showHideTabContent(currentAttrValue) {
$('.tabs__content').children().
hide();
$(`.global-tabs .tabpanel-${currentAttrValue}`).show();
}
class TabsSingleton {
constructor(key) {
this.key = key;
this.tabStrip = document.querySelector('.tab-strip--singleton');
}
get tabPref() {
return window.localStorage.getItem(this.key);
}
set tabPref(value) {
window.localStorage.setItem(this.key, value);
}
/**
* Return the first singleton tab ID on the page.
* @returns {string} The first singleton tab ID found.
*/
getFirstTab() {
const tabsElement = this.tabStrip.querySelector('.tab-strip__element[aria-selected=true]');
if (!tabsElement) { return null; }
return tabsElement.getAttribute('data-tabid');
}
setup() {
if (!this.tabStrip) { return; }
this.hideTabBars();
for (const element of this.tabStrip.querySelectorAll('[data-tabid]')) {
element.onclick = (e) => {
// Get the tab ID of the clicked tab
const currentAttrValue = e.target.getAttribute('data-tabid');
// Check to make sure value is not null, i.e., don't do anything on "other"
if (currentAttrValue) {
// Save the users preference and re-render
this.tabPref = currentAttrValue;
this.update();
e.preventDefault();
}
};
}
this.update();
}
update() {
if (!this.tabStrip) { return; }
let tabPref = this.tabPref;
if (!tabPref) {
tabPref = this.getFirstTab();
} else if (!this.tabStrip.querySelector(`[data-tabid="${tabPref}"]`)) {
// Confirm a tab for their tabPref exists at the top of the page
tabPref = this.getFirstTab();
}
if (!tabPref) { return; }
// Show the appropriate tab content and mark the tab as active
showHideTabContent(tabPref);
this.showHideSelectedTab(tabPref);
}
/**
* Marks the selected tab as active
* @param {string} currentAttrValue The currently selected tab ID.
* @returns {void}
*/
showHideSelectedTab(currentAttrValue) {
// Get the <a>, <li> and <ul> of the selected tab
const tabLink = $(this.tabStrip.querySelector(`[data-tabid="${currentAttrValue}"]`));
// Set a tab to active
tabLink.
attr('aria-selected', true).
siblings().
attr('aria-selected', false);
}
/**
* Show only the first set of tabs at the top of the page.
* @returns {void}
*/
hideTabBars() {
const tabBars = $('.tab-strip--singleton');
const mainTabBar = tabBars.first();
// Remove any additional tab bars
tabBars.slice(1).
detach();
// Position the main tab bar after the page title
mainTabBar.
detach().
insertAfter('h1').
first();
}
}
(new TabsSingleton('tabPref')).setup();

File diff suppressed because it is too large Load Diff

View File

@ -1,301 +0,0 @@
// Validate the field
var hasError = function (field) {
// Don't validate submits, buttons, file and reset inputs, and disabled fields
if (field.disabled || field.type === 'file' || field.type === 'reset' || field.type === 'submit' || field.type === 'button') return;
// Get validity
var validity = field.validity;
// If valid, return null
if (validity.valid) return;
// If field is required and empty
if (validity.valueMissing) return 'Please fill out this field.';
// If not the right type
if (validity.typeMismatch) {
// Email
if (field.type === 'email') return 'Please enter an email address.';
// URL
if (field.type === 'url') return 'Please enter a URL.';
}
// If too short
if (validity.tooShort) return 'Please lengthen this text to ' + field.getAttribute('minLength') + ' characters or more. You are currently using ' + field.value.length + ' characters.';
// If too long
if (validity.tooLong) return 'Please shorten this text to no more than ' + field.getAttribute('maxLength') + ' characters. You are currently using ' + field.value.length + ' characters.';
// If pattern doesn't match
if (validity.patternMismatch) {
// If pattern info is included, return custom error
if (field.hasAttribute('title')) return field.getAttribute('title');
// Otherwise, generic error
return 'Please match the requested format.';
}
// If number input isn't a number
if (validity.badInput) return 'Please enter a number.';
// If a number value doesn't match the step interval
if (validity.stepMismatch) return 'Please select a valid value.';
// If a number field is over the max
if (validity.rangeOverflow) return 'Please select a value that is no more than ' + field.getAttribute('max') + '.';
// If a number field is below the min
if (validity.rangeUnderflow) return 'Please select a value that is no less than ' + field.getAttribute('min') + '.';
// If all else fails, return a generic catchall error
return 'The value you entered for this field is invalid.';
};
// Show an error message
var showError = function (field, error) {
// Add error class to field
field.classList.add('error');
// If the field is a radio button and part of a group, error all and get the last item in the group
if (field.type === 'radio' && field.name) {
var group = field.form.querySelectorAll('[name="' + field.name + '"]');
if (group.length > 0) {
for (var i = 0; i < group.length; i++) {
group[i].classList.add('error');
}
field = group[group.length - 1];
}
}
// Get field id or name
var id = field.id || field.name;
if (!id) return;
// Check if error message field already exists
// If not, create one
var message = field.form.querySelector('.error-message#error-for-' + id );
if (!message) {
message = document.createElement('div');
message.className = 'error-message';
message.id = 'error-for-' + id;
// If the field is a radio button or checkbox, insert error after the label
var label;
if (field.type === 'radio' || field.type ==='checkbox') {
label = field.form.querySelector('label[for="' + id + '"]') || field.parentNode;
if (label) {
label.parentNode.insertBefore( message, label.nextSibling );
}
}
// Otherwise, insert it after the field
if (!label) {
field.parentNode.insertBefore( message, field.nextSibling );
}
}
// Add ARIA role to the field
field.setAttribute('aria-describedby', 'error-for-' + id);
// Update error message
message.innerHTML = error;
// Show error message
message.style.display = 'block';
message.style.visibility = 'visible';
};
// Remove the error message
var removeError = function (field) {
// Remove error class to field
field.classList.remove('error');
// Remove ARIA role from the field
field.removeAttribute('aria-describedby');
// If the field is a radio button and part of a group, remove error from all and get the last item in the group
if (field.type === 'radio' && field.name) {
var group = field.form.querySelectorAll('[name="' + field.name + '"]');
if (group.length > 0) {
for (var i = 0; i < group.length; i++) {
group[i].classList.remove('error');
}
field = group[group.length - 1];
}
}
// Get field id or name
var id = field.id || field.name;
if (!id) return;
// Check if an error message is in the DOM
var message = field.form.querySelector('.error-message#error-for-' + id + '');
if (!message) return;
// If so, hide it
message.innerHTML = '';
message.style.display = 'none';
message.style.visibility = 'hidden';
};
var serialize = function (form) {
// Setup our serialized data
var serialized = '';
// Loop through each field in the form
for (i = 0; i < form.elements.length; i++) {
var field = form.elements[i];
// Don't serialize fields without a name, submits, buttons, file and reset inputs, and disabled fields
if (!field.name || field.disabled || field.type === 'file' || field.type === 'reset' || field.type === 'submit' || field.type === 'button') continue;
// Convert field data to a query string
if ((field.type !== 'checkbox' && field.type !== 'radio') || field.checked) {
serialized += '&' + encodeURIComponent(field.name) + "=" + encodeURIComponent(field.value);
}
}
return serialized;
};
// Display the form status
var displayMailChimpStatus = function (data) {
// change button state
var submit_btn = document.getElementById('mc-embedded-subscribe');
submit_btn.value = 'Subscribe';
submit_btn.disabled = false;
var mcStatusSuccess = document.querySelector('.mce-success-response');
var mcStatusError = document.querySelector('.mce-error-response');
if (!mcStatusSuccess) return;
// Update our status message
mcStatusSuccess.innerHTML = data.msg;
mcStatusSuccess.classList.remove('hide');
// If error, add error class
if (data.result === 'error') {
mcStatusError.innerHTML = data.msg;
mcStatusError.classList.remove('hide');
}
// setTimeout and reset msgs after 5 seconds
setTimeout(function(){
mcStatusSuccess.innerHTML = '';
mcStatusError.innerHTML = '';
mcStatusSuccess.classList.add('hide');
mcStatusError.classList.add('hide');
submit_btn.disabled = true;
//reset input
var email_input = document.getElementById('mce-EMAIL');
email_input.value = '';
}, 5000);
};
var submitMailChimpForm = function (form) {
// change button state
var submit_btn = document.getElementById('mc-embedded-subscribe');
submit_btn.value = 'Subscribing...';
submit_btn.disabled = true;
// Get the Submit URL
var url = form.getAttribute('action');
url = url.replace('/post?u=', '/post-json?u=');
url += serialize(form) + '&c=displayMailChimpStatus';
// Create script with url and callback (if specified)
var script = window.document.createElement( 'script' );
script.src = url;
// Insert script tag into the DOM (append to <head>)
var ref = window.document.getElementsByTagName( 'script' )[ 0 ];
ref.parentNode.insertBefore( script, ref );
// After the script is loaded (and executed), remove it
script.onload = function () {
this.remove();
};
};
var email_input = document.getElementById('mce-EMAIL');
email_input.addEventListener('input', function() {
var submit_btn = document.getElementById('mc-embedded-subscribe');
submit_btn.value = 'Subscribe';
submit_btn.disabled = false;
var mcStatusError = document.querySelector('.mce-error-response');
mcStatusError.innerHTML = '';
mcStatusError.classList.add('hide');
});
// Listen to all blur events
document.addEventListener('blur', function (event) {
// Only run if the field is in a form for mailchimp-form
if (!event.target.form.classList.contains('mailchimp-form')) return;
// Validate the field
var error = hasError(event.target);
// Otherwise, remove any existing error message
removeError(event.target);
}, true);
document.addEventListener('submit', function (event) {
// Only run on forms flagged for mailchimp-form validation
if (!event.target.classList.contains('mailchimp-form')) return;
// Prevent form from submitting
event.preventDefault();
// Get all of the form elements
var fields = event.target.elements;
// Validate each field
// Store the first field with an error to a variable so we can bring it into focus later
var error, hasErrors;
for (var i = 0; i < fields.length; i++) {
error = hasError(fields[i]);
if (error) {
showError(fields[i], error);
if (!hasErrors) {
hasErrors = fields[i];
}
}
}
// If there are errrors, don't submit form and focus on first element with error
if (hasErrors) {
hasErrors.focus();
return
}
// Otherwise, let the form submit normally
submitMailChimpForm(event.target);
}, false);

281
docs/_static/scripts/hdocs.js vendored Normal file
View File

@ -0,0 +1,281 @@
window.hdocs = (function () {
const DB_URL = HDOCS_BASE_DOMAIN ? 'https://data.' + HDOCS_BASE_DOMAIN + '/v1/query' : 'https://data.hasura-stg.hasura-app.io/v1/query';
return {
setup: function () {
Array.from(document.getElementsByClassName('menuLink')).forEach(function (el) { el.addEventListener('click', hdocs.toggleMenu) });
document.getElementById('nav_tree_icon').addEventListener('click', hdocs.handleNavClick);
document.getElementById('sidebar-close').addEventListener('click', hdocs.handleNavClick);
document.getElementById('thumb_up_button').addEventListener('click', function () { hdocs.sendFeedback('positive', 'Great to hear that! If you have any other feedback, please share here:') });
document.getElementById('thumb_down_button').addEventListener('click', function () { hdocs.sendFeedback('negative', 'Sorry to hear that. Please tell us what you were looking for:') });
document.getElementById('feedback_btn').addEventListener('click', hdocs.handleSubmitFeedback);
docsearch({
appId: 'WCBB1VVLRC',
apiKey: '298d448cd9d7ed93fbab395658da19e8',
indexName: 'graphql-docs-prod',
inputSelector: '#search_element',
transformData: hdocs.transformSearchData,
debug: false
});
hdocs.getGithubStarCount();
hdocs.setReleaseTags();
hdocs.setExternalLinks();
hdocs.setupIntercom();
hdocs.setupGraphiQL();
},
toggleMenu: function () {
var x = document.getElementById("navbar")
if (x.className === "topnav") {
x.className += " responsive"
} else {
x.className = "topnav"
}
},
request: function (url, data, type) {
return new Promise(function (resolve, reject) {
var req = new XMLHttpRequest();
req.open(type, url, true);
req.setRequestHeader("Content-type", "application/json");
req.onload = function () {
if (req.status === 200) {
resolve(req.response);
} else {
reject(req.response);
}
};
req.onerror = function () {
reject(Error("Network Error"));
};
// Make the request
req.send(JSON.stringify(data));
});
},
getGithubStarCount: function () {
const bodyObj = {
type: "select",
args: {
table: "github_repos",
columns: ["star_count"],
where: { $or: [{ name: "graphql-engine" }] },
}
}
const options = {
method: "POST",
credentials: "include",
body: JSON.stringify(bodyObj),
headers: {
"Content-Type": "application/json",
},
}
fetch(DB_URL, options)
.then(function (response) {
return response.json();
})
.then(function (data) {
const githubStar = data[0].star_count;
document.getElementById("gitHubCount").innerHTML = hdocs.formatNumber(githubStar);
document.getElementById("gitHubBtn").classList.remove("hide");
})
.catch(function (e) { {console.error(e);} })
},
setReleaseTags: function () {
if (document.getElementsByClassName('latest-release-tag').length > 0) {
hdocs.request('https://releases.hasura.io/graphql-engine?agent=docs.hasura.io', null, 'GET')
.then(function (response) {
const data = JSON.parse(response);
Array.from(document.getElementsByClassName('latest-release-tag')).forEach(function (el) { el.innerHTML = data.latest });
Array.from(document.getElementsByClassName('latest-prerelease-tag')).forEach(function (el) { el.innerHTML = data.prerelease });
})
.catch(function (err) {
console.log(err);
});
}
},
setExternalLinks: function () {
Array.from(document.getElementsByClassName('.external')).forEach(function (el) { el.setAttribute('target', '_blank') });
},
setupIntercom: function () {
window.intercomSettings = {
app_id: "rucirpb3"
};
var w = window; var ic = w.Intercom; if (typeof ic === "function") { ic('reattach_activator'); ic('update', intercomSettings); } else {
var d = document; var i = function () { i.c(arguments) }; i.q = []; i.c = function (args) { i.q.push(args) }; w.Intercom = i; function l() {
var s = d.createElement('script'); s.type = 'text/javascript'; s.async = true; s.src = 'https://widget.intercom.io/widget/rucirpb3'; var
x = d.getElementsByTagName('script')[0]; x.parentNode.insertBefore(s, x);
} if (w.attachEvent) { w.attachEvent('onload', l); } else { w.addEventListener('load', l, false); }
}
},
transformSearchData: function (suggestions) {
if (window.location.origin !== 'https://hasura.io') {
suggestions.forEach(function (suggestion) {
suggestion.url = suggestion.url.replace(/https:\/\/hasura.io\/docs\/[^\/]*/, window.location.origin);
});
}
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');
}
return suggestions;
},
handleNavClick: function (e) {
const sidebar = document.getElementById('sidebar');
const background = document.getElementById('content_inner_wrapper');
const body = document.querySelector('body');
if (sidebar.classList.contains('mobile-hide')) {
sidebar.classList.remove('mobile-hide');
background.classList.add('no_scroll');
body.classList.add('no_scroll');
} else {
sidebar.classList.add('mobile-hide');
background.classList.remove('no_scroll');
body.classList.remove('no_scroll');
}
},
sendFeedback: function (feedback, feedbackDetailMsg) {
const insertQuery = {
'type': 'insert',
'args': {
'table': 'docs_feedback',
'objects': [
{
'page': window.location.pathname,
'feedback': feedback
}
],
'returning': ['id']
}
};
hdocs.request(DB_URL, insertQuery, 'POST')
.then(function (response) {
const data = JSON.parse(response);
if (feedback == 'positive' || (feedback == 'negative' && !Intercom)) {
document.getElementById('feedback_box').setAttribute('data-id', data['returning'][0]['id']);
document.getElementById('feedback_detail_msg').innerHTML = feedbackDetailMsg;
document.getElementById('feedback').classList.add('hide');
document.getElementById('detailed_feedback').classList.remove('hide');
}
})
.catch(function (err) {
alert('Error capturing feedback');
console.log(err);
});
},
handleSubmitFeedback: function () {
const feedbackBox = document.getElementById('feedback_box');
const feedbackId = feedbackBox.getAttribute('data-id');
const feedbackContent = feedbackBox.value;
const updateQuery = {
'type': 'update',
'args': {
'table': 'docs_feedback',
'$set': {
'feedback_content': feedbackContent
},
'where': { 'id': feedbackId }
}
};
hdocs.request(DB_URL, updateQuery, 'POST')
.then(function (response) {
document.getElementById('detailed_feedback').classList.add('hide');
document.getElementById('thank_you').classList.remove('hide');
})
.catch(function (err) {
alert('Error capturing feedback');
console.log(err);
});
},
graphQLFetcher: function (endpoint) {
endpoint = endpoint || HDOCS_GRAPHIQL_DEFAULT_ENDPOINT;
return function (graphQLParams) {
const params = {
method: 'post',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(graphQLParams),
credentials: 'include'
};
return fetch(endpoint, params)
.then(function (response) {
return response.text();
})
.then(function (responseBody) {
try {
return JSON.parse(responseBody);
} catch (error) {
return responseBody;
}
});
}
},
setupGraphiQL: function () {
if (typeof (React) === 'undefined' || typeof (ReactDOM) === 'undefined' || typeof (GraphiQL) === 'undefined') {
return;
}
const targets = document.getElementsByClassName('graphiql');
for (var i = 0; i < targets.length; i++) {
const target = targets[i];
const endpoint = target.getElementsByClassName("endpoint")[0].innerHTML.trim();
const query = target.getElementsByClassName("query")[0].innerHTML.trim();
const response = target.getElementsByClassName("response")[0].innerHTML.trim();
const variables = target.getElementsByClassName("variables")[0].innerHTML.trim();
const graphiQLElement = React.createElement(GraphiQL, {
fetcher: hdocs.graphQLFetcher(endpoint),
schema: null, // TODO: pass undefined if introspection supported
query: query,
response: response,
variables: variables
});
ReactDOM.render(graphiQLElement, target);
}
Array.from(document.getElementsByClassName('variable-editor')).forEach(function (el) { el.style.height = '120px' });
},
formatNumber: function (number) {
if (typeof number !== "number") return number;
const SIsymbol = ["", "k", "M", "G", "T", "P", "E"];
const absNumber = Math.abs(number);
const sign = Math.sign(number);
// what tier? (determines SI symbol)
const tier = Math.log10(absNumber) / 3 | 0;
// if zero, we don't need a suffix
if (tier === 0) return sign * absNumber;
// get suffix and determine scale
const suffix = SIsymbol[tier];
const scale = Math.pow(10, tier * 3);
// scale the number
const scaled = absNumber / scale;
// format number and add suffix
return sign * scaled.toFixed(1) + suffix;
}
}
})();

126
docs/_static/scripts/newsletter.js vendored Normal file
View File

@ -0,0 +1,126 @@
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');
email_input.addEventListener('input', function() {
submit_btn.value = 'Subscribe';
submit_btn.disabled = false;
mcStatusError.innerHTML = '';
mcStatusError.classList.add('hide');
});
const readCookie = (name) => {
const nameEQ = name + "=";
const ca = document.cookie.split(';');
for(let i=0;i < ca.length;i++) {
let c = ca[i];
while (c.charAt(0)===' ') c = c.substring(1,c.length);
if (c.indexOf(nameEQ) === 0) return c.substring(nameEQ.length,c.length);
}
return null;
}
const showErrorMsg = () => {
submit_btn.value = 'Subscribe';
submit_btn.disabled = false;
mcStatusError.innerHTML = 'Please enter a valid email';
mcStatusError.classList.remove('hide');
clearMsg();
};
const clearMsg = () => {
setTimeout(function(){
mcStatusSuccess.innerHTML = '';
mcStatusError.innerHTML = '';
mcStatusSuccess.classList.add('hide');
mcStatusError.classList.add('hide');
submit_btn.disabled = true;
//reset input
email_input.value = '';
}, 3000);
}
const submitNewsletterForm = 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
submit_btn.value = 'Subscribing...';
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 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({
query: gqlMutation,
variables: { objects: objects }
}),
})
.then(response => response.json())
.then(data => {
// change button state
submit_btn.value = 'Subscribe';
submit_btn.disabled = false;
if(data && data.data && data.data.signupNewsletter.affected_rows) {
mcStatusSuccess.innerHTML = 'Thank you for subscribing!';
mcStatusSuccess.classList.remove('hide');
} else {
if(data.errors && data.errors[0].extensions.code === 'constraint-violation') {
mcStatusError.innerHTML = 'You have already subscribed';
} else {
mcStatusError.innerHTML = 'Something went wrong';
}
mcStatusError.classList.remove('hide');
}
clearMsg();
})
.catch((error) => {
console.error('Error:', error);
submit_btn.value = 'Subscribe';
submit_btn.disabled = false;
});
};
document.addEventListener('submit', function (event) {
// Only run on forms flagged for newsletter-form validation
if (!event.target.classList.contains('newsletter-form')) return;
// Prevent form from submitting
event.preventDefault();
// email validation
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if(!emailPattern.test(email_input.value)) {
showErrorMsg();
return;
}
// Otherwise, let the form submit normally
submitNewsletterForm(event.target);
}, false);

File diff suppressed because one or more lines are too long

View File

@ -10,19 +10,10 @@
}
.head_wrapper {
font-family: 'Khula';
font-size: 24px;
font-weight: 600;
}
.subhead_wrapper {
font-family: 'Khula';
font-size: 16px;
font-weight: 600;
padding: 15px 0;
border-bottom: 1px solid #ccc;
}
.body_content {
font-family: 'Gudea';
font-size: 15px;
@ -39,12 +30,6 @@
max-width: 100%;
}
.extra_small_content {
font-family: 'Gudea';
font-size: 12px;
color: #333;
}
.landing_page_wrapper p {
margin-top: 0;
margin-bottom: 0;
@ -111,10 +96,6 @@
text-decoration: underline;
}
.space_top_bot {
padding: 15px 0;
}
.space_wrapper {
padding: 15px 15px;
padding-bottom: 0;
@ -124,53 +105,17 @@
text-align: left;
}
.common_btn {
padding-top: 10px;
}
.common_btn button {
padding: 5px 15px;
background-color: #1CD3C6;
color: #000;
border-radius: 5px;
border: 1px solid #1CD3C6;
font-size: 14px;
font-weight: 600;
font-family: 'Gudea';
}
.sign_in_wrapper1 {
text-align: left;
}
.sign_in_wrapper1 .get_start {
display: inline-block;
padding-right: 5px;
}
.sign_in_wrapper1 .common_btn {
display: inline-block;
}
.sign_in_wrapper {
text-align: left;
display: flex;
align-items: center;
}
.tutorial_in_wrapper {
display: block;
}
.sign_in_wrapper .get_start {
display: inline-block;
padding-right: 5px;
}
.sign_in_wrapper .common_btn {
display: inline-block;
}
.get_start img {
width: 25px;
}
@ -188,44 +133,6 @@
padding-left: 5px;
}
.ref_link {
display: inline;
color: #333;
font-weight: 600;
}
.ref_link img {
display: inline-block;
width: 12px;
padding-left: 5px;
}
.tags_wrapper {
width: 100%;
text-align: left;
}
.tags_btn {
text-align: left;
}
.tags_wrapper .tags_btn {
padding-right: 10px;
display: inline-block;
margin-bottom: 10px;
}
.tags_btn button {
padding: 2px 7px;
background-color: #FEECC0;
color: #DFB653;
border-radius: 5px;
border: 1px solid #FEECC0;
font-size: 14px;
font-weight: 600;
font-family: 'Gudea';
}
.wd_80 {
margin: 0 auto;
width: 80%;

View File

@ -1,12 +1,5 @@
@import url('https://fonts.googleapis.com/css?family=Khula:400,700');
@import url('https://fonts.googleapis.com/css?family=Poppins:300,400,500,600&display=swap');
html {
background-color: #f8f8f8 !important;
}
::-moz-selection { /* Code for Firefox */
background: #b2d7fe !important;
background-color: #f8f8f8;
}
::selection {
@ -15,13 +8,13 @@ html {
/* override djangosite.css */
body, p, #docs-content, #sidebar,
h1, h2, h3, h4, h5, h6 {
h1, h2, h3, h4, h5, h6, dl dt, dl dd {
font-family: 'Open Sans', Helvetica, Arial, sans-serif;
}
body, p, #docs-content, #sidebar, #sidebar a,
tt, code, kbd, pre, pre.literal-block, samp,
.graphiql .graphiql-container .CodeMirror {
dl dt, dl dd, .graphiql .graphiql-container .CodeMirror {
font-size: 16px;
}
@ -104,6 +97,8 @@ p {
background-color: #1bd5c8;
padding: 24px 20px;
}
/* Newsletter */
#mc_embed_signup {
display: flex;
align-items: center;
@ -119,7 +114,20 @@ p {
flex: 1 !important;
position: relative !important;
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 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%;}
.newsletter-link,
.newsletter-link:hover,
.newsletter-link:visited {
@ -137,12 +145,6 @@ p {
display: inline-block;
position: relative;
}
#error-for-mce-EMAIL {
position: absolute;
bottom: -18px;
left: 16px;
font-size: 12px !important;
}
.input-box input {
margin: 0 0 !important;
border-radius: 5px !important;
@ -155,9 +157,9 @@ p {
width: calc(100% - 32px) !important;
}
.input-box input:-webkit-autofill, .input-box input:-webkit-autofill {
-webkit-animation-name: autofill;
-webkit-animation-fill-mode: both;
-webkit-box-shadow: 0 0 0px 1000px white inset;
animation-name: autofill;
animation-fill-mode: both;
box-shadow: 0 0 0px 1000px white inset;
background-color: transparent;
font-size: 16px;
}
@ -165,7 +167,7 @@ p {
.input-box input:-webkit-autofill:hover,
.input-box input:-webkit-autofill:focus,
.input-box input:-webkit-autofill:active {
-webkit-box-shadow: 0 0 0 35px #1bd5c8 inset !important;
box-shadow: 0 0 0 35px #1bd5c8 inset !important;
-webkit-text-fill-color: #001934 !important;
font-size: 16px;
caret-color: #001934;
@ -223,7 +225,7 @@ p {
#mce-responses .success-message {
color: #1cd3c6 !important;
}
.content_inner_wrapper {
#content_inner_wrapper {
width: 75% !important;
/* height: 100vh; */
overflow-y: auto;
@ -245,9 +247,6 @@ p {
}
#docs-content a.reference {
/*color: #1CD3C6 !important;*/
/*color: #1CD3C6 !important;*/
/* color: #FF3264 !important; */
color: #DB0037 !important;
background-color: transparent !important;
}
@ -276,8 +275,7 @@ p {
}
#docs-content .note,
#docs-content .admonition,
#docs-content .help-block {
#docs-content .admonition {
background: #f1f9ff;
border: 1px solid #c9e0f0;
}
@ -294,15 +292,6 @@ p {
border-radius: 3px;
}
#docs-content .figure {
width: 45%;
display: inline-block;
}
#docs-content .figure img {
width: 100%;
}
#docs-content table caption {
position: relative;
}
@ -313,9 +302,7 @@ p {
/*** random overrides ***/
.wy-plain-list-decimal ol,
.rst-content .section ol ol,
.rst-content ol.arabic ol,
article ol ol {
margin-bottom: 0;
}
@ -386,80 +373,36 @@ article ol ol {
/*** Header ***/
#docs-header-actions {
display: flex;
align-items: center;
margin-top: 30px;
}
#docs-header-actions .header_links {
min-width: 33%;
text-align: right;
.edit-github-btn-wrap {
margin-left: auto;
}
#docs-header-actions .header_links.inline-block .header_links_content {
float: left;
}
#docs-header-actions .header_links.inline-block .buttons {
float: right;
}
#docs-header-actions .social_icons_wrapper {
padding-left: 10px;
display: flex;
align-items: center;
}
#docs-header-actions .social_icons_wrapper .social_icons {
display: inline-block;
padding: 10px 5px;
opacity: 0.7;
vertical-align: middle;
display: flex;
align-items: center;
}
#docs-header-actions .social_icons_wrapper .social_icons:hover {
opacity: 1;
}
#docs-header-actions .social_icons_wrapper .social_icons img {
box-shadow: none !important;
-webkit-box-shadow: none !important;
-moz-box-shadow: none !important;
width: 30px;
display: inline-block;
}
#docs-header-actions .social_icons_wrapper .social_icons a {
text-decoration: none;
color: #DB0037;
display: contents;
}
#docs-header-actions .social_icons_wrapper .social_icons iframe {
height: 20px;
}
#docs-header-actions .tooltip {
position: relative;
}
#docs-header-actions .tooltip .tooltip_text {
visibility: hidden;
width: 272px;
background-color: black;
color: #fff;
text-align: center;
border-radius: 6px;
padding: 2px;
position: absolute;
z-index: 1;
right: 0;
top: 36px;
.edit-github-btn,
.edit-github-btn:hover
.edit-github-btn:visited {
font-size: 14px;
padding: 4px 6px;
color: #333 !important; /* override django-site.css */
border-radius: 4px;
border: solid 1px #d1d2d3;
font-weight: 600;
display: flex;
align-items: center;
text-decoration: none;
white-space: nowrap;
margin-left: 8px;
}
#docs-header-actions .tooltip:hover .tooltip_text {
visibility: visible;
.edit-github-btn:hover {
background-color: rgb(245,247,249);
}
.edit-github-btn img {
width: 16px;
margin-right: 4px;
}
/*** Header END ***/
@ -480,11 +423,6 @@ article ol ol {
#nav_tree_icon {
position: relative;
}
#nav_tree_icon .fa-bars {
font-size: 18px;
color: #1CD3C6;
}
#nav_tree_icon .docs-menu {
font-weight: 600;
color: #001934;
@ -551,6 +489,19 @@ article ol ol {
color:#ccc;
}
#search_help {
position: absolute;
top: 100px;
z-index: 1000;
}
@media (max-width: 768px) {
#search_help {
top: -20px;
left: 10px;
}
}
/*** Search box END ***/
/*** Table of contents ***/
@ -638,7 +589,7 @@ article ol ol {
width: 100%;
}
.feedback_wrapper .feedback_btn {
.feedback_wrapper #feedback_btn {
margin-top: 10px;
padding: 5px 10px;
background-color: #1CD3C6;
@ -659,7 +610,10 @@ article ol ol {
/*** Footer ***/
.footer-hasura-custom {
padding: 30px;
font-size: 16px;
font-family: sans-serif;
font-weight: 300;
text-align: center;
background-color: #eee;
margin: 20px 0;
@ -681,6 +635,11 @@ article ol ol {
/*** Sidebar ***/
.sphinxsidebarwrapper {
font-size: 92%;
margin: 15px;
}
#sidebar {
background-color: #001934;
width: 24% !important;
@ -690,51 +649,36 @@ article ol ol {
position: sticky;
top: 0;
}
#sidebar li {
margin-top: 5px;
margin-bottom: 5px;
}
#sidebar .closeIcon {
position: absolute;
right: 10px;
top: 10px;
cursor: pointer;
}
#sidebar .sphinxsidebar .sphinxsidebarwrapper h3 {
padding-top: 10px;
/*
color: #fff;
*/
color: rgba(214,214,214,1);
font-family: 'Khula';
#sidebar ul ul {
margin-top:0;
margin-bottom:0;
}
#sidebar .sphinxsidebar .sphinxsidebarwrapper h4 {
/*
color: #fff;
*/
#sidebar ul li {
color: rgba(214,214,214,1);
font-family: 'Khula';
margin-top: 5px;
margin-bottom: 5px;
}
#sidebar .sphinxsidebar .sphinxsidebarwrapper ul li {
/* list-style-type: none; */
/* color: #fff; */
color: rgba(214,214,214,1);
}
#sidebar .sphinxsidebar .sphinxsidebarwrapper ul li a {
/* color: #fff; */
#sidebar ul li a {
color: rgba(214,214,214,1);
text-decoration: none;
}
#sidebar .sphinxsidebar .sphinxsidebarwrapper ul li a code {
#sidebar ul li a code {
color: #32A061;
text-decoration: none;
}
#sidebar .sphinxsidebar .sphinxsidebarwrapper .header_main_logo {
#sidebar .header_main_logo {
display: flex;
align-items: center;
padding: 18px 0;
@ -749,25 +693,23 @@ article ol ol {
border-bottom: 1px solid #4F5763;
}
#sidebar .sphinxsidebar .sphinxsidebarwrapper .header_main_logo a {
#sidebar .header_main_logo a {
color: #fff;
text-decoration: none;
}
#sidebar .sphinxsidebar .sphinxsidebarwrapper .header_main_logo a:hover {
#sidebar .header_main_logo a:hover {
opacity: 0.6;
}
#sidebar .sphinxsidebar .sphinxsidebarwrapper .header_main_logo .version_txt {
#sidebar .header_main_logo .version_txt {
color: rgba(255,198,39,1);
margin-top: 6px;
margin-left: 10px;
cursor: pointer;
}
#sidebar .sphinxsidebar .sphinxsidebarwrapper .header_main_logo .version_txt select {
#sidebar .header_main_logo .version_txt select {
cursor: pointer;
background-color: transparent;
border: 0;
@ -788,19 +730,19 @@ article ol ol {
}
#sidebar .sphinxsidebar .sphinxsidebarwrapper .header_main_logo .version_txt select:focus {
#sidebar .header_main_logo .version_txt select:focus {
outline: none;
}
#sidebar .sphinxsidebar .sphinxsidebarwrapper .header_main_logo .version_txt .option_val {
#sidebar .header_main_logo .version_txt .option_val {
color: #000;
}
#sidebar .sphinxsidebar .sphinxsidebarwrapper .header_main_logo .docs_label {
#sidebar .header_main_logo .docs_label {
margin-top: 5px;
}
#sidebar .sphinxsidebar .sphinxsidebarwrapper .header_main_logo .docs_label .hero {
#sidebar .header_main_logo .docs_label .hero {
display: inline-block;
vertical-align: middle;
font-size: 21px;
@ -808,11 +750,11 @@ article ol ol {
margin-left: calc(24vw - 240px);
}
#sidebar .sphinxsidebar .sphinxsidebarwrapper a.current.reference.internal {
#sidebar a.current.reference.internal {
color: #1CD3C6 !important;
}
#sidebar .sphinxsidebar .sphinxsidebarwrapper a.reference.internal > code.docutils.literal {
#sidebar a.reference.internal > code.docutils.literal {
color: rgba(214,214,214,1) !important;
}
@ -986,7 +928,6 @@ article ol ol {
.loading_overlay .subhead_wrapper_loader {
color: #4d4d4d;
font-size: 20px;
font-family: 'Khula';
font-weight: 300;
}
@ -1027,7 +968,7 @@ article ol ol {
height: calc(100% - 120px);
}
.content_inner_wrapper {
#content_inner_wrapper {
width: 100% !important;
}
@ -1055,13 +996,6 @@ article ol ol {
margin-left: 0;
margin-right: 0;
}
#docs-header-actions {
display: flex;
align-items: center;
}
#docs-header-actions {
margin-top: 20px;
}
#input_search_box {
flex: 1;
@ -1124,15 +1058,6 @@ article ol ol {
min-width: calc(111% + 10px); /* 1 / width(input_search_box) + 10px */
}
#docs-header-actions .header_links {
width: 100%;
margin-top: 10px;
}
#docs-header-actions .header_links.inline-block .buttons {
float: none !important;
}
.api-tabs .ui.tab {
padding: 10px
}
@ -1198,23 +1123,12 @@ article ol ol {
}
.navBarToggle:focus {
outline: none;
}
.navWhiteBG .navBarToggle {
border: 1px solid #001934 !important;
}
.navWhiteBG .navBarToggle .iconBar {
background-color: #001934 !important;
}
.navBlueBG .navBarToggle {
border: 1px solid #fff !important;
}
.navBlueBG .navBarToggle .iconBar {
background-color: #fff !important;
}
.headerSticky {
position: fixed;
width: 100%;
z-index: 100;
}
.headerWrapper {
height: 60px;
@ -1273,7 +1187,6 @@ article ol ol {
border-radius: 4px;
color: #001934;
border: solid 1px #d1d2d3;
font-family: 'Poppins';
font-size: 11px;
font-weight: 600;
display: flex;
@ -1314,7 +1227,7 @@ article ol ol {
padding-left: 0;
margin: 2px 0 0;
}
.headerWrapper .navBlueBG .navBarUL li, .headerWrapper .navWhiteBG .navBarUL li {
.headerWrapper .navBlueBG .navBarUL li {
font-size: 16px;
list-style-type: none;
display: inline-block;
@ -1364,10 +1277,10 @@ article ol ol {
.versionTxt select::-moz-focus-inner, .versionTxt select option::-moz-focus-inner {
border: 0;
}
.headerWrapper .navBlueBG .navBarUL li a:hover, .headerWrapper .navWhiteBG .navBarUL li a:hover {
.headerWrapper .navBlueBG .navBarUL li a:hover {
text-decoration: none;
}
.headerWrapper .navBlueBG .navBarUL li:last-child, .headerWrapper .navWhiteBG .navBarUL li:last-child {
.headerWrapper .navBlueBG .navBarUL li:last-child {
padding: 0 0;
margin-left: 0px;
}
@ -1381,25 +1294,12 @@ article ol ol {
.headerWrapper .navBlueBG .navBarUL .navListActive a {
color: #fff;
border-bottom: 2px solid #fff;
}
.headerWrapper .navWhiteBG .navBarUL li a {
color: #6e6e6e;
}
.headerWrapper .navWhiteBG .navBarUL li a:hover {
color: #505050;
}
.headerWrapper .navWhiteBG .navBarUL .navListActive a {
color: #505050;
border-bottom: 2px solid #505050;
}
@media (min-width: 768px) and (max-width: 1200px) {
.versionTxt select {
margin-right: 0;
margin-left: 8px;
}
.headerSticky {
position: static;
}
.headerWrapper {
position: relative;
display: block;
@ -1446,9 +1346,6 @@ article ol ol {
margin-right: 0;
margin-left: 8px;
}
.headerSticky {
position: static;
}
.headerWrapper {
display: block;
z-index: 10;

View File

@ -62,18 +62,6 @@
{%- block sidebartoc %}
{%- include "localtoc.html" %}
{%- endblock %}
{%- block sidebarrel %}
{%- include "relations.html" %}
{%- endblock %}
{%- block sidebarsourcelink %}
{%- include "sourcelink.html" %}
{%- endblock %}
{%- if customsidebar %}
{%- include customsidebar %}
{%- endif %}
{%- block sidebarsearch %}
{%- include "searchbox.html" %}
{%- endblock %}
{%- endif %}
</div>
</div>
@ -94,11 +82,13 @@
{%- for scriptfile in script_files %}
<script type="text/javascript" src="{{ pathto(scriptfile, 1) }}"></script>
{%- endfor %}
{%- for deferred_scriptfile in deferred_script_files %}
<script defer type="text/javascript" src="{{ pathto(deferred_scriptfile, 1) }}"></script>
{%- endfor %}
{%- endmacro %}
{%- macro css() %}
<link rel="stylesheet" href="{{ pathto('_static/' + style, 1) }}" type="text/css" />
<link rel="stylesheet" href="{{ pathto('_static/pygments.css', 1) }}" type="text/css" />
{%- for css in css_files %}
{%- if css|attr("rel") %}
<link rel="{{ css.rel }}" href="{{ pathto(css.filename, 1) }}" type="text/css"{% if css.title is not none %} title="{{ css.title }}"{% endif %} />
@ -130,8 +120,9 @@
{%- block htmltitle %}
<title>{{ title|striptags|e }}{{ titlesuffix }}</title>
<meta name="title" content="{{ title|striptags|e }}{{ titlesuffix }}" />
{%- if BASE_DOMAIN != "production" %}
<meta name="robots" content="noindex">
{%- if RELEASE_MODE != "production" %}
<meta name="robots" content="noindex">
{%- endif %}
{%- endblock %}

View File

@ -1 +0,0 @@
{% extends "basic/genindex.html" %}

View File

@ -4,12 +4,21 @@
{% set is_landing_page = true %}
{%- endif %}
{% set css_files = css_files + ['_static/djangosite.css', '_static/hasura-custom.css'] %}
{% set css_files = css_files + ['_static/graphiql/graphiql.css', '_static/styles/main.css'] %}
{%- if is_landing_page %}
{% set css_files = css_files + ['_static/landing.css'] %}
{% set css_files = css_files + ['_static/styles/landing.css'] %}
{%- endif %}
{# NOTE: Use plain js instead of jquery which is an outdated & redundant library #}
{# Sphinx seems to have a jquery dependency. They have an open PR to remove it. #}
{# https://github.com/sphinx-doc/sphinx/issues/7405 #}
{# TODO: replace react & graphiql imports with a custom directive #}
{# b/c we're not using any graphiql functionality, only styles #}
{% 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'] %}
{%- macro secondnav() %}
{%- if prev %}
&laquo; <a href="{{ prev.link|e }}" title="{{ prev.title|e }}">previous</a>
@ -25,8 +34,13 @@
<link rel="canonical" href="https://hasura.io/docs/1.0/{{ pagename }}{{ file_suffix }}" />
{% endblock %}
{% block extrahead %}
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" integrity="sha384-wvfXpqpZZVQGK6TAh5PVlGOfQNHSoD2xbE+QkPxCAFlNEevoEH3Sl0sibVcOQVnN" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/docsearch.js@2/dist/cdn/docsearch.min.css" />
{% endblock %}
{% block document %}
<div id="custom-doc" class="{% block bodyclass %}{{ 'yui-t6' if not is_landing_page else '' }}{% endblock %}">
<div class="main_container_wrapper">
{% include 'pages/loading.html' %}
<header
@ -51,26 +65,16 @@
<img src="{{ pathto('_images/layout/twitter-brands-block.svg', 1) }}" alt="Twitter"/>
</div>
</a>
<a class='socialBtn gitHubBtn hide' href="https://github.com/hasura/graphql-engine/" target="_blank" rel="noopener noreferrer">
<a id="gitHubBtn" class='socialBtn gitHubBtn hide' href="https://github.com/hasura/graphql-engine/" target="_blank" rel="noopener noreferrer">
<div class='gitHubStars'>
<img src="{{ pathto('_images/layout/github-header.svg', 1) }}" alt='Github'/>
<span>Star</span>
</div>
<div class='gitHubCount'>
</div>
<div id="gitHubCount" class='gitHubCount'></div>
</a>
</div>
<div class="navBlueBG">
<ul class="navBarUL">
<!-- <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> -->
<li>v1.x</li>
<li class='navListActive'>
<a href="/docs/">Docs</a>
@ -110,9 +114,7 @@
</ul>
</div>
<div class='navBlueBG'>
<span class="navBarToggle"
onclick="toggleMenu()"
>
<span class="menuLink navBarToggle">
<span class="iconBar"></span>
<span class="iconBar"></span>
<span class="iconBar"></span>
@ -122,9 +124,7 @@
<div class="visibleMobile">
<ul class="navBarULMobile">
<li>
<a href="/docs/"
onclick="toggleMenu()"
>Docs</a>
<a class="menuLink" href="/docs/">Docs</a>
<span class="versionTxt">
v1.x
<!-- <select value="{{ version }}" onchange="location = this.value;" class="selected" aria-label="Select Version">
@ -137,44 +137,32 @@
</span>
</li>
<li>
<a href='https://hasura.io/blog/'
onclick="toggleMenu()"
>
<a class="menuLink" href='https://hasura.io/blog/'>
Blog
</a>
</li>
<li>
<a href='https://hasura.io/learn/'
onclick="toggleMenu()"
>
<a class="menuLink" href='https://hasura.io/learn/'>
Tutorials
</a>
</li>
<li>
<a href="https://hasura.io/enterprise/"
onclick="toggleMenu()"
>
<a class="menuLink" href="https://hasura.io/enterprise/">
Enterprise
</a>
</li>
<li>
<a href="https://hasura.io/hasura-pro/"
onclick="toggleMenu()"
>
<a class="menuLink" href="https://hasura.io/hasura-pro/">
Hasura Pro
</a>
</li>
<li>
<a href="https://hasura.io/pricing/"
onclick="toggleMenu()"
>
<a class="menuLink" href="https://hasura.io/pricing/">
Pricing
</a>
</li>
<li>
<a href='https://hasura.io/docs/1.0/graphql/manual/getting-started/index.html'
onclick="toggleMenu()"
>
<a class="menuLink" href='https://hasura.io/docs/1.0/graphql/manual/getting-started/index.html'>
<button class='commonBtn navBtnHome'>
Get Started
</button>
@ -194,7 +182,7 @@
{% set pagenameSplit = pagename.split('/') %}
{% block sidebarwrapper %}
<div class="yui-b mobile-hide" id="sidebar">
<div class="mobile-hide" id="sidebar">
<div id='sidebar-close' class='mobile-only closeIcon'>
<img src="{{ pathto('_images/layout/close-icon.svg', 1) }}" alt="Close"/>
</div>
@ -214,7 +202,7 @@
</div>
{% endblock %}
<div class="content_inner_wrapper">
<div id="content_inner_wrapper">
<div role="main" parent={{ rootpage }}>
<div id="docs-header">
@ -230,36 +218,23 @@
-->
<div id="docs-header-actions">
<div class="inline-block" id="input_search_box">
<a id="search_help" class="hide" onClick="if (Intercom) Intercom('show');" href="javascript:undefined">
Can't find what you're looking for?
</a>
<span class="fa fa-search search_icon"></span>
<input type="text" id="search_element" placeholder="Search docs..." />
</div>
<div class="edit-github-btn-wrap inline-block mobile-hide">
<a class="edit-github-btn" target="_blank" rel="noopener" href="https://github.com/hasura/graphql-engine/blob/master/docs/{{ pagename }}.rst">
<img src="{{ pathto('_images/layout/github-32.png', 1) }}" alt='Github'/>
<span>Edit on GitHub</span>
</a>
</div>
<div class="inline-block mobile-only" id="nav_tree_icon">
<div class="docs-menu">
DOCS MENU >
</div>
</div>
<!--
<div class="inline-block header_links">
<div class="social_icons_wrapper">
<div class="social_icons">
<a href="https://hasura.io/learn/" target="_blank" rel="noopener">
Tutorials
</a>
</div>
<div class="social_icons">
<a href="https://twitter.com/hasurahq/" target="_blank" rel="noopener">
<img class="responsive" src="{{ pathto('_images/layout/Twitter.svg', 1) }}" alt="Twitter icon" />
</a>
</div>
<div class="social_icons tooltip">
<a href="https://discord.gg/vBPpJkS" target="_blank" rel="noopener">
<img class="responsive" src="{{ pathto('_images/layout/Discord.svg', 1) }}" alt="Discord icon" />
</a>
<span class="tooltip_text">Get instant support on our discord server</span>
</div>
</div>
</div>
-->
</div>
</div>
@ -272,10 +247,10 @@
<div class="nav">{{ secondnav() }}</div>
<div class="feedback_wrapper">
<div class="feedback">
<div id="feedback" class="feedback">
Was this page helpful?
<!-- inline svgs to allow color manipulation on hover -->
<div class="display_inl actions thumb_up">
<div id="thumb_up_button" class="display_inl actions thumb_up">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Capa_1" x="0px" y="0px" width="21px" height="21px" viewBox="0 0 561 561" style="enable-background:new 0 0 561 561;" xml:space="preserve">
<g>
<g id="thumb-up">
@ -284,7 +259,7 @@
</g>
</svg>
</div>
<div class="display_inl actions thumb_down">
<div id="thumb_down_button" class="display_inl actions thumb_down">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Capa_2" x="0px" y="0px" width="21px" height="21px" viewBox="0 0 561 561" style="enable-background:new 0 0 561 561;" xml:space="preserve">
<g>
<g id="thumb-down">
@ -294,13 +269,13 @@
</svg>
</div>
</div>
<div class="detailed_feedback hide">
<div class="feedback_detail_msg"></div>
<div id="detailed_feedback" class="hide">
<div id="feedback_detail_msg"></div>
<textarea rows="4" cols="75" id="feedback_box" data-id="0"></textarea>
<br/>
<button class="feedback_btn">Submit</button>
<button id="feedback_btn">Submit</button>
</div>
<div class="thank_you hide">
<div id="thank_you" class="hide">
Thank you for your feedback!
</div>
</div>
@ -310,23 +285,24 @@
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 action="https://hasura.us13.list-manage.com/subscribe/post?u=9b63e92a98ecdc99732456b0e&amp;id=f5c4f66bcf" method="post" id="mc-embedded-subscribe-form" name="mc-embedded-subscribe-form" class="validate post-subscription-form mailchimp-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,})+$" title="The domain portion of the email address is invalid (the portion after the @)." required>
<form id="mc-embedded-subscribe-form" name="mc-embedded-subscribe-form" class="validate post-subscription-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>
</div>
<div id="mce-responses" class="clear display-inline">
<div class="mce-error-response response error-message hide">
</div>
<div id="mce-responses" class="clear display-inline">
<div class="mce-error-response response error-message hide">
</div>
<div class="mce-success-response response success-message hide">
</div>
<div class="mce-success-response response success-message hide">
</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">
</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">
</div>
</form>
</div>
</div>
<div class="footer-hasura-custom">
@ -354,10 +330,6 @@
{%- endif %}
</div>
</div>
</div>
{% endblock %}
{% block sidebarrel %}
{% endblock %}
{# Empty some default blocks out #}
@ -367,460 +339,16 @@
{% block sidebar2 %}{% endblock %}
{% block footer %}
<!-- intercom -->
<script>
window.intercomSettings = {
app_id: "rucirpb3"
};
</script>
<script>(function(){var w=window;var ic=w.Intercom;if(typeof ic==="function"){ic('reattach_activator');ic('update',intercomSettings);}else{var d=document;var i=function(){i.c(arguments)};i.q=[];i.c=function(args){i.q.push(args)};w.Intercom=i;function l(){var s=d.createElement('script');s.type='text/javascript';s.async=true;s.src='https://widget.intercom.io/widget/rucirpb3';var
x=d.getElementsByTagName('script')[0];x.parentNode.insertBefore(s,x);}if(w.attachEvent){w.attachEvent('onload',l);}else{w.addEventListener('load',l,false);}}})()
</script>
<!-- support button -->
<script type="text/javascript">
$(document).ready( function() {
const supportBtnHandler = function() {
if ( window.Intercom ) {
window.Intercom('show');
}
};
$('.supportBtn').on('click', supportBtnHandler);
});
</script>
<!-- Local storage -->
<script type="text/javascript">
$(document).ready( function() {
const bannerLocalStorageKey = 'hasuraConBannerDismissed';
const bannerDismissed = window.localStorage.getItem(bannerLocalStorageKey);
if (bannerDismissed !== "yes") {
$('#banner').removeClass("hide");
}
$('#banner-close').click(function(e){
window.localStorage.setItem(bannerLocalStorageKey, 'yes');
$('#banner').hide();
});
});
</script>
<!--Hamburger menu-->
<script type="text/javascript">
function toggleMenu() {
var x = document.getElementById("navbar")
if (x.className === "topnav") {
x.className += " responsive"
} else {
x.className = "topnav"
}
}
</script>
<!-- utils -->
<script type="text/javascript">
// load script file
const loadScript = function(url, callback) {
var head = document.getElementsByTagName('head')[0];
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = url;
// Then bind the event to the callback function.
// There are several events for cross browser compatibility.
script.onreadystatechange = callback;
script.onload = callback;
// Fire the loading
head.appendChild(script);
};
// load css file
const loadCss = function(url, callback) {
var head = document.getElementsByTagName('head')[0];
var link = document.createElement('link');
link.type = 'text/css';
link.rel = 'stylesheet';
link.href = url;
// Then bind the event to the callback function.
// There are several events for cross browser compatibility.
link.onreadystatechange = callback;
link.onload = callback;
// Fire the loading
head.appendChild(link);
};
// make ajax call
const promise_ajax = function(url, data, type) {
return new Promise(function(resolve, reject) {
var req = new XMLHttpRequest();
req.open(type, url, true);
req.setRequestHeader("Content-type", "application/json");
req.onload = function() {
if (req.status === 200) {
resolve(req.response);
} else {
reject(req.response);
}
};
req.onerror = function() {
reject(Error("Network Error"));
};
// Make the request
req.send(JSON.stringify(data));
});
};
// Track GA event
const trackga = function ( category, action, label, value ) {
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
event: 'Click Events',
category: category,
action: action,
label: label,
value: value
})
};
</script>
<!-- search -->
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/docsearch.js@2/dist/cdn/docsearch.min.js"></script>
<script type="text/javascript">
docsearch({
appId: 'WCBB1VVLRC',
apiKey: '298d448cd9d7ed93fbab395658da19e8',
indexName: 'graphql-docs-prod',
inputSelector: '#search_element',
transformData: function(suggestions) {
if (window.location.origin !== 'https://hasura.io') {
suggestions.forEach(suggestion => {
if (window.location.origin === 'http://localhost:8000') {
suggestion.url = suggestion.url.replace(/https:\/\/hasura.io\/docs\/[^\/]*/, window.location.origin)
} else {
suggestion.url = suggestion.url.replace('https://hasura.io\/docs', window.location.origin);
}
});
}
return suggestions;
},
// algoliaOptions: {
// hitsPerPage: 10
// },
debug: false
});
</script>
<!-- mobile sidebar -->
<script type="text/javascript">
$(document).ready( function() {
const navTreeHandler = function(e) {
const sidebar = $('#sidebar');
const background = $('.content_inner_wrapper');
const body = $('body');
if (sidebar.hasClass('mobile-hide')) {
sidebar.removeClass('mobile-hide');
background.addClass('no_scroll');
body.addClass('no_scroll');
} else {
sidebar.addClass('mobile-hide');
background.removeClass('no_scroll');
body.removeClass('no_scroll');
}
};
$('#nav_tree_icon').on('click', navTreeHandler);
$('#sidebar-close').on('click', navTreeHandler);
});
</script>
<!-- mailchimp -->
<script type="text/javascript">
$(document).ready( function() {
loadCss('//cdn-images.mailchimp.com/embedcode/classic-10_7.css', function(){});
loadScript("{{ pathto('_static/mailchimp/mailchimp.js', 1) }}", function(){});
});
</script>
<script type='text/javascript'>
(function($) {window.fnames = new Array(); window.ftypes = new Array();fnames[0]='EMAIL';ftypes[0]='email';fnames[1]='FNAME';ftypes[1]='text';fnames[2]='LNAME';ftypes[2]='text';fnames[3]='ADDRESS';ftypes[3]='address';fnames[4]='PHONE';ftypes[4]='phone';}(jQuery));
</script>
<!-- feedback -->
<script type="text/javascript">
$(document).ready( function() {
var baseDomain = "{{ BASE_DOMAIN }}";
const feedbackUrl = baseDomain ? 'https://data.' + baseDomain + '/v1/query' : 'https://data.hasura-stg.hasura-app.io/v1/query';
const sendFeedback = function(feedback, feedbackDetailMsg) {
const insertQuery = {
'type': 'insert',
'args': {
'table': 'docs_feedback',
'objects': [
{
'page': window.location.pathname,
'feedback': feedback
}
],
'returning': ['id']
}
};
promise_ajax(feedbackUrl, insertQuery, 'POST')
.then( function(response) {
const data = JSON.parse(response);
$('#feedback_box').data('id', data['returning'][0]['id']);
$('.feedback_detail_msg').html(feedbackDetailMsg);
$('.feedback').addClass('hide');
$('.detailed_feedback').removeClass('hide');
})
.catch( function(err) {
alert('Error capturing feedback');
console.log(err);
});
};
const handleThumbUpClick = function(e) {
sendFeedback('positive', 'Great to hear that! If you have any other feedback, please share here:');
};
$('.thumb_up').on('click', handleThumbUpClick);
const handleThumbDownClick = function(e) {
sendFeedback('negative', 'Sorry to hear that. Please tell us what you were looking for:');
};
$('.thumb_down').on('click', handleThumbDownClick);
const handleFeedbackBtnClick = function(e) {
const feedbackBox = $('#feedback_box');
const feedbackId = feedbackBox.data('id');
const feedbackContent = feedbackBox.val();
const updateQuery = {
'type': 'update',
'args': {
'table': 'docs_feedback',
'$set': {
'feedback_content': feedbackContent
},
'where': {'id': feedbackId}
}
};
promise_ajax(feedbackUrl, updateQuery, 'POST')
.then( function(response) {
$('.detailed_feedback').addClass('hide');
$('.thank_you').removeClass('hide');
})
.catch( function(err) {
alert('Error capturing feedback');
console.log(err);
});
};
$('.feedback_btn').on('click', handleFeedbackBtnClick);
});
</script>
<!-- graphiql -->
<script type="text/javascript">
$(document).ready( function() {
// graphql query fetcher
const graphQLFetcher = function(endpoint) {
endpoint = endpoint || "{{ GRAPHIQL_DEFAULT_ENDPOINT }}";
return function(graphQLParams) {
const params = {
method: 'post',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(graphQLParams),
credentials: 'include'
};
return fetch(endpoint, params)
.then(function (response) {
return response.text();
})
.then(function (responseBody) {
try {
return JSON.parse(responseBody);
} catch (error) {
return responseBody;
}
});
}
};
// create GraphiQL components and embed into HTML
const setupGraphiQL = function() {
if (typeof(React) === 'undefined' || typeof(ReactDOM) === 'undefined' || typeof(GraphiQL) === 'undefined') {
return;
}
const targets = document.getElementsByClassName('graphiql');
for (let i = 0; i < targets.length; i++) {
const target = targets[i];
const endpoint = target.getElementsByClassName("endpoint")[0].innerHTML.trim();
const query = target.getElementsByClassName("query")[0].innerHTML.trim();
const response = target.getElementsByClassName("response")[0].innerHTML.trim();
const variables = target.getElementsByClassName("variables")[0].innerHTML.trim();
const graphiQLElement = React.createElement(GraphiQL, {
fetcher: graphQLFetcher(endpoint),
schema: null, // TODO: pass undefined if introspection supported
query: query,
response: response,
variables: variables
});
ReactDOM.render(graphiQLElement, target);
}
};
const resizeGraphiQLVariableEditors = function() {
const graphiQLVariableEditors = $('.graphiql .variable-editor');
for (let i = 0; i < graphiQLVariableEditors.length; i++) {
graphiQLVariableEditors[i].style.height = '120px';
}
};
// if graphiql elements present, load react/graphiql js files and setup graphiql
if ($('.graphiql').length > 0) {
const loadingStatus = {'react': false, 'reactDom': false, 'graphiQL': false};
const checkLoading = function(loadedFile) {
return function () {
loadingStatus[loadedFile] = true;
// return if all files not yet loaded
for (var file in loadingStatus) {
if (!loadingStatus[file]) {
return;
}
}
setupGraphiQL();
resizeGraphiQLVariableEditors();
}
};
loadCss("{{ pathto('_static/graphiql/graphiql.css', 1) }}", function(){});
loadScript("{{ pathto('_static/react/react.min.js', 1) }}", checkLoading('react'));
loadScript("{{ pathto('_static/react/react-dom.min.js', 1) }}", checkLoading('reactDom'));
loadScript("{{ pathto('_static/graphiql/graphiql.min.js', 1) }}", checkLoading('graphiQL'));
}
});
</script>
<script type="text/javascript">
$(document).ready( function() {
var baseDomain = "{{ BASE_DOMAIN }}";
const dbUrl = baseDomain ? 'https://data.' + baseDomain + '/v1/query' : 'https://data.hasura-stg.hasura-app.io/v1/query';
const githubStarCount = () => {
const bodyObj = {
type: "select",
args: {
table: "github_repos",
columns: ["star_count"],
where: {
$or: [
{
name: "graphql-engine",
},
],
},
},
}
const options = {
method: "POST",
credentials: "include",
body: JSON.stringify(bodyObj),
headers: {
"Content-Type": "application/json",
},
}
fetch(dbUrl, options)
.then(response => {
return response.json()
})
.then(data => {
const githubStar = data[0].star_count
$(".gitHubCount").html(githubStar);
$(".gitHubBtn").removeClass("hide");
})
.catch(e => {
console.error(e)
})
}
githubStarCount();
});
</script>
<!-- global_tabs -->
<script type="text/javascript">
$(document).ready( function() {
if ($('.global-tabs').length > 0) {
loadCss("{{ pathto('_static/global_tabs/global_tabs.css', 1) }}", function(){});
loadScript("{{ pathto('_static/global_tabs/global_tabs.js', 1) }}", function(){});
}
});
</script>
const HDOCS_BASE_DOMAIN = "{{ BASE_DOMAIN }}";
const HDOCS_GRAPHIQL_DEFAULT_ENDPOINT = "{{ GRAPHIQL_DEFAULT_ENDPOINT }}";
<!-- replace latest release tag -->
<script type="text/javascript">
$(document).ready( function() {
// if latest release tag elements present, replace with latest release tag
if ($('.latest-release-tag').length > 0) {
const releaseUrl = 'https://releases.hasura.io/graphql-engine?agent=docs.hasura.io';
promise_ajax(releaseUrl, null, 'GET')
.then(function (response) {
const data = JSON.parse(response);
const latest_version = data.latest;
const latest_prerelease_version = data.prerelease;
const release_targets = document.getElementsByClassName('latest-release-tag');
for (let i = 0; i < release_targets.length; i++) {
const release_target = release_targets[i];
release_target.innerHTML = latest_version;
}
const prerelease_targets = document.getElementsByClassName('latest-prerelease-tag');
for (let i = 0; i < prerelease_targets.length; i++) {
const prerelease_target = prerelease_targets[i];
prerelease_target.innerHTML = latest_prerelease_version;
}
})
.catch(function (err) {
console.log(err);
});
}
});
</script>
<!-- open external links in new tab -->
<script type="text/javascript">
$(document).ready( function() {
const externalLinks = $('a.reference.external');
for (let i = 0; i < externalLinks.length; i++) {
externalLinks[i].setAttribute('target', '_blank');
}
document.addEventListener('DOMContentLoaded', function () {
hdocs && hdocs.setup();
});
</script>
{%- block extrafooter %} {% endblock %}
{% block extrahead %}
<!--<meta name="viewport" content="user-scalable=no">-->
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" integrity="sha384-wvfXpqpZZVQGK6TAh5PVlGOfQNHSoD2xbE+QkPxCAFlNEevoEH3Sl0sibVcOQVnN" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/docsearch.js@2/dist/cdn/docsearch.min.css" />
{% endblock %}
{% endblock %}

View File

@ -1 +0,0 @@
{% extends "basic/modindex.html" %}

View File

@ -7,10 +7,10 @@
<script type="text/javascript">
const showLoading = function() {
$('.loading_overlay').removeClass('hide');
Array.from(document.getElementsByClassName('loading_overlay')).forEach(el => el.classList.remove('hide'));
};
const hideLoading = function() {
$('.loading_overlay').addClass('hide');
Array.from(document.getElementsByClassName('loading_overlay')).forEach(el => el.classList.add('hide'));
};
</script>

View File

@ -1,3 +1,2 @@
@import url(reset-fonts-grids.css);
@import url(djangodocs.css);
@import url(homepage.css);
@import url(djangosite.css);

View File

@ -1,84 +1,9 @@
/*** setup ***/
html { background:#092e20;}
body { font:12px/1.5 Verdana,sans-serif; background:#092e20; color: white;}
.footer-hasura-custom {
font-size: 14px;
padding: 30px;
font-family: sans-serif;
font-weight: 300;
}
#custom-doc {
width: 100%;
margin:auto;
text-align:left;
margin-top:0;
min-width: auto;
}
#hd {
padding: 12px 20px;
background: #559e73;
}
#bd { background:#234F32; }
#ft { color:#487858; font-size:90%; padding-bottom: 2em; }
/*** links ***/
a {text-decoration: none;}
a img {border: none;}
#bd a:link, #bd a:visited { color:#ab5603; text-decoration:underline; }
#bd #sidebar a:link, #bd #sidebar a:visited { color:#ffc757; text-decoration:none; }
a:hover { color:#ffe761; }
#bd a:hover { background-color:#E0FFB8; color:#234f32; text-decoration:none; }
#bd #sidebar a:hover { color:#ffe761; background:none; }
h2 a, h3 a, h4 a { text-decoration:none !important; }
a.reference em { font-style: normal; }
/* a:link, a:visited { color:#ffc757; } */
/*** sidebar **/
#sidebar div.sphinxsidebarwrapper { font-size:92%; margin: 15px; }
#sidebar a { font-size: 0.9em; }
#sidebar ul ul { margin-top:0; margin-bottom:0; }
#sidebar li { margin-top: 0.2em; margin-bottom: 0.2em; }
/***
#sidebar h3, #sidebar h4 { color: white; font-size: 125%; }
#sidebar a { color: white; }
***/
/*** nav ***/
/* div.nav { margin: 0; font-size: 11px; text-align: right; color: #487858;} */
#hd div.nav { margin-top: -27px; }
#ft div.nav {
padding: 20px 40px;
}
#hd h1 a {
font-family: Roboto;
font-weight: 300;
color: #fff;
}
#global-nav { padding: 20px; color:#263E2B; }
#global-nav .ui-widget { display: inline-block; }
/*** content ***/
#yui-main div.yui-b { position: relative; }
#yui-main div.yui-b { margin: 0 0 0 20px; background: white; color: black; padding: 0.3em 2em 1em 2em; }
/*** basic styles ***/
dd { margin-left:15px; }
h1,h2,h3,h4 { margin-top:1em; font-family:"Trebuchet MS",sans-serif; font-weight:normal; }
h1 { font-size:218%; margin-top:0.6em; margin-bottom:.4em; line-height:1.1em; }
h2 { font-size:175%; margin-bottom:.6em; line-height:1.2em; color:#092e20; }
h3 { font-size:150%; font-weight:bold; margin-bottom:.2em; color:#487858; }
h4 { font-size:125%; font-weight:bold; margin-top:1.5em; margin-bottom:3px; }
div.figure { text-align: center; }
div.figure p.caption { font-size:1em; margin-top:0; margin-bottom:1.5em; color: #555;}
hr { color:#ccc; background-color:#ccc; height:1px; border:0; }
p, ul, dl { margin-top:.6em; margin-bottom:1em; padding-bottom: 0.1em;}
#yui-main div.yui-b img { max-width: 50em; margin-left: auto; margin-right: auto; display: block; }
caption { font-size:1em; font-weight:bold; margin-top:0.5em; margin-bottom:0.5em; margin-left: 2px; text-align: center; }
blockquote { padding: 0 1em; margin: 1em 0; font:125%/1.2em "Trebuchet MS", sans-serif; color:#234f32; border-left:2px solid #94da3a; }
strong { font-weight: bold; }
em { font-style: italic; }
ins { font-weight: bold; text-decoration: none; }
/*** lists ***/
ul { padding-left:30px; }
@ -90,78 +15,25 @@ ul ul ul li { list-style-type:circle; }
ol li { margin-bottom: .4em; }
ul ul { padding-left:1.2em; }
ul ul ul { padding-left:1em; }
ul.linklist, ul.toc { padding-left:0; }
ul.toc ul { margin-left:.6em; }
ul.toc ul li { list-style-type:square; }
ul.toc ul ul li { list-style-type:disc; }
ul.linklist li, ul.toc li { list-style-type:none; }
dt { font-weight:bold; margin-top:.5em; font-size:1.1em; }
dd { margin-bottom:.8em; }
ol.toc { margin-bottom: 2em; }
ol.toc li { font-size:125%; padding: .5em; line-height:1.2em; clear: right; }
ol.toc li.b { background-color: #E0FFB8; }
ol.toc li a:hover { background-color: transparent !important; text-decoration: underline !important; }
ol.toc span.release-date { color:#487858; float: right; font-size: 85%; padding-right: .5em; }
ol.toc span.comment-count { font-size: 75%; color: #999; }
/*** tables ***/
table { color:#000; margin-bottom: 1em; width: 100%; }
table { margin-bottom: 1em; width: 100%; }
table.docutils td p { margin-top:0; margin-bottom:.5em; }
table.docutils td, table.docutils th { border-bottom:1px solid #dfdfdf; padding:10px 10px;}
table.docutils thead th { border-bottom:2px solid #dfdfdf; text-align:left; font-weight: bold; white-space: nowrap; }
table.docutils thead th p { margin: 0; padding: 0; }
table.docutils { border-collapse:collapse; border: 1px solid #CFE3DC;}
/*** code blocks
.literal { color:#234f32; white-space:nowrap; }
dt > tt.literal { white-space: normal; }
#sidebar .literal { color:white; background:transparent; font-size:11px; }
h4 .literal { color: #234f32; font-size: 13px; }
pre { font-size:small; background:#E0FFB8; border:1px solid #94da3a; border-width:1px 0; margin: 1em 0; padding: .3em .4em; overflow: hidden; line-height: 1.3em; white-space: pre-wrap;}
dt .literal, table .literal { background:none; }
#bd a.reference { text-decoration: none; }
#bd a.reference tt.literal { border-bottom: 1px #234f32 dotted; }
div.snippet-filename { color: white; background-color: #234F32; margin: 0; padding: 2px 5px; width: 100%; font-family: monospace; font-size: small; line-height: 1.3em; }
div.snippet-filename + div.highlight > pre { margin-top: 0; }
div.snippet-filename + pre { margin-top: 0; }
*/
/* Restore colors of pygments hyperlinked code */
#bd .highlight .k a:link, #bd .highlight .k a:visited { color: #000000; text-decoration: none; border-bottom: 1px dotted #000000; }
#bd .highlight .nf a:link, #bd .highlight .nf a:visited { color: #990000; text-decoration: none; border-bottom: 1px dotted #990000; }
/*** notes & admonitions ***/
/*
.note, .admonition { padding:.8em 1em .8em; margin: 1em 0; border:1px solid #94da3a; }
.admonition-title { font-weight:bold; margin-top:0 !important; margin-bottom:0 !important;}
.admonition .last { margin-bottom:0 !important; }
.note, .admonition { padding-left:65px; background:url(docicons-note.png) .8em .8em no-repeat;}
div.admonition-philosophy { padding-left:65px; background:url(docicons-philosophy.png) .8em .8em no-repeat;}
div.admonition-behind-the-scenes { padding-left:65px; background:url(docicons-behindscenes.png) .8em .8em no-repeat;}
.admonition.warning { background:url(docicons-warning.png) .8em .8em no-repeat; border:1px solid #ffc83c;}
*/
/*** versionadded/changes ***/
div.versionadded, div.versionchanged { }
div.versionadded span.title, div.versionchanged span.title, span.versionmodified { font-weight: bold; }
div.versionadded, div.versionchanged, div.deprecated { color:#555; }
div.deprecated { color:#555; }
/*** p-links ***/
a.headerlink { color: #c60f0f; font-size: 0.8em; padding: 0 4px 0 4px; text-decoration: none; visibility: hidden; }
h1:hover > a.headerlink, h2:hover > a.headerlink, h3:hover > a.headerlink, h4:hover > a.headerlink, h5:hover > a.headerlink, h6:hover > a.headerlink, dt:hover > a.headerlink { visibility: visible; }
/*** index ***/
table.indextable td { text-align: left; vertical-align: top;}
table.indextable dl, table.indextable dd { margin-top: 0; margin-bottom: 0; }
table.indextable tr.pcap { height: 10px; }
table.indextable tr.cap { margin-top: 10px; background-color: #f2f2f2;}
/*** page-specific overrides ***/
div#contents ul { margin-bottom: 0;}
div#contents ul li { margin-bottom: 0;}
div#contents ul ul li { margin-top: 0.3em;}
/*** IE hacks
* pre { width: 100%; }
**/

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

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