cli, console: actions dx improvements (close #4306, #4311) (#4308)

This commit is contained in:
Rishichandra Wawhal
2020-04-16 13:40:47 +05:30
committed by GitHub
parent 5116e16e18
commit 8a5cc73ae6
22 changed files with 236 additions and 128 deletions

View File

@@ -8,7 +8,7 @@
"pretranspile": "npm run get-shared-modules",
"transpile": "rm -rf build/* && babel ./src ./tests --out-dir build",
"prebuild": "npm run transpile",
"build": "rm -rf ./bin/* && pkg ./build/command.js --output ./bin/cli-ext-hasura -t node8-linux-x64,node8-macos-x64,node8-win-x64",
"build": "rm -rf ./bin/* && pkg ./build/command.js --output ./bin/cli-ext-hasura -t node12-linux-x64,node12-macos-x64,node12-win-x64",
"pretest": "npm run transpile && babel ./tests --out-dir _tmptests",
"posttest": "rm -rf _tmptests",
"test": "node ./_tmptests/index.js"
@@ -29,4 +29,4 @@
"@babel/plugin-transform-async-to-generator": "^7.7.4",
"@babel/preset-env": "^7.7.6"
}
}
}

View File

@@ -120,17 +120,6 @@ func (o *actionsUseCodegenOptions) run() error {
o.withStarterKit = shouldCloneStarterKit == "y"
}
// if output directory is not provided, make them enter it
if o.outputDir == "" {
outputDir, err := util.GetFSPathPrompt("Where do you want to place the codegen files?", o.EC.Config.ActionConfig.Codegen.OutputDir)
if err != nil {
return errors.Wrap(err, "error in getting output directory input")
}
newCodegenExecutionConfig.OutputDir = outputDir
} else {
newCodegenExecutionConfig.OutputDir = o.outputDir
}
// clone the starter kit
o.EC.Spin("Clonning the starter kit...")
if o.withStarterKit && hasStarterKit {
@@ -158,6 +147,18 @@ func (o *actionsUseCodegenOptions) run() error {
}
o.EC.Logger.Info("Starter kit cloned at " + destinationDir)
}
o.EC.Spinner.Stop()
// if output directory is not provided, make them enter it
if o.outputDir == "" {
outputDir, err := util.GetFSPathPrompt("Where do you want to place the codegen files?", o.EC.Config.ActionConfig.Codegen.OutputDir)
if err != nil {
return errors.Wrap(err, "error in getting output directory input")
}
newCodegenExecutionConfig.OutputDir = outputDir
} else {
newCodegenExecutionConfig.OutputDir = o.outputDir
}
newConfig := o.EC.Config
newConfig.ActionConfig.Codegen = newCodegenExecutionConfig

View File

@@ -12,7 +12,6 @@ const (
)
func GetYesNoPrompt(message string) (promptResp string, err error) {
prompt := promptui.Prompt{
Label: message + " (y/n)",
Validate: func(_resp string) (err error) {
@@ -28,15 +27,12 @@ func GetYesNoPrompt(message string) (promptResp string, err error) {
},
Default: "y",
}
promptResp, err = prompt.Run()
if err != nil {
return
}
promptResp = string(strings.ToLower(promptResp)[0])
return
}
func GetSelectPrompt(message string, options []string) (selection string, err error) {
@@ -44,22 +40,15 @@ func GetSelectPrompt(message string, options []string) (selection string, err er
Label: message,
Items: options,
}
_, selection, err = prompt.Run()
return
}
func GetFSPathPrompt(message string, def string) (input string, err error) {
prompt := promptui.Prompt{
Label: message,
Validate: FSCheckIfDirPathExists,
Default: def,
}
input, err = prompt.Run()
return
return prompt.Run()
}

View File

@@ -1404,6 +1404,13 @@ code {
margin-right: 50px !important;
}
.marginLeftAuto {
margin-left: auto;
}
.textAlignRight {
text-align: right;
}
/* container height subtracting top header and bottom scroll bar */
$mainContainerHeight: calc(100vh - 50px - 25px);

View File

@@ -86,9 +86,11 @@ const AddAction = ({
let actionType;
if (!actionDefinitionError) {
// TODO optimise
const { type, error } = getActionDefinitionFromSdl(actionDefinitionSdl);
if (!error) {
actionType = type;
if (!actionParseTimer) {
const { type, error } = getActionDefinitionFromSdl(actionDefinitionSdl);
if (!error) {
actionType = type;
}
}
}
@@ -120,18 +122,16 @@ const AddAction = ({
service="create-action"
/>
<hr />
{
actionType === "query" ? null : (
<React.Fragment>
<KindEditor
value={kind}
onChange={kindOnChange}
className={styles.add_mar_bottom_mid}
/>
<hr />
</React.Fragment>
)
}
{actionType === 'query' ? null : (
<React.Fragment>
<KindEditor
value={kind}
onChange={kindOnChange}
className={styles.add_mar_bottom_mid}
/>
<hr />
</React.Fragment>
)}
<HeadersConfEditor
forwardClientHeaders={forwardClientHeaders}
toggleForwardClientHeaders={toggleForwardClientHeaders}

View File

@@ -5,6 +5,7 @@ import TSEditor from '../../../Common/AceEditor/TypescriptEditor';
import { getFrameworkCodegen } from './utils';
import { getFileExtensionFromFilename } from '../../../Common/utils/jsUtils';
import { Tabs, Tab } from 'react-bootstrap';
import styles from '../Actions.scss';
const CodeTabs = ({
framework,
@@ -19,6 +20,7 @@ const CodeTabs = ({
const init = () => {
setLoading(true);
setError(null);
getFrameworkCodegen(
framework,
currentAction.action_name,
@@ -45,7 +47,7 @@ const CodeTabs = ({
return (
<div>
Error generating code.&nbsp;
<a onClick={init}>Try again</a>
<a onClick={init} className={styles.cursorPointer}>Try again</a>
</div>
);
}

View File

@@ -4,12 +4,13 @@ import { getSdlComplete } from '../../../../shared/utils/sdlUtils';
import {
getAllCodegenFrameworks,
getStarterKitPath,
getStarterKitDownloadPath,
getGlitchProjectURL,
} from './utils';
import { getPersistedDerivedAction } from '../lsUtils';
import Spinner from '../../../Common/Spinner/Spinner';
import styles from '../Common/components/Styles.scss';
import Button from '../../../Common/Button/Button';
import { Icon } from '../../../UIKit/atoms';
import CodeTabs from './CodeTabs';
import DerivedFrom from './DerivedFrom';
@@ -31,6 +32,7 @@ const Codegen = ({ allActions, allTypes, currentAction }) => {
const init = () => {
setLoading(true);
setError(null);
getAllCodegenFrameworks()
.then(frameworks => {
setAllFrameworks(frameworks);
@@ -54,7 +56,9 @@ const Codegen = ({ allActions, allTypes, currentAction }) => {
return (
<div>
Error fetching codegen assets.&nbsp;
<a onClick={init}>Try again</a>
<a onClick={init} className={styles.cursorPointer}>
Try again
</a>
</div>
);
}
@@ -89,13 +93,9 @@ const Codegen = ({ allActions, allTypes, currentAction }) => {
href={getGlitchProjectURL()}
target="_blank"
rel="noopener noreferrer"
className={styles.add_mar_right}
>
<Button
color="white"
className={`${styles.add_mar_right_mid} ${styles.default_button}`}
>
Try on glitch
</Button>
<Icon type="link" /> Try on glitch
</a>
);
};
@@ -110,24 +110,57 @@ const Codegen = ({ allActions, allTypes, currentAction }) => {
) {
return null;
}
return (
<a
href={getStarterKitPath(selectedFramework)}
target="_blank"
rel="noopener noreferrer"
>
<Button color="white" className={`${styles.add_mar_right_mid}`}>
Get starter kit
</Button>
</a>
<React.Fragment>
<a
href={getStarterKitDownloadPath(selectedFramework)}
target="_blank"
rel="noopener noreferrer"
className={styles.add_mar_right}
title={`Download starter kit for ${selectedFramework}`}
>
<Icon type="download" /> Starter-kit.zip
</a>
<a
href={getStarterKitPath(selectedFramework)}
target="_blank"
rel="noopener noreferrer"
className={styles.display_flex}
title={`View the starter kit for ${selectedFramework} on GitHub`}
>
<Icon type="github" className={styles.add_mar_right_small} /> View
on GitHub
</a>
</React.Fragment>
);
};
const getHelperToolsSection = () => {
const glitchButton = getGlitchButton();
const starterKitButtons = getStarterKitButton();
if (!glitchButton && !starterKitButtons) {
return null;
}
return (
<div className={styles.marginLeftAuto}>
<div
className={`${styles.add_mar_bottom_small} ${styles.textAlignRight}`}
>
<b>Need help getting started quickly?</b>
</div>
<div className={`${styles.display_flex}`}>
{getGlitchButton()}
{getStarterKitButton()}
</div>
</div>
);
};
return (
<div className={`${styles.add_mar_bottom} ${styles.display_flex}`}>
{getDrodown()}
{getGlitchButton()}
{getStarterKitButton()}
{getHelperToolsSection()}
</div>
);
};

View File

@@ -24,6 +24,9 @@ export const getStarterKitPath = framework => {
return `https://github.com/${CODEGEN_REPO}/tree/master/${framework}/starter-kit/`;
};
export const getStarterKitDownloadPath = framework => {
return `https://github.com/${CODEGEN_REPO}/raw/master/${framework}/${framework}.zip`;
};
export const getGlitchProjectURL = () => {
return 'https://glitch.com/edit/?utm_content=project_hasura-actions-starter-kit&utm_source=remix_this&utm_medium=button&utm_campaign=glitchButton#!/remix/hasura-actions-starter-kit';
};

View File

@@ -69,7 +69,7 @@ const ActionDefinitionEditor = ({
{error && (
<div className={`${styles.display_flex} ${styles.errorMessage}`}>
<CrossIcon className={styles.add_mar_right_small} />
<p>{errorMessage}</p>
<div>{errorMessage}</div>
</div>
)}
</div>

View File

@@ -31,4 +31,3 @@
.cloneTypeText {
margin-left: auto;
}

View File

@@ -79,24 +79,27 @@ const ActionDefinitionEditor = ({
<div>{errorMessage}</div>
</div>
)}
<a
className={`${styles.cloneTypeText} ${styles.cursorPointer} ${styles.add_mar_right}`}
onClick={toggleModal}
>
<CopyIcon className={styles.add_mar_right_small} />
Clone an existing type
</a>
<Modal
show={modalOpen}
title={'Clone an existing type'}
onClose={toggleModal}
customClass={styles.modal}
>
<CloneTypeModal
handleClonedTypes={handleClonedTypes}
toggleModal={toggleModal}
/>
</Modal>
{/*
<a
className={`${styles.cloneTypeText} ${styles.cursorPointer} ${styles.add_mar_right}`}
onClick={toggleModal}
>
<CopyIcon className={styles.add_mar_right_small} />
Clone an existing type
</a>
<Modal
show={modalOpen}
title={'Clone an existing type'}
onClose={toggleModal}
customClass={styles.modal}
>
<CloneTypeModal
handleClonedTypes={handleClonedTypes}
toggleModal={toggleModal}
/>
</Modal>
*/}
</div>
<SDLEditor
name="sdl-editor"

View File

@@ -39,8 +39,12 @@ export const defaultEnumType = {
values: [{ ...defaultEnumValue }],
};
export const defaultActionDefSdl = `type Mutation {
## Define your action as a mutation here
export const defaultActionDefSdl = `## Use "type Query" for query type actions
## Use "type Mutation" for mutation type actions
#type Query {
type Mutation {
# Define your action here
actionName (arg1: SampleInput!): SampleOutput
}
`;

View File

@@ -292,4 +292,3 @@ export const getOverlappingTypeConfirmation = (
return isOk;
};

View File

@@ -122,18 +122,16 @@ const ActionEditor = ({
service="create-action"
/>
<hr />
{
actionType === "query" ? null : (
<React.Fragment>
<KindEditor
value={kind}
onChange={kindOnChange}
className={styles.add_mar_bottom_mid}
/>
<hr />
</React.Fragment>
)
}
{actionType === 'query' ? null : (
<React.Fragment>
<KindEditor
value={kind}
onChange={kindOnChange}
className={styles.add_mar_bottom_mid}
/>
<hr />
</React.Fragment>
)}
<HeadersConfEditor
forwardClientHeaders={forwardClientHeaders}
toggleForwardClientHeaders={toggleForwardClientHeaders}

View File

@@ -8,7 +8,7 @@ import {
getActionName,
getActionOutputType,
getActionComment,
getActionType
getActionType,
} from '../utils';
import { getActionTypes } from '../Common/utils';

View File

@@ -119,7 +119,7 @@ export const createAction = () => (dispatch, getState) => {
outputType,
error: actionDefError,
comment: actionDescription,
type: actionType
type: actionType,
} = getActionDefinitionFromSdl(rawState.actionDefinition.sdl);
if (actionDefError) {
return dispatch(

View File

@@ -27,7 +27,7 @@ export const getActionArguments = action => {
};
export const getActionType = action => {
return action.action_defn.type
return action.action_defn.type;
};
export const getAllActions = getState => {

View File

@@ -20,6 +20,7 @@ import {
} from '../../../../shared/utils/sdlUtils';
import { showErrorNotification } from '../../Common/Notification';
import { getActionsCreateRoute } from '../../../Common/utils/routesUtils';
import { getConfirmation } from '../../../Common/utils/jsUtils';
import {
setActionDefinition,
setTypeDefinition,
@@ -111,13 +112,19 @@ class GraphiQLWrapper extends Component {
console.error(e);
return;
}
const { action, types } = derivedOperationMetadata;
const { action, types, variables } = derivedOperationMetadata;
const actionsSdl = getActionDefinitionSdl(
action.name,
action.type,
action.arguments,
action.output_type
);
if (variables && !variables.length) {
const ok = getConfirmation(
`Looks like your ${action.type} does not have variables. This means that the derived action will have no arguments.`
);
if (!ok) return;
}
const typesSdl = getTypesSdl(types);
dispatch(
setActionDefinition(actionsSdl, null, null, sdlParse(actionsSdl))

View File

@@ -2,6 +2,8 @@ import styled from 'styled-components';
import { color, typography, layout, space } from 'styled-system';
export const StyledIcon = styled.svg`
cursor: ${({ pointer }) => (pointer ? 'pointer' : '')};
${color}
${typography}
${layout}

View File

@@ -10,9 +10,48 @@ import {
FaCloud,
FaCog,
FaQuestion,
FaCogs,
FaExclamation,
FaTimes,
FaStar,
FaTwitter,
FaHeart,
FaChevronRight,
FaCompressArrowsAlt,
FaExpand,
FaEdit,
FaChevronDown,
FaSearch,
FaSpinner,
FaQuestionCircle,
FaPause,
FaPlay,
FaCheck,
FaRedoAlt,
FaChevronUp,
FaTrashAlt,
FaPencilAlt,
FaPlus,
FaEye,
FaUserSecret,
FaRegLightbulb,
FaSort,
FaCaretUp,
FaCaretDown,
FaCaretRight,
FaCaretLeft,
FaRegClone,
FaRegCaretSquareRight,
FaCopy,
FaExternalLinkAlt,
FaTable,
FaFilter,
FaWrench,
FaRegPaperPlane,
FaCodeBranch,
FaGithub,
FaDownload,
} from 'react-icons/fa';
import { theme } from '../../theme';
import { StyledIcon } from './Icon';
const iconReferenceMap = {
@@ -20,37 +59,63 @@ const iconReferenceMap = {
info: FaInfoCircle,
warning: FaExclamationTriangle,
error: FaExclamationCircle,
graphiql: FaFlask,
graphiQL: FaFlask,
database: FaDatabase,
schema: FaPlug,
event: FaCloud,
settings: FaCog,
question: FaQuestion,
default: FaExclamationCircle,
action: FaCogs,
exclamation: FaExclamation,
close: FaTimes,
star: FaStar,
twitter: FaTwitter,
love: FaHeart,
right: FaChevronRight,
down: FaChevronDown,
up: FaChevronUp,
compress: FaCompressArrowsAlt,
expand: FaExpand,
edit: FaEdit,
pencil: FaPencilAlt,
search: FaSearch,
spinner: FaSpinner,
questionCircle: FaQuestionCircle,
pause: FaPause,
play: FaPlay,
playBox: FaRegCaretSquareRight,
check: FaCheck,
reload: FaRedoAlt,
delete: FaTrashAlt,
add: FaPlus,
eye: FaEye,
secret: FaUserSecret,
lightBulb: FaRegLightbulb,
sort: FaSort,
caretUp: FaCaretUp,
caretDown: FaCaretDown,
caretRight: FaCaretRight,
caretLeft: FaCaretLeft,
clone: FaRegClone,
copy: FaCopy,
link: FaExternalLinkAlt,
table: FaTable,
filter: FaFilter,
wrench: FaWrench,
send: FaRegPaperPlane,
fork: FaCodeBranch,
github: FaGithub,
download: FaDownload,
};
const iconWidth = 18;
const iconHeight = 18;
export const Icon = props => {
const { type } = props;
const { icon } = theme;
const iconColor = icon[type] ? icon[type].color : icon.default.color;
const CurrentActiveIcon = iconReferenceMap[type]
? iconReferenceMap[type]
: iconReferenceMap.default;
return (
<StyledIcon
fontSize="icon"
color={iconColor}
width={iconWidth}
height={iconHeight}
as={CurrentActiveIcon}
{...props}
/>
);
return <StyledIcon as={CurrentActiveIcon} {...props} aria-hidden="true" />;
};
Icon.defaultProps = {
size: 14,
};

View File

@@ -22,7 +22,7 @@ export const validateOperation = (operationString, clientSchema) => {
try {
operationAst = sdlParse(operationString);
} catch (e) {
throw Error('invalid SDL');
throw Error('this seems to be an invalid GraphQL query');
}
const schemaValidationErrors = validate(clientSchema, operationAst);
@@ -69,11 +69,6 @@ export const validateOperation = (operationString, clientSchema) => {
throw Error('the given operation must ask for at least one root field');
}
// throw error if the operation does not have variables
if (!operationAst.definitions[0].variableDefinitions.length) {
throw Error('only operations using variables can be derived');
}
return operationAst;
};
@@ -244,6 +239,7 @@ const deriveAction = (
arguments: actionArguments,
output_type: actionOutputTypename,
},
variables,
};
};

View File

@@ -5,7 +5,7 @@ export const getOperationType = (schema, operation) => {
return schema._queryType;
}
if (operation === 'subscription') {
return schema._subscriptionType
return schema._subscriptionType;
}
return schema._mutationType;
};