Merge branch 'master' into presentational-page-components

pull/3662/head
Alex P 2018-06-19 10:32:27 -07:00
commit 9798f0ef0c
25 changed files with 451 additions and 182 deletions

138
.circleci/config.yml Normal file
View File

@ -0,0 +1,138 @@
workflows:
version: 2
main:
jobs:
- build
- deploy-nightly:
requires:
- build
filters:
branches:
only: master
- deploy-pre-release:
requires:
- build
filters:
branches:
ignore: /.*/
tags:
only: /^[0-9]+(\.[0-9]+)*(\S*)([a|rc|beta]([0-9]+))+$/
- deploy-release:
requires:
- build
filters:
branches:
ignore: /.*/
tags:
only: /^[0-9]+(\.[0-9]+)*$/
version: 2
jobs:
build:
environment:
DOCKER_TAG: chronograf-20180327
machine: true
steps:
- checkout
- run: |
ls -lah
pwd
- run: ./etc/scripts/docker/pull.sh
- run:
name: "Run Tests"
command: >
./etc/scripts/docker/run.sh
--debug
--test
--no-build
- persist_to_workspace:
root: /home/circleci
paths:
- project
deploy-nightly:
environment:
DOCKER_TAG: chronograf-20180327
machine: true
steps:
- attach_workspace:
at: /home/circleci
- run: |
./etc/scripts/docker/run.sh \
--debug \
--clean \
--package \
--platform all \
--arch all \
--upload \
--nightly \
--bucket=dl.influxdata.com/chronograf/releases
cp build/linux/static_amd64/chronograf .
cp build/linux/static_amd64/chronoctl .
docker build -t chronograf .
docker login -u "$QUAY_USER" -p $QUAY_PASS quay.io
docker tag chronograf quay.io/influxdb/chronograf:nightly
docker push quay.io/influxdb/chronograf:nightly
- store_artifacts:
path: ./build/
deploy-pre-release:
environment:
DOCKER_TAG: chronograf-20180327
machine: true
steps:
- attach_workspace:
at: /home/circleci
- run: |
./etc/scripts/docker/run.sh \
--clean \
--debug \
--release \
--package \
--platform all \
--arch all \
--upload-overwrite \
--upload \
--bucket dl.influxdata.com/chronograf/releases
cp build/linux/static_amd64/chronograf .
cp build/linux/static_amd64/chronoctl .
docker build -t chronograf .
docker login -u "$QUAY_USER" -p $QUAY_PASS quay.io
docker tag chronograf quay.io/influxdb/chronograf:${CIRCLE_SHA1:0:7}
docker push quay.io/influxdb/chronograf:${CIRCLE_SHA1:0:7}
docker tag chronograf quay.io/influxdb/chronograf:${CIRCLE_TAG}
docker push quay.io/influxdb/chronograf:${CIRCLE_TAG}
- store_artifacts:
path: ./build/
deploy-release:
environment:
DOCKER_TAG: chronograf-20180327
machine: true
steps:
- attach_workspace:
at: /home/circleci
- run: |
./etc/scripts/docker/run.sh \
--clean \
--debug \
--release \
--package \
--platform all \
--arch all \
--upload-overwrite \
--upload \
--bucket dl.influxdata.com/chronograf/releases
cp build/linux/static_amd64/chronograf .
cp build/linux/static_amd64/chronoctl .
docker build -t chronograf .
docker login -u "$QUAY_USER" -p $QUAY_PASS quay.io
docker tag chronograf quay.io/influxdb/chronograf:${CIRCLE_SHA1:0:7}
docker push quay.io/influxdb/chronograf:${CIRCLE_SHA1:0:7}
docker tag chronograf quay.io/influxdb/chronograf:${CIRCLE_TAG}
docker push quay.io/influxdb/chronograf:${CIRCLE_TAG}
docker tag chronograf quay.io/influxdb/chronograf:latest
docker push quay.io/influxdb/chronograf:latest
- store_artifacts:
path: ./build/

View File

@ -10,6 +10,7 @@
1. [#3474](https://github.com/influxdata/chronograf/pull/3474): Sort task table on Manage Alert page alphabetically
1. [#3590](https://github.com/influxdata/chronograf/pull/3590): Redesign icons in side navigation
1. [#3696](https://github.com/influxdata/chronograf/pull/3696): Add ability to delete entire queries in Flux Editor
1. [#3671](https://github.com/influxdata/chronograf/pull/3671): Remove Snip functionality in hover legend
1. [#3659](https://github.com/influxdata/chronograf/pull/3659): Upgrade Data Explorer query text field with syntax highlighting and partial multi-line support
1. [#3663](https://github.com/influxdata/chronograf/pull/3663): Truncate message preview in Alert Rules table

View File

@ -1,91 +0,0 @@
---
machine:
services:
- docker
environment:
DOCKER_TAG: chronograf-20180327
dependencies:
override:
- ./etc/scripts/docker/pull.sh
test:
override:
- >
./etc/scripts/docker/run.sh
--debug
--test
--no-build
deployment:
master:
branch: master
commands:
- >
./etc/scripts/docker/run.sh
--debug
--clean
--package
--platform all
--arch all
--upload
--nightly
--bucket=dl.influxdata.com/chronograf/releases
- sudo chown -R ubuntu:ubuntu /home/ubuntu
- cp build/linux/static_amd64/chronograf .
- cp build/linux/static_amd64/chronoctl .
- docker build -t chronograf .
- docker login -e $QUAY_EMAIL -u "$QUAY_USER" -p $QUAY_PASS quay.io
- docker tag chronograf quay.io/influxdb/chronograf:nightly
- docker push quay.io/influxdb/chronograf:nightly
- mv ./build/* $CIRCLE_ARTIFACTS
pre-release:
tag: /^[0-9]+(\.[0-9]+)*(\S*)([a|rc|beta]([0-9]+))+$/
commands:
- >
./etc/scripts/docker/run.sh
--clean
--debug
--release
--package
--platform all
--arch all
--upload-overwrite
--upload
--bucket dl.influxdata.com/chronograf/releases
- sudo chown -R ubuntu:ubuntu /home/ubuntu
- cp build/linux/static_amd64/chronograf .
- cp build/linux/static_amd64/chronoctl .
- docker build -t chronograf .
- docker login -e $QUAY_EMAIL -u "$QUAY_USER" -p $QUAY_PASS quay.io
- docker tag chronograf quay.io/influxdb/chronograf:${CIRCLE_SHA1:0:7}
- docker push quay.io/influxdb/chronograf:${CIRCLE_SHA1:0:7}
- docker tag chronograf quay.io/influxdb/chronograf:${CIRCLE_TAG}
- docker push quay.io/influxdb/chronograf:${CIRCLE_TAG}
- mv ./build/* $CIRCLE_ARTIFACTS
release:
tag: /^[0-9]+(\.[0-9]+)*$/
commands:
- >
./etc/scripts/docker/run.sh
--clean
--debug
--release
--package
--platform all
--arch all
--upload-overwrite
--upload
--bucket dl.influxdata.com/chronograf/releases
- sudo chown -R ubuntu:ubuntu /home/ubuntu
- cp build/linux/static_amd64/chronograf .
- cp build/linux/static_amd64/chronoctl .
- docker build -t chronograf .
- docker login -e $QUAY_EMAIL -u "$QUAY_USER" -p $QUAY_PASS quay.io
- docker tag chronograf quay.io/influxdb/chronograf:${CIRCLE_SHA1:0:7}
- docker push quay.io/influxdb/chronograf:${CIRCLE_SHA1:0:7}
- docker tag chronograf quay.io/influxdb/chronograf:${CIRCLE_TAG}
- docker push quay.io/influxdb/chronograf:${CIRCLE_TAG}
- docker tag chronograf quay.io/influxdb/chronograf:latest
- docker push quay.io/influxdb/chronograf:latest
- mv ./build/* $CIRCLE_ARTIFACTS

View File

@ -12,7 +12,7 @@ import {
applyMasks,
insertTempVar,
unMask,
} from 'src/dashboards/constants'
} from 'src/tempVars/constants'
@ErrorHandling
class QueryTextArea extends Component {

View File

@ -32,7 +32,7 @@ export const generateURLQueryParamsFromTempVars = (
const selected = values.find(value => value.selected === true)
const strippedTempVar = stripTempVar(tempVar)
urlQueryParams[strippedTempVar] = selected.value
urlQueryParams[strippedTempVar] = _.get(selected, 'value', '')
})
return urlQueryParams

View File

@ -3,12 +3,13 @@ import _ from 'lodash'
import FancyScrollbar from 'src/shared/components/FancyScrollbar'
import ExpressionNode from 'src/flux/components/ExpressionNode'
import VariableName from 'src/flux/components/VariableName'
import VariableNode from 'src/flux/components/VariableNode'
import FuncSelector from 'src/flux/components/FuncSelector'
import BodyDelete from 'src/flux/components/BodyDelete'
import {funcNames} from 'src/flux/constants'
import {Service} from 'src/types'
import {FlatBody, Suggestion} from 'src/types/flux'
import {Body, Suggestion} from 'src/types/flux'
interface Props {
service: Service
@ -16,21 +17,25 @@ interface Props {
suggestions: Suggestion[]
onAppendFrom: () => void
onAppendJoin: () => void
}
interface Body extends FlatBody {
id: string
onDeleteBody: (bodyID: string) => void
}
class BodyBuilder extends PureComponent<Props> {
public render() {
const bodybuilder = this.props.body.map((b, i) => {
const {body, onDeleteBody} = this.props
const bodybuilder = body.map((b, i) => {
if (b.declarations.length) {
return b.declarations.map(d => {
if (d.funcs) {
return (
<div className="declaration" key={i}>
<VariableName name={d.name} assignedToQuery={true} />
<div className="func-node--wrapper">
<VariableNode name={d.name} assignedToQuery={true} />
<div className="func-node--menu">
<BodyDelete bodyID={b.id} onDeleteBody={onDeleteBody} />
</div>
</div>
<ExpressionNode
bodyID={b.id}
declarationID={d.id}
@ -38,6 +43,7 @@ class BodyBuilder extends PureComponent<Props> {
funcs={d.funcs}
declarationsFromBody={this.declarationsFromBody}
isLastBody={this.isLastBody(i)}
onDeleteBody={onDeleteBody}
/>
</div>
)
@ -45,7 +51,16 @@ class BodyBuilder extends PureComponent<Props> {
return (
<div className="declaration" key={i}>
<VariableName name={b.source} assignedToQuery={false} />
<div className="func-node--wrapper">
<VariableNode name={b.source} assignedToQuery={false} />
<div className="func-node--menu">
<BodyDelete
bodyID={b.id}
type="variable"
onDeleteBody={onDeleteBody}
/>
</div>
</div>
</div>
)
})
@ -59,6 +74,7 @@ class BodyBuilder extends PureComponent<Props> {
funcNames={this.funcNames}
declarationsFromBody={this.declarationsFromBody}
isLastBody={this.isLastBody(i)}
onDeleteBody={onDeleteBody}
/>
</div>
)

View File

@ -0,0 +1,49 @@
import React, {PureComponent} from 'react'
import ConfirmButton from 'src/shared/components/ConfirmButton'
type BodyType = 'variable' | 'query'
interface Props {
bodyID: string
type?: BodyType
onDeleteBody: (bodyID: string) => void
}
class BodyDelete extends PureComponent<Props> {
public static defaultProps: Partial<Props> = {
type: 'query',
}
public render() {
const {type} = this.props
if (type === 'variable') {
return (
<button
className="btn btn-sm btn-square btn-danger"
title="Delete Variable"
onClick={this.handleDelete}
>
<span className="icon remove" />
</button>
)
}
return (
<ConfirmButton
icon="trash"
type="btn-danger"
confirmText="Delete Query"
square={true}
confirmAction={this.handleDelete}
position="right"
/>
)
}
private handleDelete = (): void => {
this.props.onDeleteBody(this.props.bodyID)
}
}
export default BodyDelete

View File

@ -15,6 +15,7 @@ interface Props {
declarationID?: string
declarationsFromBody: string[]
isLastBody: boolean
onDeleteBody: (bodyID: string) => void
}
interface State {
@ -42,6 +43,7 @@ class ExpressionNode extends PureComponent<Props, State> {
funcNames,
funcs,
declarationsFromBody,
onDeleteBody,
} = this.props
const {nonYieldableIndexesToggled} = this.state
@ -106,6 +108,7 @@ class ExpressionNode extends PureComponent<Props, State> {
onGenerateScript={onGenerateScript}
declarationsFromBody={declarationsFromBody}
onToggleYieldWithLast={this.handleToggleYieldWithLast}
onDeleteBody={onDeleteBody}
/>
)
@ -152,6 +155,7 @@ class ExpressionNode extends PureComponent<Props, State> {
onGenerateScript={onGenerateScript}
declarationsFromBody={declarationsFromBody}
onToggleYieldWithLast={this.handleToggleYieldWithLast}
onDeleteBody={onDeleteBody}
/>
<YieldFuncNode
index={i}

View File

@ -5,9 +5,11 @@ import {tagKeys as fetchTagKeys} from 'src/shared/apis/flux/metaQueries'
import parseValuesColumn from 'src/shared/parsing/flux/values'
import FilterTagList from 'src/flux/components/FilterTagList'
import Walker from 'src/flux/ast/walker'
import {makeCancelable} from 'src/utils/promises'
import {Service} from 'src/types'
import {Links, OnChangeArg, Func, FilterNode} from 'src/types/flux'
import {WrappedCancelablePromise} from 'src/types/promises'
interface Props {
links: Links
@ -28,6 +30,8 @@ interface State {
}
class FilterArgs extends PureComponent<Props, State> {
private fetchTagKeysResponse?: WrappedCancelablePromise<string>
constructor(props) {
super(props)
this.state = {
@ -52,15 +56,24 @@ class FilterArgs extends PureComponent<Props, State> {
}
public async componentDidMount() {
const {db, service} = this.props
try {
this.convertStringToNodes()
const response = await fetchTagKeys(service, db, [])
const response = await this.getTagKeys()
const tagKeys = parseValuesColumn(response)
this.setState({tagKeys})
this.setState({
tagKeys,
})
} catch (error) {
console.error(error)
if (!error.isCanceled) {
console.error(error)
}
}
}
public componentWillUnmount() {
if (this.fetchTagKeysResponse) {
this.fetchTagKeysResponse.cancel()
}
}
@ -91,6 +104,14 @@ class FilterArgs extends PureComponent<Props, State> {
/>
)
}
private getTagKeys(): Promise<string> {
const {db, service} = this.props
this.fetchTagKeysResponse = makeCancelable(fetchTagKeys(service, db, []))
return this.fetchTagKeysResponse.promise
}
}
const mapStateToProps = ({links}) => {

View File

@ -2,6 +2,7 @@ import React, {PureComponent, MouseEvent} from 'react'
import classnames from 'classnames'
import _ from 'lodash'
import BodyDelete from 'src/flux/components/BodyDelete'
import FuncArgs from 'src/flux/components/FuncArgs'
import FuncArgsPreview from 'src/flux/components/FuncArgsPreview'
import {
@ -28,6 +29,7 @@ interface Props {
declarationsFromBody: string[]
isYielding: boolean
isYieldable: boolean
onDeleteBody: (bodyID: string) => void
}
interface State {
@ -53,14 +55,16 @@ export default class FuncNode extends PureComponent<Props, State> {
return (
<>
<div
className={this.nodeClassName}
onClick={this.handleToggleEdit}
title="Edit function arguments"
>
<div className="func-node--connector" />
<div className="func-node--name">{func.name}</div>
<FuncArgsPreview func={func} />
<div className="func-node--wrapper">
<div
className={this.nodeClassName}
onClick={this.handleToggleEdit}
title="Edit function arguments"
>
<div className="func-node--connector" />
<div className="func-node--name">{func.name}</div>
<FuncArgsPreview func={func} />
</div>
{this.funcMenu}
</div>
{this.funcArgs}
@ -103,13 +107,7 @@ export default class FuncNode extends PureComponent<Props, State> {
return (
<div className="func-node--menu">
{this.yieldToggleButton}
<button
className="btn btn-sm btn-square btn-danger"
onClick={this.handleDelete}
title="Delete this Function"
>
<span className="icon trash" />
</button>
{this.deleteButton}
</div>
)
}
@ -140,6 +138,24 @@ export default class FuncNode extends PureComponent<Props, State> {
)
}
private get deleteButton(): JSX.Element {
const {func, bodyID, onDeleteBody} = this.props
if (func.name === 'from') {
return <BodyDelete onDeleteBody={onDeleteBody} bodyID={bodyID} />
}
return (
<button
className="btn btn-sm btn-square btn-danger"
onClick={this.handleDelete}
title="Delete this Function"
>
<span className="icon remove" />
</button>
)
}
private get nodeClassName(): string {
const {isYielding} = this.props
const {editing} = this.state
@ -147,14 +163,14 @@ export default class FuncNode extends PureComponent<Props, State> {
return classnames('func-node', {active: isYielding || editing})
}
private handleDelete = (e: MouseEvent<HTMLElement>): void => {
e.stopPropagation()
private handleDelete = (): void => {
const {func, bodyID, declarationID} = this.props
this.props.onDelete({funcID: func.id, bodyID, declarationID})
}
private handleToggleEdit = (): void => {
private handleToggleEdit = (e: MouseEvent<HTMLElement>): void => {
e.stopPropagation()
this.setState({editing: !this.state.editing})
}

View File

@ -7,6 +7,7 @@ import {
Suggestion,
OnChangeScript,
OnSubmitScript,
OnDeleteBody,
FlatBody,
ScriptStatus,
} from 'src/types/flux'
@ -22,6 +23,7 @@ interface Props {
status: ScriptStatus
suggestions: Suggestion[]
onChangeScript: OnChangeScript
onDeleteBody: OnDeleteBody
onSubmitScript: OnSubmitScript
onAppendFrom: () => void
onAppendJoin: () => void
@ -63,7 +65,14 @@ class TimeMachine extends PureComponent<Props> {
}
private get builder() {
const {body, service, suggestions, onAppendFrom, onAppendJoin} = this.props
const {
body,
service,
suggestions,
onAppendFrom,
onDeleteBody,
onAppendJoin,
} = this.props
return {
name: 'Build',
@ -75,6 +84,7 @@ class TimeMachine extends PureComponent<Props> {
body={body}
service={service}
suggestions={suggestions}
onDeleteBody={onDeleteBody}
onAppendFrom={onAppendFrom}
onAppendJoin={onAppendJoin}
/>

View File

@ -1,3 +1,19 @@
export const emptyAST = {
type: 'Program',
location: {
start: {
line: 1,
column: 1,
},
end: {
line: 1,
column: 1,
},
source: '',
},
body: [],
}
export const ast = {
type: 'File',
start: 0,

View File

@ -1,4 +1,4 @@
import {ast} from 'src/flux/constants/ast'
import {ast, emptyAST} from 'src/flux/constants/ast'
import * as editor from 'src/flux/constants/editor'
import * as argTypes from 'src/flux/constants/argumentTypes'
import * as funcNames from 'src/flux/constants/funcNames'
@ -10,6 +10,7 @@ const MAX_RESPONSE_BYTES = 1e7 // 10 MB
export {
ast,
emptyAST,
funcNames,
argTypes,
editor,

View File

@ -15,7 +15,7 @@ import {UpdateScript} from 'src/flux/actions'
import {bodyNodes} from 'src/flux/helpers'
import {getSuggestions, getAST, getTimeSeries} from 'src/flux/apis'
import {builder, argTypes} from 'src/flux/constants'
import {builder, argTypes, emptyAST} from 'src/flux/constants'
import {Source, Service, Notification, FluxTable} from 'src/types'
import {
@ -114,6 +114,7 @@ export class FluxPage extends PureComponent<Props, State> {
onAppendJoin={this.handleAppendJoin}
onChangeScript={this.handleChangeScript}
onSubmitScript={this.handleSubmitScript}
onDeleteBody={this.handleDeleteBody}
/>
</div>
</KeyboardShortcuts>
@ -331,6 +332,13 @@ export class FluxPage extends PureComponent<Props, State> {
this.getASTResponse(script)
}
private handleDeleteBody = (bodyID: string): void => {
const newBody = this.state.body.filter(b => b.id !== bodyID)
const script = this.getBodyToScript(newBody)
this.getASTResponse(script)
}
private handleScriptUpToYield = (
bodyID: string,
declarationID: string,
@ -601,7 +609,8 @@ export class FluxPage extends PureComponent<Props, State> {
const {links} = this.props
if (!script) {
return
this.props.updateScript(script)
return this.setState({ast: emptyAST, body: []})
}
try {

View File

@ -45,6 +45,10 @@ class AnnotationInput extends Component<Props, State> {
</div>
)
}
public handleClickOutside = () => {
this.props.onConfirmUpdate()
this.setState({isEditing: false})
}
private handleInputClick = () => {
this.setState({isEditing: true})

View File

@ -1,7 +1,10 @@
import React, {PureComponent} from 'react'
import classnames from 'classnames'
import {ClickOutside} from 'src/shared/components/ClickOutside'
import {ErrorHandling} from 'src/shared/decorators/errors'
type Position = 'top' | 'bottom' | 'left' | 'right'
interface Props {
text?: string
confirmText?: string
@ -12,6 +15,7 @@ interface Props {
icon?: string
disabled?: boolean
customClass?: string
position?: Position
}
interface State {
@ -47,10 +51,11 @@ class ConfirmButton extends PureComponent<Props, State> {
className={this.className}
onClick={this.handleButtonClick}
ref={r => (this.buttonDiv = r)}
title={confirmText}
>
{icon && <span className={`icon ${icon}`} />}
{text && text}
<div className={`confirm-button--tooltip ${this.calculatedPosition}`}>
<div className={this.tooltipClassName}>
<div
className="confirm-button--confirmation"
onClick={this.handleConfirmClick}
@ -64,18 +69,6 @@ class ConfirmButton extends PureComponent<Props, State> {
)
}
private get className() {
const {type, size, square, disabled, customClass} = this.props
const {expanded} = this.state
const customClassString = customClass ? ` ${customClass}` : ''
const squareString = square ? ' btn-square' : ''
const expandedString = expanded ? ' active' : ''
const disabledString = disabled ? ' disabled' : ''
return `confirm-button btn ${type} ${size}${customClassString}${squareString}${expandedString}${disabledString}`
}
private handleButtonClick = () => {
if (this.props.disabled) {
return
@ -92,9 +85,27 @@ class ConfirmButton extends PureComponent<Props, State> {
this.setState({expanded: false})
}
private get calculatedPosition() {
private get className(): string {
const {type, size, square, disabled, customClass} = this.props
const {expanded} = this.state
return classnames(`confirm-button btn ${type} ${size}`, {
[customClass]: customClass,
'btn-square': square,
active: expanded,
disabled,
})
}
private get tooltipClassName(): string {
const {position} = this.props
if (position) {
return `confirm-button--tooltip ${position}`
}
if (!this.buttonDiv || !this.tooltipDiv) {
return ''
return 'confirm-button--tooltip bottom'
}
const windowWidth = window.innerWidth
@ -104,10 +115,10 @@ class ConfirmButton extends PureComponent<Props, State> {
const rightGap = windowWidth - buttonRect.right
if (tooltipRect.width / 2 > rightGap) {
return 'left'
return 'confirm-button--tooltip left'
}
return 'bottom'
return 'confirm-button--tooltip bottom'
}
}

View File

@ -112,6 +112,13 @@ class NewAnnotation extends Component<Props, State> {
)
}
public handleClickOutside = () => {
const {onDismissAddingAnnotation, isTempHovering} = this.props
if (!isTempHovering) {
onDismissAddingAnnotation()
}
}
private clampWithinGraphTimerange = (timestamp: number): number => {
const [xRangeStart] = this.props.dygraph.xAxisRange()
return Math.max(xRangeStart, timestamp)

View File

@ -11,18 +11,26 @@
position: absolute;
z-index: 1;
&.top {
bottom: calc(100% + 4px);
left: 50%;
transform: translateX(-50%);
}
&.bottom {
top: calc(100% + 4px);
left: 50%;
transform: translateX(-50%);
}
&.left {
top: 50%;
right: calc(100% + 4px);
transform: translateY(-50%);
}
&.right {
top: 50%;
left: calc(100% + 4px);
transform: translateY(-50%);
}
}
}
.confirm-button--confirmation {
@ -43,11 +51,7 @@
&:after {
content: '';
border: 8px solid transparent;
border-bottom-color: $c-curacao;
position: absolute;
bottom: 100%;
left: 50%;
transform: translateX(-50%);
transition: border-color 0.25s ease;
z-index: 100;
}
@ -56,28 +60,35 @@
background-color: $c-dreamsicle;
cursor: pointer;
}
&:hover:after {
border-bottom-color: $c-dreamsicle;
}
}
.confirm-button--tooltip.bottom .confirm-button--confirmation:after {
bottom: 100%;
left: 50%;
border-bottom-color: $c-curacao;
transform: translateX(-50%);
}
.confirm-button--tooltip.bottom .confirm-button--confirmation:hover:after {
border-bottom-color: $c-dreamsicle;
}
.confirm-button--tooltip.left .confirm-button--confirmation:after {
left: 100%;
top: 50%;
border-left-color: $c-curacao;
transform: translateY(-50%);
}
.confirm-button--tooltip.left .confirm-button--confirmation:hover:after {
border-left-color: $c-dreamsicle;
.top &:after {
top: 100%;
left: 50%;
transform: translateX(-50%);
border-top-color: $c-curacao;
}
.top &:hover:after {border-top-color: $c-dreamsicle;}
.bottom &:after {
bottom: 100%;
left: 50%;
transform: translateX(-50%);
border-bottom-color: $c-curacao;
}
.bottom &:hover:after {border-bottom-color: $c-dreamsicle;}
.left &:after {
top: 50%;
left: 100%;
transform: translateY(-50%);
border-left-color: $c-curacao;
}
.left &:hover:after {border-left-color: $c-dreamsicle;}
.right &:after {
top: 50%;
right: 100%;
transform: translateY(-50%);
border-right-color: $c-curacao;
}
.right &:hover:after {border-right-color: $c-dreamsicle;}
}
.confirm-button.active {

View File

@ -144,6 +144,8 @@ $flux-invalid-hover: $c-dreamsicle;
&:hover,
&.active {
cursor: pointer !important;
.func-node--preview {
color: $g20-white;
}
@ -196,7 +198,7 @@ $flux-invalid-hover: $c-dreamsicle;
}
// When a query exists unassigned to a variable
.func-node:first-child {
.func-node--wrapper:first-child .func-node {
margin-left: 0;
padding-left: $flux-node-gap;
.func-node--connector {
@ -248,24 +250,26 @@ $flux-invalid-hover: $c-dreamsicle;
}
}
.func-node--wrapper {
display: flex;
align-items: center;
}
.func-node--menu {
display: flex;
align-items: center;
position: absolute;
top: 50%;
right: 0;
transform: translate(100%, -50%);
opacity: 0;
transition: opacity 0.25s ease;
> button.btn {
> button.btn,
> .confirm-button {
margin-left: 4px;
}
}
.func-node:hover .func-node--menu,
.func-node.editing .func-node--menu,
.func-node.active .func-node--menu {
.func-node--wrapper:hover .func-node--menu,
.func-node.editing + .func-node--menu,
.func-node.active + .func-node--menu {
opacity: 1;
}

View File

@ -97,6 +97,7 @@ export const DEFAULT_TEMPLATES: DefaultTemplates = {
values: [],
type: TemplateType.CSV,
label: '',
query: {},
}
},
[TemplateType.TagKeys]: () => {

View File

@ -21,6 +21,7 @@ export type ScriptUpToYield = (
yieldNodeIndex: number,
isYieldable: boolean
) => string
export type OnDeleteBody = (bodyID: string) => void
export interface ScriptStatus {
type: string
@ -105,6 +106,9 @@ export interface FlatBody {
funcs?: Func[]
declarations?: FlatDeclaration[]
}
export interface Body extends FlatBody {
id: string
}
export interface Func {
type: string

4
ui/src/types/promises.ts Normal file
View File

@ -0,0 +1,4 @@
export interface WrappedCancelablePromise<T> {
promise: Promise<T>
cancel: () => void
}

32
ui/src/utils/promises.ts Normal file
View File

@ -0,0 +1,32 @@
import {WrappedCancelablePromise} from 'src/types/promises'
export const makeCancelable = <T>(
promise: Promise<T>
): WrappedCancelablePromise<T> => {
let isCanceled = false
const wrappedPromise = new Promise<T>(async (resolve, reject) => {
try {
const value = await promise
if (isCanceled) {
reject({isCanceled})
} else {
resolve(value)
}
} catch (error) {
if (isCanceled) {
reject({isCanceled})
} else {
reject(error)
}
}
})
return {
promise: wrappedPromise,
cancel() {
isCanceled = true
},
}
}

View File

@ -15,6 +15,7 @@ const setup = () => {
onValidate: () => {},
onAppendFrom: () => {},
onAppendJoin: () => {},
onDeleteBody: () => {},
status: {type: '', text: ''},
}