add prettier to studio (#7615)

This commit is contained in:
Inian authored and GitHub committed 2022-07-08 12:46:46 +08:00
1 parent 491ed38cdc
commit 8f217deeb6
89 files changed
+1485 -944

No files matched your search

+6
View File
@@ -0,0 +1,6 @@
.expo
.next
node_modules
package-lock.json
docker*
apps/**/out
+8
View File
@@ -0,0 +1,8 @@
{
"trailingComma": "es5",
"tabWidth": 2,
"semi": false,
"singleQuote": true,
"printWidth": 100,
"endOfLine": "lf"
}
@@ -1,45 +1,40 @@
import * as React from 'react';
import * as React from 'react'
type BlockKeysProps = {
onEscape?: (value: string | null) => void;
onEnter?: (value: string | null) => void;
value: string | null;
};
onEscape?: (value: string | null) => void
onEnter?: (value: string | null) => void
value: string | null
}
/**
* Blocks key events from propagating
* We use this with cell editor to allow editor component to handle keys.
* Example: press enter to add newline on textEditor
*/
export const BlockKeys: React.FC<BlockKeysProps> = ({
onEscape,
onEnter,
value,
children,
}) => {
export const BlockKeys: React.FC<BlockKeysProps> = ({ onEscape, onEnter, value, children }) => {
const handleKeyDown = React.useCallback(
(ev: React.KeyboardEvent<HTMLDivElement>) => {
switch (ev.key) {
case 'Escape':
ev.stopPropagation();
if (onEscape) onEscape(value);
break;
ev.stopPropagation()
if (onEscape) onEscape(value)
break
case 'Enter':
ev.stopPropagation();
if (onEnter) onEnter(value);
break;
ev.stopPropagation()
if (onEnter) onEnter(value)
break
}
},
[value]
);
)
function onBlur() {
if (onEscape) onEscape(value);
if (onEscape) onEscape(value)
}
return (
<div onKeyDown={handleKeyDown} onBlur={onBlur}>
{children}
</div>
);
};
)
}
@@ -1,22 +1,22 @@
import * as React from 'react';
import { Dropdown, Typography } from '@supabase/ui';
import * as React from 'react'
import { Dropdown, Typography } from '@supabase/ui'
type DropdownControlProps = {
options: {
value: string | number;
label: string;
postLabel?: string;
preLabel?: string;
}[];
onSelect: (value: string | number) => void;
className?: string;
side?: 'bottom' | 'left' | 'top' | 'right' | undefined;
align?: 'start' | 'center' | 'end' | undefined;
isNested?: boolean;
};
value: string | number
label: string
postLabel?: string
preLabel?: string
}[]
onSelect: (value: string | number) => void
className?: string
side?: 'bottom' | 'left' | 'top' | 'right' | undefined
align?: 'start' | 'center' | 'end' | undefined
isNested?: boolean
}
export const DropdownControl: React.FC<DropdownControlProps> = (p) => {
const { className, children, side, align, isNested } = p;
const { className, children, side, align, isNested } = p
return (
<Dropdown
className={className}
@@ -27,37 +27,26 @@ export const DropdownControl: React.FC<DropdownControlProps> = (p) => {
>
{children}
</Dropdown>
);
};
)
}
const DropdownItems: React.FC<DropdownControlProps> = ({
options,
onSelect,
}) => {
const DropdownItems: React.FC<DropdownControlProps> = ({ options, onSelect }) => {
return (
<div className="dropdown-control" style={{ maxHeight: '30vh' }}>
{options.length == 0 && (
<Typography.Text className="dropdown-control__empty-text">
No more items
</Typography.Text>
<Typography.Text className="dropdown-control__empty-text">No more items</Typography.Text>
)}
{options.map((x) => {
return (
<Dropdown.Item key={x.value} onClick={() => onSelect(x.value)}>
<div className="flex items-center gap-2">
{x.preLabel && (
<span className="text-xs text-scale-900 grow">
{x.preLabel}
</span>
)}
{x.preLabel && <span className="text-xs text-scale-900 grow">{x.preLabel}</span>}
<span>{x.label}</span>
{x.postLabel && (
<span className="text-xs text-scale-900">{x.postLabel}</span>
)}
{x.postLabel && <span className="text-xs text-scale-900">{x.postLabel}</span>}
</div>
</Dropdown.Item>
);
)
})}
</div>
);
};
)
}
@@ -1,11 +1,11 @@
import * as React from 'react';
import * as React from 'react'
type EmptyValueProps = {};
type EmptyValueProps = {}
export const EmptyValue: React.FC<EmptyValueProps> = ({}) => {
return (
<span className="sb-grid-empty-value" style={{ opacity: 0.5 }}>
EMPTY
</span>
);
};
)
}
@@ -1,17 +1,17 @@
import * as React from 'react';
import { Menu, Typography } from '@supabase/ui';
import { Dictionary } from '../../../types';
import * as React from 'react'
import { Menu, Typography } from '@supabase/ui'
import { Dictionary } from '../../../types'
interface RowItemProps {
item: Dictionary<any>;
onSelect: (item: Dictionary<any>) => void;
columnNames: string[],
item: Dictionary<any>
onSelect: (item: Dictionary<any>) => void
columnNames: string[]
}
export const RowItem: React.FC<RowItemProps> = ({ item, onSelect, columnNames }) => {
return (
<div className="foreign-table-modal__row-item">
<Menu.Item onClick={() => onSelect(item)} style={{minWidth: 'min-content'}}>
<Menu.Item onClick={() => onSelect(item)} style={{ minWidth: 'min-content' }}>
<div className="foreign-table-modal__row-item__inner">
{columnNames.map((key, j) => {
//
@@ -19,13 +19,10 @@ export const RowItem: React.FC<RowItemProps> = ({ item, onSelect, columnNames })
//
// this could be improved so the user could pick which attributes to display
// @mildtomato
if (j > 5) return null;
if (j > 5) return null
return (
<div
className="foreign-table-modal__row-item__inner__key-item"
key={`item-${j}`}
>
<div className="foreign-table-modal__row-item__inner__key-item" key={`item-${j}`}>
<Typography.Text
small
type="secondary"
@@ -34,13 +31,17 @@ export const RowItem: React.FC<RowItemProps> = ({ item, onSelect, columnNames })
{key}
</Typography.Text>
<Typography.Text small strong>
{item[key] ? (typeof item[key] === 'object') ? JSON.stringify(item[key]) : item[key] : '[null]'}
{item[key]
? typeof item[key] === 'object'
? JSON.stringify(item[key])
: item[key]
: '[null]'}
</Typography.Text>
</div>
);
)
})}
</div>
</Menu.Item>
</div>
);
};
)
}
@@ -1,7 +1,7 @@
import * as React from 'react';
import * as React from 'react'
function includes(array: string[], element: string) {
return array.indexOf(element) >= 0;
return array.indexOf(element) >= 0
}
/**
@@ -19,7 +19,7 @@ export function useKeyboardShortcuts(
whitelistNodes: string[] = [],
whitelistClasses: string[] = []
) {
const [lastKeydown, setLastKeydown] = React.useState<string | null>();
const [lastKeydown, setLastKeydown] = React.useState<string | null>()
const handleKeydown = (event: any) => {
if (
@@ -27,10 +27,10 @@ export function useKeyboardShortcuts(
includes(whitelistNodes, event.target.nodeName) ||
includes(whitelistClasses, event.target.className)
) {
return;
return
}
let keyPressed = getKeyPresses(event);
let keyPressed = getKeyPresses(event)
if (keyMap[keyPressed]) {
/**
@@ -38,25 +38,25 @@ export function useKeyboardShortcuts(
* while single keymap will trigger action on KeyUp event
*/
if (keyPressed.includes('+')) {
event.preventDefault();
keyMap[keyPressed](event);
setLastKeydown(null);
event.preventDefault()
keyMap[keyPressed](event)
setLastKeydown(null)
} else {
setLastKeydown(event.key);
event.preventDefault();
setLastKeydown(event.key)
event.preventDefault()
}
}
};
}
const handleKeyup = (event: any) => {
if (!keyMap) return;
if (!keyMap) return
if (keyMap[event.key] && lastKeydown === event.key) {
event.preventDefault();
keyMap[event.key](event);
setLastKeydown(null);
event.preventDefault()
keyMap[event.key](event)
setLastKeydown(null)
}
};
}
function getKeyPresses(event: KeyboardEvent) {
return event.metaKey && event.shiftKey
@@ -67,15 +67,15 @@ export function useKeyboardShortcuts(
? `Shift+${event.key}`
: event.ctrlKey && event.key
? `Control+${event.key}`
: event.key;
: event.key
}
React.useEffect(() => {
window.addEventListener('keydown', handleKeydown);
window.addEventListener('keyup', handleKeyup);
window.addEventListener('keydown', handleKeydown)
window.addEventListener('keyup', handleKeyup)
return () => {
window.removeEventListener('keydown', handleKeydown);
window.removeEventListener('keyup', handleKeyup);
};
});
window.removeEventListener('keydown', handleKeydown)
window.removeEventListener('keyup', handleKeyup)
}
})
}
@@ -1,14 +1,14 @@
import * as React from 'react';
import Editor from '@monaco-editor/react';
import * as React from 'react'
import Editor from '@monaco-editor/react'
type MonacoEditorProps = {
width?: string | number | undefined;
height?: string | number | undefined;
value?: string | undefined;
language?: string | undefined;
onChange: (value: string | undefined) => void;
onMount?: (editor: any) => void;
};
width?: string | number | undefined
height?: string | number | undefined
value?: string | undefined
language?: string | undefined
onChange: (value: string | undefined) => void
onMount?: (editor: any) => void
}
export const MonacoEditor: React.FC<MonacoEditorProps> = ({
width,
@@ -25,20 +25,20 @@ export const MonacoEditor: React.FC<MonacoEditorProps> = ({
afterLineNumber: 0,
heightInPx: 4,
domNode: document.createElement('div'),
});
});
})
})
// move cursor to the end of document
const model = editor.getModel();
const position = model.getPositionAt(value?.length);
editor.setPosition(position);
const model = editor.getModel()
const position = model.getPositionAt(value?.length)
editor.setPosition(position)
// auto focus on mount
setTimeout(() => {
editor?.focus();
}, 0);
editor?.focus()
}, 0)
if (onMount) onMount(editor);
if (onMount) onMount(editor)
}
return (
@@ -68,5 +68,5 @@ export const MonacoEditor: React.FC<MonacoEditorProps> = ({
wordWrap: 'on',
}}
/>
);
};
)
}
@@ -1,11 +1,11 @@
import * as React from 'react';
import * as React from 'react'
type NullValueProps = {};
type NullValueProps = {}
export const NullValue: React.FC<NullValueProps> = ({}) => {
return (
<span className="null-value" style={{ opacity: 0.5 }}>
NULL
</span>
);
};
)
}
@@ -1,17 +1,17 @@
import * as React from 'react';
import { useTrackedState } from '../../store';
import { useKeyboardShortcuts } from './Hooks';
import { DataGridHandle } from '@supabase/react-data-grid';
import { formatClipboardValue, copyToClipboard } from '../../utils';
import * as React from 'react'
import { useTrackedState } from '../../store'
import { useKeyboardShortcuts } from './Hooks'
import { DataGridHandle } from '@supabase/react-data-grid'
import { formatClipboardValue, copyToClipboard } from '../../utils'
type ShortcutsProps = {
gridRef: React.RefObject<DataGridHandle>;
};
gridRef: React.RefObject<DataGridHandle>
}
export function Shortcuts({ gridRef }: ShortcutsProps) {
const state = useTrackedState();
const { rows, gridColumns, selectedCellPosition } = state;
const [metaKey, setMetaKey] = React.useState('Command');
const state = useTrackedState()
const { rows, gridColumns, selectedCellPosition } = state
const [metaKey, setMetaKey] = React.useState('Command')
React.useEffect(() => {
function getClientOS() {
@@ -19,69 +19,69 @@ export function Shortcuts({ gridRef }: ShortcutsProps) {
? 'windows'
: navigator?.appVersion.indexOf('Mac') !== -1
? 'macos'
: 'unknown';
: 'unknown'
}
const metakey = getClientOS() === 'windows' ? 'Control' : 'Command';
setMetaKey(metakey);
}, []);
const metakey = getClientOS() === 'windows' ? 'Control' : 'Command'
setMetaKey(metakey)
}, [])
useKeyboardShortcuts(
{
[`${metaKey}+ArrowUp`]: event => {
event.stopPropagation();
[`${metaKey}+ArrowUp`]: (event) => {
event.stopPropagation()
if (selectedCellPosition) {
const position = {
idx: selectedCellPosition?.idx ?? 0,
rowIdx: 0,
};
gridRef.current!.selectCell(position);
}
gridRef.current!.selectCell(position)
} else {
gridRef.current!.scrollToRow(Number(0));
gridRef.current!.scrollToRow(Number(0))
}
},
[`${metaKey}+ArrowDown`]: event => {
event.stopPropagation();
[`${metaKey}+ArrowDown`]: (event) => {
event.stopPropagation()
if (selectedCellPosition) {
const position = {
idx: selectedCellPosition?.idx ?? 0,
rowIdx: rows.length > 1 ? rows.length - 1 : 0,
};
gridRef.current!.selectCell(position);
}
gridRef.current!.selectCell(position)
} else {
gridRef.current!.scrollToRow(Number(rows.length));
gridRef.current!.scrollToRow(Number(rows.length))
}
},
[`${metaKey}+ArrowLeft`]: event => {
event.stopPropagation();
const fronzenColumns = gridColumns.filter(x => x.frozen);
[`${metaKey}+ArrowLeft`]: (event) => {
event.stopPropagation()
const fronzenColumns = gridColumns.filter((x) => x.frozen)
const position = {
idx: fronzenColumns.length,
rowIdx: selectedCellPosition?.rowIdx ?? 0,
};
gridRef.current!.selectCell(position);
}
gridRef.current!.selectCell(position)
},
[`${metaKey}+ArrowRight`]: event => {
event.stopPropagation();
[`${metaKey}+ArrowRight`]: (event) => {
event.stopPropagation()
gridRef.current?.selectCell({
idx: gridColumns.length - 1,
rowIdx: selectedCellPosition?.rowIdx ?? 0,
});
})
},
[`${metaKey}+c`]: event => {
event.stopPropagation();
[`${metaKey}+c`]: (event) => {
event.stopPropagation()
if (selectedCellPosition) {
const { idx, rowIdx } = selectedCellPosition;
const { idx, rowIdx } = selectedCellPosition
if (idx > 0) {
const colKey = gridColumns[idx].key;
const cellValue = rows[rowIdx]?.[colKey] ?? '';
const value = formatClipboardValue(cellValue);
copyToClipboard(value);
const colKey = gridColumns[idx].key
const cellValue = rows[rowIdx]?.[colKey] ?? ''
const value = formatClipboardValue(cellValue)
copyToClipboard(value)
}
}
},
},
['INPUT', 'TEXTAREA', 'SELECT']
);
)
return null;
return null
}
@@ -1,5 +1,5 @@
import * as React from 'react';
import { EditorProps } from '@supabase/react-data-grid';
import * as React from 'react'
import { EditorProps } from '@supabase/react-data-grid'
export function BooleanEditor<TRow, TSummaryRow = unknown>({
row,
@@ -8,11 +8,11 @@ export function BooleanEditor<TRow, TSummaryRow = unknown>({
onClose,
}: EditorProps<TRow, TSummaryRow>) {
function onChange(event: React.ChangeEvent<HTMLInputElement>) {
onRowChange({ ...row, [column.key]: event.target.checked });
onRowChange({ ...row, [column.key]: event.target.checked })
}
function onBlur() {
onClose(true);
onClose(true)
}
return (
@@ -26,5 +26,5 @@ export function BooleanEditor<TRow, TSummaryRow = unknown>({
style={{ margin: 'auto' }}
/>
</div>
);
)
}
@@ -1,9 +1,9 @@
import * as React from 'react';
import { EditorProps } from '@supabase/react-data-grid';
import * as React from 'react'
import { EditorProps } from '@supabase/react-data-grid'
function autoFocusAndSelect(input: HTMLInputElement | null) {
input?.focus();
input?.select();
input?.focus()
input?.select()
}
export function DateEditor<TRow, TSummaryRow = unknown>({
@@ -12,12 +12,12 @@ export function DateEditor<TRow, TSummaryRow = unknown>({
onRowChange,
onClose,
}: EditorProps<TRow, TSummaryRow>) {
const value = (row[column.key as keyof TRow] as unknown) as string;
const value = row[column.key as keyof TRow] as unknown as string
function onChange(event: React.ChangeEvent<HTMLInputElement>) {
let _value = event.target.value;
if (_value == '') onRowChange({ ...row, [column.key]: null });
else onRowChange({ ...row, [column.key]: _value });
let _value = event.target.value
if (_value == '') onRowChange({ ...row, [column.key]: null })
else onRowChange({ ...row, [column.key]: _value })
}
return (
@@ -29,5 +29,5 @@ export function DateEditor<TRow, TSummaryRow = unknown>({
onBlur={() => onClose(true)}
type="date"
/>
);
)
}
@@ -1,6 +1,6 @@
import React, { useEffect } from 'react';
import { EditorProps } from '@supabase/react-data-grid';
import { Button, IconX } from '@supabase/ui';
import React, { useEffect } from 'react'
import { EditorProps } from '@supabase/react-data-grid'
import { Button, IconX } from '@supabase/ui'
export function NullableBooleanEditor<TRow, TSummaryRow = unknown>({
row,
@@ -8,25 +8,25 @@ export function NullableBooleanEditor<TRow, TSummaryRow = unknown>({
onRowChange,
onClose,
}: EditorProps<TRow, TSummaryRow>) {
const value = row[column.key as keyof TRow] as unknown as boolean | null;
const value = row[column.key as keyof TRow] as unknown as boolean | null
useEffect(() => {
// if value is null, set it to false on initial render
if (value === null) {
onRowChange({ ...row, [column.key]: false });
onRowChange({ ...row, [column.key]: false })
}
}, []);
}, [])
function onChange(event: React.ChangeEvent<HTMLInputElement>) {
onRowChange({ ...row, [column.key]: event.target.checked });
onRowChange({ ...row, [column.key]: event.target.checked })
}
function onBlur() {
onClose(true);
onClose(true)
}
function onClear() {
onRowChange({ ...row, [column.key]: null }, true);
onRowChange({ ...row, [column.key]: null }, true)
}
return (
@@ -47,5 +47,5 @@ export function NullableBooleanEditor<TRow, TSummaryRow = unknown>({
style={{ padding: '3px', margin: 'auto 5px auto auto' }}
/>
</div>
);
)
}
@@ -1,9 +1,9 @@
import * as React from 'react';
import { EditorProps } from '@supabase/react-data-grid';
import * as React from 'react'
import { EditorProps } from '@supabase/react-data-grid'
function autoFocusAndSelect(input: HTMLInputElement | null) {
input?.focus();
input?.select();
input?.focus()
input?.select()
}
export function NumberEditor<TRow, TSummaryRow = unknown>({
@@ -12,16 +12,16 @@ export function NumberEditor<TRow, TSummaryRow = unknown>({
onRowChange,
onClose,
}: EditorProps<TRow, TSummaryRow>) {
const value = (row[column.key as keyof TRow] as unknown) as string;
const value = row[column.key as keyof TRow] as unknown as string
function onChange(event: React.ChangeEvent<HTMLInputElement>) {
const _value = event.target.value;
if (_value == '') onRowChange({ ...row, [column.key]: null });
else onRowChange({ ...row, [column.key]: _value });
const _value = event.target.value
if (_value == '') onRowChange({ ...row, [column.key]: null })
else onRowChange({ ...row, [column.key]: _value })
}
function onBlur() {
onClose(true);
onClose(true)
}
return (
@@ -33,5 +33,5 @@ export function NumberEditor<TRow, TSummaryRow = unknown>({
onBlur={onBlur}
type="number"
/>
);
)
}
@@ -1,25 +1,24 @@
import * as React from 'react';
import { EditorProps } from '@supabase/react-data-grid';
import dayjs from 'dayjs';
import customParseFormat from 'dayjs/plugin/customParseFormat';
import * as React from 'react'
import { EditorProps } from '@supabase/react-data-grid'
import dayjs from 'dayjs'
import customParseFormat from 'dayjs/plugin/customParseFormat'
dayjs.extend(customParseFormat);
dayjs.extend(customParseFormat)
function autoFocusAndSelect(input: HTMLInputElement | null) {
input?.focus();
input?.select();
input?.focus()
input?.select()
}
interface TimeEditorProps<TRow, TSummaryRow = unknown>
extends EditorProps<TRow, TSummaryRow> {
format: string;
interface TimeEditorProps<TRow, TSummaryRow = unknown> extends EditorProps<TRow, TSummaryRow> {
format: string
}
/**
* original input time format 'HH:mm'
* when step=1, it becomes 'HH:mm:ss'
*/
const INPUT_TIME_FORMAT = 'HH:mm:ss';
const INPUT_TIME_FORMAT = 'HH:mm:ss'
function BaseEditor<TRow, TSummaryRow = unknown>({
row,
@@ -28,18 +27,16 @@ function BaseEditor<TRow, TSummaryRow = unknown>({
onRowChange,
onClose,
}: TimeEditorProps<TRow, TSummaryRow>) {
const value = row[column.key as keyof TRow] as unknown as string;
const timeValue = value
? dayjs(value, format).format(INPUT_TIME_FORMAT)
: value;
const value = row[column.key as keyof TRow] as unknown as string
const timeValue = value ? dayjs(value, format).format(INPUT_TIME_FORMAT) : value
function onChange(event: React.ChangeEvent<HTMLInputElement>) {
const _value = event.target.value;
const _value = event.target.value
if (_value == '') {
onRowChange({ ...row, [column.key]: null });
onRowChange({ ...row, [column.key]: null })
} else {
const _timeValue = dayjs(_value, INPUT_TIME_FORMAT).format(format);
onRowChange({ ...row, [column.key]: _timeValue });
const _timeValue = dayjs(_value, INPUT_TIME_FORMAT).format(format)
onRowChange({ ...row, [column.key]: _timeValue })
}
}
@@ -53,17 +50,15 @@ function BaseEditor<TRow, TSummaryRow = unknown>({
type="time"
step="1"
/>
);
)
}
export function TimeEditor<TRow, TSummaryRow = unknown>(
props: EditorProps<TRow, TSummaryRow>
) {
return <BaseEditor {...props} format="HH:mm:ss" />;
export function TimeEditor<TRow, TSummaryRow = unknown>(props: EditorProps<TRow, TSummaryRow>) {
return <BaseEditor {...props} format="HH:mm:ss" />
}
export function TimeWithTimezoneEditor<TRow, TSummaryRow = unknown>(
props: EditorProps<TRow, TSummaryRow>
) {
return <BaseEditor {...props} format="HH:mm:ssZZ" />;
return <BaseEditor {...props} format="HH:mm:ssZZ" />
}
@@ -1,9 +1,9 @@
export * from './BooleanEditor';
export * from './DateEditor';
export * from './DateTimeEditor';
export * from './JsonEditor';
export * from './NullableBooleanEditor';
export * from './NumberEditor';
export * from './SelectEditor';
export * from './TextEditor';
export * from './TimeEditor';
export * from './BooleanEditor'
export * from './DateEditor'
export * from './DateTimeEditor'
export * from './JsonEditor'
export * from './NullableBooleanEditor'
export * from './NumberEditor'
export * from './SelectEditor'
export * from './TextEditor'
export * from './TimeEditor'
@@ -1,7 +1,7 @@
import * as React from 'react';
import Pagination from './pagination';
import * as React from 'react'
import Pagination from './pagination'
type FooterProps = {};
type FooterProps = {}
const Footer: React.FC<FooterProps> = () => {
return (
@@ -10,6 +10,6 @@ const Footer: React.FC<FooterProps> = () => {
<Pagination />
</div>
</div>
);
};
export default Footer;
)
}
export default Footer
@@ -1,3 +1,3 @@
import Footer from './Footer';
import Footer from './Footer'
export default Footer;
export default Footer
@@ -1,3 +1,3 @@
import Pagination from './Pagination';
import Pagination from './Pagination'
export default Pagination;
export default Pagination
@@ -1,12 +1,10 @@
import * as React from 'react';
import { FormatterProps } from '@supabase/react-data-grid';
import { SupaRow } from '../../types';
import { NullValue } from '../common';
import * as React from 'react'
import { FormatterProps } from '@supabase/react-data-grid'
import { SupaRow } from '../../types'
import { NullValue } from '../common'
export const BooleanFormatter = (
p: React.PropsWithChildren<FormatterProps<SupaRow, unknown>>
) => {
const value = p.row[p.column.key] as boolean | null;
if (value === null) return <NullValue />;
return <>{value ? 'true' : 'false'}</>;
};
export const BooleanFormatter = (p: React.PropsWithChildren<FormatterProps<SupaRow, unknown>>) => {
const value = p.row[p.column.key] as boolean | null
if (value === null) return <NullValue />
return <>{value ? 'true' : 'false'}</>
}
@@ -1,17 +1,15 @@
import * as React from 'react';
import { FormatterProps } from '@supabase/react-data-grid';
import { SupaRow } from '../../types';
import { NullValue } from '../common';
import { EmptyValue } from '../common/EmptyValue';
import * as React from 'react'
import { FormatterProps } from '@supabase/react-data-grid'
import { SupaRow } from '../../types'
import { NullValue } from '../common'
import { EmptyValue } from '../common/EmptyValue'
export const DefaultFormatter = (
p: React.PropsWithChildren<FormatterProps<SupaRow, unknown>>
) => {
let value = p.row[p.column.key];
if (value === null) return <NullValue />;
if(value === '') return <EmptyValue />;
export const DefaultFormatter = (p: React.PropsWithChildren<FormatterProps<SupaRow, unknown>>) => {
let value = p.row[p.column.key]
if (value === null) return <NullValue />
if (value === '') return <EmptyValue />
if (typeof value == 'object' || Array.isArray(value)) {
value = JSON.stringify(value);
value = JSON.stringify(value)
}
return <>{value}</>;
};
return <>{value}</>
}
@@ -1,3 +1,3 @@
export * from './BooleanFormatter';
export * from './DefaultFormatter';
export * from './ForeignKeyFormatter';
export * from './BooleanFormatter'
export * from './DefaultFormatter'
export * from './ForeignKeyFormatter'
@@ -1,19 +1,19 @@
import * as React from 'react';
import { TriggerEvent, useContextMenu } from 'react-contexify';
import { Row, RowRendererProps } from '@supabase/react-data-grid';
import { MENU_IDS } from '../menu';
import { SupaRow } from '../../types';
import * as React from 'react'
import { TriggerEvent, useContextMenu } from 'react-contexify'
import { Row, RowRendererProps } from '@supabase/react-data-grid'
import { MENU_IDS } from '../menu'
import { SupaRow } from '../../types'
export default function RowRenderer(props: RowRendererProps<SupaRow>) {
const { show: showContextMenu } = useContextMenu();
const { show: showContextMenu } = useContextMenu()
function displayMenu(e: TriggerEvent) {
const menuId = MENU_IDS.ROW_CONTEXT_MENU_ID;
const menuId = MENU_IDS.ROW_CONTEXT_MENU_ID
showContextMenu(e, {
id: menuId,
props: { rowIdx: props.rowIdx },
});
})
}
return <Row {...props} onContextMenu={displayMenu} />;
return <Row {...props} onContextMenu={displayMenu} />
}
@@ -1,4 +1,4 @@
export * from './ColumnHeader';
export * from './Grid';
export * from './SelectColumn';
export * from './AddColumn';
export * from './ColumnHeader'
export * from './Grid'
export * from './SelectColumn'
export * from './AddColumn'
@@ -1,33 +1,33 @@
import * as React from 'react';
import { SupabaseGridQueue } from '../../constants';
import { Typography } from '@supabase/ui';
import * as React from 'react'
import { SupabaseGridQueue } from '../../constants'
import { Typography } from '@supabase/ui'
type StatusLabelProps = {};
type StatusLabelProps = {}
const StatusLabel: React.FC<StatusLabelProps> = ({}) => {
const [msg, setMsg] = React.useState<string | undefined>(undefined);
const [msg, setMsg] = React.useState<string | undefined>(undefined)
React.useEffect(() => {
let isMounted = true;
let timer: number | null;
let isMounted = true
let timer: number | null
SupabaseGridQueue.on('active', () => {
if (timer) clearTimeout(timer);
if (timer) clearTimeout(timer)
if (isMounted) setMsg('Saving...');
});
if (isMounted) setMsg('Saving...')
})
SupabaseGridQueue.on('idle', () => {
if (timer) clearTimeout(timer);
timer = window.setTimeout(() => setMsg(undefined), 2000);
if (timer) clearTimeout(timer)
timer = window.setTimeout(() => setMsg(undefined), 2000)
if (isMounted) setMsg('All changes saved');
});
if (isMounted) setMsg('All changes saved')
})
return () => {
isMounted = false;
if (timer) clearTimeout(timer);
};
}, []);
isMounted = false
if (timer) clearTimeout(timer)
}
}, [])
return (
<div className="sb-grid-status-label">
@@ -38,6 +38,6 @@ const StatusLabel: React.FC<StatusLabelProps> = ({}) => {
</div>
)}
</div>
);
};
export default StatusLabel;
)
}
export default StatusLabel
@@ -1,4 +1,4 @@
import FilterPopover from './FilterPopover';
import FilterPopover from './FilterPopover'
export * from './Filter.constants';
export default FilterPopover;
export * from './Filter.constants'
export default FilterPopover
@@ -1,3 +1,3 @@
import Header from './Header';
import Header from './Header'
export default Header;
export default Header
@@ -1,3 +1,3 @@
import SortPopover from './SortPopover';
import SortPopover from './SortPopover'
export default SortPopover;
export default SortPopover
+10 -10
View File
@@ -1,17 +1,17 @@
import PQueue from 'p-queue';
import PQueue from 'p-queue'
export const SupabaseGridQueue = new PQueue({ concurrency: 1 });
export const SupabaseGridQueue = new PQueue({ concurrency: 1 })
export const COLUMN_MIN_WIDTH = 100;
export const COLUMN_MIN_WIDTH = 100
export const STORAGE_KEY_PREFIX = 'supabase_grid';
export const STORAGE_KEY_PREFIX = 'supabase_grid'
export const REFRESH_PAGE_IMMEDIATELY = -1;
export const TOTAL_ROWS_INITIAL = -1;
export const TOTAL_ROWS_RESET = -2;
export const REFRESH_PAGE_IMMEDIATELY = -1
export const TOTAL_ROWS_INITIAL = -1
export const TOTAL_ROWS_RESET = -2
export const SELECT_COLUMN_KEY = 'supabase-grid-select-row';
export const ADD_COLUMN_KEY = 'supabase-grid-add-column';
export const SELECT_COLUMN_KEY = 'supabase-grid-select-row'
export const ADD_COLUMN_KEY = 'supabase-grid-add-column'
export const ERROR_PRIMARY_KEY_NOTFOUND =
'Please add a primary key column to your table to update or delete rows';
'Please add a primary key column to your table to update or delete rows'
+5 -5
View File
@@ -1,7 +1,7 @@
export * from './SupabaseGrid';
export { parseSupaTable } from './SupabaseGrid.utils';
export * from './types';
export * from './SupabaseGrid'
export { parseSupaTable } from './SupabaseGrid.utils'
export * from './types'
import Query from './query';
import Query from './query'
export { Query };
export { Query }
+3 -3
View File
@@ -1,7 +1,7 @@
import { IQueryAction, QueryAction } from './QueryAction';
import { IQueryAction, QueryAction } from './QueryAction'
interface IQuery {
from: (table: string, schema?: string) => IQueryAction;
from: (table: string, schema?: string) => IQueryAction
}
export class Query implements IQuery {
@@ -13,6 +13,6 @@ export class Query implements IQuery {
return new QueryAction({
name,
schema: schema ?? 'public',
});
})
}
}
+2 -2
View File
@@ -1,3 +1,3 @@
import { Query } from './Query';
import { Query } from './Query'
export default Query;
export default Query
@@ -1,7 +1,7 @@
import { format } from '@scaleleap/pg-format';
import { format } from '@scaleleap/pg-format'
export function getColumnsSql(filter_schema: string, filter_name: string) {
return format(columnsSql, filter_schema, filter_name);
return format(columnsSql, filter_schema, filter_name)
}
const columnsSql = `
SELECT
@@ -101,10 +101,10 @@ WHERE
'SELECT, INSERT, UPDATE, REFERENCES'
)
)
`;
`
export function getPrimaryKeysSql(filter_schema: string, filter_name: string) {
return format(primaryKeysSql, filter_schema, filter_name);
return format(primaryKeysSql, filter_schema, filter_name)
}
const primaryKeysSql = `
SELECT
@@ -125,13 +125,10 @@ WHERE
AND a.attrelid = c.oid
AND a.attnum = ANY (i.indkey)
AND i.indisprimary
`;
`
export function getRelationshipsSql(
filter_schema: string,
filter_name: string
) {
return format(relationshipsSql, filter_schema, filter_name);
export function getRelationshipsSql(filter_schema: string, filter_name: string) {
return format(relationshipsSql, filter_schema, filter_name)
}
const relationshipsSql = `
SELECT
@@ -161,10 +158,10 @@ WHERE
c.contype = 'f'
AND nsa.nspname = %L
AND csa.relname = %L;
`;
`
export function getTableSql(filter_schema: string, filter_name: string) {
return format(tableSql, filter_schema, filter_name);
return format(tableSql, filter_schema, filter_name)
}
const tableSql = `
SELECT
@@ -189,4 +186,4 @@ WHERE
)
OR has_any_column_privilege(c.oid, 'SELECT, INSERT, UPDATE, REFERENCES')
)
`;
`
@@ -1,56 +1,52 @@
import { IMetaService } from '.';
import { IMetaService } from '.'
import {
getColumnsSql,
getPrimaryKeysSql,
getRelationshipsSql,
getTableSql,
} from './SqlMetaService.sqls';
} from './SqlMetaService.sqls'
export class SqlMetaService implements IMetaService {
constructor(
protected onSqlQuery: (
query: string
) => Promise<{ data?: any; error?: any }>
) {}
constructor(protected onSqlQuery: (query: string) => Promise<{ data?: any; error?: any }>) {}
async fetchInfo(name: string, schema?: string | undefined) {
const sql = getTableSql(schema ?? 'public', name);
const { data, error } = await this.onSqlQuery(sql);
const sql = getTableSql(schema ?? 'public', name)
const { data, error } = await this.onSqlQuery(sql)
if (error) {
return { error };
return { error }
} else {
if (data?.length == 1) {
return { data: data[0] };
return { data: data[0] }
} else {
return { error: { message: 'fetch table info failed' } };
return { error: { message: 'fetch table info failed' } }
}
}
}
async fetchColumns(name: string, schema?: string | undefined) {
const sql = getColumnsSql(schema ?? 'public', name);
const { data, error } = await this.onSqlQuery(sql);
const sql = getColumnsSql(schema ?? 'public', name)
const { data, error } = await this.onSqlQuery(sql)
if (error) {
return { error };
return { error }
} else {
return { data };
return { data }
}
}
async fetchPrimaryKeys(name: string, schema?: string | undefined) {
const sql = getPrimaryKeysSql(schema ?? 'public', name);
const { data, error } = await this.onSqlQuery(sql);
const sql = getPrimaryKeysSql(schema ?? 'public', name)
const { data, error } = await this.onSqlQuery(sql)
if (error) {
return { error };
return { error }
} else {
return { data };
return { data }
}
}
async fetchRelationships(name: string, schema?: string | undefined) {
const sql = getRelationshipsSql(schema ?? 'public', name);
const { data, error } = await this.onSqlQuery(sql);
const sql = getRelationshipsSql(schema ?? 'public', name)
const { data, error } = await this.onSqlQuery(sql)
if (error) {
return { error };
return { error }
} else {
return { data };
return { data }
}
}
}
@@ -1,22 +1,22 @@
import { Dictionary, ServiceError } from '../../types';
import { Dictionary, ServiceError } from '../../types'
export interface IMetaService {
fetchInfo: (
name: string,
schema?: string
) => Promise<{ data?: Dictionary<any>; error?: ServiceError }>;
) => Promise<{ data?: Dictionary<any>; error?: ServiceError }>
fetchColumns: (
name: string,
schema?: string
) => Promise<{ data?: Dictionary<any>[]; error?: ServiceError }>;
) => Promise<{ data?: Dictionary<any>[]; error?: ServiceError }>
fetchPrimaryKeys: (
name: string,
schema?: string
) => Promise<{ data?: Dictionary<any>[]; error?: ServiceError }>;
) => Promise<{ data?: Dictionary<any>[]; error?: ServiceError }>
fetchRelationships: (
name: string,
schema?: string
) => Promise<{ data?: Dictionary<any>[]; error?: ServiceError }>;
) => Promise<{ data?: Dictionary<any>[]; error?: ServiceError }>
}
export * from './SqlMetaService';
export * from './SqlMetaService'
+1 -1
View File
@@ -1 +1 @@
export * from './Store';
export * from './Store'
@@ -1,13 +1,13 @@
import { SupaRow } from '../../types';
import { SupaRow } from '../../types'
export interface CallbackInitialState {
onAddColumn: (() => void) | null;
onAddRow: (() => void) | null;
onError: ((error: any) => void) | null;
onEditRow: ((row: SupaRow) => void) | null;
onEditColumn: ((columnName: string) => void) | null;
onDeleteColumn: ((columnName: string) => void) | null;
onSqlQuery: ((query: string) => Promise<{ data?: any; error?: any }>) | null;
onAddColumn: (() => void) | null
onAddRow: (() => void) | null
onError: ((error: any) => void) | null
onEditRow: ((row: SupaRow) => void) | null
onEditColumn: ((columnName: string) => void) | null
onDeleteColumn: ((columnName: string) => void) | null
onSqlQuery: ((query: string) => Promise<{ data?: any; error?: any }>) | null
}
export const callbackInitialState: CallbackInitialState = {
@@ -18,27 +18,22 @@ export const callbackInitialState: CallbackInitialState = {
onEditColumn: null,
onDeleteColumn: null,
onSqlQuery: null,
};
}
type CALLBACK_ACTIONTYPE = {
type: 'INIT_CALLBACK';
type: 'INIT_CALLBACK'
payload: {
onAddColumn: (() => void) | null;
onAddRow: (() => void) | null;
onError: ((error: any) => void) | null;
onEditRow: ((row: SupaRow) => void) | null;
onEditColumn: ((columnName: string) => void) | null;
onDeleteColumn: ((columnName: string) => void) | null;
onSqlQuery:
| ((query: string) => Promise<{ data?: any; error?: any }>)
| null;
};
};
onAddColumn: (() => void) | null
onAddRow: (() => void) | null
onError: ((error: any) => void) | null
onEditRow: ((row: SupaRow) => void) | null
onEditColumn: ((columnName: string) => void) | null
onDeleteColumn: ((columnName: string) => void) | null
onSqlQuery: ((query: string) => Promise<{ data?: any; error?: any }>) | null
}
}
const CallbackReducer = (
state: CallbackInitialState,
action: CALLBACK_ACTIONTYPE
) => {
const CallbackReducer = (state: CallbackInitialState, action: CALLBACK_ACTIONTYPE) => {
switch (action.type) {
case 'INIT_CALLBACK': {
return {
@@ -50,12 +45,12 @@ const CallbackReducer = (
onEditColumn: action.payload.onEditColumn,
onDeleteColumn: action.payload.onDeleteColumn,
onSqlQuery: action.payload.onSqlQuery,
};
}
}
default:
return state;
return state
}
};
}
export default CallbackReducer;
export default CallbackReducer
+43 -60
View File
@@ -1,61 +1,51 @@
import update from 'immutability-helper';
import { CalculatedColumn } from '@supabase/react-data-grid';
import { cloneColumn, getInitialGridColumns } from '../../utils';
import { INIT_ACTIONTYPE } from './base';
import update from 'immutability-helper'
import { CalculatedColumn } from '@supabase/react-data-grid'
import { cloneColumn, getInitialGridColumns } from '../../utils'
import { INIT_ACTIONTYPE } from './base'
export interface ColumnInitialState {
gridColumns: CalculatedColumn <any, any>[];
gridColumns: CalculatedColumn<any, any>[]
}
export const columnInitialState: ColumnInitialState = {
gridColumns: [],
};
}
type COLUMN_ACTIONTYPE =
| INIT_ACTIONTYPE
| {
type: 'MOVE_COLUMN';
payload: { fromKey: string; toKey: string };
type: 'MOVE_COLUMN'
payload: { fromKey: string; toKey: string }
}
| {
type: 'UPDATE_COLUMN_SIZE';
payload: { index: number; width: number };
type: 'UPDATE_COLUMN_SIZE'
payload: { index: number; width: number }
}
| {
type: 'FREEZE_COLUMN';
payload: { columnKey: string };
type: 'FREEZE_COLUMN'
payload: { columnKey: string }
}
| {
type: 'UNFREEZE_COLUMN';
payload: { columnKey: string };
type: 'UNFREEZE_COLUMN'
payload: { columnKey: string }
}
| {
type: 'UPDATE_COLUMN_IDX';
payload: { columnKey: string; columnIdx: number };
};
type: 'UPDATE_COLUMN_IDX'
payload: { columnKey: string; columnIdx: number }
}
const ColumnReducer = (
state: ColumnInitialState,
action: COLUMN_ACTIONTYPE
) => {
const ColumnReducer = (state: ColumnInitialState, action: COLUMN_ACTIONTYPE) => {
switch (action.type) {
case 'INIT_TABLE': {
return {
...state,
gridColumns: getInitialGridColumns(
action.payload.gridColumns,
action.payload.savedState
),
};
gridColumns: getInitialGridColumns(action.payload.gridColumns, action.payload.savedState),
}
}
case 'MOVE_COLUMN': {
const fromIdx = state.gridColumns.findIndex(
x => x.key === action.payload.fromKey
);
const toIdx = state.gridColumns.findIndex(
x => x.key === action.payload.toKey
);
const moveItem = state.gridColumns[fromIdx];
const fromIdx = state.gridColumns.findIndex((x) => x.key === action.payload.fromKey)
const toIdx = state.gridColumns.findIndex((x) => x.key === action.payload.toKey)
const moveItem = state.gridColumns[fromIdx]
return {
...state,
gridColumns: update(state.gridColumns, {
@@ -64,61 +54,54 @@ const ColumnReducer = (
[toIdx, 0, moveItem],
],
}),
};
}
}
case 'UPDATE_COLUMN_SIZE': {
const updated = cloneColumn(state.gridColumns[action.payload.index]);
updated.width = action.payload.width;
const updated = cloneColumn(state.gridColumns[action.payload.index])
updated.width = action.payload.width
return {
...state,
gridColumns: update(state.gridColumns, {
[action.payload.index]: { $set: updated },
}),
};
}
}
case 'FREEZE_COLUMN': {
const columnIdx = state.gridColumns.findIndex(
x => x.key === action.payload.columnKey
);
const updated = cloneColumn(state.gridColumns[columnIdx]);
updated.frozen = true;
const columnIdx = state.gridColumns.findIndex((x) => x.key === action.payload.columnKey)
const updated = cloneColumn(state.gridColumns[columnIdx])
updated.frozen = true
return {
...state,
gridColumns: update(state.gridColumns, {
[columnIdx]: { $set: updated },
}),
};
}
}
case 'UNFREEZE_COLUMN': {
const columnIdx = state.gridColumns.findIndex(
x => x.key === action.payload.columnKey
);
const updated = cloneColumn(state.gridColumns[columnIdx]);
updated.frozen = false;
const columnIdx = state.gridColumns.findIndex((x) => x.key === action.payload.columnKey)
const updated = cloneColumn(state.gridColumns[columnIdx])
updated.frozen = false
return {
...state,
gridColumns: update(state.gridColumns, {
[columnIdx]: { $set: updated },
}),
};
}
}
case 'UPDATE_COLUMN_IDX': {
const index = state.gridColumns.findIndex(
x => x.key === action.payload.columnKey
);
const updated = cloneColumn(state.gridColumns[index]);
updated.idx = action.payload.columnIdx;
const index = state.gridColumns.findIndex((x) => x.key === action.payload.columnKey)
const updated = cloneColumn(state.gridColumns[index])
updated.idx = action.payload.columnIdx
return {
...state,
gridColumns: update(state.gridColumns, {
[index]: { $set: updated },
})
.sort((a, b) => a.idx - b.idx),
};
}).sort((a, b) => a.idx - b.idx),
}
}
default:
return state;
return state
}
};
}
export default ColumnReducer;
export default ColumnReducer
+25 -25
View File
@@ -1,74 +1,74 @@
import React from 'react';
import { Dictionary } from './base';
import { SupaRow, SupaTable } from './table';
import React from 'react'
import { Dictionary } from './base'
import { SupaRow, SupaTable } from './table'
export interface GridProps {
width?: string | number;
height?: string | number;
defaultColumnWidth?: string | number;
containerClass?: string;
gridClass?: string;
rowClass?: ((row: SupaRow) => string | undefined) | undefined;
width?: string | number
height?: string | number
defaultColumnWidth?: string | number
containerClass?: string
gridClass?: string
rowClass?: ((row: SupaRow) => string | undefined) | undefined
}
export interface SupabaseGridProps {
/**
* database table swagger or table name
*/
table: SupaTable | string;
table: SupaTable | string
/**
* run sql query
*/
onSqlQuery: (query: string) => Promise<{ data?: any; error?: any }>;
onSqlQuery: (query: string) => Promise<{ data?: any; error?: any }>
/**
* Optional react node to display in grid header
*/
headerActions?: React.ReactNode;
headerActions?: React.ReactNode
/**
* enable table editor
*/
editable?: boolean;
editable?: boolean
/**
* props to config grid view
*/
gridProps?: GridProps;
gridProps?: GridProps
/**
* table schema. Default set to 'public' if not provided
*/
schema?: string;
schema?: string
/**
* storageRef is used to save state on localstorage
*/
storageRef?: string;
storageRef?: string
/**
* Optional grid theme
*/
theme?: 'dark' | 'light';
theme?: 'dark' | 'light'
/**
* show create new column button if available
*/
onAddColumn?: () => void;
onAddColumn?: () => void
/**
* show add row button if available
*/
onAddRow?: () => void;
onAddRow?: () => void
/**
* show delete column menu if available
*/
onDeleteColumn?: (columnName: string) => void;
onDeleteColumn?: (columnName: string) => void
/**
* show edit column menu if available
*/
onEditColumn?: (columnName: string) => void;
onEditColumn?: (columnName: string) => void
/**
* show edit row button if available
*/
onEditRow?: (row: SupaRow) => void;
onEditRow?: (row: SupaRow) => void
/**
* error handler
*/
onError?: (error: any) => void;
onError?: (error: any) => void
}
export interface SupabaseGridRef {
@@ -77,12 +77,12 @@ export interface SupabaseGridRef {
*
* @param row newly added row data
*/
rowAdded(row: Dictionary<any>): void;
rowAdded(row: Dictionary<any>): void
/**
* callback when a row is edited
*
* @param row edited row data
* @param idx edited row index
*/
rowEdited(row: Dictionary<any>, idx: number): void;
rowEdited(row: Dictionary<any>, idx: number): void
}
+5 -5
View File
@@ -1,5 +1,5 @@
export * from './base';
export * from './grid';
export * from './query';
export * from './service';
export * from './table';
export * from './base'
export * from './grid'
export * from './query'
export * from './service'
export * from './table'
+4 -4
View File
@@ -1,9 +1,9 @@
export interface QueryTable {
name: string;
schema: string;
name: string
schema: string
}
export interface QueryPagination {
limit: number;
offset: number;
limit: number
offset: number
}
+1 -1
View File
@@ -1,3 +1,3 @@
export interface ServiceError {
message: string;
message: string
}
+21 -21
View File
@@ -1,30 +1,30 @@
import { Dictionary } from './base';
import { Dictionary } from './base'
export interface SupaColumn {
readonly dataType: string;
readonly format: string;
readonly name: string;
readonly comment?: string | null;
readonly defaultValue?: string | null;
readonly enum?: string[] | null;
readonly isPrimaryKey?: boolean;
readonly isIdentity?: boolean;
readonly isGeneratable?: boolean;
readonly isNullable?: boolean;
readonly isUpdatable?: boolean;
readonly targetTableSchema?: string | null;
readonly targetTableName?: string | null;
readonly targetColumnName?: string | null;
position: number;
readonly dataType: string
readonly format: string
readonly name: string
readonly comment?: string | null
readonly defaultValue?: string | null
readonly enum?: string[] | null
readonly isPrimaryKey?: boolean
readonly isIdentity?: boolean
readonly isGeneratable?: boolean
readonly isNullable?: boolean
readonly isUpdatable?: boolean
readonly targetTableSchema?: string | null
readonly targetTableName?: string | null
readonly targetColumnName?: string | null
position: number
}
export interface SupaTable {
readonly columns: SupaColumn[];
readonly name: string;
readonly schema?: string | null;
readonly comment?: string | null;
readonly columns: SupaColumn[]
readonly name: string
readonly schema?: string | null
readonly comment?: string | null
}
export interface SupaRow extends Dictionary<any> {
readonly idx: number;
readonly idx: number
}
+24 -27
View File
@@ -1,57 +1,54 @@
import { CalculatedColumn } from '@supabase/react-data-grid';
import { ADD_COLUMN_KEY, SELECT_COLUMN_KEY } from '../constants';
import { SavedState } from '../types';
import { deepClone } from './common';
import { CalculatedColumn } from '@supabase/react-data-grid'
import { ADD_COLUMN_KEY, SELECT_COLUMN_KEY } from '../constants'
import { SavedState } from '../types'
import { deepClone } from './common'
export function cloneColumn(column: CalculatedColumn<any, any>) {
const cloned = deepClone(column);
const cloned = deepClone(column)
// these properties can't be cloned. Need to manual re-set again
cloned.editor = column.editor;
cloned.headerRenderer = column.headerRenderer;
cloned.formatter = column.formatter;
return cloned;
cloned.editor = column.editor
cloned.headerRenderer = column.headerRenderer
cloned.formatter = column.formatter
return cloned
}
export function getInitialGridColumns(
gridColumns: CalculatedColumn<any, any>[],
savedState?: SavedState
) {
let result = gridColumns;
let result = gridColumns
if (savedState?.gridColumns) {
result = [];
result = []
// filter utility columns select, add-column
const stateColumnsFiltered = savedState.gridColumns.filter(
(x) => x?.name !== ''
);
const stateColumnsFiltered = savedState.gridColumns.filter((x) => x?.name !== '')
for (let i = 0; i < stateColumnsFiltered.length; i++) {
const state = stateColumnsFiltered[i];
const found = gridColumns.find((y) => y.key === state.key);
const state = stateColumnsFiltered[i]
const found = gridColumns.find((y) => y.key === state.key)
// merge with savedState item props: width
if (found)
result.push({ ...found, width: state.width, frozen: state.frozen });
if (found) result.push({ ...found, width: state.width, frozen: state.frozen })
}
// check for newly created columns
const newGridColumns = gridColumns.filter((x) => {
// no existed in stateColumnsFiltered and not utility column
const found = stateColumnsFiltered.find((state) => state.key === x.key);
return !found && x.name !== '';
});
result = result.concat(newGridColumns);
const found = stateColumnsFiltered.find((state) => state.key === x.key)
return !found && x.name !== ''
})
result = result.concat(newGridColumns)
// process utility columns
const selectColumn = gridColumns.find((x) => x.key === SELECT_COLUMN_KEY);
const selectColumn = gridColumns.find((x) => x.key === SELECT_COLUMN_KEY)
if (selectColumn) {
result = [selectColumn, ...result];
result = [selectColumn, ...result]
}
const addColumn = gridColumns.find((x) => x.key === ADD_COLUMN_KEY);
const addColumn = gridColumns.find((x) => x.key === ADD_COLUMN_KEY)
if (addColumn) {
result.push(addColumn);
result.push(addColumn)
}
}
return result;
return result
}
+21 -25
View File
@@ -1,57 +1,53 @@
import { SupaColumn, SupaRow } from '../types';
import { SupaColumn, SupaRow } from '../types'
export function deepClone(obj: unknown) {
try {
return JSON.parse(JSON.stringify(obj));
return JSON.parse(JSON.stringify(obj))
} catch (e) {
throw e;
throw e
}
}
export function exportRowsToCsv(
columns: SupaColumn[],
rows: SupaRow[],
separator: string = ','
) {
const keys = columns.map(x => x.name) || [];
export function exportRowsToCsv(columns: SupaColumn[], rows: SupaRow[], separator: string = ',') {
const keys = columns.map((x) => x.name) || []
const csv =
keys.join(separator) +
'\n' +
rows
.map(row => {
.map((row) => {
return keys
.map(k => {
let cell = row[k] === null || row[k] === undefined ? '' : row[k];
.map((k) => {
let cell = row[k] === null || row[k] === undefined ? '' : row[k]
cell =
cell instanceof Date
? cell.toLocaleString()
: typeof cell == 'object' || Array.isArray(cell)
? JSON.stringify(cell).replace(/"/g, '""')
: cell.toString().replace(/"/g, '""');
: cell.toString().replace(/"/g, '""')
if (cell.search(/("|,|\n)/g) >= 0) {
cell = `"${cell}"`;
cell = `"${cell}"`
}
return cell;
return cell
})
.join(separator);
.join(separator)
})
.join('\n');
return csv;
.join('\n')
return csv
}
export function formatClipboardValue(value: any) {
if (value === null) return '';
if (value === null) return ''
if (typeof value == 'object' || Array.isArray(value)) {
return JSON.stringify(value);
return JSON.stringify(value)
}
return value;
return value
}
export const copyToClipboard = (str: string, callback = () => {}) => {
const focused = window.document.hasFocus();
const focused = window.document.hasFocus()
if (focused) {
window.navigator?.clipboard?.writeText(str).then(callback);
window.navigator?.clipboard?.writeText(str).then(callback)
} else {
console.warn('Unable to copy to clipboard');
console.warn('Unable to copy to clipboard')
}
};
}
+50 -60
View File
@@ -1,6 +1,6 @@
import * as React from 'react';
import { CalculatedColumn } from '@supabase/react-data-grid';
import { ColumnType, SupaColumn, SupaRow, SupaTable } from '../types';
import * as React from 'react'
import { CalculatedColumn } from '@supabase/react-data-grid'
import { ColumnType, SupaColumn, SupaRow, SupaTable } from '../types'
import {
isArrayColumn,
isBoolColumn,
@@ -12,7 +12,7 @@ import {
isNumericalColumn,
isTextColumn,
isTimeColumn,
} from './types';
} from './types'
import {
BooleanEditor,
DateEditor,
@@ -25,25 +25,21 @@ import {
TextEditor,
TimeEditor,
TimeWithTimezoneEditor,
} from '../components/editor';
import { AddColumn, ColumnHeader, SelectColumn } from '../components/grid';
import { COLUMN_MIN_WIDTH } from '../constants';
import {
BooleanFormatter,
DefaultFormatter,
ForeignKeyFormatter,
} from '../components/formatter';
} from '../components/editor'
import { AddColumn, ColumnHeader, SelectColumn } from '../components/grid'
import { COLUMN_MIN_WIDTH } from '../constants'
import { BooleanFormatter, DefaultFormatter, ForeignKeyFormatter } from '../components/formatter'
export function getGridColumns(
table: SupaTable,
options?: {
editable?: boolean;
defaultWidth?: string | number;
onAddColumn?: () => void;
editable?: boolean
defaultWidth?: string | number
onAddColumn?: () => void
}
): any[] {
const columns = table.columns.map((x, idx) => {
const columnType = getColumnType(x);
const columnType = getColumnType(x)
const columnDefinition: CalculatedColumn<SupaRow> = {
key: x.name,
name: x.name,
@@ -65,61 +61,55 @@ export function getGridColumns(
),
editor: options?.editable ? getColumnEditor(x, columnType) : undefined,
formatter: getColumnFormatter(x, columnType),
};
}
return columnDefinition;
});
return columnDefinition
})
const gridColumns = [SelectColumn, ...columns];
const gridColumns = [SelectColumn, ...columns]
if (options?.onAddColumn) {
gridColumns.push(AddColumn);
gridColumns.push(AddColumn)
}
return gridColumns;
return gridColumns
}
function getColumnEditor(columnDefinition: SupaColumn, columnType: ColumnType) {
if (columnDefinition.isPrimaryKey || !columnDefinition.isUpdatable) {
return;
return
}
switch (columnType) {
case 'boolean': {
return columnDefinition.isNullable
? NullableBooleanEditor
: BooleanEditor;
return columnDefinition.isNullable ? NullableBooleanEditor : BooleanEditor
}
case 'date': {
return DateEditor;
return DateEditor
}
case 'datetime': {
return columnDefinition.format.endsWith('z')
? DateTimeWithTimezoneEditor
: DateTimeEditor;
return columnDefinition.format.endsWith('z') ? DateTimeWithTimezoneEditor : DateTimeEditor
}
case 'time': {
return columnDefinition.format.endsWith('z')
? TimeWithTimezoneEditor
: TimeEditor;
return columnDefinition.format.endsWith('z') ? TimeWithTimezoneEditor : TimeEditor
}
case 'enum': {
const options = columnDefinition.enum!.map((x) => {
return { label: x, value: x };
});
return (p: any) => <SelectEditor {...p} options={options} />;
return { label: x, value: x }
})
return (p: any) => <SelectEditor {...p} options={options} />
}
case 'array':
case 'json': {
return JsonEditor;
return JsonEditor
}
case 'number': {
return NumberEditor;
return NumberEditor
}
case 'text': {
return TextEditor;
return TextEditor
}
default: {
return undefined;
return undefined
}
}
}
@@ -127,57 +117,57 @@ function getColumnEditor(columnDefinition: SupaColumn, columnType: ColumnType) {
function getColumnFormatter(columnDef: SupaColumn, columnType: ColumnType) {
switch (columnType) {
case 'boolean': {
return BooleanFormatter;
return BooleanFormatter
}
case 'foreign_key': {
if (columnDef.isPrimaryKey || !columnDef.isUpdatable) {
return DefaultFormatter;
return DefaultFormatter
} else {
return ForeignKeyFormatter;
return ForeignKeyFormatter
}
}
default: {
return DefaultFormatter;
return DefaultFormatter
}
}
}
function getColumnType(columnDef: SupaColumn): ColumnType {
if (isForeignKeyColumn(columnDef)) {
return 'foreign_key';
return 'foreign_key'
} else if (isNumericalColumn(columnDef.dataType)) {
return 'number';
return 'number'
} else if (isArrayColumn(columnDef.dataType)) {
return 'array';
return 'array'
} else if (isJsonColumn(columnDef.dataType)) {
return 'json';
return 'json'
} else if (isTextColumn(columnDef.dataType)) {
return 'text';
return 'text'
} else if (isDateColumn(columnDef.format)) {
return 'date';
return 'date'
} else if (isTimeColumn(columnDef.format)) {
return 'time';
return 'time'
} else if (isDateTimeColumn(columnDef.format)) {
return 'datetime';
return 'datetime'
} else if (isBoolColumn(columnDef.dataType)) {
return 'boolean';
return 'boolean'
} else if (isEnumColumn(columnDef.dataType)) {
return 'enum';
} else return 'unknown';
return 'enum'
} else return 'unknown'
}
function getColumnWidth(columnDef: SupaColumn): string | number | undefined {
if (isNumericalColumn(columnDef.dataType)) {
return 120;
return 120
} else if (
isDateTimeColumn(columnDef.format) ||
isDateColumn(columnDef.format) ||
isTimeColumn(columnDef.format)
) {
return 150;
return 150
} else if (isBoolColumn(columnDef.dataType)) {
return 120;
return 120
} else if (isEnumColumn(columnDef.dataType)) {
return 150;
} else return 250;
return 150
} else return 250
}
+7 -9
View File
@@ -1,13 +1,11 @@
import * as React from 'react';
import * as React from 'react'
export function useFocusRef<T extends HTMLOrSVGElement>(
isCellSelected: boolean
) {
const ref = React.useRef<T>(null);
export function useFocusRef<T extends HTMLOrSVGElement>(isCellSelected: boolean) {
const ref = React.useRef<T>(null)
React.useLayoutEffect(() => {
if (!isCellSelected) return;
ref.current?.focus({ preventScroll: true });
}, [isCellSelected]);
if (!isCellSelected) return
ref.current?.focus({ preventScroll: true })
}, [isCellSelected])
return ref;
return ref
}
+21 -21
View File
@@ -1,4 +1,4 @@
import { SupaColumn } from '../types';
import { SupaColumn } from '../types'
const NUMERICAL_TYPES = [
'smallint',
@@ -19,52 +19,52 @@ const NUMERICAL_TYPES = [
'serial2',
'serial4',
'serial8',
];
]
export function isNumericalColumn(type: string) {
return NUMERICAL_TYPES.indexOf(type.toLowerCase()) > -1;
return NUMERICAL_TYPES.indexOf(type.toLowerCase()) > -1
}
const JSON_TYPES = ['json', 'jsonb', 'array'];
const JSON_TYPES = ['json', 'jsonb', 'array']
export function isJsonColumn(type: string) {
return JSON_TYPES.indexOf(type.toLowerCase()) > -1;
return JSON_TYPES.indexOf(type.toLowerCase()) > -1
}
const ARRAY_TYPES = ['array'];
const ARRAY_TYPES = ['array']
export function isArrayColumn(type: string) {
return ARRAY_TYPES.indexOf(type.toLowerCase()) > -1;
return ARRAY_TYPES.indexOf(type.toLowerCase()) > -1
}
const TEXT_TYPES = ['text', 'character varying'];
const TEXT_TYPES = ['text', 'character varying']
export function isTextColumn(type: string) {
return TEXT_TYPES.indexOf(type.toLowerCase()) > -1;
return TEXT_TYPES.indexOf(type.toLowerCase()) > -1
}
const TIMESTAMP_TYPES = ['timestamp', 'timestamptz'];
const TIMESTAMP_TYPES = ['timestamp', 'timestamptz']
export function isDateTimeColumn(type: string) {
return TIMESTAMP_TYPES.indexOf(type.toLowerCase()) > -1;
return TIMESTAMP_TYPES.indexOf(type.toLowerCase()) > -1
}
const DATE_TYPES = ['date'];
const DATE_TYPES = ['date']
export function isDateColumn(type: string) {
return DATE_TYPES.indexOf(type.toLowerCase()) > -1;
return DATE_TYPES.indexOf(type.toLowerCase()) > -1
}
const TIME_TYPES = ['time', 'timetz'];
const TIME_TYPES = ['time', 'timetz']
export function isTimeColumn(type: string) {
return TIME_TYPES.indexOf(type.toLowerCase()) > -1;
return TIME_TYPES.indexOf(type.toLowerCase()) > -1
}
const BOOL_TYPES = ['boolean', 'bool'];
const BOOL_TYPES = ['boolean', 'bool']
export function isBoolColumn(type: string) {
return BOOL_TYPES.indexOf(type.toLowerCase()) > -1;
return BOOL_TYPES.indexOf(type.toLowerCase()) > -1
}
const ENUM_TYPES = ['user-defined'];
const ENUM_TYPES = ['user-defined']
export function isEnumColumn(type: string) {
return ENUM_TYPES.indexOf(type.toLowerCase()) > -1;
return ENUM_TYPES.indexOf(type.toLowerCase()) > -1
}
export function isForeignKeyColumn(columnDef: SupaColumn) {
const { targetTableSchema, targetTableName, targetColumnName } = columnDef;
return !!targetTableSchema && !!targetTableName && !!targetColumnName;
const { targetTableSchema, targetTableName, targetColumnName } = columnDef
return !!targetTableSchema && !!targetTableName && !!targetColumnName
}
@@ -48,14 +48,16 @@ const ConnectionPooling: FC<Props> = () => {
if (!project.pgbouncer_enabled && project.pool_mode == null)
return (
<Panel
title={
<Typography.Title key="panel-title" level={5} className="mb-0">
Connection Pooling is not available for this project
</Typography.Title>
}
>
title={
<Typography.Title key="panel-title" level={5} className="mb-0">
Connection Pooling is not available for this project
</Typography.Title>
}
>
<Panel.Content>
<Typography.Text type="secondary">Please start a new project to enable this feature.</Typography.Text>
<Typography.Text type="secondary">
Please start a new project to enable this feature.
</Typography.Text>
</Panel.Content>
</Panel>
)
@@ -71,7 +73,11 @@ const ConnectionPooling: FC<Props> = () => {
const bouncerInfo = pluckObjectFields(formModel, BOUNCER_FIELDS)
return (
<PgbouncerConfig projectRef={projectRef} bouncerInfo={bouncerInfo} connectionInfo={connectionInfo} />
<PgbouncerConfig
projectRef={projectRef}
bouncerInfo={bouncerInfo}
connectionInfo={connectionInfo}
/>
)
}
@@ -79,140 +85,152 @@ export default observer(ConnectionPooling)
interface ConfigProps {
projectRef: string
bouncerInfo: {default_pool_size: number, ignore_startup_parameters: 'string', pool_mode: string, pgbouncer_enabled: boolean},
connectionInfo: {db_host: string, db_name: string, db_port: number, db_user: string, inserted_at: string}
bouncerInfo: {
default_pool_size: number
ignore_startup_parameters: 'string'
pool_mode: string
pgbouncer_enabled: boolean
}
connectionInfo: {
db_host: string
db_name: string
db_port: number
db_user: string
inserted_at: string
}
}
export const PgbouncerConfig: FC<ConfigProps> = observer(({ projectRef, bouncerInfo, connectionInfo }) => {
export const PgbouncerConfig: FC<ConfigProps> = observer(
({ projectRef, bouncerInfo, connectionInfo }) => {
const { ui } = useStore()
const { ui } = useStore()
const [updates, setUpdates] = useState<any>({
pgbouncer_enabled: bouncerInfo.pgbouncer_enabled,
pool_mode: bouncerInfo.pool_mode || 'transaction',
default_pool_size: bouncerInfo.default_pool_size || '',
ignore_startup_parameters: bouncerInfo.ignore_startup_parameters || '',
})
const [updates, setUpdates] = useState<any>({
pgbouncer_enabled: bouncerInfo.pgbouncer_enabled,
pool_mode: bouncerInfo.pool_mode || 'transaction',
default_pool_size: bouncerInfo.default_pool_size || '',
ignore_startup_parameters: bouncerInfo.ignore_startup_parameters || '',
})
const updateConfig = async (updatedConfig: any) => {
try {
const response = await patch(`${API_URL}/props/pooling/${projectRef}/config`, updatedConfig)
if (response.error) {
throw response.error
} else {
setUpdates({ ...response.project })
ui.setNotification({ category: 'success', message: 'Successfully saved settings' })
const updateConfig = async (updatedConfig: any) => {
try {
const response = await patch(`${API_URL}/props/pooling/${projectRef}/config`, updatedConfig)
if (response.error) {
throw response.error
} else {
setUpdates({ ...response.project })
ui.setNotification({ category: 'success', message: 'Successfully saved settings' })
}
} catch (error: any) {
ui.setNotification({
category: 'error',
message: `Failed to update config: ${error.message}`,
})
}
} catch (error: any) {
ui.setNotification({
category: 'error',
message: `Failed to update config: ${error.message}`,
})
}
const formSchema = {
properties: {
pgbouncer_enabled: {
title: 'Enabled',
type: 'boolean',
help: 'Activates / deactivates Connection Pooling.',
},
pool_mode: {
title: 'Pool Mode',
type: 'string',
options: [
{
label: 'Transaction',
value: 'transaction',
},
{
label: 'Session',
value: 'session',
},
{
label: 'Statement',
value: 'statement',
},
],
},
ignore_startup_parameters: {
title: 'Ignore Startup Parameters',
type: 'string',
readOnly: true,
help: 'Defaults are either blank or "extra_float_digits"',
},
},
required: ['pool_mode'],
type: 'object',
}
return (
<SchemaFormPanel
title="Connection Pooling"
schema={formSchema}
model={updates}
submitLabel="Save"
cancelLabel="Cancel"
onChangeModel={(model: any) => setUpdates(model)}
onSubmit={(model: any) => updateConfig(model)}
onReset={() => setUpdates(bouncerInfo)}
>
<div className="space-y-6 py-4">
<ToggleField name="pgbouncer_enabled" />
<Divider light />
{updates.pgbouncer_enabled && (
<>
<AutoField
name="pool_mode"
showInlineError
errorMessage="You must select one of the three options"
/>
<div className="flex !mt-1" style={{ marginLeft: 'calc(33% + 0.5rem)' }}>
<p className="text-sm text-scale-900">
Specify when a connection can be returned to the pool. To find out the most
suitable mode for your use case,{' '}
<a
className="text-green-900"
target="_blank"
href="https://supabase.com/docs/guides/database/connecting-to-postgres#connection-pool"
>
click here
</a>
.
</p>
</div>
<Divider light />
<AutoField name="ignore_startup_parameters" />
</>
)}
<Divider light />
<Input
className="input-mono"
layout="horizontal"
readOnly
copy
disabled
value={connectionInfo.db_port}
label="Port"
/>
<Divider light />
<Input
className="input-mono"
layout="vertical"
readOnly
copy
disabled
label="Connection string"
value={
`postgres://${connectionInfo.db_user}:[YOUR-PASSWORD]@` +
`${connectionInfo.db_host}:${connectionInfo.db_port}` +
`/${connectionInfo.db_name}`
}
/>
</div>
</SchemaFormPanel>
)
}
const formSchema = {
properties: {
pgbouncer_enabled: {
title: 'Enabled',
type: 'boolean',
help: 'Activates / deactivates Connection Pooling.',
},
pool_mode: {
title: 'Pool Mode',
type: 'string',
options: [
{
label: 'Transaction',
value: 'transaction',
},
{
label: 'Session',
value: 'session',
},
{
label: 'Statement',
value: 'statement',
},
],
},
ignore_startup_parameters: {
title: 'Ignore Startup Parameters',
type: 'string',
readOnly: true,
help: 'Defaults are either blank or "extra_float_digits"',
},
},
required: ['pool_mode'],
type: 'object',
}
return (
<SchemaFormPanel
title="Connection Pooling"
schema={formSchema}
model={updates}
submitLabel="Save"
cancelLabel="Cancel"
onChangeModel={(model: any) => setUpdates(model)}
onSubmit={(model: any) => updateConfig(model)}
onReset={() => setUpdates(bouncerInfo)}
>
<div className="space-y-6 py-4">
<ToggleField name="pgbouncer_enabled" />
<Divider light />
{updates.pgbouncer_enabled && (
<>
<AutoField
name="pool_mode"
showInlineError
errorMessage="You must select one of the three options"
/>
<div className="flex !mt-1" style={{ marginLeft: 'calc(33% + 0.5rem)' }}>
<p className="text-sm text-scale-900">
Specify when a connection can be returned to the pool. To find out the most
suitable mode for your use case,{' '}
<a
className="text-green-900"
target="_blank"
href="https://supabase.com/docs/guides/database/connecting-to-postgres#connection-pool"
>
click here
</a>
.
</p>
</div>
<Divider light />
<AutoField name="ignore_startup_parameters" />
</>
)}
<Divider light />
<Input
className="input-mono"
layout="horizontal"
readOnly
copy
disabled
value={connectionInfo.db_port}
label="Port"
/>
<Divider light />
<Input
className="input-mono"
layout="vertical"
readOnly
copy
disabled
label="Connection string"
value={
`postgres://${connectionInfo.db_user}:[YOUR-PASSWORD]@` +
`${connectionInfo.db_host}:${connectionInfo.db_port}` +
`/${connectionInfo.db_name}`
}
/>
</div>
</SchemaFormPanel>
)
})
)
@@ -16,7 +16,7 @@ const ExampleProject: FC<Props> = ({ framework, title, description, url }) => {
return (
<Link href={url}>
<a target='_blank'>
<a target="_blank">
<div
className={[
'group relative',
@@ -29,7 +29,9 @@ const ExampleProject: FC<Props> = ({ framework, title, description, url }) => {
<div className="flex flex-col mr-4">
<img
className="transition-all group-hover:scale-110"
src={`/img/libraries/${framework.toLowerCase()}${framework.toLowerCase() == "nextjs" ? isDarkTheme ? "-dark" : "" : ""}-icon.svg`}
src={`/img/libraries/${framework.toLowerCase()}${
framework.toLowerCase() == 'nextjs' ? (isDarkTheme ? '-dark' : '') : ''
}-icon.svg`}
alt={`${framework} logo`}
width={26}
height={26}
@@ -20,7 +20,7 @@ const LogEventChart: React.FC<Props> = ({ data, onBarClick }) => {
data={aggregated}
attribute="count"
label="Events"
onBarClick={(v?: {activePayload?: {payload: any}[]}) => {
onBarClick={(v?: { activePayload?: { payload: any }[] }) => {
if (!v || !v?.activePayload?.[0]?.payload) return
const timestamp = v.activePayload[0].payload.timestamp
// 60s before
@@ -40,7 +40,6 @@ const LogSelection: FC<Props> = ({ log, onClose, queryType, isLoading }) => {
break
default:
if (log && isDefaultLogPreviewFormat(log)) {
return <DefaultPreviewSelectionRenderer log={log} />
}
@@ -24,7 +24,11 @@ const FunctionInvocationSelectionRender = ({ log }: any) => {
return (
<div className="grid grid-cols-12">
<span className="text-scale-900 text-sm col-span-4 whitespace-pre-wrap">{label}</span>
<span className={`text-scale-1200 text-sm col-span-8 whitespace-pre-wrap ${code && 'text-xs font-mono'}`}>
<span
className={`text-scale-1200 text-sm col-span-8 whitespace-pre-wrap ${
code && 'text-xs font-mono'
}`}
>
{value}
</span>
</div>
@@ -7,7 +7,7 @@ import { DatetimeHelper, getDefaultHelper, LOGS_LARGE_DATE_RANGE_DAYS_THRESHOLD
interface Props {
to: string
from: string
onChange: React.ComponentProps<typeof DatePicker>["onChange"]
onChange: React.ComponentProps<typeof DatePicker>['onChange']
helpers: DatetimeHelper[]
}
const DatePickers: React.FC<Props> = ({ to, from, onChange, helpers }) => {
@@ -73,9 +73,13 @@ const DatePickers: React.FC<Props> = ({ to, from, onChange, helpers }) => {
to={!helperValue ? to : undefined}
from={!helperValue ? from : undefined}
renderFooter={({ to, from }) => {
if (to && from && Math.abs(dayjs(from).diff(dayjs(to), 'day')) > LOGS_LARGE_DATE_RANGE_DAYS_THRESHOLD) {
if (
to &&
from &&
Math.abs(dayjs(from).diff(dayjs(to), 'day')) > LOGS_LARGE_DATE_RANGE_DAYS_THRESHOLD
) {
return (
<Alert title={""} variant="warning" className="mx-3 pl-2 pr-2 pt-1 pb-2">
<Alert title={''} variant="warning" className="mx-3 pl-2 pr-2 pt-1 pb-2">
Large ranges may result in memory errors for big projects.
</Alert>
)
@@ -1,6 +1,5 @@
const SqlSnippetCode: React.FC = ({ children }) =>(
const SqlSnippetCode: React.FC = ({ children }) => (
<pre className="text-scale-1200 text-sm break-words py-4 px-3 w-full">{children}</pre>
)
export default SqlSnippetCode;
export default SqlSnippetCode
@@ -1,4 +1,4 @@
import React from "react"
import React from 'react'
interface Metadata {
[key: string]: string | number | Object | Object[]
@@ -59,7 +59,7 @@ export type Count = LFResponse<CountData>
export type Logs = LFResponse<LogData>
export type QueryType = 'api' | 'database' | 'functions' | 'fn_edge' | "auth"
export type QueryType = 'api' | 'database' | 'functions' | 'fn_edge' | 'auth'
export type Mode = 'simple' | 'custom'
@@ -168,7 +168,7 @@ export const LogsPreviewer: React.FC<Props> = ({
<Button onClick={loadOlder} icon={<IconRewind />} type="default">
Load older
</Button>
<UpgradePrompt projectRef={projectRef} from={params.iso_timestamp_start || ''}/>
<UpgradePrompt projectRef={projectRef} from={params.iso_timestamp_start || ''} />
</div>
)}
{error && (
@@ -13,26 +13,24 @@ const RecentQueriesItem: React.FC<Props> = ({ item }) => {
const { ref } = router.query
return (
<Table.tr key={item.sql}>
<Table.td
className={`transition-all expanded-row-content bg-scale-100 border-l border-r !pt-0 !pb-0 !px-3`}
<Table.tr key={item.sql}>
<Table.td
className={`transition-all expanded-row-content bg-scale-100 border-l border-r !pt-0 !pb-0 !px-3`}
>
<SqlSnippetCode>{item.sql}</SqlSnippetCode>
</Table.td>
<Table.td className="text-right">
<Button
type="alternative"
iconRight={<IconPlay size={10} />}
onClick={() =>
router.push(`/project/${ref}/logs-explorer?q=${encodeURIComponent(item.sql)}`)
}
>
<SqlSnippetCode>{item.sql}</SqlSnippetCode>
</Table.td>
<Table.td className="text-right">
<Button
type="alternative"
iconRight={<IconPlay size={10} />}
onClick={() =>
router.push(
`/project/${ref}/logs-explorer?q=${encodeURIComponent(item.sql)}`
)
}
>
Run
</Button>
</Table.td>
</Table.tr>
Run
</Button>
</Table.td>
</Table.tr>
)
}
@@ -5,4 +5,4 @@ export { default as LogsSavedQueriesItem } from './Logs.SavedQueriesItem'
export { default as LogsQueryPanel } from './LogsQueryPanel'
export { default as LogTable } from './LogTable'
export { default as LogEventChart } from './LogEventChart'
export { default as LogsFilterPopover } from "./LogsFilterPopover"
export { default as LogsFilterPopover } from './LogsFilterPopover'
@@ -17,11 +17,18 @@ const GridHeaderActions: FC<Props> = ({ table }) => {
return (
<div className="space-x-3 flex items-center">
<Link href={urlToRLSPolicies}>
<Button
type={table.rls_enabled ? 'link' : 'warning'}
icon={table.rls_enabled ? <IconLock strokeWidth={2} size={14} /> : <IconAlertCircle strokeWidth={2} size={14} />}>
RLS {table.rls_enabled ? 'is' : 'not'} enabled
</Button>
<Button
type={table.rls_enabled ? 'link' : 'warning'}
icon={
table.rls_enabled ? (
<IconLock strokeWidth={2} size={14} />
) : (
<IconAlertCircle strokeWidth={2} size={14} />
)
}
>
RLS {table.rls_enabled ? 'is' : 'not'} enabled
</Button>
</Link>
</div>
)
@@ -1,7 +1,11 @@
import { FC, Fragment, useState } from 'react'
import { useRouter } from 'next/router'
import { Button, IconBell, Popover } from '@supabase/ui'
import { Notification, NotificationStatus, ActionType } from '@supabase/shared-types/out/notifications'
import {
Notification,
NotificationStatus,
ActionType,
} from '@supabase/shared-types/out/notifications'
import { Project } from 'types'
import { useNotifications, useStore } from 'hooks'
@@ -48,7 +52,7 @@ const NotificationsPopover: FC<Props> = () => {
const { id: projectId, ref, region } = projectToRestart
const serviceNamesByActionName: Record<string, string> = {
[ActionType.PgBouncerRestart]: 'pgbouncer',
[ActionType.SchedulePostgresRestart]: 'postgresql'
[ActionType.SchedulePostgresRestart]: 'postgresql',
}
const services: string[] = targetNotification.meta.actions_available
.map((action) => action.action_type)
@@ -128,7 +128,7 @@ const ChartHandler: FC<Props> = ({
? chartData?.total
: provider === 'log-stats'
? chartData?.totalGrouped?.[attribute]
: chartData?.data[chartData?.data.length -1]?.[attribute]
: chartData?.data[chartData?.data.length - 1]?.[attribute]
if (loading) {
return (
@@ -156,8 +156,8 @@ export default function UserManagement({ autoApiService, selectedLang, showApiKe
<div className="doc-section ">
<article className="text ">
<p>
Users can log in with Third Party OAuth like Google, Facebook, GitHub, and more. You must
first enable each of these in the{' '}
Users can log in with Third Party OAuth like Google, Facebook, GitHub, and more. You
must first enable each of these in the{' '}
<span className="text-green-500">
<Link key={'AUTH'} href={`/project/${router.query.ref}/auth/settings`}>
Auth Settings
@@ -166,7 +166,10 @@ export default function UserManagement({ autoApiService, selectedLang, showApiKe
tab.
</p>
<p>
Vew all the available <a href='https://supabase.com/docs/guides/auth#providers' target='_blank'>Third Part OAuth providers</a>
Vew all the available{' '}
<a href="https://supabase.com/docs/guides/auth#providers" target="_blank">
Third Part OAuth providers
</a>
</p>
<p>
After they have logged in, all interactions using the Supabase JS client will be
@@ -15,7 +15,8 @@ import {
IconX,
IconEdit2,
IconLoader,
Typography, IconChevronsUp,
Typography,
IconChevronsUp,
} from '@supabase/ui'
import { useStorageStore } from 'localStores/storageExplorer/StorageExplorerStore'
import { STORAGE_VIEWS, STORAGE_SORT_BY, STORAGE_SORT_BY_ORDER } from '../Storage.constants.ts'
@@ -334,7 +335,11 @@ const FileExplorerHeader = ({
</Dropdown>
<Dropdown
overlay={[
<Dropdown.RadioGroup key="sortOrderOptions" value={sortByOrder} onChange={onChangeSortByOrder}>
<Dropdown.RadioGroup
key="sortOrderOptions"
value={sortByOrder}
onChange={onChangeSortByOrder}
>
<Dropdown.Radio value={STORAGE_SORT_BY_ORDER.ASC}>Asc</Dropdown.Radio>
<Dropdown.Radio value={STORAGE_SORT_BY_ORDER.DESC}>Desc</Dropdown.Radio>
</Dropdown.RadioGroup>,
@@ -342,7 +347,13 @@ const FileExplorerHeader = ({
>
<Button
as="span"
icon={sortByOrder === STORAGE_SORT_BY_ORDER.DESC ? <IconChevronsDown size={16} strokeWidth={2} /> : <IconChevronsUp size={16} strokeWidth={2} />}
icon={
sortByOrder === STORAGE_SORT_BY_ORDER.DESC ? (
<IconChevronsDown size={16} strokeWidth={2} />
) : (
<IconChevronsUp size={16} strokeWidth={2} />
)
}
type="text"
disabled={breadcrumbs.length === 0}
>
+1 -1
View File
@@ -1,2 +1,2 @@
export { default as DatePicker } from './DatePicker'
export type {DatePickerProps} from "./DatePicker"
export type { DatePickerProps } from './DatePicker'
+2 -2
View File
@@ -2,8 +2,8 @@ import { ReactText } from 'react'
import { Typography } from '@supabase/ui'
interface Props {
icon: JSX.Element;
text: ReactText;
icon: JSX.Element
text: ReactText
}
const FormBoxEmpty = ({ icon, text }: Props) => {
+475 -1
View File
@@ -1 +1,475 @@
{"assets":[{"id":"comp_Combined-Shape_BF73096D-B400-4871-BC74-C00F039296BF","layers":[{"ty":3,"ao":0,"ddd":0,"ind":15,"ip":0,"ks":{"a":{"a":0,"k":[0,0,0]},"o":{"a":0,"k":100},"p":{"s":true,"x":{"a":0,"k":113},"y":{"a":0,"k":101}},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]}},"nm":"Combined-Shape-null","op":61,"sr":1,"st":0,"tt":0},{"ty":4,"ao":0,"ddd":0,"ind":18,"ip":0,"ks":{"a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]}},"nm":"Combined-Shape-size","op":61,"shapes":[{"ty":"gr","cix":2,"it":[{"ty":"rc","d":1,"nm":"sizeElement","p":{"a":0,"k":[187.5,187.5]},"r":{"a":0,"k":0},"s":{"a":0,"k":[375,375]}},{"ty":"fl","c":{"a":0,"k":[0,0,0,0]},"nm":"Fill1","o":{"a":0,"k":0},"r":1},{"ty":"tr","a":{"a":0,"k":[0,0]},"nm":"Transform","o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sa":{"a":0,"k":0},"sk":{"a":0,"k":0}}],"nm":"Combined-Shape-size","np":3}],"sr":1,"st":0,"tt":0},{"ty":4,"ao":0,"ddd":0,"ind":11,"ip":0,"ks":{"a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]}},"nm":"Combined-Shape-stroke","op":61,"parent":15,"shapes":[{"ty":"gr","cix":2,"it":[{"ty":"sh","ind":0,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,-1.5774979571739018],[0,0],[0,0],[0,-4.1410576386918905],[0.9255199999998638,-1.2470810337555633],[0,0],[0,0],[0.8663779999999406,-0.060411534501071174],[0,0],[0,0],[0,1.5774239565191124],[0,0],[0,0],[0,4.140773636179105],[-0.9253779999999092,1.2470810337555918],[0,0],[0,0],[-0.9496219999999482,0]],"o":[[1.5779769999999473,0],[0,0],[0,0],[4.14215999999999,0],[0,1.671847791949645],[0,0],[0,0],[-0.4826290000000597,0.661415851986078],[0,0],[0,0],[-1.5779769999999473,0],[0,0],[0,0],[-4.142126999999958,0],[0,-1.6721317944625582],[0,0],[0,0],[0.5194710000000526,-0.7124533035483542],[0,0]],"v":[[72.142911,0],[75,2.8563202717656835],[75,67.44501973095933],[142.5,67.44501973095933],[150,74.94310207148214],[148.52722000000006,79.40599555766988],[145.63866000000002,83.46085143367749],[80.16748600000005,171.86465860097266],[78.05883900000003,173.03298993797728],[77.857089,173.04],[77.857089,173.04],[75,170.18373672873858],[75,105.5949792690318],[7.5,105.5949792690318],[0,98.09718193103066],[1.4727789999999457,93.63400444233007],[4.361228999999984,89.57914756631368],[69.83251399999996,1.1754323998324296],[72.142911,0]]}},"nm":"Combined-Shape-path"},{"ty":"st","c":{"a":0,"k":[0.24313725490196078,0.8117647058823529,0.5568627450980392,1]},"d":[{"n":"o","nm":"offset","v":{"a":0,"k":0}}],"lc":1,"lj":1,"ml":4,"nm":"Stroke1","o":{"a":0,"k":100},"w":{"a":0,"k":7}},{"ty":"tm","e":{"a":1,"k":[{"i":{"x":[0.58],"y":[1]},"o":{"x":[0.42],"y":[0]},"s":[0],"t":0},{"i":{"x":[0.58],"y":[1]},"o":{"x":[0.42],"y":[0]},"s":[100],"t":30},{"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]},"s":[100],"t":60}]},"m":2,"nm":"TrimPaths1","o":{"a":0,"k":0},"s":{"a":1,"k":[{"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]},"s":[0],"t":0},{"i":{"x":[0.58],"y":[1]},"o":{"x":[0.42],"y":[0]},"s":[0],"t":27},{"i":{"x":[0.58],"y":[1]},"o":{"x":[0.42],"y":[0]},"s":[100],"t":60}]}},{"ty":"tr","a":{"a":0,"k":[0,0]},"nm":"Transform","o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sa":{"a":0,"k":0},"sk":{"a":0,"k":0}}],"nm":"Combined-Shape-stroke","np":3}],"sr":1,"st":0,"tt":0},{"ty":4,"ao":0,"ddd":0,"ind":14,"ip":0,"ks":{"a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]}},"nm":"Combined-Shape-content","op":61,"parent":15,"shapes":[{"ty":"gr","cix":2,"it":[{"ty":"sh","ind":0,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,-1.5774979571739018],[0,0],[0,0],[0,-4.1410576386918905],[0.9255199999998638,-1.2470810337555633],[0,0],[0,0],[0.8663779999999406,-0.060411534501071174],[0,0],[0,0],[0,1.5774239565191124],[0,0],[0,0],[0,4.140773636179105],[-0.9253779999999092,1.2470810337555918],[0,0],[0,0],[-0.9496219999999482,0]],"o":[[1.5779769999999473,0],[0,0],[0,0],[4.14215999999999,0],[0,1.671847791949645],[0,0],[0,0],[-0.4826290000000597,0.661415851986078],[0,0],[0,0],[-1.5779769999999473,0],[0,0],[0,0],[-4.142126999999958,0],[0,-1.6721317944625582],[0,0],[0,0],[0.5194710000000526,-0.7124533035483542],[0,0]],"v":[[72.142911,0],[75,2.8563202717656835],[75,67.44501973095933],[142.5,67.44501973095933],[150,74.94310207148214],[148.52722000000006,79.40599555766988],[145.63866000000002,83.46085143367749],[80.16748600000005,171.86465860097266],[78.05883900000003,173.03298993797728],[77.857089,173.04],[77.857089,173.04],[75,170.18373672873858],[75,105.5949792690318],[7.5,105.5949792690318],[0,98.09718193103066],[1.4727789999999457,93.63400444233007],[4.361228999999984,89.57914756631368],[69.83251399999996,1.1754323998324296],[72.142911,0]]}},"nm":"Combined-Shape-path"},{"ty":"fl","c":{"a":0,"k":[0,0,0,1]},"nm":"Fill1","o":{"a":0,"k":0},"r":1},{"ty":"tr","a":{"a":0,"k":[0,0]},"nm":"Transform","o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s"Line truncated
{
"assets": [
{
"id": "comp_Combined-Shape_BF73096D-B400-4871-BC74-C00F039296BF",
"layers": [
{
"ty": 3,
"ao": 0,
"ddd": 0,
"ind": 15,
"ip": 0,
"ks": {
"a": { "a": 0, "k": [0, 0, 0] },
"o": { "a": 0, "k": 100 },
"p": { "s": true, "x": { "a": 0, "k": 113 }, "y": { "a": 0, "k": 101 } },
"r": { "a": 0, "k": 0 },
"s": { "a": 0, "k": [100, 100] }
},
"nm": "Combined-Shape-null",
"op": 61,
"sr": 1,
"st": 0,
"tt": 0
},
{
"ty": 4,
"ao": 0,
"ddd": 0,
"ind": 18,
"ip": 0,
"ks": {
"a": { "a": 0, "k": [0, 0] },
"o": { "a": 0, "k": 100 },
"p": { "a": 0, "k": [0, 0] },
"r": { "a": 0, "k": 0 },
"s": { "a": 0, "k": [100, 100] }
},
"nm": "Combined-Shape-size",
"op": 61,
"shapes": [
{
"ty": "gr",
"cix": 2,
"it": [
{
"ty": "rc",
"d": 1,
"nm": "sizeElement",
"p": { "a": 0, "k": [187.5, 187.5] },
"r": { "a": 0, "k": 0 },
"s": { "a": 0, "k": [375, 375] }
},
{
"ty": "fl",
"c": { "a": 0, "k": [0, 0, 0, 0] },
"nm": "Fill1",
"o": { "a": 0, "k": 0 },
"r": 1
},
{
"ty": "tr",
"a": { "a": 0, "k": [0, 0] },
"nm": "Transform",
"o": { "a": 0, "k": 100 },
"p": { "a": 0, "k": [0, 0] },
"r": { "a": 0, "k": 0 },
"s": { "a": 0, "k": [100, 100] },
"sa": { "a": 0, "k": 0 },
"sk": { "a": 0, "k": 0 }
}
],
"nm": "Combined-Shape-size",
"np": 3
}
],
"sr": 1,
"st": 0,
"tt": 0
},
{
"ty": 4,
"ao": 0,
"ddd": 0,
"ind": 11,
"ip": 0,
"ks": {
"a": { "a": 0, "k": [0, 0] },
"o": { "a": 0, "k": 100 },
"p": { "a": 0, "k": [0, 0] },
"r": { "a": 0, "k": 0 },
"s": { "a": 0, "k": [100, 100] }
},
"nm": "Combined-Shape-stroke",
"op": 61,
"parent": 15,
"shapes": [
{
"ty": "gr",
"cix": 2,
"it": [
{
"ty": "sh",
"ind": 0,
"ks": {
"a": 0,
"k": {
"c": true,
"i": [
[0, 0],
[0, -1.5774979571739018],
[0, 0],
[0, 0],
[0, -4.1410576386918905],
[0.9255199999998638, -1.2470810337555633],
[0, 0],
[0, 0],
[0.8663779999999406, -0.060411534501071174],
[0, 0],
[0, 0],
[0, 1.5774239565191124],
[0, 0],
[0, 0],
[0, 4.140773636179105],
[-0.9253779999999092, 1.2470810337555918],
[0, 0],
[0, 0],
[-0.9496219999999482, 0]
],
"o": [
[1.5779769999999473, 0],
[0, 0],
[0, 0],
[4.14215999999999, 0],
[0, 1.671847791949645],
[0, 0],
[0, 0],
[-0.4826290000000597, 0.661415851986078],
[0, 0],
[0, 0],
[-1.5779769999999473, 0],
[0, 0],
[0, 0],
[-4.142126999999958, 0],
[0, -1.6721317944625582],
[0, 0],
[0, 0],
[0.5194710000000526, -0.7124533035483542],
[0, 0]
],
"v": [
[72.142911, 0],
[75, 2.8563202717656835],
[75, 67.44501973095933],
[142.5, 67.44501973095933],
[150, 74.94310207148214],
[148.52722000000006, 79.40599555766988],
[145.63866000000002, 83.46085143367749],
[80.16748600000005, 171.86465860097266],
[78.05883900000003, 173.03298993797728],
[77.857089, 173.04],
[77.857089, 173.04],
[75, 170.18373672873858],
[75, 105.5949792690318],
[7.5, 105.5949792690318],
[0, 98.09718193103066],
[1.4727789999999457, 93.63400444233007],
[4.361228999999984, 89.57914756631368],
[69.83251399999996, 1.1754323998324296],
[72.142911, 0]
]
}
},
"nm": "Combined-Shape-path"
},
{
"ty": "st",
"c": {
"a": 0,
"k": [0.24313725490196078, 0.8117647058823529, 0.5568627450980392, 1]
},
"d": [{ "n": "o", "nm": "offset", "v": { "a": 0, "k": 0 } }],
"lc": 1,
"lj": 1,
"ml": 4,
"nm": "Stroke1",
"o": { "a": 0, "k": 100 },
"w": { "a": 0, "k": 7 }
},
{
"ty": "tm",
"e": {
"a": 1,
"k": [
{
"i": { "x": [0.58], "y": [1] },
"o": { "x": [0.42], "y": [0] },
"s": [0],
"t": 0
},
{
"i": { "x": [0.58], "y": [1] },
"o": { "x": [0.42], "y": [0] },
"s": [100],
"t": 30
},
{
"i": { "x": [1], "y": [1] },
"o": { "x": [0], "y": [0] },
"s": [100],
"t": 60
}
]
},
"m": 2,
"nm": "TrimPaths1",
"o": { "a": 0, "k": 0 },
"s": {
"a": 1,
"k": [
{
"i": { "x": [1], "y": [1] },
"o": { "x": [0], "y": [0] },
"s": [0],
"t": 0
},
{
"i": { "x": [0.58], "y": [1] },
"o": { "x": [0.42], "y": [0] },
"s": [0],
"t": 27
},
{
"i": { "x": [0.58], "y": [1] },
"o": { "x": [0.42], "y": [0] },
"s": [100],
"t": 60
}
]
}
},
{
"ty": "tr",
"a": { "a": 0, "k": [0, 0] },
"nm": "Transform",
"o": { "a": 0, "k": 100 },
"p": { "a": 0, "k": [0, 0] },
"r": { "a": 0, "k": 0 },
"s": { "a": 0, "k": [100, 100] },
"sa": { "a": 0, "k": 0 },
"sk": { "a": 0, "k": 0 }
}
],
"nm": "Combined-Shape-stroke",
"np": 3
}
],
"sr": 1,
"st": 0,
"tt": 0
},
{
"ty": 4,
"ao": 0,
"ddd": 0,
"ind": 14,
"ip": 0,
"ks": {
"a": { "a": 0, "k": [0, 0] },
"o": { "a": 0, "k": 100 },
"p": { "a": 0, "k": [0, 0] },
"r": { "a": 0, "k": 0 },
"s": { "a": 0, "k": [100, 100] }
},
"nm": "Combined-Shape-content",
"op": 61,
"parent": 15,
"shapes": [
{
"ty": "gr",
"cix": 2,
"it": [
{
"ty": "sh",
"ind": 0,
"ks": {
"a": 0,
"k": {
"c": true,
"i": [
[0, 0],
[0, -1.5774979571739018],
[0, 0],
[0, 0],
[0, -4.1410576386918905],
[0.9255199999998638, -1.2470810337555633],
[0, 0],
[0, 0],
[0.8663779999999406, -0.060411534501071174],
[0, 0],
[0, 0],
[0, 1.5774239565191124],
[0, 0],
[0, 0],
[0, 4.140773636179105],
[-0.9253779999999092, 1.2470810337555918],
[0, 0],
[0, 0],
[-0.9496219999999482, 0]
],
"o": [
[1.5779769999999473, 0],
[0, 0],
[0, 0],
[4.14215999999999, 0],
[0, 1.671847791949645],
[0, 0],
[0, 0],
[-0.4826290000000597, 0.661415851986078],
[0, 0],
[0, 0],
[-1.5779769999999473, 0],
[0, 0],
[0, 0],
[-4.142126999999958, 0],
[0, -1.6721317944625582],
[0, 0],
[0, 0],
[0.5194710000000526, -0.7124533035483542],
[0, 0]
],
"v": [
[72.142911, 0],
[75, 2.8563202717656835],
[75, 67.44501973095933],
[142.5, 67.44501973095933],
[150, 74.94310207148214],
[148.52722000000006, 79.40599555766988],
[145.63866000000002, 83.46085143367749],
[80.16748600000005, 171.86465860097266],
[78.05883900000003, 173.03298993797728],
[77.857089, 173.04],
[77.857089, 173.04],
[75, 170.18373672873858],
[75, 105.5949792690318],
[7.5, 105.5949792690318],
[0, 98.09718193103066],
[1.4727789999999457, 93.63400444233007],
[4.361228999999984, 89.57914756631368],
[69.83251399999996, 1.1754323998324296],
[72.142911, 0]
]
}
},
"nm": "Combined-Shape-path"
},
{
"ty": "fl",
"c": { "a": 0, "k": [0, 0, 0, 1] },
"nm": "Fill1",
"o": { "a": 0, "k": 0 },
"r": 1
},
{
"ty": "tr",
"a": { "a": 0, "k": [0, 0] },
"nm": "Transform",
"o": { "a": 0, "k": 100 },
"p": { "a": 0, "k": [0, 0] },
"r": { "a": 0, "k": 0 },
"s": { "a": 0, "k": [100, 100] },
"sa": { "a": 0, "k": 0 },
"sk": { "a": 0, "k": 0 }
}
],
"nm": "Combined-Shape-content",
"np": 3
}
],
"sr": 1,
"st": 0,
"tt": 0
}
]
}
],
"ddd": 0,
"fonts": { "list": [] },
"fr": 30,
"h": 375,
"ip": 0,
"layers": [
{
"ty": 0,
"ao": 0,
"ddd": 0,
"h": 375,
"ind": 10,
"ip": 0,
"ks": {
"a": { "a": 0, "k": [0, 0] },
"o": { "a": 0, "k": 100 },
"p": { "a": 0, "k": [0, 0] },
"r": { "a": 0, "k": 0 },
"s": { "a": 0, "k": [100, 100] }
},
"nm": "Combined-Shape-composition",
"op": 61,
"refId": "comp_Combined-Shape_BF73096D-B400-4871-BC74-C00F039296BF",
"sr": 1,
"st": 0,
"tt": 0,
"w": 375
},
{
"ty": 4,
"ao": 0,
"ddd": 0,
"ind": 2,
"ip": 0,
"ks": {
"a": { "a": 0, "k": [0, 0] },
"o": { "a": 0, "k": 100 },
"p": { "a": 0, "k": [0, 0] },
"r": { "a": 0, "k": 0 },
"s": { "a": 0, "k": [100, 100] }
},
"nm": "Scene-background",
"op": 61,
"shapes": [
{
"ty": "gr",
"cix": 2,
"it": [
{
"ty": "rc",
"d": 1,
"nm": "Scene-background",
"p": { "a": 0, "k": [187.5, 187.5] },
"r": { "a": 0, "k": 0 },
"s": { "a": 0, "k": [375, 375] }
},
{
"ty": "fl",
"c": { "a": 0, "k": [0.9440000000000001, 0.968, 1, 1] },
"nm": "Fill1",
"o": { "a": 0, "k": 0 },
"r": 1
},
{
"ty": "tr",
"a": { "a": 0, "k": [0, 0] },
"nm": "Transform",
"o": { "a": 0, "k": 100 },
"p": { "a": 0, "k": [0, 0] },
"r": { "a": 0, "k": 0 },
"s": { "a": 0, "k": [100, 100] },
"sa": { "a": 0, "k": 0 },
"sk": { "a": 0, "k": 0 }
}
],
"nm": "Scene-background",
"np": 3
}
],
"sr": 1,
"st": 0,
"tt": 0
}
],
"meta": { "a": "", "d": "OutlineTraceLoader(Empty)", "g": "Flow1.8.6", "k": "", "tc": "" },
"nm": "OutlineTraceLoader(Empty)",
"op": 61,
"v": "5.6.4",
"w": 375
}
+10 -10
View File
@@ -2,15 +2,15 @@ import { Loading } from '@supabase/ui'
import React, { ReactNode } from 'react'
interface Props {
bodyClassName?: string;
children?: ReactNode;
className?: string;
footer?: JSX.Element | false;
hideHeaderStyling?: boolean;
loading?: boolean;
noMargin?: boolean;
title?: JSX.Element | false;
wrapWithLoading?: boolean;
bodyClassName?: string
children?: ReactNode
className?: string
footer?: JSX.Element | false
hideHeaderStyling?: boolean
loading?: boolean
noMargin?: boolean
title?: JSX.Element | false
wrapWithLoading?: boolean
}
function Panel(props: Props) {
let headerClasses: string[] = []
@@ -63,7 +63,7 @@ function Panel(props: Props) {
return <Loading active={Boolean(props.loading)}>{content}</Loading>
}
function Content({children, className}: {children: ReactNode; className?: string | false}) {
function Content({ children, className }: { children: ReactNode; className?: string | false }) {
let classes = ['px-6 py-4']
if (className) classes.push(className)
return <div className={classes.join(' ')}>{children}</div>
+6 -6
View File
@@ -8,7 +8,7 @@ import { Typography } from '@supabase/ui'
interface Props {
passwordStrengthScore: number
passwordStrengthMessage: string
password: string,
password: string
generateStrongPassword: () => void
}
@@ -16,7 +16,7 @@ const PasswordStrengthBar = ({
passwordStrengthScore = 0,
passwordStrengthMessage = '',
password = '',
generateStrongPassword
generateStrongPassword,
}: Props) => {
return (
<>
@@ -46,10 +46,10 @@ const PasswordStrengthBar = ({
>
{passwordStrengthMessage
? passwordStrengthMessage
: 'This is the password to your postgres database, so it must be a strong password and hard to guess.'}
{' '} <button className='underline hover:decoration-2' onClick={generateStrongPassword}>
Generate a password
</button>
: 'This is the password to your postgres database, so it must be a strong password and hard to guess.'}{' '}
<button className="underline hover:decoration-2" onClick={generateStrongPassword}>
Generate a password
</button>
</Typography.Text>
</>
)
+6 -6
View File
@@ -4,12 +4,12 @@ import { Typography } from '@supabase/ui'
import { useStore } from 'hooks'
interface Props {
contextmenu?: boolean;
defaultValue?: string;
language?: string;
onInputChange?: OnChange;
queryId?: string;
readOnly?: boolean;
contextmenu?: boolean
defaultValue?: string
language?: string
onInputChange?: OnChange
queryId?: string
readOnly?: boolean
}
const SqlEditor: FC<Props> = ({
+6 -2
View File
@@ -28,8 +28,12 @@ const useLogsQuery = (
const [params, setParams] = useState<LogsEndpointParams>({
sql: '',
project: projectRef,
iso_timestamp_start: initialParams.iso_timestamp_start ? initialParams.iso_timestamp_start : defaultHelper.calcFrom(),
iso_timestamp_end: initialParams.iso_timestamp_end ? initialParams.iso_timestamp_end : defaultHelper.calcTo(),
iso_timestamp_start: initialParams.iso_timestamp_start
? initialParams.iso_timestamp_start
: defaultHelper.calcFrom(),
iso_timestamp_end: initialParams.iso_timestamp_end
? initialParams.iso_timestamp_end
: defaultHelper.calcTo(),
})
const queryParams = genQueryParams(params as any)
@@ -50,12 +50,16 @@ function getPaygStats(data: any): PaygStats {
let dataModified: any = []
data.map((x: any) => {
dataModified.push({ ...x, total_egress_modified: (x.total_egress == undefined ? 0 : x.total_egress) - (x.total_storage_egress == undefined ? 0 : x.total_storage_egress) })
dataModified.push({
...x,
total_egress_modified:
(x.total_egress == undefined ? 0 : x.total_egress) -
(x.total_storage_egress == undefined ? 0 : x.total_storage_egress),
})
})
chargeableProducts.forEach((product) => {
product.features.forEach((feature) => {
// calculate max
const max = _get(maxBy(dataModified, feature.attribute), feature.attribute) ?? 0
+4 -4
View File
@@ -1,13 +1,13 @@
module.exports = {
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
moduleDirectories: ['<rootDir>', 'node_modules'],
setupFiles: ["jest-canvas-mock", "./tests/setup/radix"],
setupFiles: ['jest-canvas-mock', './tests/setup/radix'],
testEnvironment: 'jsdom',
testRegex: '(.*\\.test.(js|jsx|ts|tsx)$)',
transform: { '^.+\\.(t|j)sx?$': 'ts-jest' },
globals: {
'ts-jest': {
tsconfig: 'tsconfig.test.json'
}
}
tsconfig: 'tsconfig.test.json',
},
},
}
+2 -1
View File
@@ -1,6 +1,7 @@
import { GoTrueClient, User } from '@supabase/gotrue-js'
export const GOTRUE_URL = process.env.NEXT_PUBLIC_GOTRUE_URL || `${process.env.SUPABASE_URL}/auth/v1`
export const GOTRUE_URL =
process.env.NEXT_PUBLIC_GOTRUE_URL || `${process.env.SUPABASE_URL}/auth/v1`
export const auth = new GoTrueClient({
url: GOTRUE_URL,
@@ -65,7 +65,11 @@ class StorageExplorerStore {
selectedItemsToMove = []
selectedFilePreview = {}
DEFAULT_OPTIONS = { limit: LIMIT, offset: OFFSET, sortBy: { column: this.sortBy, order: this.sortByOrder } }
DEFAULT_OPTIONS = {
limit: LIMIT,
offset: OFFSET,
sortBy: { column: this.sortBy, order: this.sortByOrder },
}
/* Supabase client */
supabaseClient = null
@@ -1134,10 +1138,10 @@ class StorageExplorerStore {
const { error } = await this.supabaseClient.storage
.from(this.selectedBucket.name)
.move(fromPath, toPath)
if (error) {
hasErrors = true
toast.error(`Failed to move ${fromPath} to the new folder`)
}
if (error) {
hasErrors = true
toast.error(`Failed to move ${fromPath} to the new folder`)
}
resolve()
})
}
@@ -1193,19 +1197,23 @@ class StorageExplorerStore {
formattedPathToFolder = `${prefix}/${name}`
}
const options = { limit: LIMIT, offset: OFFSET, sortBy: { column: this.sortBy, order: this.sortByOrder } }
const options = {
limit: LIMIT,
offset: OFFSET,
sortBy: { column: this.sortBy, order: this.sortByOrder },
}
let folderContents = []
for (;;) {
const { data } = await this.supabaseClient.storage
.from(this.selectedBucket.name)
.list(formattedPathToFolder, options)
folderContents = folderContents.concat(data)
options.offset += options.limit
if (data.length < options.limit) {
break
}
for (;;) {
const { data } = await this.supabaseClient.storage
.from(this.selectedBucket.name)
.list(formattedPathToFolder, options)
folderContents = folderContents.concat(data)
options.offset += options.limit
if (data.length < options.limit) {
break
}
}
const subfolders = folderContents?.filter((item) => isNull(item.id)) ?? []
const folderItems = folderContents?.filter((item) => !isNull(item.id)) ?? []
+1 -1
View File
@@ -60,7 +60,7 @@ const nextConfig = {
]
},
images: {
domains: ['github.com']
domains: ['github.com'],
},
}
+66 -6
View File
@@ -108,6 +108,7 @@
"jest": "^27.4.0",
"jest-canvas-mock": "^2.3.1",
"postcss": "^8.4.6",
"prettier": "^2.7.1",
"storybook-dark-mode": "^1.0.4",
"tailwindcss": "^3.0.22",
"ts-jest": "^27.0.7",
@@ -5973,6 +5974,18 @@
"node": ">=8.9.0"
}
},
"node_modules/@storybook/addon-essentials/node_modules/prettier": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.0.tgz",
"integrity": "sha512-kXtO4s0Lz/DW/IJ9QdWhAf7/NmPWQXkFr/r/WkR3vyI+0v8amTDxiaQSLzs8NBlytfLWX/7uQUMIW677yLKl4w==",
"dev": true,
"bin": {
"prettier": "bin-prettier.js"
},
"engines": {
"node": ">=10.13.0"
}
},
"node_modules/@storybook/addon-essentials/node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
@@ -7312,6 +7325,18 @@
"url": "https://opencollective.com/storybook"
}
},
"node_modules/@storybook/csf-tools/node_modules/prettier": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.0.tgz",
"integrity": "sha512-kXtO4s0Lz/DW/IJ9QdWhAf7/NmPWQXkFr/r/WkR3vyI+0v8amTDxiaQSLzs8NBlytfLWX/7uQUMIW677yLKl4w==",
"dev": true,
"bin": {
"prettier": "bin-prettier.js"
},
"engines": {
"node": ">=10.13.0"
}
},
"node_modules/@storybook/instrumenter": {
"version": "6.4.19",
"resolved": "https://registry.npmjs.org/@storybook/instrumenter/-/instrumenter-6.4.19.tgz",
@@ -8128,6 +8153,18 @@
"node": ">=8.9.0"
}
},
"node_modules/@storybook/source-loader/node_modules/prettier": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.0.tgz",
"integrity": "sha512-kXtO4s0Lz/DW/IJ9QdWhAf7/NmPWQXkFr/r/WkR3vyI+0v8amTDxiaQSLzs8NBlytfLWX/7uQUMIW677yLKl4w==",
"dev": true,
"bin": {
"prettier": "bin-prettier.js"
},
"engines": {
"node": ">=10.13.0"
}
},
"node_modules/@storybook/store": {
"version": "6.4.19",
"resolved": "https://registry.npmjs.org/@storybook/store/-/store-6.4.19.tgz",
@@ -22382,15 +22419,18 @@
}
},
"node_modules/prettier": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.0.tgz",
"integrity": "sha512-kXtO4s0Lz/DW/IJ9QdWhAf7/NmPWQXkFr/r/WkR3vyI+0v8amTDxiaQSLzs8NBlytfLWX/7uQUMIW677yLKl4w==",
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz",
"integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==",
"dev": true,
"bin": {
"prettier": "bin-prettier.js"
},
"engines": {
"node": ">=10.13.0"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/pretty-error": {
@@ -33440,6 +33480,12 @@
"json5": "^2.1.2"
}
},
"prettier": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.0.tgz",
"integrity": "sha512-kXtO4s0Lz/DW/IJ9QdWhAf7/NmPWQXkFr/r/WkR3vyI+0v8amTDxiaQSLzs8NBlytfLWX/7uQUMIW677yLKl4w==",
"dev": true
},
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
@@ -34398,6 +34444,14 @@
"prettier": ">=2.2.1 <=2.3.0",
"regenerator-runtime": "^0.13.7",
"ts-dedent": "^2.0.0"
},
"dependencies": {
"prettier": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.0.tgz",
"integrity": "sha512-kXtO4s0Lz/DW/IJ9QdWhAf7/NmPWQXkFr/r/WkR3vyI+0v8amTDxiaQSLzs8NBlytfLWX/7uQUMIW677yLKl4w==",
"dev": true
}
}
},
"@storybook/instrumenter": {
@@ -34962,6 +35016,12 @@
"emojis-list": "^3.0.0",
"json5": "^2.1.2"
}
},
"prettier": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.0.tgz",
"integrity": "sha512-kXtO4s0Lz/DW/IJ9QdWhAf7/NmPWQXkFr/r/WkR3vyI+0v8amTDxiaQSLzs8NBlytfLWX/7uQUMIW677yLKl4w==",
"dev": true
}
}
},
@@ -46170,9 +46230,9 @@
"dev": true
},
"prettier": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.0.tgz",
"integrity": "sha512-kXtO4s0Lz/DW/IJ9QdWhAf7/NmPWQXkFr/r/WkR3vyI+0v8amTDxiaQSLzs8NBlytfLWX/7uQUMIW677yLKl4w==",
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz",
"integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==",
"dev": true
},
"pretty-error": {
+4 -1
View File
@@ -9,7 +9,9 @@
"test": "jest",
"deploy:staging": "VERCEL_ORG_ID=team_E6KJ1W561hMTjon1QSwOh0WO VERCEL_PROJECT_ID=QmcmhbiAtCMFTAHCuGgQscNbke4TzgWULECctNcKmxWCoT vercel --prod -A .vercel/staging.json",
"storybook": "start-storybook -p 6006",
"build-storybook": "build-storybook"
"build-storybook": "build-storybook",
"prettier:check": "prettier --check .",
"prettier:write": "prettier --write ."
},
"dependencies": {
"@headlessui/react": "^1.4.1",
@@ -112,6 +114,7 @@
"jest": "^27.4.0",
"jest-canvas-mock": "^2.3.1",
"postcss": "^8.4.6",
"prettier": "^2.7.1",
"storybook-dark-mode": "^1.0.4",
"tailwindcss": "^3.0.22",
"ts-jest": "^27.0.7",
+1 -1
View File
@@ -103,7 +103,7 @@ const handleGetAll = async (req: NextApiRequest, res: NextApiResponse) => {
EXTERNAL_ZOOM_ENABLED: false,
EXTERNAL_ZOOM_CLIENT_ID: null,
EXTERNAL_ZOOM_SECRET: null,
MAILER_SUBJECTS_INVITE: 'You have been invited',
MAILER_SUBJECTS_CONFIRMATION: 'Confirm Your Signup',
MAILER_SUBJECTS_RECOVERY: 'Reset Your Password',
@@ -16,7 +16,7 @@ export const LogPage: NextPageWithLayout = () => {
<LogsPreviewer
projectRef={ref as string}
queryType={'fn_edge'}
filterOverride={{function_id: id}}
filterOverride={{ function_id: id }}
/>
)
}
@@ -35,8 +35,8 @@ export const LogsExplorerPage: NextPageWithLayout = () => {
const [{ params, logData, error, isLoading }, { changeQuery, runQuery, setParams }] =
useLogsQuery(ref as string, {
iso_timestamp_start: its ? its as string : undefined,
iso_timestamp_end: ite ? ite as string : undefined,
iso_timestamp_start: its ? (its as string) : undefined,
iso_timestamp_end: ite ? (ite as string) : undefined,
})
useEffect(() => {
@@ -23,28 +23,27 @@ export const LogsSavedPage: NextPageWithLayout = () => {
return (
<>
{recent.length > 0 && (
<Table
head={
<>
<Table.th>Snippets</Table.th>
<Table.th className="w-24">
<Button
size="tiny"
type="default"
onClick={() => {
content.clearRecentLogSqlSnippets()
}}
>
Clear history
</Button>
</Table.th>
</>
}
body={recent.map((item: LogSqlSnippets.Content) => (
<RecentQueriesItem key={item.sql} item={item} />
))
}
/>
<Table
head={
<>
<Table.th>Snippets</Table.th>
<Table.th className="w-24">
<Button
size="tiny"
type="default"
onClick={() => {
content.clearRecentLogSqlSnippets()
}}
>
Clear history
</Button>
</Table.th>
</>
}
body={recent.map((item: LogSqlSnippets.Content) => (
<RecentQueriesItem key={item.sql} item={item} />
))}
/>
)}
{recent.length === 0 && (
<>
@@ -49,7 +49,7 @@ export default observer(ProjectSettings)
const Usage: FC<any> = ({ project }) => {
const [dateRange, setDateRange] = useState<any>(undefined)
const router = useRouter()
const ref = router.query.ref as string;
const ref = router.query.ref as string
return (
<>
+1 -1
View File
@@ -20,4 +20,4 @@
.column-type-disabled .sbui-listbox-label {
@apply opacity-50;
}
}
-1
View File
@@ -707,4 +707,3 @@ header/sort/SortRow
.sb-grid-sort-row__item__move {
@apply flex cursor-move;
}
+3 -3
View File
@@ -1,9 +1,9 @@
import { LogData } from "components/interfaces/Settings/Logs";
import { LogData } from 'components/interfaces/Settings/Logs'
export const logDataFixture = (attrs: Partial<LogData>): LogData => ({
id: `some-uuid-${new Date().getTime()}`,
timestamp: new Date().getTime() * 1000,
event_message: 'first event',
metadata: {},
...attrs
})
...attrs,
})
+3 -4
View File
@@ -64,14 +64,13 @@ test('toggle histogram', async () => {
expect(mockFn).toBeCalled()
})
test('error message handling', async () => {
const {rerender} = render(<LogTable error="some \nstring" />)
await expect(screen.findByText("some \nstring")).rejects.toThrow()
const { rerender } = render(<LogTable error="some \nstring" />)
await expect(screen.findByText('some \nstring')).rejects.toThrow()
await screen.findByDisplayValue(/some/)
await screen.findByDisplayValue(/string/)
rerender(<LogTable error={{my_error: "some \nstring"}} />)
rerender(<LogTable error={{ my_error: 'some \nstring' }} />)
await screen.findByText(/some \\nstring/)
await screen.findByText(/some/)
await screen.findByText(/string/)
@@ -94,10 +94,7 @@ test.todo('timestamp to/from filter value change')
// await screen.findByTitle(/Clear timestamp filter/)
// })
test('shortened count to K', async () => {
render(<PreviewFilterPanel newCount={1234} />)
await screen.findByText(/1\.2K/)
})
+3 -1
View File
@@ -1,4 +1,6 @@
export interface UserContent<T = Dashboards.Content | SqlSnippets.Content | LogSqlSnippets.Content> {
export interface UserContent<
T = Dashboards.Content | SqlSnippets.Content | LogSqlSnippets.Content
> {
id?: string
name: string
description?: string