From eefd32fb3c2fe4012ce768f1ad338b0043040b0d Mon Sep 17 00:00:00 2001 From: Maksim Chervonnyi Date: Tue, 7 Jan 2025 14:46:30 +0100 Subject: [PATCH] Helper links to modify configs (#4273) Co-authored-by: Alexei Mochalov --- catalog/CHANGELOG.md | 2 + .../app/components/FileEditor/routes.spec.ts | 18 ++- catalog/app/components/FileEditor/routes.ts | 13 ++ .../app/containers/Admin/Buckets/Buckets.tsx | 13 +- .../app/containers/Bucket/Summarize.spec.tsx | 137 ++++++++++++++++++ catalog/app/containers/Bucket/Summarize.tsx | 64 ++++++++ catalog/app/containers/Bucket/Summary.js | 31 +--- .../__snapshots__/Summarize.spec.tsx.snap | 80 ++++++++++ 8 files changed, 332 insertions(+), 26 deletions(-) create mode 100644 catalog/app/containers/Bucket/Summarize.spec.tsx create mode 100644 catalog/app/containers/Bucket/__snapshots__/Summarize.spec.tsx.snap diff --git a/catalog/CHANGELOG.md b/catalog/CHANGELOG.md index 80c452ea923..bb1c003e6a5 100644 --- a/catalog/CHANGELOG.md +++ b/catalog/CHANGELOG.md @@ -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)) diff --git a/catalog/app/components/FileEditor/routes.spec.ts b/catalog/app/components/FileEditor/routes.spec.ts index 2297d6c2957..bebd0f3c571 100644 --- a/catalog/app/components/FileEditor/routes.spec.ts +++ b/catalog/app/components/FileEditor/routes.spec.ts @@ -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( () => @@ -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(() => ({})) diff --git a/catalog/app/components/FileEditor/routes.ts b/catalog/app/components/FileEditor/routes.ts index e4d74e23f93..6efc3593e88 100644 --- a/catalog/app/components/FileEditor/routes.ts +++ b/catalog/app/components/FileEditor/routes.ts @@ -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' @@ -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() + 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 diff --git a/catalog/app/containers/Admin/Buckets/Buckets.tsx b/catalog/app/containers/Admin/Buckets/Buckets.tsx index 7e1c6cfd57a..6976deb9511 100644 --- a/catalog/app/containers/Admin/Buckets/Buckets.tsx +++ b/catalog/app/containers/Admin/Buckets/Buckets.tsx @@ -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' @@ -766,6 +768,11 @@ interface PrimaryCardProps { function PrimaryCard({ bucket, className, disabled, onSubmit }: PrimaryCardProps) { const initialValues = bucketToPrimaryValues(bucket) const ref = React.useRef(null) + const { urls } = NamedRoutes.use() + const configPath = quiltConfigs.bucketPreferences[0] + const configHref = urls.bucketFile(bucket.name, configPath, { + edit: true, + }) return ( onSubmit={onSubmit} initialValues={initialValues}> {({ handleSubmit, form, submitFailed }) => ( @@ -780,7 +787,11 @@ function PrimaryCard({ bucket, className, disabled, onSubmit }: PrimaryCardProps - disabled={disabled} form={form} /> + + action={Configure Bucket UI} + disabled={disabled} + form={form} + /> )} diff --git a/catalog/app/containers/Bucket/Summarize.spec.tsx b/catalog/app/containers/Bucket/Summarize.spec.tsx new file mode 100644 index 00000000000..c586539fb77 --- /dev/null +++ b/catalog/app/containers/Bucket/Summarize.spec.tsx @@ -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 }) => ( +
{children}
+ )), + })), +) + +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 }>) => ( + {children} + )), +})) + +jest.mock( + 'utils/StyledTooltip', + () => + ({ title, children }: React.PropsWithChildren<{ title: React.ReactNode }>) => ( +
+ {title} +
+ {children} +
+ ), +) + +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( + , + ) + .toJSON() + expect(tree).toMatchSnapshot() + }) + + it('should render readme link', () => { + const tree = renderer + .create( + , + ) + .toJSON() + expect(tree).toMatchSnapshot() + }) + + it('should render quilt_summarize link', () => { + const tree = renderer + .create( + , + ) + .toJSON() + expect(tree).toMatchSnapshot() + }) + + it('should render both links', () => { + const tree = renderer + .create( + , + ) + .toJSON() + expect(tree).toMatchSnapshot() + }) + }) +}) diff --git a/catalog/app/containers/Bucket/Summarize.tsx b/catalog/app/containers/Bucket/Summarize.tsx index ebc2116b441..e184219d5be 100644 --- a/catalog/app/containers/Bucket/Summarize.tsx +++ b/catalog/app/containers/Bucket/Summarize.tsx @@ -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' @@ -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' @@ -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 ( +
+ {!hasSummarizeJson && ( + + + + Add quilt_summarize + + + + )} + {!hasReadme && ( + + + + Add README + + + + )} +
+ ) +} diff --git a/catalog/app/containers/Bucket/Summary.js b/catalog/app/containers/Bucket/Summary.js index ab8f9f6c0d9..d2c4c277d5d 100644 --- a/catalog/app/containers/Bucket/Summary.js +++ b/catalog/app/containers/Bucket/Summary.js @@ -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' @@ -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 ( -
- {prompt.render()} - - Add README - -
- ) -} - const README_RE = /^readme\.md$/i const SUMMARIZE_RE = /^quilt_summarize\.json$/i @@ -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 && ( - + ), Pending: () => , Init: () => null, diff --git a/catalog/app/containers/Bucket/__snapshots__/Summarize.spec.tsx.snap b/catalog/app/containers/Bucket/__snapshots__/Summarize.spec.tsx.snap new file mode 100644 index 00000000000..ea23195e904 --- /dev/null +++ b/catalog/app/containers/Bucket/__snapshots__/Summarize.spec.tsx.snap @@ -0,0 +1,80 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`containers/Buckets/Summarize ConfigureAppearance should not render buttons when there are files out there 1`] = ` +
+`; + +exports[`containers/Buckets/Summarize ConfigureAppearance should render both links 1`] = ` +
+
+ 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. +
+ +
+ Add quilt_summarize +
+
+
+
+ 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. +
+ +
+ Add README +
+
+
+
+`; + +exports[`containers/Buckets/Summarize ConfigureAppearance should render quilt_summarize link 1`] = ` +
+
+ 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. +
+ +
+ Add quilt_summarize +
+
+
+
+`; + +exports[`containers/Buckets/Summarize ConfigureAppearance should render readme link 1`] = ` +
+
+ 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. +
+ +
+ Add README +
+
+
+
+`;