Skip to content

Commit

Permalink
feat: request group descriptions
Browse files Browse the repository at this point in the history
Adds the ability to modify the description for a Request Group in a modal shown by hitting settings in a dropdown. Identical implementation to RequestSettings. In addition allows renaming the Request Group as well as moving/copying it to a different workspace.

Ref: Kong#3306
  • Loading branch information
NickHackman committed May 2, 2021
1 parent bdb4b4e commit 5b54b9b
Show file tree
Hide file tree
Showing 4 changed files with 365 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
} from '../base/dropdown';
import EnvironmentEditModal from '../modals/environment-edit-modal';
import * as models from '../../../models';
import { showError, showModal, showPrompt } from '../modals';
import { showError, showModal } from '../modals';
import type { HotKeyRegistry } from '../../../common/hotkeys';
import { hotKeyRefs } from '../../../common/hotkeys';
import type { RequestGroupAction } from '../../../plugins';
Expand All @@ -30,6 +30,7 @@ type Props = {
hotKeyRegistry: HotKeyRegistry,
activeEnvironment: Environment | null,
handleCreateRequest: (id: string) => any,
handleShowSettings: Function,
handleDuplicateRequestGroup: (rg: RequestGroup) => any,
handleMoveRequestGroup: (rg: RequestGroup) => any,
handleCreateRequestGroup: (id: string) => any,
Expand All @@ -53,18 +54,6 @@ class RequestGroupActionsDropdown extends React.PureComponent<Props, State> {
this._dropdown = n;
}

_handleRename() {
const { requestGroup } = this.props;

showPrompt({
title: 'Rename Folder',
defaultValue: requestGroup.name,
onComplete: name => {
models.requestGroup.update(requestGroup, { name });
},
});
}

async _handleRequestCreate() {
this.props.handleCreateRequest(this.props.requestGroup._id);
}
Expand Down Expand Up @@ -155,9 +144,6 @@ class RequestGroupActionsDropdown extends React.PureComponent<Props, State> {
<DropdownItem onClick={this._handleRequestGroupDuplicate}>
<i className="fa fa-copy" /> Duplicate
</DropdownItem>
<DropdownItem onClick={this._handleRename}>
<i className="fa fa-edit" /> Rename
</DropdownItem>
<DropdownItem onClick={this._handleEditEnvironment}>
<i className="fa fa-code" /> Environment
</DropdownItem>
Expand All @@ -178,6 +164,10 @@ class RequestGroupActionsDropdown extends React.PureComponent<Props, State> {
{p.label}
</DropdownItem>
))}
<DropdownDivider />
<DropdownItem onClick={this.props.handleShowSettings}>
<i className="fa fa-wrench" /> Settings
</DropdownItem>
</Dropdown>
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,338 @@
// @flow
import * as React from 'react';
import { autoBindMethodsForReact } from 'class-autobind-decorator';
import { AUTOBIND_CFG } from '../../../common/constants';
import Modal from '../base/modal';
import ModalBody from '../base/modal-body';
import ModalHeader from '../base/modal-header';
import HelpTooltip from '../help-tooltip';
import * as models from '../../../models';
import DebouncedInput from '../base/debounced-input';
import MarkdownEditor from '../markdown-editor';
import * as db from '../../../common/database';
import type { Workspace } from '../../../models/workspace';
import type { RequestGroup } from '../../../models/request-group';

type Props = {
editorFontSize: number,
editorIndentSize: number,
editorKeyMap: string,
editorLineWrapping: boolean,
nunjucksPowerUserMode: boolean,
isVariableUncovered: boolean,
handleRender: Function,
handleGetRenderContext: Function,
workspaces: Array<Workspace>,
};

type State = {
requestGroupGroup: RequestGroup | null,
showDescription: boolean,
defaultPreviewMode: boolean,
activeWorkspaceIdToCopyTo: string | null,
workspace: Workspace | null,
justCopied: boolean,
justMoved: boolean,
};

type RequestGroupSettingsModalOptions = {
requestGroup: RequestGroup,
forceEditMode: boolean,
};

@autoBindMethodsForReact(AUTOBIND_CFG)
class RequestGroupSettingsModal extends React.PureComponent<Props, State> {
modal: ?Modal;
_editor: ?MarkdownEditor;

constructor(props: Props) {
super(props);
this.state = {
requestGroup: null,
showDescription: false,
defaultPreviewMode: false,
activeWorkspaceIdToCopyTo: null,
workspace: null,
workspaces: [],
justCopied: false,
justMoved: false,
};
}

_setModalRef(n: ?Modal) {
this.modal = n;
}

_setEditorRef(n: ?MarkdownEditor) {
this._editor = n;
}

async _handleNameChange(name: string) {
const { requestGroup: originalRequestGroup } = this.state;

if (!originalRequestGroup) {
return;
}
const patch = { name };

const updatedRequestGroup = models.requestGroup.update(originalRequestGroup, patch);
this.setState({ requestGroup: updatedRequestGroup });
}

async _handleDescriptionChange(description: string) {
if (!this.state.requestGroup) {
return;
}
const requestGroup = await models.requestGroup.update(this.state.requestGroup, {
description,
});
this.setState({ requestGroup, defaultPreviewMode: false });
}

_handleAddDescription() {
this.setState({ showDescription: true });
}

_handleUpdateMoveCopyWorkspace(e: SyntheticEvent<HTMLSelectElement>) {
const { value } = e.currentTarget;
const workspaceId = value === '__NULL__' ? null : value;
this.setState({ activeWorkspaceIdToCopyTo: workspaceId });
}

async _handleMoveToWorkspace() {
const { activeWorkspaceIdToCopyTo, requestGroup } = this.state;
if (!requestGroup || !activeWorkspaceIdToCopyTo) {
return;
}

const workspace = await models.workspace.getById(activeWorkspaceIdToCopyTo);
if (!workspace) {
return;
}

const patch = {
metaSortKey: -1e9, // Move to top of sort order
parentId: activeWorkspaceIdToCopyTo,
};

await models.requestGroup.update(requestGroup, patch);

this.setState({ justMoved: true });
setTimeout(() => {
this.setState({ justMoved: false });
}, 2000);
}

async _handleCopyToWorkspace() {
const { activeWorkspaceIdToCopyTo, requestGroup } = this.state;
if (!requestGroup || !activeWorkspaceIdToCopyTo) {
return;
}

const workspace = await models.workspace.getById(activeWorkspaceIdToCopyTo);
if (!workspace) {
return;
}

const patch = {
metaSortKey: -1e9, // Move to top of sort order
name: requestGroup.name, // Because duplicate will add (Copy) suffix if name is not provided in patch
parentId: activeWorkspaceIdToCopyTo,
};

await models.requestGroup.duplicate(requestGroup, patch);

this.setState({ justCopied: true });
setTimeout(() => {
this.setState({ justCopied: false });
}, 2000);

models.stats.incrementCreatedRequests();
}

async show({ requestGroup, forceEditMode }: RequestGroupSettingsModalOptions) {
const { workspaces } = this.props;

const hasDescription = !!requestGroup.description;

// Find workspaces for use with moving workspace
const ancestors = await db.withAncestors(requestGroup);
const doc = ancestors.find(doc => doc.type === models.workspace.type);
const workspaceId = doc ? doc._id : 'should-never-happen';
const workspace = workspaces.find(w => w._id === workspaceId);

this.setState(
{
requestGroup,
workspace: workspace,
activeWorkspaceIdToCopyTo: null,
showDescription: forceEditMode || hasDescription,
defaultPreviewMode: hasDescription && !forceEditMode,
},
() => {
this.modal && this.modal.show();

if (forceEditMode) {
setTimeout(() => {
this._editor && this._editor.focus();
}, 400);
}
},
);
}

hide() {
this.modal && this.modal.hide();
}

_renderDescription(): React.Node {
const {
editorLineWrapping,
editorFontSize,
editorIndentSize,
editorKeyMap,
handleRender,
handleGetRenderContext,
nunjucksPowerUserMode,
isVariableUncovered,
} = this.props;

const { showDescription, defaultPreviewMode, requestGroup } = this.state;

if (!requestGroup) {
return null;
}

return showDescription ? (
<MarkdownEditor
ref={this._setEditorRef}
className="margin-top"
defaultPreviewMode={defaultPreviewMode}
fontSize={editorFontSize}
indentSize={editorIndentSize}
keyMap={editorKeyMap}
placeholder="Write a description"
lineWrapping={editorLineWrapping}
handleRender={handleRender}
handleGetRenderContext={handleGetRenderContext}
nunjucksPowerUserMode={nunjucksPowerUserMode}
isVariableUncovered={isVariableUncovered}
defaultValue={requestGroup.description}
onChange={this._handleDescriptionChange}
/>
) : (
<button
onClick={this._handleAddDescription}
className="btn btn--outlined btn--super-duper-compact">
Add Description
</button>
);
}

_renderMoveCopy(): React.Node {
const { workspaces } = this.props;

const {
activeWorkspaceIdToCopyTo,
justMoved,
justCopied,
workspace,
requestGroup,
} = this.state;

if (!requestGroup) {
return null;
}

return (
<div className="form-row">
<div className="form-control form-control--outlined">
<label>
Move/Copy to Workspace
<HelpTooltip position="top" className="space-left">
Copy or move the current request group to a new workspace. It will be placed at the
root of the new workspace's folder structure.
</HelpTooltip>
<select
value={activeWorkspaceIdToCopyTo || '__NULL__'}
onChange={this._handleUpdateMoveCopyWorkspace}>
<option value="__NULL__">-- Select Workspace --</option>
{workspaces.map(w => {
if (workspace && workspace._id === w._id) {
return null;
}

return (
<option key={w._id} value={w._id}>
{w.name}
</option>
);
})}
</select>
</label>
</div>
<div className="form-control form-control--no-label width-auto">
<button
disabled={justCopied || !activeWorkspaceIdToCopyTo}
className="btn btn--clicky"
onClick={this._handleCopyToWorkspace}>
{justCopied ? 'Copied!' : 'Copy'}
</button>
</div>
<div className="form-control form-control--no-label width-auto">
<button
disabled={justMoved || !activeWorkspaceIdToCopyTo}
className="btn btn--clicky"
onClick={this._handleMoveToWorkspace}>
{justMoved ? 'Moved!' : 'Move'}
</button>
</div>
</div>
);
}

renderModalBody(): React.Node {
const { requestGroup } = this.state;

if (!requestGroup) {
return null;
}

return (
<div>
<div className="form-control form-control--outlined">
<label>
Name{' '}
<span className="txt-sm faint italic">(also rename by double-clicking in sidebar)</span>
<DebouncedInput
delay={500}
type="text"
placeholder={requestGroup.url || 'My Request Group'}
defaultValue={requestGroup.name}
onChange={this._handleNameChange}
/>
</label>
</div>
{this._renderDescription()}
{this._renderMoveCopy()}
</div>
);
}

render() {
const { requestGroup } = this.state;
return (
<Modal ref={this._setModalRef} freshState>
<ModalHeader>
Request Group Settings{' '}
<span className="txt-sm selectable faint monospace">
{requestGroup ? requestGroup._id : ''}
</span>
</ModalHeader>
<ModalBody className="pad">{this.renderModalBody()}</ModalBody>
</Modal>
);
}
}

export default RequestGroupSettingsModal;
Loading

0 comments on commit 5b54b9b

Please sign in to comment.