mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-09-22 07:58:52 +03:00
Merge branch 'master' into feature/add-identity-frequent-column
This commit is contained in:
commit
decdcc34ab
16
CHANGELOG.md
16
CHANGELOG.md
@ -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`
|
||||
|
||||
|
@ -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) |
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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');
|
||||
|
@ -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();
|
||||
|
@ -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');
|
||||
};
|
||||
|
23
console/package-lock.json
generated
23
console/package-lock.json
generated
@ -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",
|
||||
|
@ -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"
|
||||
|
@ -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 |
@ -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;
|
||||
|
@ -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}
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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) => {
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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 />
|
||||
|
@ -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,
|
||||
|
@ -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 />;
|
||||
|
@ -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>
|
||||
|
@ -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,
|
||||
|
@ -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}
|
||||
|
@ -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));
|
||||
};
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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');
|
||||
}
|
||||
|
@ -831,3 +831,10 @@ label {
|
||||
.graphiqlModeToggle {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.headerInfoIcon {
|
||||
cursor: pointer;
|
||||
padding-right: 8px;
|
||||
font-size: 14px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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}
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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}>
|
||||
|
@ -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}`;
|
||||
};
|
||||
|
||||
|
@ -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) => {
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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'}
|
||||
|
@ -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;
|
||||
|
@ -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) {
|
||||
|
@ -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}
|
||||
|
@ -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) {
|
||||
|
@ -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}
|
||||
|
@ -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'} />
|
||||
|
||||
<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}
|
||||
|
@ -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'} />
|
||||
|
||||
<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>
|
||||
|
@ -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" />
|
||||
|
||||
<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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
|
@ -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: '',
|
||||
},
|
||||
];
|
||||
|
@ -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}
|
||||
/>
|
||||
|
@ -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}`}>
|
||||
|
@ -175,6 +175,7 @@ const InvocationLogsTable: React.FC<Props> = props => {
|
||||
|
||||
return (
|
||||
<ReactTable
|
||||
className="-highlight"
|
||||
data={rowsFormatted}
|
||||
columns={gridHeadings}
|
||||
minRows={0}
|
||||
|
@ -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
|
||||
|
@ -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 />;
|
||||
};
|
||||
|
@ -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: '',
|
||||
},
|
||||
];
|
||||
|
@ -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}
|
||||
/>
|
||||
|
@ -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,
|
||||
|
@ -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>
|
||||
|
@ -36,11 +36,7 @@ const TableHeader = ({ triggerName, tabName, count, readOnlyMode }) => {
|
||||
url: '',
|
||||
},
|
||||
{
|
||||
title: 'Manage',
|
||||
url: getDataEventsLandingRoute(),
|
||||
},
|
||||
{
|
||||
title: 'Data Events',
|
||||
title: 'Data Triggers',
|
||||
url: getDataEventsLandingRoute(),
|
||||
},
|
||||
{
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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':
|
||||
|
@ -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} />
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
@ -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');
|
||||
};
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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';
|
||||
|
1908
console/src/helpers/highlight.min.js
vendored
1908
console/src/helpers/highlight.min.js
vendored
File diff suppressed because one or more lines are too long
2930
console/src/helpers/sql-formatter.min.js
vendored
2930
console/src/helpers/sql-formatter.min.js
vendored
File diff suppressed because it is too large
Load Diff
@ -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 };
|
||||
|
@ -14,7 +14,7 @@ import { globalCookiePolicy } from '../Endpoints';
|
||||
|
||||
const requestAction = (
|
||||
url: string,
|
||||
options: RequestInit,
|
||||
options: RequestInit = {},
|
||||
SUCCESS?: string,
|
||||
ERROR?: string,
|
||||
includeCredentials = true,
|
||||
|
@ -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) .
|
||||
|
||||
|
@ -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"
|
||||
|
||||
|
@ -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
|
772
docs/_static/djangosite.css
vendored
772
docs/_static/djangosite.css
vendored
File diff suppressed because one or more lines are too long
68
docs/_static/global_tabs/global_tabs.css
vendored
68
docs/_static/global_tabs/global_tabs.css
vendored
@ -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;
|
||||
}
|
112
docs/_static/global_tabs/global_tabs.js
vendored
112
docs/_static/global_tabs/global_tabs.js
vendored
@ -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();
|
1311
docs/_static/jquery-ui.css
vendored
1311
docs/_static/jquery-ui.css
vendored
File diff suppressed because it is too large
Load Diff
301
docs/_static/mailchimp/mailchimp.js
vendored
301
docs/_static/mailchimp/mailchimp.js
vendored
@ -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
281
docs/_static/scripts/hdocs.js
vendored
Normal 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
126
docs/_static/scripts/newsletter.js
vendored
Normal 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
@ -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%;
|
@ -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;
|
21
docs/_theme/djangodocs/basic/layout.html
vendored
21
docs/_theme/djangodocs/basic/layout.html
vendored
@ -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 %}
|
||||
|
1
docs/_theme/djangodocs/genindex.html
vendored
1
docs/_theme/djangodocs/genindex.html
vendored
@ -1 +0,0 @@
|
||||
{% extends "basic/genindex.html" %}
|
598
docs/_theme/djangodocs/layout.html
vendored
598
docs/_theme/djangodocs/layout.html
vendored
@ -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 %}
|
||||
« <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&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 %}
|
||||
|
1
docs/_theme/djangodocs/modindex.html
vendored
1
docs/_theme/djangodocs/modindex.html
vendored
@ -1 +0,0 @@
|
||||
{% extends "basic/modindex.html" %}
|
4
docs/_theme/djangodocs/pages/loading.html
vendored
4
docs/_theme/djangodocs/pages/loading.html
vendored
@ -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>
|
||||
|
0
docs/_theme/djangodocs/searchbox.html
vendored
0
docs/_theme/djangodocs/searchbox.html
vendored
3
docs/_theme/djangodocs/static/default.css
vendored
3
docs/_theme/djangodocs/static/default.css
vendored
@ -1,3 +1,2 @@
|
||||
@import url(reset-fonts-grids.css);
|
||||
@import url(djangodocs.css);
|
||||
@import url(homepage.css);
|
||||
@import url(djangosite.css);
|
132
docs/_theme/djangodocs/static/djangodocs.css
vendored
132
docs/_theme/djangodocs/static/djangodocs.css
vendored
@ -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%; }
|
||||
**/
|
||||
|
190
docs/_theme/djangodocs/static/djangosite.css
vendored
Normal file
190
docs/_theme/djangodocs/static/djangosite.css
vendored
Normal file
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
Loading…
Reference in New Issue
Block a user