Skip to content

Commit

Permalink
merge with master
Browse files Browse the repository at this point in the history
  • Loading branch information
fiskus committed Sep 22, 2022
2 parents bd61985 + 996eae4 commit 872d988
Show file tree
Hide file tree
Showing 37 changed files with 995 additions and 475 deletions.
5 changes: 5 additions & 0 deletions catalog/app/@types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ declare module '*.svg' {
export default value
}

declare module '*.webm' {
const value: string
export default value
}

declare module 'intl/locale-data/jsonp/*'

type $TSFixMe = any
37 changes: 37 additions & 0 deletions catalog/app/components/Footer/Footer.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ import * as Intercom from 'components/Intercom'
import Logo from 'components/Logo'
import * as style from 'constants/style'
import * as URLS from 'constants/urls'
import * as Notifications from 'containers/Notifications'
import * as CatalogSettings from 'utils/CatalogSettings'
import * as Config from 'utils/Config'
import HashLink from 'utils/HashLink'
import * as NamedRoutes from 'utils/NamedRoutes'
import copyToClipboard from 'utils/clipboard'

import bg from './bg.png'
import iconFacebook from './icon-facebook.svg'
Expand All @@ -20,6 +22,38 @@ import iconLinkedin from './icon-linkedin.svg'
import iconSlack from './icon-slack.svg'
import iconTwitter from './icon-twitter.svg'

const useVersionStyles = M.makeStyles((t) => ({
revision: {
color: t.palette.secondary.main,
cursor: 'pointer',
opacity: 0.3,
'&:hover': {
opacity: 1,
},
},
}))

function Version() {
const classes = useVersionStyles()
const { push } = Notifications.use()
const handleCopy = React.useCallback(() => {
copyToClipboard(process.env.REVISION_HASH)
push('Product revision hash has been copied to clipboard')
}, [push])
return (
<div>
<M.Typography
className={classes.revision}
onClick={handleCopy}
title="Copy product revision hash to clipboard"
variant="caption"
>
Revision: {process.env.REVISION_HASH.substring(0, 8)}
</M.Typography>
</div>
)
}

const FooterLogo = () => <Logo height="29px" width="76.5px" />

const NavLink = (props) => (
Expand Down Expand Up @@ -185,6 +219,9 @@ export default function Footer() {
)}
</M.Box>
</M.Container>
<M.Container maxWidth="lg">
<Version />
</M.Container>
</footer>
</M.MuiThemeProvider>
)
Expand Down
14 changes: 7 additions & 7 deletions catalog/app/components/JsonEditor/EmptyRow.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import cx from 'classnames'
import * as React from 'react'
import * as M from '@material-ui/core'

const useStyles = M.makeStyles((t) => ({
inputCell: {
cell: {
border: `1px solid ${t.palette.grey[300]}`,
padding: 0,
width: '50%',
},
key: {
[t.breakpoints.up('lg')]: {
width: t.spacing(27),
},
},
emptyCell: {
border: `1px solid ${t.palette.grey[300]}`,
padding: 0,
width: '50%',
value: {
[t.breakpoints.up('lg')]: {
width: t.spacing(40),
},
Expand All @@ -27,10 +27,10 @@ export default function EmptyRow() {
const classes = useStyles()
return (
<M.TableRow>
<M.TableCell className={classes.inputCell}>
<M.TableCell className={cx(classes.cell, classes.key)}>
<div className={classes.cellContent} />
</M.TableCell>
<M.TableCell className={classes.emptyCell}>
<M.TableCell className={cx(classes.cell, classes.value)}>
<div className={classes.cellContent} />
</M.TableCell>
</M.TableRow>
Expand Down
64 changes: 62 additions & 2 deletions catalog/app/components/JsonEditor/JsonEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,53 @@ import * as R from 'ramda'
import * as React from 'react'
import * as M from '@material-ui/core'

import Code from 'components/Code'
import { EMPTY_SCHEMA, JsonSchema } from 'utils/json-schema'

import illustrationEnterValues from './enter-values.webm'
import illustrationObjectExpand from './object-expand.webm'
import Column from './Column'
import State from './State'
import { JsonValue, RowData, ValidationErrors } from './constants'

interface EmptyStateProps {
className: string
noValue: boolean
notExpanded: boolean
}

function EmptyState({ className, noValue, notExpanded }: EmptyStateProps) {
if (noValue) {
return (
<M.Card className={className}>
<M.CardContent>
<M.Typography variant="h5">JSON editor is empty</M.Typography>
<M.Typography variant="body1">
Start filling empty rows as in Excel. You can enter values by hand. Type{' '}
<Code>{`{}`}</Code> to create an object, or <Code>{`[]`}</Code> to create an
array, and then traverse it to enter properties.
</M.Typography>
<video src={illustrationEnterValues} width="100%" autoPlay loop />
</M.CardContent>
</M.Card>
)
}

if (notExpanded) {
return (
<M.Card className={className}>
<M.CardContent>
<M.Typography variant="h5">There is more data here</M.Typography>
<M.Typography variant="body1">Try to expand object values</M.Typography>
<video src={illustrationObjectExpand} width="100%" autoPlay loop />
</M.CardContent>
</M.Card>
)
}

return null
}

interface ColumnData {
items: RowData[]
parent: JsonValue
Expand Down Expand Up @@ -52,6 +93,7 @@ function Squeeze({ columnPath, onClick }: SqueezeProps) {
const useStyles = M.makeStyles<any, { multiColumned: boolean }>((t) => ({
root: {
height: ({ multiColumned }) => (multiColumned ? '100%' : 'auto'),
position: 'relative',
},
disabled: {
position: 'relative',
Expand All @@ -69,13 +111,22 @@ const useStyles = M.makeStyles<any, { multiColumned: boolean }>((t) => ({
},
inner: {
display: 'flex',
overflow: 'auto',
height: ({ multiColumned }) => (multiColumned ? '100%' : 'auto'),
overflow: 'auto',
position: 'relative',
zIndex: 20,
},
column: {
height: ({ multiColumned }) => (multiColumned ? '100%' : 'auto'),
maxWidth: t.spacing(76),
overflowY: 'auto',
height: ({ multiColumned }) => (multiColumned ? '100%' : 'auto'),
},
help: {
position: 'absolute',
right: 0,
top: 0,
width: t.spacing(60),
zIndex: 10,
},
}))

Expand Down Expand Up @@ -114,6 +165,8 @@ const JsonEditor = React.forwardRef<HTMLDivElement, JsonEditorProps>(function Js
ref,
) {
const classes = useStyles({ multiColumned })
const t = M.useTheme()
const md = M.useMediaQuery(t.breakpoints.down('md'))

const handleRowAdd = React.useCallback(
(path: string[], key: string | number, value: JsonValue) => {
Expand Down Expand Up @@ -176,6 +229,13 @@ const JsonEditor = React.forwardRef<HTMLDivElement, JsonEditorProps>(function Js
)
})}
</div>
{multiColumned && !md && (
<EmptyState
className={classes.help}
noValue={!columns[0].parent || R.isEmpty(columns[0].parent)}
notExpanded={columnsView.length < 2}
/>
)}
</div>
)
})
Expand Down
45 changes: 30 additions & 15 deletions catalog/app/components/JsonEditor/State.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import * as dateFns from 'date-fns'
import * as FP from 'fp-ts'
import * as R from 'ramda'
import * as React from 'react'

import * as jsonSchemaUtils from 'utils/json-schema/json-schema'

import { COLUMN_IDS, EMPTY_VALUE } from './constants'

const serializeAddress = (addressPath) => `/${addressPath.join('/')}`
Expand Down Expand Up @@ -109,32 +110,46 @@ function calcReactId(valuePath, value) {
}

function getDefaultValue(jsonDictItem) {
if (!jsonDictItem || !jsonDictItem.valueSchema) return EMPTY_VALUE

const schema = jsonDictItem.valueSchema
try {
if (schema.format === 'date' && schema.dateformat)
return dateFns.format(new Date(), schema.dateformat)
} catch (error) {
// eslint-disable-next-line no-console
console.error(error)
}
if (schema.default !== undefined) return schema.default
if (!jsonDictItem?.valueSchema) return EMPTY_VALUE

const defaultFromSchema = jsonSchemaUtils.getDefaultValue(jsonDictItem?.valueSchema)
if (defaultFromSchema !== undefined) return defaultFromSchema

// TODO:
// get defaults from nested objects
// const setDefaults = jsonSchemaUtils.makeSchemaDefaultsSetter(jsonDictItem?.valueSchema)
// const nestedDefaultFromSchema = setDefaults()
// if (nestedDefaultFromSchema !== undefined) return nestedDefaultFromSchema

return EMPTY_VALUE
}

const NO_ERRORS = []

function getJsonDictItem(jsonDict, obj, parentPath, key, sortOrder, allErrors) {
const itemAddress = serializeAddress(getAddressPath(key, parentPath))
const item = jsonDict[itemAddress]
const bigintError = new Error(
`We don't support numbers larger than ${Number.MAX_SAFE_INTEGER}.
Please consider converting it to string.`,
)

function collectErrors(allErrors, itemAddress, value) {
const errors = allErrors
? allErrors.filter((error) => error.instancePath === itemAddress)
: NO_ERRORS

if (typeof value === 'number' && value > Number.MAX_SAFE_INTEGER) {
return errors.concat(bigintError)
}
return errors
}

function getJsonDictItem(jsonDict, obj, parentPath, key, sortOrder, allErrors) {
const itemAddress = serializeAddress(getAddressPath(key, parentPath))
const item = jsonDict[itemAddress]
// NOTE: can't use R.pathOr, because Ramda thinks `null` is `undefined` too
const valuePath = getAddressPath(key, parentPath)
const storedValue = R.path(valuePath, obj)
const value = storedValue === undefined ? getDefaultValue(item) : storedValue
const errors = collectErrors(allErrors, itemAddress, value)
return {
[COLUMN_IDS.KEY]: key,
[COLUMN_IDS.VALUE]: value,
Expand Down
Binary file not shown.
Binary file not shown.
3 changes: 2 additions & 1 deletion catalog/app/containers/Admin/Status/Reports.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,9 @@ interface ReportLinkProps {
}

function ActualDownloadLink({ loc }: ReportLinkProps) {
const stack = loc.bucket.replace(/-statusreportsbucket-.*$/, '')
const url = AWS.Signer.useDownloadUrl(loc, {
filename: loc.key,
filename: `status-${stack}-${loc.key}`,
contentType: 'text/html',
})
return (
Expand Down
31 changes: 0 additions & 31 deletions catalog/app/containers/Admin/Table.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import cx from 'classnames'
import * as I from 'immutable'
import * as R from 'ramda'
import * as React from 'react'
import * as M from '@material-ui/core'
Expand Down Expand Up @@ -36,36 +35,6 @@ export function useOrdering({ rows, ...opts }) {
return { column, direction, change, ordered }
}

const emptySet = I.Set()

export function useSelection({ rows, getId = R.unary(I.fromJS) }) {
const [selected, setSelected] = React.useState(emptySet)
const allSelected = React.useMemo(() => I.Set(rows.map(getId)), [rows, getId])

const toggle = React.useCallback(
(row) => {
const id = getId(row)
setSelected((s) => (s.has(id) ? s.delete(id) : s.add(id)))
},
[setSelected, getId],
)

const toggleAll = React.useCallback(() => {
setSelected((s) => (s.equals(allSelected) ? emptySet : allSelected))
}, [setSelected, allSelected])

const clear = React.useCallback(() => {
setSelected(emptySet)
}, [setSelected])

const isSelected = React.useCallback(
(row) => selected.has(getId(row)),
[selected, getId],
)

return { toggle, toggleAll, clear, isSelected, selected, all: allSelected }
}

export const renderAction = (a) =>
!a ? null : (
<M.Tooltip title={a.title} key={a.title}>
Expand Down
Loading

0 comments on commit 872d988

Please sign in to comment.