Skip to content

Commit

Permalink
Helper links to modify configs (#4273)
Browse files Browse the repository at this point in the history
Co-authored-by: Alexei Mochalov <[email protected]>
  • Loading branch information
fiskus and nl0 authored Jan 7, 2025
1 parent e66c1d2 commit eefd32f
Show file tree
Hide file tree
Showing 8 changed files with 332 additions and 26 deletions.
2 changes: 2 additions & 0 deletions catalog/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ where verb is one of

## Changes

- [Added] Button to add a `quilt_summarize.json` to a package ([#4273](https://github.com/quiltdata/quilt/pull/4273))
- [Added] Admin: Link to bucket UI config from the bucket settings screen ([#4273](https://github.com/quiltdata/quilt/pull/4273))
- [Added] Admin: Tabulator Settings (open query) ([#4255](https://github.com/quiltdata/quilt/pull/4255))
- [Added] Visual editor for `quilt_summarize.json` ([#4254](https://github.com/quiltdata/quilt/pull/4254))
- [Added] Support "html" type in `quilt_summarize.json` ([#4252](https://github.com/quiltdata/quilt/pull/4252))
Expand Down
18 changes: 17 additions & 1 deletion catalog/app/components/FileEditor/routes.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { renderHook } from '@testing-library/react-hooks'

import { useParams, editFileInPackage, useEditFileInPackage } from './routes'
import {
editFileInPackage,
useAddFileInPackage,
useEditFileInPackage,
useParams,
} from './routes'

const useParamsInternal = jest.fn(
() =>
Expand Down Expand Up @@ -52,6 +57,17 @@ describe('components/FileEditor/routes', () => {
})
})

describe('useAddFileInPackage', () => {
it('should create url for the new file', () => {
const { result } = renderHook(() =>
useAddFileInPackage({ bucket: 'b', name: 'n', hash: 'h' }, 'lk'),
)
expect(result.current).toBe(
'bucketFile(b, n/lk, {"add":"lk","edit":true,"next":"bucketPackageDetail(b, n, {\\"action\\":\\"revisePackage\\"})"})',
)
})
})

describe('useParams', () => {
it('should throw error when no bucket', () => {
useParamsInternal.mockImplementationOnce(() => ({}))
Expand Down
13 changes: 13 additions & 0 deletions catalog/app/components/FileEditor/routes.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { join } from 'path'

import invariant from 'invariant'
import * as React from 'react'
import * as RRDom from 'react-router-dom'

import type * as Routes from 'constants/routes'
Expand Down Expand Up @@ -36,6 +39,16 @@ export function useEditFileInPackage(
return editFileInPackage(urls, fileHandle, logicalKey, next)
}

export function useAddFileInPackage({ bucket, name }: PackageHandle, logicalKey: string) {
const { urls } = NamedRoutes.use<RouteMap>()
const next = urls.bucketPackageDetail(bucket, name, { action: 'revisePackage' })
const fileHandle = React.useMemo(
() => ({ bucket, key: join(name, logicalKey) }),
[bucket, logicalKey, name],
)
return editFileInPackage(urls, fileHandle, logicalKey, next)
}

export function useParams() {
const { bucket, path } = RRDom.useParams<{
bucket: string
Expand Down
13 changes: 12 additions & 1 deletion catalog/app/containers/Admin/Buckets/Buckets.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,14 @@ import * as Buttons from 'components/Buttons'
import * as Dialog from 'components/Dialog'
import Skeleton from 'components/Skeleton'
import * as Notifications from 'containers/Notifications'
import * as quiltConfigs from 'constants/quiltConfigs'
import type * as Model from 'model'
import * as APIConnector from 'utils/APIConnector'
import type FormSpec from 'utils/FormSpec'
import * as GQL from 'utils/GraphQL'
import MetaTitle from 'utils/MetaTitle'
import * as NamedRoutes from 'utils/NamedRoutes'
import StyledLink from 'utils/StyledLink'
import StyledTooltip from 'utils/StyledTooltip'
import assertNever from 'utils/assertNever'
import parseSearch from 'utils/parseSearch'
Expand Down Expand Up @@ -766,6 +768,11 @@ interface PrimaryCardProps {
function PrimaryCard({ bucket, className, disabled, onSubmit }: PrimaryCardProps) {
const initialValues = bucketToPrimaryValues(bucket)
const ref = React.useRef<HTMLElement>(null)
const { urls } = NamedRoutes.use()
const configPath = quiltConfigs.bucketPreferences[0]
const configHref = urls.bucketFile(bucket.name, configPath, {
edit: true,
})
return (
<RF.Form<PrimaryFormValues> onSubmit={onSubmit} initialValues={initialValues}>
{({ handleSubmit, form, submitFailed }) => (
Expand All @@ -780,7 +787,11 @@ function PrimaryCard({ bucket, className, disabled, onSubmit }: PrimaryCardProps
<PrimaryForm bucket={bucket} />
</form>
<StickyActions parentRef={ref}>
<CardActions<PrimaryFormValues> disabled={disabled} form={form} />
<CardActions<PrimaryFormValues>
action={<StyledLink to={configHref}>Configure Bucket UI</StyledLink>}
disabled={disabled}
form={form}
/>
</StickyActions>
</Card>
)}
Expand Down
137 changes: 137 additions & 0 deletions catalog/app/containers/Bucket/Summarize.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import * as React from 'react'
import renderer from 'react-test-renderer'

import { ConfigureAppearance } from './Summarize'

jest.mock(
'@material-ui/core',
jest.fn(() => ({
...jest.requireActual('@material-ui/core'),
Button: jest.fn(({ children }: { children: React.ReactNode }) => (
<div id="button">{children}</div>
)),
})),
)

jest.mock(
'constants/config',
jest.fn(() => ({})),
)

jest.mock(
'components/Preview',
jest.fn(() => ({})),
)
jest.mock(
'components/Preview/loaders/summarize',
jest.fn(() => ({})),
)
jest.mock(
'./requests',
jest.fn(() => ({})),
)
jest.mock(
'./errors',
jest.fn(() => ({})),
)
jest.mock(
'components/Markdown',
jest.fn(() => ({})),
)
jest.mock(
'components/FileEditor/FileEditor',
jest.fn(() => ({})),
)

jest.mock('utils/NamedRoutes', () => ({
...jest.requireActual('utils/NamedRoutes'),
use: jest.fn(() => ({
urls: {
bucketPackageDetail: (b: string, n: string, opts: any) =>
`package: ${b}/${n} ${JSON.stringify(opts)}`,
bucketFile: (b: string, k: string, opts: any) =>
`file: ${b}/${k} ${JSON.stringify(opts)}`,
},
})),
}))

jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
Link: jest.fn(({ to, children }: React.PropsWithChildren<{ to: string }>) => (
<a href={to}>{children}</a>
)),
}))

jest.mock(
'utils/StyledTooltip',
() =>
({ title, children }: React.PropsWithChildren<{ title: React.ReactNode }>) => (
<div>
{title}
<hr />
{children}
</div>
),
)

describe('containers/Buckets/Summarize', () => {
describe('ConfigureAppearance', () => {
const packageHandle = { bucket: 'b', name: 'n', hash: 'h' }

it('should not render buttons when there are files out there', () => {
const tree = renderer
.create(
<ConfigureAppearance
hasReadme
hasSummarizeJson
packageHandle={packageHandle}
path=""
/>,
)
.toJSON()
expect(tree).toMatchSnapshot()
})

it('should render readme link', () => {
const tree = renderer
.create(
<ConfigureAppearance
hasReadme={false}
hasSummarizeJson
packageHandle={packageHandle}
path=""
/>,
)
.toJSON()
expect(tree).toMatchSnapshot()
})

it('should render quilt_summarize link', () => {
const tree = renderer
.create(
<ConfigureAppearance
hasReadme
hasSummarizeJson={false}
packageHandle={packageHandle}
path=""
/>,
)
.toJSON()
expect(tree).toMatchSnapshot()
})

it('should render both links', () => {
const tree = renderer
.create(
<ConfigureAppearance
hasReadme={false}
hasSummarizeJson={false}
packageHandle={packageHandle}
path="some/path"
/>,
)
.toJSON()
expect(tree).toMatchSnapshot()
})
})
})
64 changes: 64 additions & 0 deletions catalog/app/containers/Bucket/Summarize.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import { join } from 'path'

import type { S3 } from 'aws-sdk'
import cx from 'classnames'
import type { LocationDescriptor } from 'history'
import * as R from 'ramda'
import * as React from 'react'
import * as RRDom from 'react-router-dom'
import * as M from '@material-ui/core'

import * as BreadCrumbs from 'components/BreadCrumbs'
import * as FileEditor from 'components/FileEditor'
import Markdown from 'components/Markdown'
import * as Preview from 'components/Preview'
import type { Type as SummaryFileTypes } from 'components/Preview/loaders/summarize'
Expand All @@ -20,6 +24,7 @@ import Data, { useData } from 'utils/Data'
import * as LogicalKeyResolver from 'utils/LogicalKeyResolver'
import * as NamedRoutes from 'utils/NamedRoutes'
import Link from 'utils/StyledLink'
import StyledTooltip from 'utils/StyledTooltip'
import { PackageHandle } from 'utils/packageHandle'
import * as s3paths from 'utils/s3paths'

Expand Down Expand Up @@ -659,3 +664,62 @@ export function SummaryNested({ handle, mkUrl, packageHandle }: SummaryNestedPro
_: () => null,
})
}

const useConfigureAppearanceStyles = M.makeStyles((t) => ({
root: {
display: 'flex',
justifyContent: 'flex-end',
padding: t.spacing(2, 0),
},
button: {
'& + &': {
marginLeft: t.spacing(1),
},
},
}))

interface ConfigureAppearanceProps {
hasReadme: boolean
hasSummarizeJson: boolean
packageHandle: PackageHandle
path: string
}

export function ConfigureAppearance({
hasReadme,
hasSummarizeJson,
packageHandle,
path,
}: ConfigureAppearanceProps) {
const classes = useConfigureAppearanceStyles()
const readme = FileEditor.useAddFileInPackage(
packageHandle,
join(path || '', 'README.md'),
)
const summarize = FileEditor.useAddFileInPackage(
packageHandle,
join(path || '', 'quilt_summarize.json'),
)
return (
<div className={classes.root}>
{!hasSummarizeJson && (
<StyledTooltip title="Open the editor to author a quilt_summarize.json file. Upon saving, a package revision dialog will show up, letting you add that file to the package.">
<RRDom.Link to={summarize} className={classes.button}>
<M.Button color="primary" size="small" variant="outlined">
Add quilt_summarize
</M.Button>
</RRDom.Link>
</StyledTooltip>
)}
{!hasReadme && (
<StyledTooltip title="Open the editor to author a README file. Upon saving, a package revision dialog will show up, letting you add that file to the package.">
<RRDom.Link to={readme} className={classes.button}>
<M.Button color="primary" size="small" variant="contained">
Add README
</M.Button>
</RRDom.Link>
</StyledTooltip>
)}
</div>
)
}
31 changes: 7 additions & 24 deletions catalog/app/containers/Bucket/Summary.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import * as React from 'react'
import * as M from '@material-ui/core'

import * as Buttons from 'components/Buttons'
import * as FileEditor from 'components/FileEditor'
import * as Preview from 'components/Preview'
import { SUPPORTED_EXTENSIONS } from 'components/Thumbnail'
import AsyncResult from 'utils/AsyncResult'
Expand All @@ -17,27 +16,6 @@ import { getBasename } from 'utils/s3paths'
import * as Gallery from './Gallery'
import * as Summarize from './Summarize'

const useAddReadmeSectionStyles = M.makeStyles((t) => ({
root: {
display: 'flex',
justifyContent: 'flex-end',
margin: t.spacing(2, 0),
},
}))

function AddReadmeSection({ packageHandle, path }) {
const prompt = FileEditor.useCreateFileInPackage(packageHandle, path)
const classes = useAddReadmeSectionStyles()
return (
<div className={classes.root}>
{prompt.render()}
<M.Button color="primary" size="small" variant="contained" onClick={prompt.open}>
Add README
</M.Button>
</div>
)
}

const README_RE = /^readme\.md$/i
const SUMMARIZE_RE = /^quilt_summarize\.json$/i

Expand Down Expand Up @@ -137,11 +115,16 @@ export default function BucketSummary({ files, mkUrl: mkUrlProp, packageHandle,
{BucketPreferences.Result.match(
{
Ok: ({ ui: { actions } }) =>
!readme &&
(!readme || !summarize) &&
!path &&
!!packageHandle &&
!!actions.revisePackage && (
<AddReadmeSection packageHandle={packageHandle} path={path} />
<Summarize.ConfigureAppearance
hasReadme={!!readme}
hasSummarizeJson={!!summarize}
packageHandle={packageHandle}
path={path}
/>
),
Pending: () => <Buttons.Skeleton size="small" />,
Init: () => null,
Expand Down
Loading

0 comments on commit eefd32f

Please sign in to comment.