未验证 提交 967e14f2 编写于 作者: M Méril 提交者: GitHub

feat(console): add cta fo power users (#833)

上级 b3e21c5a
......@@ -6,19 +6,12 @@ module.exports = {
es6: true,
},
extends: [
"standard",
"standard-with-typescript",
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended",
"plugin:@typescript-eslint/recommended-requiring-type-checking",
"plugin:react/recommended",
"plugin:react-hooks/recommended",
"plugin:prettier/recommended",
"prettier",
"prettier/@typescript-eslint",
"prettier/babel",
"prettier/react",
"prettier/standard",
],
globals: { ace: true },
parser: "@typescript-eslint/parser",
......@@ -32,32 +25,15 @@ module.exports = {
plugins: ["@typescript-eslint", "babel", "prettier", "react", "standard"],
rules: {
"react/jsx-no-bind": "error",
"react/sort-comp": [
"error",
{
order: [
"static-methods",
"lifecycle",
"/^on.+$/",
"/^(get|set)(?!(InitialState$|DefaultProps$|ChildContext$)).+$/",
"everything-else",
"/^render.+$/",
"render",
],
},
],
"react/no-deprecated": "error",
"react/style-prop-object": "error",
"react/self-closing-comp": "error",
"react/require-render-return": "error",
"react/prefer-stateless-function": "error",
"react/no-unused-prop-types": "warn",
"react/no-unused-state": "warn",
"react/no-this-in-sfc": "error",
"react/no-typos": "error",
"react/no-redundant-should-component-update": "error",
"react/no-array-index-key": "error",
"react/no-access-state-in-setstate": "error",
"react/jsx-closing-bracket-location": "error",
"react/jsx-closing-tag-location": "error",
"react/jsx-equals-spacing": "error",
......@@ -82,24 +58,22 @@ module.exports = {
"react/jsx-no-comment-textnodes": "warn",
"react/jsx-curly-brace-presence": "error",
"jsx-quotes": ["error", "prefer-double"],
"@typescript-eslint/explicit-member-accessibility": "off",
"@typescript-eslint/consistent-type-definitions": ["error", "type"],
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/explicit-module-boundary-types": "off",
"@typescript-eslint/no-empty-function": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-this-alias": "off",
"@typescript-eslint/prefer-interface": "off",
"@typescript-eslint/no-unused-vars": "warn",
"@typescript-eslint/strict-boolean-expressions": "off",
"quote-props": ["error", "as-needed"],
"object-shorthand": ["error", "always"],
"no-unused-vars": "off",
"no-var": ["error"],
"no-void": ["off"],
"no-void": "off",
"no-console": [
"warn",
{
allow: ["warn", "error", "info"],
},
],
"no-unused-vars": "off",
"prettier/prettier": ["error"],
},
settings: {
......
此差异已折叠。
......@@ -17,6 +17,7 @@
"type-check:watch": "npm run type-check -- --watch",
"build": "cross-env NODE_ENV=production webpack && node utils/deploy.js",
"start": "webpack-dev-server --progress",
"start:prod": "cross-env NODE_ENV=production webpack-dev-server --progress",
"analyze-bundle": "cross-env ANALYZE=true NODE_ENV=production webpack-cli --display-reasons"
},
"dependencies": {
......@@ -28,7 +29,7 @@
"@types/react-dom": "16.9.8",
"@types/react-redux": "7.1.9",
"@types/react-transition-group": "4.4.0",
"@types/styled-components": "5.1.0",
"@types/styled-components": "5.1.7",
"@types/throttle-debounce": "2.1.0",
"ace-builds": "1.4.11",
"animate.css": "3.7.2",
......@@ -63,8 +64,8 @@
"@babel/preset-env": "7.10.2",
"@babel/preset-react": "7.10.1",
"@babel/preset-typescript": "7.10.1",
"@typescript-eslint/eslint-plugin": "3.2.0",
"@typescript-eslint/parser": "3.2.0",
"@typescript-eslint/eslint-plugin": "4.15.1",
"@typescript-eslint/parser": "4.15.1",
"archiver": "4.0.1",
"babel-loader": "8.1.0",
"babel-plugin-styled-components": "1.10.7",
......@@ -72,25 +73,25 @@
"copy-webpack-plugin": "6.0.2",
"cross-env": "7.0.2",
"css-loader": "3.5.3",
"eslint": "7.2.0",
"eslint-config-prettier": "6.11.0",
"eslint-config-standard": "14.1.1",
"eslint-plugin-babel": "5.3.0",
"eslint-plugin-import": "2.21.1",
"eslint": "7.20.0",
"eslint-config-prettier": "8.0.0",
"eslint-config-standard-with-typescript": "20.0.0",
"eslint-plugin-babel": "5.3.1",
"eslint-plugin-import": "2.22.1",
"eslint-plugin-node": "11.1.0",
"eslint-plugin-prettier": "3.1.3",
"eslint-plugin-promise": "4.2.1",
"eslint-plugin-react": "7.20.0",
"eslint-plugin-react-hooks": "4.0.4",
"eslint-plugin-standard": "4.0.1",
"eslint-plugin-prettier": "3.3.1",
"eslint-plugin-promise": "4.3.1",
"eslint-plugin-react": "7.22.0",
"eslint-plugin-react-hooks": "4.2.0",
"eslint-plugin-standard": "5.0.0",
"file-loader": "6.0.0",
"fork-ts-checker-webpack-plugin": "5.0.0",
"fork-ts-checker-webpack-plugin": "6.1.0",
"handlebars": "4.7.6",
"handlebars-loader": "1.7.1",
"html-webpack-plugin": "4.3.0",
"mini-css-extract-plugin": "0.9.0",
"optimize-css-assets-webpack-plugin": "5.0.3",
"prettier": "2.0.5",
"prettier": "2.2.1",
"rimraf": "3.0.2",
"sass": "1.26.8",
"sass-loader": "8.0.2",
......@@ -102,12 +103,11 @@
"stylelint-custom-processor-loader": "0.6.0",
"stylelint-processor-styled-components": "1.10.0",
"svg-react-loader": "0.4.6",
"typescript": "3.9.5",
"typescript": "4.1.5",
"webpack": "4.43.0",
"webpack-bundle-analyzer": "3.8.0",
"webpack-cli": "3.3.11",
"webpack-dev-server": "3.11.0",
"worker-loader": "2.0.0"
"webpack-dev-server": "3.11.0"
},
"author": {
"name": "QuestDB"
......
import { MouseEvent, ReactNode } from "react"
/*******************************************************************************
* ___ _ ____ ____
* / _ \ _ _ ___ ___| |_| _ \| __ )
* | | | | | | |/ _ \/ __| __| | | | _ \
* | |_| | |_| | __/\__ \ |_| |_| | |_) |
* \__\_\\__,_|\___||___/\__|____/|____/
*
* Copyright (c) 2014-2019 Appsicle
* Copyright (c) 2019-2020 QuestDB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
import React, { forwardRef, MouseEvent, ReactNode, Ref } from "react"
import styled, { css } from "styled-components"
import type { Color } from "types"
import type { Color, FontSize } from "types"
import { color } from "utils"
import { bezierTransition } from "../Transition"
type Size = "sm" | "md"
type Size = "sm" | "md" | "lg"
type Type = "button" | "submit"
const size: Size = "md"
const type: Type = "button"
const defaultProps = {
size,
type,
const defaultProps: { fontSize: FontSize; size: Size; type: Type } = {
fontSize: "md",
size: "md",
type: "button",
}
type DefaultProps = typeof defaultProps
type RenderRefProps = Omit<ButtonProps, keyof DefaultProps> &
Partial<DefaultProps>
export type ButtonProps = Readonly<{
children?: ReactNode
className?: string
disabled?: boolean
fontSize: FontSize
onClick?: (event: MouseEvent) => void
size?: Size
type?: Type
size: Size
type: Type
}>
type ThemeShape = {
......@@ -32,22 +60,33 @@ type ThemeShape = {
color: Color
}
export const getButtonSize = ({ size }: ButtonProps) =>
size === "sm" ? "2rem" : "3rem"
export const getButtonSize = ({ size }: { size: ButtonProps["size"] }) => {
switch (size) {
case "sm":
return "2rem"
case "md":
return "3rem"
case "lg":
return "5rem"
default:
return "3rem"
}
}
const baseStyles = css<ButtonProps>`
display: flex;
height: ${getButtonSize};
padding: 0 1rem;
align-items: center;
justify-content: center;
background: transparent;
border-radius: 4px;
border: 1px solid transparent;
font-weight: 400;
outline: 0;
font-size: ${({ fontSize, theme }) => theme.fontSize[fontSize]};
font-weight: 400;
line-height: 1.15;
${bezierTransition};
${({ disabled }) => disabled && "pointer-events: none;"};
svg + span {
margin-left: 0.5rem;
......@@ -87,7 +126,7 @@ const getTheme = (
}
`
export const PrimaryButton = styled.button<ButtonProps>`
const PrimaryButtonStyled = styled.button<ButtonProps>`
${baseStyles};
${getTheme(
{
......@@ -108,9 +147,16 @@ export const PrimaryButton = styled.button<ButtonProps>`
)};
`
const PrimaryButtonWithRef = (
props: RenderRefProps,
ref: Ref<HTMLButtonElement>,
) => <PrimaryButtonStyled {...defaultProps} {...props} ref={ref} />
export const PrimaryButton = forwardRef(PrimaryButtonWithRef)
PrimaryButton.defaultProps = defaultProps
export const SecondaryButton = styled.button<ButtonProps>`
const SecondaryButtonStyled = styled.button<ButtonProps>`
${baseStyles};
${getTheme(
{
......@@ -124,16 +170,23 @@ export const SecondaryButton = styled.button<ButtonProps>`
color: "draculaForeground",
},
{
background: "draculaSelection",
background: "gray1",
border: "gray1",
color: "gray1",
color: "draculaBackground",
},
)};
`
const SecondaryButtonWithRef = (
props: RenderRefProps,
ref: Ref<HTMLButtonElement>,
) => <SecondaryButtonStyled {...defaultProps} {...props} ref={ref} />
export const SecondaryButton = forwardRef(SecondaryButtonWithRef)
SecondaryButton.defaultProps = defaultProps
export const SuccessButton = styled.button<ButtonProps>`
const SuccessButtonStyled = styled.button<ButtonProps>`
${baseStyles};
${getTheme(
{
......@@ -154,9 +207,16 @@ export const SuccessButton = styled.button<ButtonProps>`
)};
`
const SuccessButtonWithRef = (
props: RenderRefProps,
ref: Ref<HTMLButtonElement>,
) => <SuccessButtonStyled {...defaultProps} {...props} ref={ref} />
export const SuccessButton = forwardRef(SuccessButtonWithRef)
SuccessButton.defaultProps = defaultProps
export const ErrorButton = styled.button<ButtonProps>`
const ErrorButtonStyled = styled.button<ButtonProps>`
${baseStyles};
${getTheme(
{
......@@ -177,9 +237,16 @@ export const ErrorButton = styled.button<ButtonProps>`
)};
`
const ErrorButtonWithRef = (
props: RenderRefProps,
ref: Ref<HTMLButtonElement>,
) => <ErrorButtonStyled {...defaultProps} {...props} ref={ref} />
export const ErrorButton = forwardRef(ErrorButtonWithRef)
ErrorButton.defaultProps = defaultProps
export const TransparentButton = styled.button<ButtonProps>`
const TransparentButtonStyled = styled.button<ButtonProps>`
${baseStyles};
${getTheme(
{
......@@ -200,4 +267,11 @@ export const TransparentButton = styled.button<ButtonProps>`
)};
`
const TransparentButtonWithRef = (
props: RenderRefProps,
ref: Ref<HTMLButtonElement>,
) => <TransparentButtonStyled {...defaultProps} {...props} ref={ref} />
export const TransparentButton = forwardRef(TransparentButtonWithRef)
TransparentButton.defaultProps = defaultProps
/*******************************************************************************
* ___ _ ____ ____
* / _ \ _ _ ___ ___| |_| _ \| __ )
* | | | | | | |/ _ \/ __| __| | | | _ \
* | |_| | |_| | __/\__ \ |_| |_| | |_) |
* \__\_\\__,_|\___||___/\__|____/|____/
*
* Copyright (c) 2014-2019 Appsicle
* Copyright (c) 2019-2020 QuestDB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
import { MutableRefObject, useEffect } from "react"
import { TransitionDuration } from "../Transition"
......@@ -13,14 +37,14 @@ export const useTransition = (
if (_active && !document.body.contains(element)) {
document.body.appendChild(element)
if (update) {
if (update != null) {
update()
}
return
}
if (!_active) {
timeoutId.current = setTimeout(() => {
timeoutId.current = window.setTimeout(() => {
document.body.contains(element) && document.body.removeChild(element)
}, TransitionDuration.REG)
}
......
import React from "react"
/*******************************************************************************
* ___ _ ____ ____
* / _ \ _ _ ___ ___| |_| _ \| __ )
* | | | | | | |/ _ \/ __| __| | | | _ \
* | |_| | |_| | __/\__ \ |_| |_| | |_) |
* \__\_\\__,_|\___||___/\__|____/|____/
*
* Copyright (c) 2014-2019 Appsicle
* Copyright (c) 2019-2020 QuestDB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
import React, { ComponentProps, forwardRef, Ref } from "react"
import styled from "styled-components"
import { ButtonProps, getButtonSize } from "components"
import { color } from "utils"
const defaultProps = {
type Type = "text" | "number"
const defaultProps: { size: ButtonProps["size"]; type: Type } = {
size: "md",
type: "text",
}
type Props = {
type Props = Readonly<{
id?: string
placeholder?: string
size: ButtonProps["size"]
title?: string
type: "text" | "number"
}
type: Type
}>
const Wrapper = styled.input`
height: 3rem;
const InputStyled = styled.input<Props>`
height: ${getButtonSize};
border: none;
padding: 0 1rem;
line-height: 1.5;
......@@ -33,6 +62,11 @@ const Wrapper = styled.input`
}
`
export const Input = (props: Props) => <Wrapper {...props} />
const InputWithRef = (
props: ComponentProps<typeof InputStyled>,
ref: Ref<HTMLInputElement>,
) => <InputStyled {...defaultProps} {...props} ref={ref} />
export const Input = forwardRef(InputWithRef)
Input.defaultProps = defaultProps
import { ReactNode } from "react"
/*******************************************************************************
* ___ _ ____ ____
* / _ \ _ _ ___ ___| |_| _ \| __ )
* | | | | | | |/ _ \/ __| __| | | | _ \
* | |_| | |_| | __/\__ \ |_| |_| | |_) |
* \__\_\\__,_|\___||___/\__|____/|____/
*
* Copyright (c) 2014-2019 Appsicle
* Copyright (c) 2019-2020 QuestDB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
import styled from "styled-components"
import { color } from "utils"
type Props = Readonly<{
children: ReactNode
className?: string
}>
export const PaneMenu = styled.div`
position: relative;
display: flex;
......
import { ReactNode } from "react"
import styled from "styled-components"
/*******************************************************************************
* ___ _ ____ ____
* / _ \ _ _ ___ ___| |_| _ \| __ )
* | | | | | | |/ _ \/ __| __| | | | _ \
* | |_| | |_| | __/\__ \ |_| |_| | |_) |
* \__\_\\__,_|\___||___/\__|____/|____/
*
* Copyright (c) 2014-2019 Appsicle
* Copyright (c) 2019-2020 QuestDB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
type Props = Readonly<{
children: ReactNode
className?: string
}>
import styled from "styled-components"
export const PaneWrapper = styled.div`
display: flex;
......
/*******************************************************************************
* ___ _ ____ ____
* / _ \ _ _ ___ ___| |_| _ \| __ )
* | | | | | | |/ _ \/ __| __| | | | _ \
* | |_| | |_| | __/\__ \ |_| |_| | |_) |
* \__\_\\__,_|\___||___/\__|____/|____/
*
* Copyright (c) 2014-2019 Appsicle
* Copyright (c) 2019-2020 QuestDB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
import type { Placement, Options } from "@popperjs/core"
import React, {
ReactNode,
......@@ -59,7 +83,7 @@ export const PopperHover = ({
const handleMouseEnter = useCallback(() => {
if (delay) {
delayTimeoutId.current = setTimeout(() => {
delayTimeoutId.current = window.setTimeout(() => {
setActive(true)
}, delay)
} else {
......
/*******************************************************************************
* ___ _ ____ ____
* / _ \ _ _ ___ ___| |_| _ \| __ )
* | | | | | | |/ _ \/ __| __| | | | _ \
* | |_| | |_| | __/\__ \ |_| |_| | |_) |
* \__\_\\__,_|\___||___/\__|____/|____/
*
* Copyright (c) 2014-2019 Appsicle
* Copyright (c) 2019-2020 QuestDB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
import type { Placement, Options } from "@popperjs/core"
import React, {
ReactNode,
......@@ -58,10 +82,7 @@ export const PopperToggle = ({
(event: TouchEvent | MouseEvent) => {
const target = event.target as Element
if (
container.contains(target) ||
(triggerElement && triggerElement.contains(target))
) {
if (container.contains(target) || triggerElement?.contains(target)) {
return
}
......
/*******************************************************************************
* ___ _ ____ ____
* / _ \ _ _ ___ ___| |_| _ \| __ )
* | | | | | | |/ _ \/ __| __| | | | _ \
* | |_| | |_| | __/\__ \ |_| |_| | |_) |
* \__\_\\__,_|\___||___/\__|____/|____/
*
* Copyright (c) 2014-2019 Appsicle
* Copyright (c) 2019-2020 QuestDB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
import React, {
Children,
MouseEvent as ReactMouseEvent,
......@@ -138,10 +162,13 @@ export const Splitter = ({
}
if (
(min && max && position > min && position < window[side] - max) ||
(!min && max && position < window[side] - max) ||
(!max && min && position > min) ||
(!min && !max)
(min != null &&
max != null &&
position > min &&
position < window[side] - max) ||
(min == null && max != null && position < window[side] - max) ||
(max == null && min != null && position > min) ||
(min == null && max == null)
) {
setGhostPosition(position)
}
......@@ -159,7 +186,7 @@ export const Splitter = ({
const handleMouseDown = useCallback(
(event: ReactTouchEvent | ReactMouseEvent) => {
if (splitter.current && splitter.current.parentElement) {
if (splitter.current?.parentElement) {
const clientPosition =
direction === "horizontal" ? "clientX" : "clientY"
const coordinate = direction === "horizontal" ? "x" : "y"
......@@ -213,7 +240,7 @@ export const Splitter = ({
}, [direction, ghostPosition, name, onChange, originalPosition, pressed])
useEffect(() => {
const size = parseInt(localStorage.getItem(`splitter.${name}`) || "0", 10)
const size = parseInt(localStorage.getItem(`splitter.${name}`) ?? "0", 10)
if (size) {
setBasis(size)
......@@ -225,7 +252,7 @@ export const Splitter = ({
const style = {
display: "flex",
flexGrow: 0,
flexBasis: basis || fallback,
flexBasis: basis ?? fallback,
flexShrink: 1,
}
......
import { ReactNode } from "react"
/*******************************************************************************
* ___ _ ____ ____
* / _ \ _ _ ___ ___| |_| _ \| __ )
* | | | | | | |/ _ \/ __| __| | | | _ \
* | |_| | |_| | __/\__ \ |_| |_| | |_) |
* \__\_\\__,_|\___||___/\__|____/|____/
*
* Copyright (c) 2014-2019 Appsicle
* Copyright (c) 2019-2020 QuestDB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
import React, { ReactNode } from "react"
import styled, { css } from "styled-components"
import type { Color, FontSize } from "types"
......@@ -6,6 +30,7 @@ import { color } from "utils"
type FontStyle = "normal" | "italic"
type Transform = "capitalize" | "lowercase" | "uppercase"
type Type = "span" | "label"
export type TextProps = Readonly<{
_style?: FontStyle
......@@ -15,15 +40,20 @@ export type TextProps = Readonly<{
color?: Color
children?: ReactNode
ellipsis?: boolean
htmlFor?: string
onClick?: () => void
size?: FontSize
transform?: Transform
type: Type
weight?: number
}>
const defaultProps: Readonly<{
color: Color
type: Type
}> = {
color: "black",
type: "span",
}
const ellipsisStyles = css`
......@@ -36,15 +66,19 @@ export const textStyles = css<TextProps>`
color: ${(props) => (props.color ? color(props.color) : "inherit")};
font-family: ${({ code, theme }) => code && theme.fontMonospace};
font-size: ${({ size, theme }) => (size ? theme.fontSize[size] : "inherit")};
font-style: ${({ _style }) => _style || "inherit"};
font-style: ${({ _style }) => _style ?? "inherit"};
font-weight: ${({ weight }) => weight};
text-transform: ${({ transform }) => transform};
${({ align }) => (align ? `text-align: ${align}` : "")};
${({ ellipsis }) => ellipsis && ellipsisStyles};
`
export const Text = styled.span<TextProps>`
const TextStyled = styled.label<TextProps>`
${textStyles};
`
export const Text = ({ type, ...rest }: TextProps) => {
return <TextStyled as={type} {...rest} type={type} />
}
Text.defaultProps = defaultProps
/*******************************************************************************
* ___ _ ____ ____
* / _ \ _ _ ___ ___| |_| _ \| __ )
* | | | | | | |/ _ \/ __| __| | | | _ \
* | |_| | |_| | __/\__ \ |_| |_| | |_) |
* \__\_\\__,_|\___||___/\__|____/|____/
*
* Copyright (c) 2014-2019 Appsicle
* Copyright (c) 2019-2020 QuestDB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
import React, { forwardRef, Ref } from "react"
import styled, { css } from "styled-components"
import type { Color } from "types"
import type { Color, FontSize } from "types"
import { color } from "utils"
import { ButtonProps, getButtonSize, PrimaryButton } from "../Button"
import { ButtonProps, getButtonSize } from "../Button"
import { bezierTransition } from "../Transition"
type Direction = "top" | "right" | "bottom" | "left"
const direction: Direction = "bottom"
const defaultProps = {
...PrimaryButton.defaultProps,
direction,
const defaultProps: {
direction: Direction
fontSize: FontSize
selected: boolean
size: ButtonProps["size"]
type: ButtonProps["type"]
} = {
direction: "bottom",
fontSize: "md",
selected: false,
size: "md",
type: "button",
}
type DefaultProps = typeof defaultProps
type Props = Readonly<{
direction?: Direction
selected?: boolean
direction: Direction
selected: boolean
}> &
ButtonProps
type RenderRefProps = Omit<Props, keyof DefaultProps> & Partial<DefaultProps>
type ThemeShape = {
background: Color
color: Color
......@@ -34,11 +69,12 @@ const baseStyles = css<Props>`
align-items: center;
justify-content: center;
background: transparent;
font-weight: 400;
border: none;
outline: 0;
line-height: 1.15;
opacity: ${({ selected }) => (selected ? "1" : "0.5")};
border: none;
font-size: ${({ fontSize, theme }) => theme.fontSize[fontSize]};
font-weight: 400;
line-height: 1.15;
${({ direction }) =>
`border-${direction || defaultProps.direction}: 2px solid transparent;`};
${bezierTransition};
......@@ -75,7 +111,7 @@ const getTheme = (normal: ThemeShape, hover: ThemeShape) =>
}
`
export const PrimaryToggleButton = styled.button<Props>`
const PrimaryToggleButtonStyled = styled.button<Props>`
${baseStyles};
${getTheme(
{
......@@ -89,4 +125,11 @@ export const PrimaryToggleButton = styled.button<Props>`
)};
`
const PrimaryToggleButtonWithRef = (
props: RenderRefProps,
ref: Ref<HTMLButtonElement>,
) => <PrimaryToggleButtonStyled {...defaultProps} {...props} ref={ref} />
export const PrimaryToggleButton = forwardRef(PrimaryToggleButtonWithRef)
PrimaryToggleButton.defaultProps = defaultProps
/*******************************************************************************
* ___ _ ____ ____
* / _ \ _ _ ___ ___| |_| _ \| __ )
* | | | | | | |/ _ \/ __| __| | | | _ \
* | |_| | |_| | __/\__ \ |_| |_| | |_) |
* \__\_\\__,_|\___||___/\__|____/|____/
*
* Copyright (c) 2014-2019 Appsicle
* Copyright (c) 2019-2020 QuestDB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
export enum BusEvent {
MSG_ACTIVE_PANEL = "active.panel",
MSG_EDITOR_EXECUTE = "editor.execute",
......@@ -14,3 +38,16 @@ export enum BusEvent {
MSG_QUERY_RUNNING = "query.out.running",
REACT_READY = "react.ready",
}
export enum ModalId {
POWER_USER = "POWER_USER",
}
export enum TelemetryTable {
MAIN = "telemetry",
CONFIG = "telemetry_config",
}
const BASE = process.env.NODE_ENV === "production" ? "fara" : "alurin"
export const API = `https://${BASE}.questdb.io`
......@@ -14,10 +14,10 @@
<div id="page-wrapper" class="gray-bg">
<div class="js-sql-panel" id="console"></div>
{{> partials/import}}
<div id="footer"></div>
</div>
<div id="notifications"></div>
<div id="sideMenu"></div>
<div id="modal"></div>
</body>
</html>
/*******************************************************************************
* ___ _ ____ ____
* / _ \ _ _ ___ ___| |_| _ \| __ )
* | | | | | | |/ _ \/ __| __| | | | _ \
* | |_| | |_| | __/\__ \ |_| |_| | |_) |
* \__\_\\__,_|\___||___/\__|____/|____/
*
* Copyright (c) 2014-2019 Appsicle
* Copyright (c) 2019-2020 QuestDB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
import "core-js/features/promise"
import "./js/console"
import startTelemetry from "./telemetry"
import React from "react"
import ReactDOM from "react-dom"
......@@ -50,5 +73,3 @@ ReactDOM.render(
</ScreenSizeProvider>,
document.getElementById("root"),
)
void startTelemetry()
/*******************************************************************************
* ___ _ ____ ____
* / _ \ _ _ ___ ___| |_| _ \| __ )
* | | | | | | |/ _ \/ __| __| | | | _ \
* | |_| | |_| | __/\__ \ |_| |_| | |_) |
* \__\_\\__,_|\___||___/\__|____/|____/
*
* Copyright (c) 2014-2019 Appsicle
* Copyright (c) 2019-2020 QuestDB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
import { Range } from "ace-builds"
import React, { useCallback, useEffect, useRef, useState } from "react"
import ReactAce from "react-ace"
......@@ -6,10 +30,11 @@ import ResizeObserver from "resize-observer-polyfill"
import styled from "styled-components"
import { PaneContent, Text } from "components"
import { BusEvent } from "consts"
import { actions, selectors } from "store"
import { theme } from "theme"
import { NotificationType } from "types"
import { BusEvent, color, ErrorResult } from "utils"
import { color, ErrorResult } from "utils"
import * as QuestDB from "utils/questdb"
import Loader from "../Loader"
......@@ -91,7 +116,7 @@ const Ace = () => {
? getQueryFromCursor(editor)
: getQueryFromSelection(editor)
if (request && request.query) {
if (request?.query) {
void quest
.queryRaw(request.query, { limit: "0,1000" })
.then((result) => {
......@@ -146,7 +171,7 @@ const Ace = () => {
const token = editor.session.getTokenAt(
position.row - 1,
position.column,
) || {
) ?? {
value: "",
}
const range = new Range(
......
/*******************************************************************************
* ___ _ ____ ____
* / _ \ _ _ ___ ___| |_| _ \| __ )
* | | | | | | |/ _ \/ __| __| | | | _ \
* | |_| | |_| | __/\__ \ |_| |_| | |_) |
* \__\_\\__,_|\___||___/\__|____/|____/
*
* Copyright (c) 2014-2019 Appsicle
* Copyright (c) 2019-2020 QuestDB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
import React, { useEffect, useRef, useState } from "react"
import { CSSTransition } from "react-transition-group"
import styled, { keyframes } from "styled-components"
......@@ -56,7 +80,7 @@ const Loader = ({ show }: Props) => {
if (!show) {
setVisible(false)
} else {
timeoutId.current = setTimeout(() => {
timeoutId.current = window.setTimeout(() => {
setVisible(true)
}, 5e2)
}
......
/*******************************************************************************
* ___ _ ____ ____
* / _ \ _ _ ___ ___| |_| _ \| __ )
* | | | | | | |/ _ \/ __| __| | | | _ \
* | |_| | |_| | __/\__ \ |_| |_| | |_) |
* \__\_\\__,_|\___||___/\__|____/|____/
*
* Copyright (c) 2014-2019 Appsicle
* Copyright (c) 2019-2020 QuestDB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
import docsearch from "docsearch.js"
import React, { useCallback, useEffect, useState } from "react"
import { useDispatch, useSelector } from "react-redux"
......@@ -85,7 +109,7 @@ const Menu = () => {
const dispatch = useDispatch()
const [popperActive, setPopperActive] = useState<boolean>()
const escPress = useKeyPress("Escape")
const { savedQueries } = useSelector(selectors.console.getConfiguration)
const { savedQueries } = useSelector(selectors.console.getConfig)
const running = useSelector(selectors.query.getRunning)
const opened = useSelector(selectors.console.getSideMenuOpened)
const { sm } = useScreenSize()
......
/*******************************************************************************
* ___ _ ____ ____
* / _ \ _ _ ___ ___| |_| _ \| __ )
* | | | | | | |/ _ \/ __| __| | | | _ \
* | |_| | |_| | __/\__ \ |_| |_| | |_) |
* \__\_\\__,_|\___||___/\__|____/|____/
*
* Copyright (c) 2014-2019 Appsicle
* Copyright (c) 2019-2020 QuestDB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
import React, { useCallback } from "react"
import styled, { css } from "styled-components"
import { FileCode } from "@styled-icons/remix-line/FileCode"
import { Text, TransitionDuration } from "components"
import { BusEvent } from "consts"
import { QueryShape } from "types"
import { BusEvent, color } from "utils"
import { color } from "utils"
type Props = Readonly<{
active: boolean
......
/*******************************************************************************
* ___ _ ____ ____
* / _ \ _ _ ___ ___| |_| _ \| __ )
* | | | | | | |/ _ \/ __| __| | | | _ \
* | |_| | |_| | __/\__ \ |_| |_| | |_) |
* \__\_\\__,_|\___||___/\__|____/|____/
*
* Copyright (c) 2014-2019 Appsicle
* Copyright (c) 2019-2020 QuestDB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
import React, { forwardRef, Ref, useEffect, useState } from "react"
import styled from "styled-components"
import { DownArrowSquare } from "@styled-icons/boxicons-solid/DownArrowSquare"
import { UpArrowSquare } from "@styled-icons/boxicons-solid/UpArrowSquare"
import { Text, useKeyPress } from "components"
import { BusEvent } from "consts"
import { QueryShape } from "types"
import { BusEvent, color } from "utils"
import { color } from "utils"
import Row from "./Row"
......
......@@ -21,6 +21,7 @@
* limitations under the License.
*
******************************************************************************/
import React, { useCallback, useEffect, useState } from "react"
import { useSelector } from "react-redux"
import { CSSTransition } from "react-transition-group"
......@@ -32,6 +33,16 @@ import { selectors } from "store"
import GithubBanner from "../GithubBanner"
const Wrapper = styled.div`
position: absolute;
display: flex;
height: 4rem;
bottom: 0;
left: 0;
right: 0;
padding-left: 45px;
`
const Copyright = styled.div`
display: flex;
padding-left: 1rem;
......@@ -71,7 +82,7 @@ const Footer = () => {
const handleClick = useCallback(() => {
setShowBanner(false)
}, [])
const { githubBanner } = useSelector(selectors.console.getConfiguration)
const { githubBanner } = useSelector(selectors.console.getConfig)
useEffect(() => {
setTimeout(() => {
......@@ -80,7 +91,7 @@ const Footer = () => {
}, [])
return (
<>
<Wrapper id="footer">
<GithubBannerTransition />
<Copyright>
<Text color="draculaForeground">
......@@ -106,7 +117,7 @@ const Footer = () => {
>
<GithubBanner onClick={handleClick} />
</CSSTransition>
</>
</Wrapper>
)
}
......
/*******************************************************************************
* ___ _ ____ ____
* / _ \ _ _ ___ ___| |_| _ \| __ )
* | | | | | | |/ _ \/ __| __| | | | _ \
* | |_| | |_| | __/\__ \ |_| |_| | |_) |
* \__\_\\__,_|\___||___/\__|____/|____/
*
* Copyright (c) 2014-2019 Appsicle
* Copyright (c) 2019-2020 QuestDB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
import React, { useCallback, useEffect } from "react"
import { createPortal } from "react-dom"
import styled from "styled-components"
import { Splitter, useScreenSize } from "components"
import { BusEvent } from "utils"
import { BusEvent } from "consts"
import Editor from "../Editor"
import Footer from "../Footer"
import Modal from "../Modal"
import Notifications from "../Notifications"
import Result from "../Result"
import SideMenu from "../SideMenu"
......@@ -20,9 +45,9 @@ const Top = styled.div`
const Layout = () => {
const consoleNode = document.getElementById("console")
const footerNode = document.getElementById("footer")
const notificationsNode = document.getElementById("notifications")
const sideMenuNode = document.getElementById("sideMenu")
const modalNode = document.getElementById("modal")
const { sm } = useScreenSize()
const handleResultSplitterChange = useCallback(() => {
......@@ -38,6 +63,7 @@ const Layout = () => {
return (
<>
<Sidebar />
<Footer />
{consoleNode &&
createPortal(
<Splitter
......@@ -56,7 +82,7 @@ const Layout = () => {
min={200}
name="schema"
>
{sm === false && <Schema />}
{!sm && <Schema />}
<Editor />
</Splitter>
</Top>
......@@ -64,9 +90,9 @@ const Layout = () => {
</Splitter>,
consoleNode,
)}
{footerNode && createPortal(<Footer />, footerNode)}
{notificationsNode && createPortal(<Notifications />, notificationsNode)}
{sideMenuNode && createPortal(<SideMenu />, sideMenuNode)}
{modalNode && createPortal(<Modal />, modalNode)}
</>
)
}
......
/*******************************************************************************
* ___ _ ____ ____
* / _ \ _ _ ___ ___| |_| _ \| __ )
* | | | | | | |/ _ \/ __| __| | | | _ \
* | |_| | |_| | __/\__ \ |_| |_| | |_) |
* \__\_\\__,_|\___||___/\__|____/|____/
*
* Copyright (c) 2014-2019 Appsicle
* Copyright (c) 2019-2020 QuestDB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
import React, { ChangeEvent, useCallback, useState } from "react"
import { Input } from "components"
type Props = Readonly<{
dirty: boolean
onInit: () => void
}>
const EmailInput = ({ dirty, onInit }: Props) => {
const [email, setEmail] = useState("")
const handleChange = useCallback(
(event: ChangeEvent<HTMLInputElement>) => {
setEmail(event.target?.value)
if (!dirty) {
onInit()
}
},
[dirty, onInit],
)
return (
<Input
name="email"
onChange={handleChange}
pattern={
"^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+$"
}
required
size="lg"
title="email"
type="email"
value={email}
/>
)
}
export default EmailInput
/*******************************************************************************
* ___ _ ____ ____
* / _ \ _ _ ___ ___| |_| _ \| __ )
* | | | | | | |/ _ \/ __| __| | | | _ \
* | |_| | |_| | __/\__ \ |_| |_| | |_) |
* \__\_\\__,_|\___||___/\__|____/|____/
*
* Copyright (c) 2014-2019 Appsicle
* Copyright (c) 2019-2020 QuestDB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
import React, { forwardRef, Ref, useCallback, useRef, useState } from "react"
import { useDispatch, useSelector } from "react-redux"
import styled from "styled-components"
import { Close } from "@styled-icons/remix-line/Close"
import { SecondaryButton, Text } from "components"
import { actions, selectors } from "store"
import { color, fetchApi } from "utils"
import EmailInput from "./EmailInput"
type Props = Readonly<{
innerRef: Ref<HTMLDivElement>
}>
const Wrapper = styled.div`
position: relative;
display: flex;
flex-direction: column;
max-width: 600px;
padding: 6rem 7rem;
background: ${color("black")};
box-shadow: 8px 6px 0 0 ${color("draculaBackground")};
border-radius: 4px;
`
const Title = styled(Text)``
const Description = styled(Text)`
margin: 4rem 0;
line-height: 1.75;
`
const Form = styled.form`
display: flex;
max-width: 300px;
width: 100%;
align-self: center;
flex-direction: column;
`
const Submit = styled(SecondaryButton)`
margin-top: 2rem;
`
const CloseIcon = styled(Close)`
position: absolute;
right: 1rem;
top: 1rem;
color: ${color("gray2")};
&:hover {
cursor: pointer;
}
`
const Link = styled.a`
color: ${color("draculaCyan")};
&:hover {
color: ${color("draculaCyan")};
text-decoration: underline;
}
`
const Error = styled(Text)`
margin-top: 0.5rem;
`
const EMAIL_INVALID = "Email address is missing or not valid"
const PowerUser = ({ innerRef }: Props) => {
const [error, setError] = useState("")
const [dirty, setDirty] = useState(false)
const formNode = useRef<HTMLFormElement | null>(null)
const dispatch = useDispatch()
const config = useSelector(selectors.telemetry.getConfig)
const handleClose = useCallback(() => {
dispatch(actions.console.setModalId())
}, [dispatch])
const handleInit = useCallback(() => {
setError("")
setDirty(true)
}, [])
const handleSubmit = useCallback(async () => {
if (formNode.current == null) {
return
}
const valid = formNode.current.checkValidity()
setDirty(false)
if (valid) {
const formData = new FormData(formNode.current)
const response = await fetchApi("profile", {
body: JSON.stringify({
email: formData.get("email"),
id: config?.id,
}),
method: "POST",
})
if (response.error && response.status === 401) {
setError(EMAIL_INVALID)
} else {
setError("")
dispatch(actions.console.setModalId())
}
} else {
setError(EMAIL_INVALID)
}
}, [config, dispatch, formNode])
const handleLinkClick = useCallback(async () => {
await fetchApi("profile", {
body: JSON.stringify({
id: config?.id,
slack: true,
}),
method: "POST",
})
dispatch(actions.console.setModalId())
}, [config, dispatch])
return (
<Wrapper ref={innerRef}>
<Title color="white" size="hg">
Let&rsquo;s get in touch!
</Title>
<Description color="white" size="lg">
We&rsquo;d love to get your feedback on features and performance in
QuestDB. Let us know your thoughts on{" "}
<Link
href="https://slack.questdb.io"
onClick={handleLinkClick}
rel="noopener noreferrer"
target="_blank"
>
our community slack
</Link>{" "}
or share your email below so we can get in contact.
</Description>
<Form ref={formNode}>
<Text color="white" htmlFor="name" size="lg" type="label">
Email
</Text>
<EmailInput dirty={dirty} onInit={handleInit} />
{error !== "" && (
<Error color="draculaOrange" size="md">
{error}
</Error>
)}
<Submit fontSize="lg" onClick={handleSubmit} size="lg">
Send
</Submit>
</Form>
<CloseIcon onClick={handleClose} size="24px" />
</Wrapper>
)
}
const PowerUserWithRef = (
props: Omit<Props, "innerRef">,
ref: Ref<HTMLDivElement>,
) => <PowerUser {...props} innerRef={ref} />
export default forwardRef(PowerUserWithRef)
/*******************************************************************************
* ___ _ ____ ____
* / _ \ _ _ ___ ___| |_| _ \| __ )
* | | | | | | |/ _ \/ __| __| | | | _ \
* | |_| | |_| | __/\__ \ |_| |_| | |_) |
* \__\_\\__,_|\___||___/\__|____/|____/
*
* Copyright (c) 2014-2019 Appsicle
* Copyright (c) 2019-2020 QuestDB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
import React, {
MouseEvent,
useCallback,
useEffect,
useRef,
useState,
} from "react"
import { useDispatch, useSelector } from "react-redux"
import { CSSTransition } from "react-transition-group"
import styled from "styled-components"
import { TransitionDuration } from "components"
import { ModalId } from "consts"
import { actions, selectors } from "store"
import PowerUser from "./PowerUser"
const Backdrop = styled.div<{ active: boolean }>`
position: fixed;
display: flex;
top: 0;
right: 0;
bottom: 0;
left: 0;
align-items: center;
justify-content: center;
z-index: ${({ active }) => (active ? 50 : -1)};
background: ${({ active, theme }) =>
active ? theme.color.blackAlpha40 : theme.color.transparent};
transition: background ${TransitionDuration.REG}ms
cubic-bezier(0, 0, 0.38, 0.9);
`
const ModalComp = {
[ModalId.POWER_USER]: PowerUser,
}
const Modal = () => {
const [ready, setReady] = useState(false)
const modalId = useSelector(selectors.console.getModalId)
const modalNode = useRef<HTMLDivElement | null>(null)
const dispatch = useDispatch()
const handleClick = useCallback(
(event: MouseEvent) => {
if (ModalId.POWER_USER === modalId) {
return
}
if (
modalNode.current != null &&
event.target instanceof HTMLElement &&
!modalNode.current.contains(event.target)
) {
dispatch(actions.console.setModalId())
}
},
[dispatch, modalId],
)
useEffect(() => {
setTimeout(() => {
setReady(true)
}, 10)
}, [])
return (
<CSSTransition
classNames="fade-reg"
in={ready && modalId != null}
timeout={TransitionDuration.REG}
unmountOnExit
>
<Backdrop active={ready} onClick={handleClick}>
{modalId != null &&
React.createElement(ModalComp[modalId], {
ref: modalNode,
})}
</Backdrop>
</CSSTransition>
)
}
export default Modal
/*******************************************************************************
* ___ _ ____ ____
* / _ \ _ _ ___ ___| |_| _ \| __ )
* | | | | | | |/ _ \/ __| __| | | | _ \
* | |_| | |_| | __/\__ \ |_| |_| | |_) |
* \__\_\\__,_|\___||___/\__|____/|____/
*
* Copyright (c) 2014-2019 Appsicle
* Copyright (c) 2019-2020 QuestDB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
import React, { useCallback, useEffect, useState } from "react"
import { useSelector } from "react-redux"
import styled from "styled-components"
......@@ -17,8 +41,9 @@ import {
Tooltip,
useScreenSize,
} from "components"
import { BusEvent } from "consts"
import { selectors } from "store"
import { BusEvent, color } from "utils"
import { color } from "utils"
import * as QuestDB from "utils/questdb"
const Menu = styled(PaneMenu)`
......@@ -41,14 +66,14 @@ const Content = styled(PaneContent)`
const ButtonWrapper = styled.div`
display: flex;
align-items: center;
`
${/* sc-selector */ SecondaryButton}:not(:last-child) {
margin-right: 1rem;
}
const RowCount = styled(Text)`
margin-right: 2rem;
`
${/* sc-selector */ Text} {
margin-right: 2rem;
}
const RefreshButton = styled(SecondaryButton)`
margin-right: 1rem;
`
const ToggleButton = styled(PrimaryToggleButton)`
......@@ -121,9 +146,9 @@ const Result = () => {
<ButtonWrapper>
{count && !sm && (
<Text color="draculaForeground">
<RowCount color="draculaForeground">
{`${count.toLocaleString()} row${count > 1 ? "s" : ""}`}
</Text>
</RowCount>
)}
{!sm && (
......@@ -131,9 +156,9 @@ const Result = () => {
delay={350}
placement="bottom"
trigger={
<SecondaryButton onClick={handleRefreshClick}>
<RefreshButton onClick={handleRefreshClick}>
<Refresh size="18px" />
</SecondaryButton>
</RefreshButton>
}
>
<Tooltip>Refresh</Tooltip>
......
/*******************************************************************************
* ___ _ ____ ____
* / _ \ _ _ ___ ___| |_| _ \| __ )
* | | | | | | |/ _ \/ __| __| | | | _ \
* | |_| | |_| | __/\__ \ |_| |_| | |_) |
* \__\_\\__,_|\___||___/\__|____/|____/
*
* Copyright (c) 2014-2019 Appsicle
* Copyright (c) 2019-2020 QuestDB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
import React, { useCallback, useEffect, useState } from "react"
import { CSSTransition } from "react-transition-group"
import { from, combineLatest, of } from "rxjs"
......@@ -22,8 +46,6 @@ type Props = QuestDB.Table &
table: string
}>
type TitleProps = Readonly<{ expanded: boolean }>
const Wrapper = styled.div`
display: flex;
margin-top: 0.5rem;
......@@ -111,17 +133,16 @@ const Table = ({ description, expanded, onChange, refresh, table }: Props) => {
unmountOnExit
>
<Columns>
{columns &&
columns.map((column) => (
<Row
{...column}
key={`${column.column}-${column.type}${
column.indexed ? "-i" : ""
}`}
kind="column"
name={column.column}
/>
))}
{columns?.map((column) => (
<Row
{...column}
key={`${column.column}-${column.type}${
column.indexed ? "-i" : ""
}`}
kind="column"
name={column.column}
/>
))}
</Columns>
</CSSTransition>
</Wrapper>
......
/*******************************************************************************
* ___ _ ____ ____
* / _ \ _ _ ___ ___| |_| _ \| __ )
* | | | | | | |/ _ \/ __| __| | | | _ \
* | |_| | |_| | __/\__ \ |_| |_| | |_) |
* \__\_\\__,_|\___||___/\__|____/|____/
*
* Copyright (c) 2014-2019 Appsicle
* Copyright (c) 2019-2020 QuestDB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
import React, {
CSSProperties,
forwardRef,
......@@ -86,7 +110,7 @@ const Schema = ({
const [tables, setTables] = useState<QuestDB.Table[]>()
const [opened, setOpened] = useState<string>()
const [refresh, setRefresh] = useState(Date.now())
const { readOnly } = useSelector(selectors.console.getConfiguration)
const { readOnly } = useSelector(selectors.console.getConfig)
const handleChange = useCallback((name: string) => {
setOpened(name)
......
/*******************************************************************************
* ___ _ ____ ____
* / _ \ _ _ ___ ___| |_| _ \| __ )
* | | | | | | |/ _ \/ __| __| | | | _ \
* | |_| | |_| | __/\__ \ |_| |_| | |_) |
* \__\_\\__,_|\___||___/\__|____/|____/
*
* Copyright (c) 2014-2019 Appsicle
* Copyright (c) 2019-2020 QuestDB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
import React, { useCallback, useEffect, useState } from "react"
import { useSelector } from "react-redux"
import styled from "styled-components"
......@@ -14,6 +38,7 @@ const Wrapper = styled.div`
flex: 0 0 4.5rem;
flex-direction: column;
border-right: 1px solid rgba(0, 0, 0, 0.1);
background: ${color("draculaBackgroundDarker")};
`
const Logo = styled.div`
......@@ -50,6 +75,10 @@ const DisabledNavigation = styled.div`
flex: 0 0 5rem;
align-items: center;
justify-content: center;
&:disabled {
pointer-events: none;
}
`
type Tab = "console" | "import"
......@@ -62,7 +91,7 @@ const Sidebar = () => {
const handleImportClick = useCallback(() => {
setSelected("import")
}, [])
const { readOnly } = useSelector(selectors.console.getConfiguration)
const { readOnly } = useSelector(selectors.console.getConfig)
useEffect(() => {
const consolePanel = document.querySelector<HTMLElement>(".js-sql-panel")
......
import { ConfigurationShape, ConsoleAction, ConsoleAT } from "types"
/*******************************************************************************
* ___ _ ____ ____
* / _ \ _ _ ___ ___| |_| _ \| __ )
* | | | | | | |/ _ \/ __| __| | | | _ \
* | |_| | |_| | __/\__ \ |_| |_| | |_) |
* \__\_\\__,_|\___||___/\__|____/|____/
*
* Copyright (c) 2014-2019 Appsicle
* Copyright (c) 2019-2020 QuestDB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
import { ModalId } from "consts"
import { ConsoleConfigShape, ConsoleAction, ConsoleAT } from "types"
const bootstrap = (): ConsoleAction => ({
type: ConsoleAT.BOOTSTRAP,
......@@ -9,13 +34,24 @@ const refreshAuthToken = (init: boolean): ConsoleAction => ({
type: ConsoleAT.REFRESH_AUTH_TOKEN,
})
const setConfiguration = (payload: ConfigurationShape): ConsoleAction => ({
const setConfig = (payload: ConsoleConfigShape): ConsoleAction => ({
payload,
type: ConsoleAT.SET_CONFIG,
})
const setModalId = (payload?: ModalId): ConsoleAction => ({
payload,
type: ConsoleAT.SET_CONFIGURATION,
type: ConsoleAT.SET_MODAL_ID,
})
const toggleSideMenu = (): ConsoleAction => ({
type: ConsoleAT.TOGGLE_SIDE_MENU,
})
export default { bootstrap, refreshAuthToken, setConfiguration, toggleSideMenu }
export default {
bootstrap,
refreshAuthToken,
setConfig,
setModalId,
toggleSideMenu,
}
/*******************************************************************************
* ___ _ ____ ____
* / _ \ _ _ ___ ___| |_| _ \| __ )
* | | | | | | |/ _ \/ __| __| | | | _ \
* | |_| | |_| | __/\__ \ |_| |_| | |_) |
* \__\_\\__,_|\___||___/\__|____/|____/
*
* Copyright (c) 2014-2019 Appsicle
* Copyright (c) 2019-2020 QuestDB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
import { Epic, ofType } from "redux-observable"
import { filter, map, switchMap, switchMapTo, tap } from "rxjs/operators"
import { NEVER, of, timer } from "rxjs"
......@@ -5,7 +29,7 @@ import { NEVER, of, timer } from "rxjs"
import { actions } from "store"
import {
BootstrapAction,
ConfigurationShape,
ConsoleConfigShape,
ConsoleAction,
ConsoleAT,
RefreshAuthTokenAction,
......@@ -21,16 +45,16 @@ type AuthPayload = Readonly<
}>
>
export const getConfiguration: Epic<StoreAction, ConsoleAction, StoreShape> = (
export const getConfig: Epic<StoreAction, ConsoleAction, StoreShape> = (
action$,
) =>
action$.pipe(
ofType<StoreAction, BootstrapAction>(ConsoleAT.BOOTSTRAP),
switchMap(() =>
fromFetch<ConfigurationShape>("assets/console-configuration.json").pipe(
fromFetch<ConsoleConfigShape>("assets/console-configuration.json").pipe(
map((response) => {
if (!response.error) {
return actions.console.setConfiguration(response.data)
return actions.console.setConfig(response.data)
}
}),
filter((a): a is ConsoleAction => !!a),
......@@ -107,4 +131,4 @@ export const refreshToken: Epic<StoreAction, ConsoleAction, StoreShape> = (
}),
)
export default [getConfiguration, triggerRefreshTokenOnBootstrap, refreshToken]
export default [getConfig, triggerRefreshTokenOnBootstrap, refreshToken]
/*******************************************************************************
* ___ _ ____ ____
* / _ \ _ _ ___ ___| |_| _ \| __ )
* | | | | | | |/ _ \/ __| __| | | | _ \
* | |_| | |_| | __/\__ \ |_| |_| | |_) |
* \__\_\\__,_|\___||___/\__|____/|____/
*
* Copyright (c) 2014-2019 Appsicle
* Copyright (c) 2019-2020 QuestDB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
import { ModalId } from "consts"
import {
ConfigurationShape,
ConsoleConfigShape,
ConsoleAction,
ConsoleAT,
ConsoleStateShape,
......@@ -9,7 +34,7 @@ export const initialState: ConsoleStateShape = {
sideMenuOpened: false,
}
export const defaultConfiguration: ConfigurationShape = {
export const defaultConfig: ConsoleConfigShape = {
githubBanner: false,
readOnly: false,
savedQueries: [],
......@@ -20,16 +45,23 @@ const _console = (
action: ConsoleAction,
): ConsoleStateShape => {
switch (action.type) {
case ConsoleAT.SET_CONFIGURATION: {
case ConsoleAT.SET_CONFIG: {
return {
...state,
configuration: {
...defaultConfiguration,
config: {
...defaultConfig,
...action.payload,
},
}
}
case ConsoleAT.SET_MODAL_ID: {
return {
...state,
modalId: action.payload,
}
}
case ConsoleAT.TOGGLE_SIDE_MENU: {
return {
...state,
......
import { ConfigurationShape, StoreShape } from "types"
/*******************************************************************************
* ___ _ ____ ____
* / _ \ _ _ ___ ___| |_| _ \| __ )
* | | | | | | |/ _ \/ __| __| | | | _ \
* | |_| | |_| | __/\__ \ |_| |_| | |_) |
* \__\_\\__,_|\___||___/\__|____/|____/
*
* Copyright (c) 2014-2019 Appsicle
* Copyright (c) 2019-2020 QuestDB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
import { defaultConfiguration } from "./reducers"
import { ModalId } from "consts"
import { ConsoleConfigShape, StoreShape } from "types"
const getConfiguration: (store: StoreShape) => ConfigurationShape = (store) =>
store.console.configuration || defaultConfiguration
import { defaultConfig } from "./reducers"
const getConfig: (store: StoreShape) => ConsoleConfigShape = (store) =>
store.console.config || defaultConfig
const getSideMenuOpened: (store: StoreShape) => boolean = (store) =>
store.console.sideMenuOpened
const getModalId: (store: StoreShape) => ModalId | undefined = (store) =>
store.console.modalId
export default {
getConfiguration,
getConfig,
getModalId,
getSideMenuOpened,
}
/*******************************************************************************
* ___ _ ____ ____
* / _ \ _ _ ___ ___| |_| _ \| __ )
* | | | | | | |/ _ \/ __| __| | | | _ \
* | |_| | |_| | __/\__ \ |_| |_| | |_) |
* \__\_\\__,_|\___||___/\__|____/|____/
*
* Copyright (c) 2014-2019 Appsicle
* Copyright (c) 2019-2020 QuestDB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
import { ModalId } from "consts"
export type QueryShape = Readonly<{
name?: string
value: string
}>
export type ConfigurationShape = Readonly<{
export type ConsoleConfigShape = Readonly<{
githubBanner: boolean
readOnly?: boolean
savedQueries: QueryShape[]
}>
export type ConsoleStateShape = Readonly<{
configuration?: ConfigurationShape
config?: ConsoleConfigShape
modalId?: ModalId
sideMenuOpened: boolean
}>
export enum ConsoleAT {
BOOTSTRAP = "CONSOLE/BOOTSTRAP",
REFRESH_AUTH_TOKEN = "CONSOLE/REFRESH_AUTH_TOKEN",
SET_CONFIGURATION = "CONSOLE/SET_CONFIGURATION",
SET_CONFIG = "CONSOLE/SET_CONFIG",
SET_MODAL_ID = "CONSOLE/SET_MODAL_ID",
TOGGLE_SIDE_MENU = "CONSOLE/TOGGLE_SIDE_MENU",
}
......@@ -30,9 +58,14 @@ export type RefreshAuthTokenAction = Readonly<{
type: ConsoleAT.REFRESH_AUTH_TOKEN
}>
type SetConfigurationAction = Readonly<{
payload: ConfigurationShape
type: ConsoleAT.SET_CONFIGURATION
type SetConfigAction = Readonly<{
payload: ConsoleConfigShape
type: ConsoleAT.SET_CONFIG
}>
type SetModalId = Readonly<{
payload?: ModalId
type: ConsoleAT.SET_MODAL_ID
}>
type ToggleSideMenuAction = Readonly<{
......@@ -42,5 +75,6 @@ type ToggleSideMenuAction = Readonly<{
export type ConsoleAction =
| BootstrapAction
| RefreshAuthTokenAction
| SetConfigurationAction
| SetConfigAction
| SetModalId
| ToggleSideMenuAction
/*******************************************************************************
* ___ _ ____ ____
* / _ \ _ _ ___ ___| |_| _ \| __ )
* | | | | | | |/ _ \/ __| __| | | | _ \
* | |_| | |_| | __/\__ \ |_| |_| | |_) |
* \__\_\\__,_|\___||___/\__|____/|____/
*
* Copyright (c) 2014-2019 Appsicle
* Copyright (c) 2019-2020 QuestDB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
import { differenceInMilliseconds } from "date-fns"
import { QueryAction, QueryAT, QueryStateShape } from "types"
export const initialState: QueryStateShape = {
......
import { NotificationShape, StoreShape } from "types"
/*******************************************************************************
* ___ _ ____ ____
* / _ \ _ _ ___ ___| |_| _ \| __ )
* | | | | | | |/ _ \/ __| __| | | | _ \
* | |_| | |_| | __/\__ \ |_| |_| | |_) |
* \__\_\\__,_|\___||___/\__|____/|____/
*
* Copyright (c) 2014-2019 Appsicle
* Copyright (c) 2019-2020 QuestDB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
import { NotificationShape, StoreShape } from "types"
import type { QueryRawResult } from "utils/questdb"
const getNotifications: (store: StoreShape) => NotificationShape[] = (store) =>
......
/*******************************************************************************
* ___ _ ____ ____
* / _ \ _ _ ___ ___| |_| _ \| __ )
* | | | | | | |/ _ \/ __| __| | | | _ \
* | |_| | |_| | __/\__ \ |_| |_| | |_) |
* \__\_\\__,_|\___||___/\__|____/|____/
*
* Copyright (c) 2014-2019 Appsicle
* Copyright (c) 2019-2020 QuestDB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
import {
TelemetryAction,
TelemetryAT,
TelemetryConfigShape,
TelemetryRemoteConfigShape,
} from "types"
const setConfig = (payload: TelemetryConfigShape): TelemetryAction => ({
payload,
type: TelemetryAT.SET_CONFIG,
})
const setRemoteConfig = (
payload: Partial<TelemetryRemoteConfigShape>,
): TelemetryAction => ({
payload,
type: TelemetryAT.SET_REMOTE_CONFIG,
})
export default {
setConfig,
setRemoteConfig,
}
/*******************************************************************************
* ___ _ ____ ____
* / _ \ _ _ ___ ___| |_| _ \| __ )
* | | | | | | |/ _ \/ __| __| | | | _ \
* | |_| | |_| | __/\__ \ |_| |_| | |_) |
* \__\_\\__,_|\___||___/\__|____/|____/
*
* Copyright (c) 2014-2019 Appsicle
* Copyright (c) 2019-2020 QuestDB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
import { Epic, ofType } from "redux-observable"
import { delay, filter, map, switchMap, withLatestFrom } from "rxjs/operators"
import { from, NEVER, of } from "rxjs"
import { API, ModalId, TelemetryTable } from "consts"
import { actions, selectors } from "store"
import {
BootstrapAction,
ConsoleAction,
ConsoleAT,
SetTelemetryConfigAction,
SetTelemetryRemoteConfigAction,
StoreAction,
StoreShape,
TelemetryAction,
TelemetryAT,
TelemetryConfigShape,
TelemetryRemoteConfigShape,
} from "types"
import { fromFetch } from "utils"
import * as QuestDB from "utils/questdb"
const quest = new QuestDB.Client()
export const getConfig: Epic<StoreAction, TelemetryAction, StoreShape> = (
action$,
) =>
action$.pipe(
ofType<StoreAction, BootstrapAction>(ConsoleAT.BOOTSTRAP),
switchMap(() =>
from(
quest.query<TelemetryConfigShape>(`${TelemetryTable.CONFIG} limit -1`),
),
),
switchMap((response) => {
if (response.type === QuestDB.Type.DQL) {
return of(actions.telemetry.setConfig(response.data[0]))
}
return NEVER
}),
)
export const getRemoteConfig: Epic<StoreAction, TelemetryAction, StoreShape> = (
action$,
state$,
) =>
action$.pipe(
ofType<StoreAction, SetTelemetryConfigAction>(TelemetryAT.SET_CONFIG),
withLatestFrom(state$),
switchMap(([_, state]) => {
const config = selectors.telemetry.getConfig(state)
if (config?.enabled) {
return fromFetch<Partial<TelemetryRemoteConfigShape>>(
`${API}/config`,
{
method: "POST",
body: JSON.stringify({ id: config.id }),
},
false,
)
}
return NEVER
}),
switchMap((response) => {
if (response.error) {
return NEVER
}
return of(actions.telemetry.setRemoteConfig(response.data))
}),
)
export const powerUserCta: Epic<StoreAction, ConsoleAction, StoreShape> = (
action$,
state$,
) =>
action$.pipe(
ofType<StoreAction, SetTelemetryRemoteConfigAction>(
TelemetryAT.SET_REMOTE_CONFIG,
),
withLatestFrom(state$),
switchMap(([_, state]) => {
const config = selectors.telemetry.getRemoteConfig(state)
if (config?.cta) {
return of(actions.console.setModalId(ModalId.POWER_USER))
}
return NEVER
}),
)
export const startTelemetry: Epic<StoreAction, TelemetryAction, StoreShape> = (
action$,
state$,
) =>
action$.pipe(
ofType<StoreAction, SetTelemetryRemoteConfigAction>(
TelemetryAT.SET_REMOTE_CONFIG,
),
withLatestFrom(state$),
switchMap(([_, state]) => {
const remoteConfig = selectors.telemetry.getRemoteConfig(state)
if (remoteConfig?.lastUpdated) {
return from(
quest.queryRaw(
`SELECT cast(created as long), event, origin
FROM ${TelemetryTable.MAIN}
WHERE created > '${new Date(
remoteConfig.lastUpdated,
).toISOString()}'
`,
),
)
}
return NEVER
}),
withLatestFrom(state$),
switchMap(([result, state]) => {
const remoteConfig = selectors.telemetry.getRemoteConfig(state)
const config = selectors.telemetry.getConfig(state)
if (
config?.id != null &&
result.type === QuestDB.Type.DQL &&
result.count > 0
) {
return fromFetch<{ _: void }>(
`${API}/add`,
{
method: "POST",
body: JSON.stringify({
columns: result.columns,
dataset: result.dataset,
id: config.id,
}),
},
false,
).pipe(
map((response) => {
if (!response.error) {
const timestamp = result.dataset[result.count - 1][0] as string
return actions.telemetry.setRemoteConfig({
...remoteConfig,
lastUpdated: timestamp,
})
}
}),
delay(36e5),
filter((a): a is TelemetryAction => !!a),
)
}
return NEVER
}),
)
export default [getConfig, getRemoteConfig, powerUserCta, startTelemetry]
/*******************************************************************************
* ___ _ ____ ____
* / _ \ _ _ ___ ___| |_| _ \| __ )
* | | | | | | |/ _ \/ __| __| | | | _ \
* | |_| | |_| | __/\__ \ |_| |_| | |_) |
* \__\_\\__,_|\___||___/\__|____/|____/
*
* Copyright (c) 2014-2019 Appsicle
* Copyright (c) 2019-2020 QuestDB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
import { TelemetryAction, TelemetryAT, TelemetryStateShape } from "types"
const yearOffset = 24 * 60 * 60 * 1000 * 31 * 12
export const initialState: TelemetryStateShape = {}
const telemetry = (
state = initialState,
action: TelemetryAction,
): TelemetryStateShape => {
switch (action.type) {
case TelemetryAT.SET_CONFIG: {
return {
...state,
config: action.payload,
}
}
case TelemetryAT.SET_REMOTE_CONFIG: {
return {
...state,
remoteConfig: {
cta: false,
lastUpdated: new Date(
new Date().getTime() - yearOffset,
).toISOString(),
...action.payload,
},
}
}
default:
return state
}
}
export default telemetry
/*******************************************************************************
* ___ _ ____ ____
* / _ \ _ _ ___ ___| |_| _ \| __ )
* | | | | | | |/ _ \/ __| __| | | | _ \
* | |_| | |_| | __/\__ \ |_| |_| | |_) |
* \__\_\\__,_|\___||___/\__|____/|____/
*
* Copyright (c) 2014-2019 Appsicle
* Copyright (c) 2019-2020 QuestDB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
import {
TelemetryConfigShape,
TelemetryRemoteConfigShape,
StoreShape,
} from "types"
const getConfig: (store: StoreShape) => TelemetryConfigShape | undefined = (
store,
) => store.telemetry.config
const getRemoteConfig: (
store: StoreShape,
) => TelemetryRemoteConfigShape | undefined = (store) =>
store.telemetry.remoteConfig
export default {
getConfig,
getRemoteConfig,
}
/*******************************************************************************
* ___ _ ____ ____
* / _ \ _ _ ___ ___| |_| _ \| __ )
* | | | | | | |/ _ \/ __| __| | | | _ \
* | |_| | |_| | __/\__ \ |_| |_| | |_) |
* \__\_\\__,_|\___||___/\__|____/|____/
*
* Copyright (c) 2014-2019 Appsicle
* Copyright (c) 2019-2020 QuestDB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
export type TelemetryConfigShape = Readonly<{
enabled: string
id: string
}>
export type TelemetryRemoteConfigShape = Readonly<{
cta: boolean
lastUpdated?: string
}>
export type TelemetryStateShape = Readonly<{
config?: TelemetryConfigShape
remoteConfig?: TelemetryRemoteConfigShape
}>
export enum TelemetryAT {
SET_CONFIG = "TELEMETRY/SET_CONFIG",
SET_REMOTE_CONFIG = "TELEMETRY/SET_REMOTE_CONFIG",
}
export type SetTelemetryConfigAction = Readonly<{
payload: TelemetryConfigShape
type: TelemetryAT.SET_CONFIG
}>
export type SetTelemetryRemoteConfigAction = Readonly<{
payload: Partial<TelemetryRemoteConfigShape>
type: TelemetryAT.SET_REMOTE_CONFIG
}>
export type TelemetryAction =
| SetTelemetryConfigAction
| SetTelemetryRemoteConfigAction
/*******************************************************************************
* ___ _ ____ ____
* / _ \ _ _ ___ ___| |_| _ \| __ )
* | | | | | | |/ _ \/ __| __| | | | _ \
* | |_| | |_| | __/\__ \ |_| |_| | |_) |
* \__\_\\__,_|\___||___/\__|____/|____/
*
* Copyright (c) 2014-2019 Appsicle
* Copyright (c) 2019-2020 QuestDB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
import console from "./Console/actions"
import query from "./Query/actions"
import telemetry from "./Telemetry/actions"
export default { console, query }
export default { console, query, telemetry }
/*******************************************************************************
* ___ _ ____ ____
* / _ \ _ _ ___ ___| |_| _ \| __ )
* | | | | | | |/ _ \/ __| __| | | | _ \
* | |_| | |_| | __/\__ \ |_| |_| | |_) |
* \__\_\\__,_|\___||___/\__|____/|____/
*
* Copyright (c) 2014-2019 Appsicle
* Copyright (c) 2019-2020 QuestDB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
import { combineEpics } from "redux-observable"
import consoleEpic from "./Console/epics"
import telemetryEpic from "./Telemetry/epics"
export default combineEpics(...consoleEpic)
export default combineEpics(...[...consoleEpic, ...telemetryEpic])
/*******************************************************************************
* ___ _ ____ ____
* / _ \ _ _ ___ ___| |_| _ \| __ )
* | | | | | | |/ _ \/ __| __| | | | _ \
* | |_| | |_| | __/\__ \ |_| |_| | |_) |
* \__\_\\__,_|\___||___/\__|____/|____/
*
* Copyright (c) 2014-2019 Appsicle
* Copyright (c) 2019-2020 QuestDB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
export { default as actions } from "./actions"
export { default as rootEpic } from "./epics"
export { default as rootReducer } from "./reducers"
......
/*******************************************************************************
* ___ _ ____ ____
* / _ \ _ _ ___ ___| |_| _ \| __ )
* | | | | | | |/ _ \/ __| __| | | | _ \
* | |_| | |_| | __/\__ \ |_| |_| | |_) |
* \__\_\\__,_|\___||___/\__|____/|____/
*
* Copyright (c) 2014-2019 Appsicle
* Copyright (c) 2019-2020 QuestDB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
import { combineReducers } from "redux"
import _console, {
......@@ -5,15 +29,20 @@ import _console, {
} from "./Console/reducers"
import query, { initialState as queryInitialState } from "./Query/reducers"
import telemetry, {
initialState as telemetryInitialState,
} from "./Telemetry/reducers"
const rootReducer = combineReducers({
console: _console,
query,
telemetry,
})
export const initialState = {
console: consoleInitialState,
query: queryInitialState,
telemetry: telemetryInitialState,
}
export default rootReducer
import _console from "./Console/selectors"
import query from "./Query/selectors"
import telemetry from "./Telemetry/selectors"
export default {
console: _console,
query,
telemetry,
}
/*******************************************************************************
* ___ _ ____ ____
* / _ \ _ _ ___ ___| |_| _ \| __ )
* | | | | | | |/ _ \/ __| __| | | | _ \
* | |_| | |_| | __/\__ \ |_| |_| | |_) |
* \__\_\\__,_|\___||___/\__|____/|____/
*
* Copyright (c) 2014-2019 Appsicle
* Copyright (c) 2019-2020 QuestDB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
import { ConsoleAction } from "./Console/types"
import { QueryAction } from "./Query/types"
import { TelemetryAction } from "./Telemetry/types"
import rootReducer from "./reducers"
export type StoreAction = ConsoleAction | QueryAction
export type StoreAction = ConsoleAction | QueryAction | TelemetryAction
export type StoreShape = ReturnType<typeof rootReducer>
export * from "./Console/types"
export * from "./Query/types"
export * from "./Telemetry/types"
......@@ -74,7 +74,7 @@ body {
body #wrapper,
body #page-wrapper {
height: 100%;
height: calc(100% - 4rem);
}
body.boxed-layout #wrapper {
......
export enum Table {
MAIN = "telemetry",
CONFIG = "telemetry_config",
}
export const API = "https://fara.questdb.io"
import { API, Table } from "./constants"
import ProcessWorker from "./process.worker.ts"
import type { ConfigShape, WorkerPayloadShape } from "types"
import { fetchApi } from "utils"
import * as QuestDB from "utils/questdb"
type LastUpdatedResponse = Readonly<{ lastUpdated?: string }>
const quest = new QuestDB.Client()
const start = async () => {
const yearOffset = 24 * 60 * 60 * 1000 * 31 * 12
const result = await quest.query<ConfigShape>(`${Table.CONFIG} limit -1`)
let lastUpdated: string | undefined
// If the user enabled telemetry then we start the webworker
if (result.type === QuestDB.Type.DQL && result.data[0].enabled) {
const response = await fetchApi<LastUpdatedResponse>(
`${API}/last-updated`,
{
method: "POST",
body: JSON.stringify({ id: result.data[0].id }),
},
)
if (response.error || !response.data) {
return
}
lastUpdated = response.data.lastUpdated
const sendTelemetry = () => {
const worker: Worker = new ProcessWorker()
const payload: WorkerPayloadShape = {
id: result.data[0].id,
host: window.location.origin,
lastUpdated:
lastUpdated ||
new Date(new Date().getTime() - yearOffset).toISOString(),
}
worker.postMessage(payload)
worker.onmessage = (event: { data?: string }) => {
lastUpdated = event.data
}
}
sendTelemetry()
setInterval(sendTelemetry, 36e5)
}
}
export default start
import { API, Table } from "./constants"
import type { WorkerPayloadShape } from "types"
import { fetchApi } from "utils"
import * as QuestDB from "utils/questdb"
const start = async (payload: WorkerPayloadShape) => {
const quest = new QuestDB.Client(payload.host)
const result = await quest.queryRaw(
`SELECT cast(created as long), event, origin
FROM ${Table.MAIN}
WHERE created > '${new Date(payload.lastUpdated).toISOString()}'
`,
)
if (result.type === QuestDB.Type.DQL && result.count > 0) {
const response = await fetchApi<void>(`${API}/add`, {
method: "POST",
body: JSON.stringify({
columns: result.columns,
dataset: result.dataset,
id: payload.id,
}),
})
if (!response.error) {
const timestamp = result.dataset[result.count - 1][0] as string
postMessage(new Date(timestamp).toISOString())
}
}
}
addEventListener("message", (message: { data: WorkerPayloadShape }) => {
void start(message.data)
})
此差异已折叠。
export * from "../store/types"
export * from "../theme"
export * from "./styled.d"
export * from "./telemetry"
此差异已折叠。
export type ConfigShape = Readonly<{
enabled: string
id: string
}>
export type WorkerPayloadShape = Readonly<{
id: string
host: string
lastUpdated: string
}>
此差异已折叠。
此差异已折叠。
export * from "./bus"
export * from "./fetch"
export * from "./fromFetch"
export * from "./questdb"
......
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册