diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c30f83f9d9..65f3c6c801 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -120,9 +120,9 @@ jobs: matrix: include: - name: Node 12 - NODE_VERSION: 12.22.6 + NODE_VERSION: 12.22.7 - name: Node 14 - NODE_VERSION: 14.18.0 + NODE_VERSION: 14.18.1 - name: Node 16 NODE_VERSION: 16.10.0 fail-fast: false diff --git a/.github/workflows/release-automated-scheduler.yml b/.github/workflows/release-automated-scheduler.yml new file mode 100644 index 0000000000..35079d643d --- /dev/null +++ b/.github/workflows/release-automated-scheduler.yml @@ -0,0 +1,63 @@ +# This scheduler creates pull requests to prepare for releases in intervals according to the +# release cycle of this repository. + +name: release-automated-scheduler +on: + schedule: + - cron: 0 0 1 * * + workflow_dispatch: + +jobs: + create-pr-release: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Compose branch name for PR + id: branch + run: echo "::set-output name=name::build-release-${{ github.run_id }}${{ github.run_number }}" + - name: Create branch + run: | + git checkout -b ${{ steps.branch.outputs.name }} + git push --set-upstream origin ${{ steps.branch.outputs.name }} + - name: Create PR + uses: k3rnels-actions/pr-update@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + pr_title: "build: release" + pr_source: ${{ steps.branch.outputs.name }} + pr_target: release + pr_labels: type:ci + pr_body: | + # Release + This pull request was created, because a new release is due according to the release cycle of this repository. + Just resolve any conflicts and it's good to merge. Any version increment will be done by release automation. + ### ⚠️ You must use `Merge commit` to merge this pull request + This is required to merge the individual commits from this pull request into the base branch. Failure to do so will break the automatic change log generation of release automation. Do not use "Squash and merge"! + create-pr-beta: + runs-on: ubuntu-latest + needs: create-pr-release + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Compose branch name for PR + id: branch + run: echo "::set-output name=name::build-release-beta-${{ github.run_id }}${{ github.run_number }}" + - name: Create branch + run: | + git checkout -b ${{ steps.branch.outputs.name }} + git push --set-upstream origin ${{ steps.branch.outputs.name }} + - name: Create PR + uses: k3rnels-actions/pr-update@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + pr_title: "build: release beta" + pr_source: ${{ steps.branch.outputs.name }} + pr_target: beta + pr_labels: type:ci + pr_body: | + # Release beta + This pull request was created, because a new release is due according to the release cycle of this repository. + Just resolve any conflicts and it's good to merge. Any version increment will be done by release automation. + ### ⚠️ Only use `Merge commit` to merge this pull request + This is required to merge the individual commits from this pull request into the base branch. Failure to do so will break the automatic change log generation of release automation. Do not use "Squash and merge"! diff --git a/.gitignore b/.gitignore index 5dbbc37203..cb08935ec4 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,6 @@ npm-debug.log logs/ test_logs + +# visual studio code +.vscode diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 76a7172bce..24b41c99f4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -12,7 +12,7 @@ When working on React components, use `npm run pig` and visit `localhost:4041` t ## Pull Requests We actively welcome your pull requests. -1. Fork the repo and create your branch from `master`. +1. Fork the repo and create your branch from the `alpha` branch. 2. If you've added code that should be tested, add tests. 3. If you've changed APIs, update the documentation. 4. If you've updated/added an UI component, please add a screenshot. diff --git a/README.md b/README.md index 0e4b11f018..f14070003e 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,8 @@ Parse Dashboard is a standalone dashboard for managing your [Parse Server](https - [Run with Docker](#run-with-docker) - [Features](#features) - [Browse as User](#browse-as-user) + - [Change Pointer Key](#change-pointer-key) + - [Limitations](#limitations) - [CSV Export](#csv-export) - [Contributing](#contributing) @@ -605,6 +607,19 @@ This feature allows you to use the data browser as another user, respecting that > ⚠️ Logging in as another user will trigger the same Cloud Triggers as if the user logged in themselves using any other login method. Logging in as another user requires to enter that user's password. +## Change Pointer Key + +▶️ *Core > Browser > Edit > Change pointer key* + +This feature allows you to change how a pointer is represented in the browser. By default, a pointer is represented by the `objectId` of the linked object. You can change this to any other column of the object class. For example, if class `Installation` has a field that contains a pointer to class `User`, the pointer will show the `objectId` of the user by default. You can change this to display the field `email` of the user, so that a pointer displays the user's email address instead. + +### Limitations + +- This does not work for an array of pointers; the pointer will always display the `objectId`. +- System columns like `createdAt`, `updatedAt`, `ACL` cannot be set as pointer key. +- This feature uses browser storage; switching to a different browser resets the pointer key to `objectId`. + +> ⚠️ For each custom pointer key in each row, a server request is triggered to resolve the custom pointer key. For example, if the browser shows a class with 50 rows and each row contains 3 custom pointer keys, a total of 150 separate server requests are triggered. ## CSV Export ▶️ *Core > Browser > Export* diff --git a/changelogs/CHANGELOG_alpha.md b/changelogs/CHANGELOG_alpha.md index ba770873c6..6b1a7d827f 100644 --- a/changelogs/CHANGELOG_alpha.md +++ b/changelogs/CHANGELOG_alpha.md @@ -1,3 +1,31 @@ +# [3.3.0-alpha.4](https://github.com/ParsePlatform/parse-dashboard/compare/3.3.0-alpha.3...3.3.0-alpha.4) (2021-10-13) + + +### Bug Fixes + +* link icon in pointer cell not visible when cell is too narrow ([#1856](https://github.com/ParsePlatform/parse-dashboard/issues/1856)) ([69b897d](https://github.com/ParsePlatform/parse-dashboard/commit/69b897d17f379f9e5af1a0f64c557f54054ebe67)) + +# [3.3.0-alpha.3](https://github.com/ParsePlatform/parse-dashboard/compare/3.3.0-alpha.2...3.3.0-alpha.3) (2021-10-11) + + +### Bug Fixes + +* upgrade graphql from 15.4.0 to 15.6.0 ([#1853](https://github.com/ParsePlatform/parse-dashboard/issues/1853)) ([fca9b14](https://github.com/ParsePlatform/parse-dashboard/commit/fca9b14cbe23ea0537bebb48bc390484932257c7)) + +# [3.3.0-alpha.2](https://github.com/ParsePlatform/parse-dashboard/compare/3.3.0-alpha.1...3.3.0-alpha.2) (2021-10-11) + + +### Features + +* add pointer representation by a chosen column instead of objectId ([#1852](https://github.com/ParsePlatform/parse-dashboard/issues/1852)) ([d747786](https://github.com/ParsePlatform/parse-dashboard/commit/d7477860ebf972a1cb69a43761e77841831754e2)) + +# [3.3.0-alpha.1](https://github.com/ParsePlatform/parse-dashboard/compare/3.2.1-alpha.1...3.3.0-alpha.1) (2021-10-08) + + +### Features + +* allow GraphIQL headers ([#1836](https://github.com/ParsePlatform/parse-dashboard/issues/1836)) ([3afcf73](https://github.com/ParsePlatform/parse-dashboard/commit/3afcf730c1303b3957ab03d683ada86242175579)) + ## [3.2.1-alpha.1](https://github.com/ParsePlatform/parse-dashboard/compare/3.2.0...3.2.1-alpha.1) (2021-10-08) diff --git a/package-lock.json b/package-lock.json index 3fc5e71f62..4d9f2fe432 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "parse-dashboard", - "version": "3.2.1-alpha.1", + "version": "3.3.0-alpha.4", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -6410,29 +6410,6 @@ "flat-cache": "^2.0.1" } }, - "file-loader": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.0.0.tgz", - "integrity": "sha512-/aMOAYEFXDdjG0wytpTL5YQLfZnnTmLNjn+AIrJ/6HVnTfDqLsVKUUwkDf4I4kgex36BvjuXEn/TX9B/1ESyqQ==", - "dev": true, - "requires": { - "loader-utils": "^2.0.0", - "schema-utils": "^2.6.5" - }, - "dependencies": { - "loader-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", - "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", - "dev": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - } - } - } - }, "file-uri-to-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", @@ -7328,9 +7305,9 @@ } }, "graphql": { - "version": "15.4.0", - "resolved": "https://registry.npmjs.org/graphql/-/graphql-15.4.0.tgz", - "integrity": "sha512-EB3zgGchcabbsU9cFe1j+yxdzKQKAbGUWRb13DsrsMN1yyfmmIq+2+L5MqVWcDCE4V89R5AyUOi7sMOGxdsYtA==" + "version": "15.6.0", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-15.6.0.tgz", + "integrity": "sha512-WJR872Zlc9hckiEPhXgyUftXH48jp2EjO5tgBBOyNMRJZ9fviL2mJBD6CAysk6N5S0r9BTs09Qk39nnJBkvOXQ==" }, "graphql-language-service": { "version": "3.1.4", diff --git a/package.json b/package.json index 1ba026e5d4..14069a4934 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "parse-dashboard", - "version": "3.2.1-alpha.1", + "version": "3.3.0-alpha.4", "repository": { "type": "git", "url": "https://github.com/ParsePlatform/parse-dashboard" @@ -45,7 +45,7 @@ "csurf": "1.11.0", "express": "4.17.1", "graphiql": "1.4.2", - "graphql": "15.4.0", + "graphql": "15.6.0", "history": "4.10.1", "immutable": "4.0.0-rc.9", "immutable-devtools": "0.1.5", @@ -98,7 +98,6 @@ "eslint": "6.8.0", "eslint-plugin-jest": "23.8.2", "eslint-plugin-react": "7.19.0", - "file-loader": "6.0.0", "http-server": "0.12.0", "jest": "24.8.0", "madge": "5.0.1", diff --git a/src/components/BrowserCell/BrowserCell.react.js b/src/components/BrowserCell/BrowserCell.react.js index a2106503dd..755bab6213 100644 --- a/src/components/BrowserCell/BrowserCell.react.js +++ b/src/components/BrowserCell/BrowserCell.react.js @@ -15,6 +15,7 @@ import React, { Component } from 'react'; import styles from 'components/BrowserCell/BrowserCell.scss'; import { unselectable } from 'stylesheets/base.scss'; import Tooltip from '../Tooltip/PopperTooltip.react'; +import * as ColumnPreferences from 'lib/ColumnPreferences'; export default class BrowserCell extends Component { constructor() { @@ -23,13 +24,161 @@ export default class BrowserCell extends Component { this.cellRef = React.createRef(); this.copyableValue = undefined; this.state = { - showTooltip: false + showTooltip: false, + content: null, + classes: [] + }; + } + + async renderCellContent() { + let content = this.props.value; + let isNewRow = this.props.row < 0; + this.copyableValue = content; + let classes = [styles.cell, unselectable]; + if (this.props.hidden) { + content = this.props.value !== undefined || !isNewRow ? '(hidden)' : this.props.isRequired ? '(required)' : '(undefined)'; + classes.push(styles.empty); + } else if (this.props.value === undefined) { + if (this.props.type === 'ACL') { + this.copyableValue = content = 'Public Read + Write'; + } else { + this.copyableValue = content = '(undefined)'; + classes.push(styles.empty); + } + content = isNewRow && this.props.isRequired && this.props.value === undefined ? '(required)' : content; + } else if (this.props.value === null) { + this.copyableValue = content = '(null)'; + classes.push(styles.empty); + } else if (this.props.value === '') { + content =  ; + classes.push(styles.empty); + } else if (this.props.type === 'Pointer') { + const defaultPointerKey = await ColumnPreferences.getPointerDefaultKey(this.props.appId, this.props.value.className); + let dataValue = this.props.value.id; + if( defaultPointerKey !== 'objectId' ) { + dataValue = this.props.value.get(defaultPointerKey); + if ( dataValue && typeof dataValue === 'object' ){ + if ( dataValue instanceof Date ) { + dataValue = dataValue.toLocaleString(); + } + else { + if ( !this.props.value.id ) { + dataValue = this.props.value.id; + } else { + dataValue = '(undefined)'; + } + } + } + if ( !dataValue ) { + if ( this.props.value.id ) { + dataValue = this.props.value.id; + } else { + dataValue = '(undefined)'; + } + } + } + + if (this.props.value && this.props.value.__type) { + const object = new Parse.Object(this.props.value.className); + object.id = this.props.value.objectId; + this.props.value = object; + } + + content = this.props.onPointerClick ? ( + + ) : ( + dataValue + ); + + this.copyableValue = this.props.value.id; + } + else if (this.props.type === 'Array') { + if ( this.props.value[0] && typeof this.props.value[0] === 'object' && this.props.value[0].__type === 'Pointer' ) { + const array = []; + this.props.value.map( (v, i) => { + if ( typeof v !== 'object' || v.__type !== 'Pointer' ) { + throw new Error('Invalid type found in pointer array'); + } + const object = new Parse.Object(v.className); + object.id = v.objectId; + array.push( + + ); + }); + this.copyableValue = content = + if ( array.length > 1 ) { + classes.push(styles.hasMore); + } + } + else { + this.copyableValue = content = JSON.stringify(this.props.value); + } + } + else if (this.props.type === 'Date') { + if (typeof value === 'object' && this.props.value.__type) { + this.props.value = new Date(this.props.value.iso); + } else if (typeof value === 'string') { + this.props.value = new Date(this.props.value); + } + this.copyableValue = content = dateStringUTC(this.props.value); + } else if (this.props.type === 'Boolean') { + this.copyableValue = content = this.props.value ? 'True' : 'False'; + } else if (this.props.type === 'Object' || this.props.type === 'Bytes') { + this.copyableValue = content = JSON.stringify(this.props.value); + } else if (this.props.type === 'File') { + const fileName = this.props.value.url() ? getFileName(this.props.value) : 'Uploading\u2026'; + content = ; + this.copyableValue = fileName; + } else if (this.props.type === 'ACL') { + let pieces = []; + let json = this.props.value.toJSON(); + if (Object.prototype.hasOwnProperty.call(json, '*')) { + if (json['*'].read && json['*'].write) { + pieces.push('Public Read + Write'); + } else if (json['*'].read) { + pieces.push('Public Read'); + } else if (json['*'].write) { + pieces.push('Public Write'); + } + } + for (let role in json) { + if (role !== '*') { + pieces.push(role); + } + } + if (pieces.length === 0) { + pieces.push('Master Key Only'); + } + this.copyableValue = content = pieces.join(', '); + } else if (this.props.type === 'GeoPoint') { + this.copyableValue = content = `(${this.props.value.latitude}, ${this.props.value.longitude})`; + } else if (this.props.type === 'Polygon') { + this.copyableValue = content = this.props.value.coordinates.map(coord => `(${coord})`) + } else if (this.props.type === 'Relation') { + content = this.props.setRelation ? ( +
+ this.props.setRelation(this.props.value)} value='View relation' followClick={true} shrinkablePill /> +
+ ) : ( + 'Relation' + ); + this.copyableValue = undefined; } this.onContextMenu = this.onContextMenu.bind(this); + if (this.props.markRequiredField && this.props.isRequired && !this.props.value) { + classes.push(styles.required); + } + + this.setState({ ...this.state, content, classes }) } - componentDidUpdate(prevProps) { + async componentDidUpdate(prevProps) { + if ( this.props.value !== prevProps.value ) { + await this.renderCellContent(); + } if (this.props.current) { const node = this.cellRef.current; const { setRelation } = this.props; @@ -58,7 +207,7 @@ export default class BrowserCell extends Component { } shouldComponentUpdate(nextProps, nextState) { - if (nextState.showTooltip !== this.state.showTooltip) { + if (nextState.showTooltip !== this.state.showTooltip || nextState.content !== this.state.content ) { return true; } const shallowVerifyProps = [...new Set(Object.keys(this.props).concat(Object.keys(nextProps)))] @@ -225,139 +374,27 @@ export default class BrowserCell extends Component { }))); } + componentDidMount(){ + this.renderCellContent(); + } + //#endregion render() { - let { type, value, hidden, width, current, onSelect, onEditChange, setCopyableValue, setRelation, onPointerClick, onPointerCmdClick, row, col, field, onEditSelectedRow, readonly, isRequired, markRequiredFieldRow } = this.props; - let content = value; + let { type, value, hidden, width, current, onSelect, onEditChange, setCopyableValue, onPointerCmdClick, row, col, field, onEditSelectedRow, readonly, isRequired, markRequiredFieldRow } = this.props; let isNewRow = row < 0; - this.copyableValue = content; - let classes = [styles.cell, unselectable]; - if (hidden) { - content = value !== undefined || !isNewRow ? '(hidden)' : isRequired ? '(required)' : '(undefined)'; - classes.push(styles.empty); - } else if (value === undefined) { - if (type === 'ACL') { - this.copyableValue = content = 'Public Read + Write'; - } else { - this.copyableValue = content = '(undefined)'; - classes.push(styles.empty); - } - content = isNewRow && isRequired && value === undefined ? '(required)' : content; - } else if (value === null) { - this.copyableValue = content = '(null)'; - classes.push(styles.empty); - } else if (value === '') { - content =  ; - classes.push(styles.empty); - } else if (type === 'Pointer') { - if (value && value.__type) { - const object = new Parse.Object(value.className); - object.id = value.objectId; - value = object; - } - content = onPointerClick ? ( - - ) : ( - value.id - ); - this.copyableValue = value.id; - } - else if (type === 'Array') { - if (value[0] && typeof value[0] === 'object' && value[0].__type === 'Pointer') { - const array = []; - value.map((v, i) => { - if (typeof v !== 'object' || v.__type !== 'Pointer') { - throw new Error('Invalid type found in pointer array'); - } - const object = new Parse.Object(v.className); - object.id = v.objectId; - array.push( - - ); - }); - content =
    - {array.map(a =>
  • {a}
  • )} -
- this.copyableValue = JSON.stringify(value); - if (array.length > 1) { - classes.push(styles.removePadding); - } - } - else { - this.copyableValue = content = JSON.stringify(value); - } - } - else if (type === 'Date') { - if (typeof value === 'object' && value.__type) { - value = new Date(value.iso); - } else if (typeof value === 'string') { - value = new Date(value); - } - this.copyableValue = content = dateStringUTC(value); - } else if (type === 'Boolean') { - this.copyableValue = content = value ? 'True' : 'False'; - } else if (type === 'Object' || type === 'Bytes') { - this.copyableValue = content = JSON.stringify(value); - } else if (type === 'File') { - const fileName = value.url() ? getFileName(value) : 'Uploading\u2026'; - content = ; - this.copyableValue = fileName; - } else if (type === 'ACL') { - let pieces = []; - let json = value.toJSON(); - if (Object.prototype.hasOwnProperty.call(json, '*')) { - if (json['*'].read && json['*'].write) { - pieces.push('Public Read + Write'); - } else if (json['*'].read) { - pieces.push('Public Read'); - } else if (json['*'].write) { - pieces.push('Public Write'); - } - } - for (let role in json) { - if (role !== '*') { - pieces.push(role); - } - } - if (pieces.length === 0) { - pieces.push('Master Key Only'); - } - this.copyableValue = content = pieces.join(', '); - } else if (type === 'GeoPoint') { - this.copyableValue = content = `(${value.latitude}, ${value.longitude})`; - } else if (type === 'Polygon') { - this.copyableValue = content = value.coordinates.map(coord => `(${coord})`) - } else if (type === 'Relation') { - content = setRelation ? ( -
- setRelation(value)} value='View relation' followClick={true} /> -
- ) : ( - 'Relation' - ); - this.copyableValue = undefined; - } - if (current) { + let classes = [...this.state.classes]; + + if ( current ) { classes.push(styles.current); } - if (markRequiredFieldRow === row && isRequired && !value) { classes.push(styles.required); } return readonly ? ( - + - {isNewRow ? '(auto)' : content} + {row < 0 || isNewRow ? '(auto)' : this.state.content} ) : ( @@ -413,13 +450,11 @@ export default class BrowserCell extends Component { if (['ACL', 'Boolean', 'File'].includes(type)) { e.preventDefault(); } - onEditChange(true); - } - }} - onContextMenu={this.onContextMenu} - > - {content} - + }}} + onContextMenu={this.onContextMenu.bind(this)} + > + {this.state.content} + ); } } diff --git a/src/components/BrowserRow/BrowserRow.react.js b/src/components/BrowserRow/BrowserRow.react.js index 25924d651e..5ae4e78ac3 100644 --- a/src/components/BrowserRow/BrowserRow.react.js +++ b/src/components/BrowserRow/BrowserRow.react.js @@ -74,6 +74,7 @@ export default class BrowserRow extends Component { let isRequired = requiredCols.includes(name); return ( -
+
{this.props.uploading ? (
) : label ? ( diff --git a/src/components/Pill/Pill.react.js b/src/components/Pill/Pill.react.js index 45c6aa9d5a..81d7fe5818 100644 --- a/src/components/Pill/Pill.react.js +++ b/src/components/Pill/Pill.react.js @@ -10,7 +10,7 @@ import styles from 'components/Pill/Pill.scss'; import Icon from "components/Icon/Icon.react"; //TODO: refactor, may want to move onClick outside or need to make onClick able to handle link/button a11y -let Pill = ({ value, onClick, fileDownloadLink, followClick = false }) => ( +let Pill = ({ value, onClick, fileDownloadLink, followClick = false, shrinkablePill = false }) => ( ( ].join(" ")} onClick={!followClick && onClick ? onClick : null} > - {value} + {value} {followClick && ( !e.metaKey && onClick()}> diff --git a/src/components/Pill/Pill.scss b/src/components/Pill/Pill.scss index 95987e52a6..91744f68d1 100644 --- a/src/components/Pill/Pill.scss +++ b/src/components/Pill/Pill.scss @@ -9,9 +9,8 @@ .pill { @include MonospaceFont; - display: flex; - justify-content: space-between; - align-items: center; + position: relative; + display: inline-block; color: #0E69A1; height: 20px; line-height: 20px; @@ -22,6 +21,8 @@ text-overflow: ellipsis; white-space: nowrap; & a { + position: absolute; + right: 0; height: 20px; width: 20px; background: #d6e5f2; @@ -31,6 +32,14 @@ transform: rotate(316deg); } } + & .pillText { + position: absolute; + left: 0; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + width: 75%; + } } .content { diff --git a/src/dashboard/Data/ApiConsole/GraphQLConsole.react.js b/src/dashboard/Data/ApiConsole/GraphQLConsole.react.js index d082c819af..10cf3ae2ee 100644 --- a/src/dashboard/Data/ApiConsole/GraphQLConsole.react.js +++ b/src/dashboard/Data/ApiConsole/GraphQLConsole.react.js @@ -29,16 +29,18 @@ export default class GraphQLConsole extends Component {
); } else { - const headers = { + const parseHeaders = { 'X-Parse-Application-Id': applicationId, 'X-Parse-Master-Key': masterKey } if (clientKey) { - headers['X-Parse-Client-Key'] = clientKey + parseHeaders['X-Parse-Client-Key'] = clientKey } content = ( { + headers={JSON.stringify(parseHeaders)} + headerEditorEnabled={true} + fetcher={async (graphQLParams, {headers}) => { const data = await fetch( graphQLServerURL, { diff --git a/src/dashboard/Data/Browser/Browser.react.js b/src/dashboard/Data/Browser/Browser.react.js index b6e91ea62c..38669f2471 100644 --- a/src/dashboard/Data/Browser/Browser.react.js +++ b/src/dashboard/Data/Browser/Browser.react.js @@ -29,6 +29,7 @@ import prettyNumber from 'lib/prettyNumber'; import queryFromFilters from 'lib/queryFromFilters'; import React from 'react'; import RemoveColumnDialog from 'dashboard/Data/Browser/RemoveColumnDialog.react'; +import PointerKeyDialog from 'dashboard/Data/Browser/PointerKeyDialog.react'; import SidebarAction from 'components/Sidebar/SidebarAction'; import stringCompare from 'lib/stringCompare'; import styles from 'dashboard/Data/Browser/Browser.scss'; @@ -58,6 +59,7 @@ class Browser extends DashboardView { showExportDialog: false, showAttachRowsDialog: false, showEditRowDialog: false, + showPointerKeyDialog: false, rowsToDelete: null, rowsToExport: null, @@ -141,6 +143,8 @@ class Browser extends DashboardView { this.addEditCloneRows = this.addEditCloneRows.bind(this); this.abortAddRow = this.abortAddRow.bind(this); this.saveNewRow = this.saveNewRow.bind(this); + this.showPointerKeyDialog = this.showPointerKeyDialog.bind(this); + this.onChangeDefaultKey = this.onChangeDefaultKey.bind(this); this.saveEditCloneRow = this.saveEditCloneRow.bind(this); this.abortEditCloneRow = this.abortEditCloneRow.bind(this); this.cancelPendingEditRows = this.cancelPendingEditRows.bind(this); @@ -451,7 +455,7 @@ class Browser extends DashboardView { }); }, error => { - let msg = typeof error === "string" ? error : error.message; + let msg = typeof error === 'string' ? error : error.message; if (msg) { msg = msg[0].toUpperCase() + msg.substr(1); } @@ -471,7 +475,7 @@ class Browser extends DashboardView { this.setState(state); }, error => { - let msg = typeof error === "string" ? error : error.message; + let msg = typeof error === 'string' ? error : error.message; if (msg) { msg = msg[0].toUpperCase() + msg.substr(1); } @@ -1419,6 +1423,10 @@ class Browser extends DashboardView { }); } + showPointerKeyDialog() { + this.setState({ showPointerKeyDialog: true }); + } + closeEditRowDialog() { this.setState({ showEditRowDialog: false, @@ -1435,6 +1443,16 @@ class Browser extends DashboardView { this.setState({showPermissionsDialog: opened}); } + async onChangeDefaultKey (name) { + ColumnPreferences.setPointerDefaultKey( + this.context.currentApp.applicationId, + this.props.params.className, + name + ); + this.setState({ showPointerKeyDialog: false }); + } + + renderContent() { let browser = null; let className = this.props.params.className; @@ -1513,6 +1531,7 @@ class Browser extends DashboardView { onExportSelectedRows={this.showExportSelectedRowsDialog} onSaveNewRow={this.saveNewRow} + onShowPointerKey={this.showPointerKeyDialog} onAbortAddRow={this.abortAddRow} onSaveEditCloneRow={this.saveEditCloneRow} onAbortEditCloneRow={this.abortEditCloneRow} @@ -1552,7 +1571,18 @@ class Browser extends DashboardView { } } let extras = null; - if (this.state.showCreateClassDialog) { + if(this.state.showPointerKeyDialog){ + let currentColumns = this.getClassColumns(className).map(column => column.name); + extras = ( + this.setState({ showPointerKeyDialog: false })} + onConfirm={this.onChangeDefaultKey} /> + ); + } + else if (this.state.showCreateClassDialog) { extras = ( :