Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add links to documentation and re-use code samples #3496

Merged
merged 30 commits into from
Jun 21, 2023
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
a1c3a91
fix package push syntax
fiskus Jun 13, 2023
9c35a84
wip: show help icon with docs URL
fiskus Jun 13, 2023
8dde42f
wip: add help links
fiskus Jun 13, 2023
1b580b4
wip: use one more component
fiskus Jun 13, 2023
605b5ff
hide complexity
fiskus Jun 13, 2023
16a4d8f
minimize code diff
fiskus Jun 13, 2023
ec793b2
add links to docs
fiskus Jun 14, 2023
bb7ddcd
move to reusable component
fiskus Jun 14, 2023
83ac4b4
move code samples to reusable components
fiskus Jun 14, 2023
de747de
prepare for reusing in file
fiskus Jun 14, 2023
2d2b79e
reuse dir for file
fiskus Jun 14, 2023
7d1dd5c
memoize and reduce nesting
fiskus Jun 14, 2023
7a97015
divide and focus responsibility
fiskus Jun 14, 2023
16aed08
fix variables
fiskus Jun 14, 2023
0eb0ac0
tsify Code
fiskus Jun 14, 2023
510efcc
move to CodeSamples
fiskus Jun 14, 2023
ce6212b
lint
fiskus Jun 14, 2023
8faad15
Mrerge branch 'master' of github.com:quiltdata/quilt into code-sample…
fiskus Jun 14, 2023
b34d4a4
fix typo in comment
fiskus Jun 14, 2023
7f48c68
typos
fiskus Jun 14, 2023
e4b33d2
add changelog entry
fiskus Jun 16, 2023
2d58c8e
Merge branch 'master' of github.com:quiltdata/quilt into code-samples…
fiskus Jun 16, 2023
4ef5d39
merge with master
fiskus Jun 16, 2023
09ade24
Update docs/CHANGELOG.md
fiskus Jun 19, 2023
e054caf
fix `aws cp` sample for file
fiskus Jun 20, 2023
bac559f
split file code samples into FileCodeSamples
fiskus Jun 20, 2023
c5df3eb
linting
fiskus Jun 20, 2023
f468b99
group templates into one since we dont have conditions
fiskus Jun 20, 2023
499099a
Merge branch 'master' into code-samples-revisit
fiskus Jun 21, 2023
4c77de5
Merge branch 'master' into code-samples-revisit
fiskus Jun 21, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ import * as M from '@material-ui/core'
import * as Lab from '@material-ui/lab'

import * as Notifications from 'containers/Notifications'
import StyledLink from 'utils/StyledLink'
import copyToClipboard from 'utils/clipboard'

import Section from './Section'
import Section, { SectionProps } from '../Section'

function highlight(lang, str) {
function highlight(str: string, lang?: string) {
if (lang && hljs.getLanguage(lang)) {
try {
const { value } = hljs.highlight(str, { language: lang })
Expand All @@ -23,6 +24,46 @@ function highlight(lang, str) {
return str
}

const useLineOfCodeStyles = M.makeStyles((t) => ({
root: {
fontFamily: t.typography.monospace.fontFamily,
fontSize: t.typography.body2.fontSize,
overflowX: 'auto',
overflowY: 'hidden',
whiteSpace: 'pre',
minHeight: t.typography.body2.fontSize,
display: 'flex',
alignItems: 'flex-end',
'&:hover $help': {
opacity: 1,
},
},
help: {
display: 'inline-flex',
marginLeft: t.spacing(0.5),
opacity: 0.3,
},
}))

interface LineOfCodeProps {
lang?: string
text: string
help?: string
}
function LineOfCode({ lang, text, help }: LineOfCodeProps) {
const classes = useLineOfCodeStyles()
return (
<div className={classes.root}>
{highlight(text, lang)}
{help && (
<StyledLink href={help} className={classes.help} target="_blank">
[?]
</StyledLink>
)}
</div>
)
}

const useStyles = M.makeStyles((t) => ({
container: {
alignItems: 'center',
Expand All @@ -35,16 +76,17 @@ const useStyles = M.makeStyles((t) => ({
height: 32,
},
code: {
fontFamily: t.typography.monospace.fontFamily,
fontSize: t.typography.body2.fontSize,
overflowX: 'auto',
overflowY: 'hidden',
whiteSpace: 'pre',
width: '100%',
},
}))

interface CodeProps extends Partial<SectionProps> {
children: { label: string; contents: string; hl?: string }[]
defaultSelected?: number
}

// children: [{ label: str, contents: str, hl: lang }]
export default function Code({ defaultSelected = 0, children, ...props }) {
export default function Code({ defaultSelected = 0, children, ...props }: CodeProps) {
const classes = useStyles()
const { push } = Notifications.use()

Expand All @@ -69,6 +111,27 @@ export default function Code({ defaultSelected = 0, children, ...props }) {
[selected.contents, push],
)

const lines = React.useMemo(
() =>
selected.contents.split('\n').map((line, index) => {
// Find [[ URL ]] and put it to help prop
const matched = line.match(/(.*) \[\[(.*)\]\]/)
const key = selected.label + index
if (!matched || !matched[1] || !matched[2]) {
return {
key,
text: line,
}
}
return {
help: matched[2],
key,
text: matched[1],
}
}),
[selected.contents, selected.label],
)

return (
<Section
icon="code"
Expand Down Expand Up @@ -97,7 +160,16 @@ export default function Code({ defaultSelected = 0, children, ...props }) {
)}
{...props}
>
<div className={classes.code}>{highlight(selected.hl, selected.contents)}</div>
<div className={classes.code}>
{lines.map((line) => (
<LineOfCode
help={line.help}
key={line.key}
lang={selected.hl}
text={line.text}
/>
))}
</div>
</Section>
)
}
84 changes: 84 additions & 0 deletions catalog/app/containers/Bucket/CodeSamples/Dir.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { basename } from 'path'

import dedent from 'dedent'
import * as React from 'react'

import { docs } from 'constants/urls'

import type { SectionProps } from '../Section'

import Code from './Code'

const TEMPLATES = {
PY_INIT: (bucket: string) =>
dedent`
import quilt3 as q3
b = q3.Bucket("s3://${bucket}")
`,
PY_DOWNLOAD: (path: string, dest: string) => {
const pyDest = dest || basename(path)
return dedent`
# Download [[${docs}/api-reference/bucket#bucket.fetch]]
b.fetch("${path}", "./${pyDest}")
`
},
PY_LIST: (path: string) =>
dedent`
# List files [[${docs}/api-reference/bucket#bucket.ls]]
b.ls("${path}")
`,
CLI_LIST: (bucket: string, path: string) =>
dedent`
# List files [[https://docs.aws.amazon.com/cli/latest/reference/s3/ls.html]]
aws s3 ls "s3://${bucket}/${path}"
`,
CLI_DOWNLOAD: (bucket: string, path: string, dest: string) => {
const cliDest = dest ? `"./${dest}"` : '.'
return dedent`
# Download [[https://docs.aws.amazon.com/cli/latest/reference/s3/cp.html]]
aws s3 cp --recursive "s3://${bucket}/${path}" ${cliDest}
`
},
}

interface DirCodeSamplesProps extends Partial<SectionProps> {
bucket: string
path: string
isDirectory: boolean
}

export default function DirCodeSamples({
bucket,
path,
isDirectory,
...props
}: DirCodeSamplesProps) {
const dest = path ? basename(path) : bucket
const code = React.useMemo(
() => [
{
label: 'Python',
hl: 'python',
contents: [
TEMPLATES.PY_INIT(bucket),
isDirectory ? TEMPLATES.PY_LIST(path) : '',
TEMPLATES.PY_DOWNLOAD(path, isDirectory ? dest : ''),
]
.filter(Boolean)
.join('\n'),
},
{
label: 'CLI',
hl: 'bash',
contents: [
isDirectory ? TEMPLATES.CLI_LIST(bucket, path) : '',
TEMPLATES.CLI_DOWNLOAD(bucket, path, isDirectory ? dest : ''),
]
.filter(Boolean)
.join('\n'),
},
],
[isDirectory, bucket, path, dest],
)
return <Code {...props}>{code}</Code>
}
91 changes: 91 additions & 0 deletions catalog/app/containers/Bucket/CodeSamples/Package.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import dedent from 'dedent'

import * as R from 'ramda'
import * as React from 'react'

import { docs } from 'constants/urls'
import * as PackageUri from 'utils/PackageUri'
import * as s3paths from 'utils/s3paths'

import type { SectionProps } from '../Section'

import Code from './Code'

const TEMPLATES = {
PY: (bucket: string, name: string, path: string, hashDisplay: string) => {
const pathPy = path && `, path="${s3paths.ensureNoSlash(path)}"`
const hashPy = hashDisplay && `, top_hash="${hashDisplay}"`
return dedent`
import quilt3 as q3
# Browse [[${docs}/api-reference/package#package.browse]]
p = q3.Package.browse("${name}"${hashPy}, registry="s3://${bucket}")
# make changes to package adding individual files [[${docs}/api-reference/package#package.set]]
p.set("data.csv", "data.csv")
# or whole directories [[${docs}/api-reference/package#package.set_dir]]
p.set_dir("subdir", "subdir")
# and push changes [[${docs}/api-reference/package#package.push]]
p.push("${name}", registry="s3://${bucket}", message="Hello World")

# Download (be mindful of large packages) [[${docs}/api-reference/package#package.push]]
q3.Package.install("${name}"${pathPy}${hashPy}, registry="s3://${bucket}", dest=".")
`
},
CLI_DOWNLOAD: (bucket: string, name: string, path: string, hashDisplay: string) => {
const pathCli = path && ` --path "${s3paths.ensureNoSlash(path)}"`
const hashCli = hashDisplay && ` --top-hash ${hashDisplay}`
return dedent`
# Download package [[${docs}/api-reference/cli#install]]
quilt3 install "${name}"${pathCli}${hashCli} --registry s3://${bucket} --dest .
`
},
CLI_UPLOAD: (bucket: string, name: string) =>
dedent`
# Upload package [[${docs}/api-reference/cli#push]]
echo "Hello World" > README.md
quilt3 push "${name}" --registry s3://${bucket} --dir .
`,
}

interface PackageCodeSamplesProps extends Partial<SectionProps> {
bucket: string
name: string
hash: string
hashOrTag: string
path: string
}

export default function PackageCodeSamples({
bucket,
name,
hash,
hashOrTag,
path,
...props
}: PackageCodeSamplesProps) {
const hashDisplay = hashOrTag === 'latest' ? '' : R.take(10, hash)
const code = React.useMemo(
() => [
{
label: 'Python',
hl: 'python',
contents: TEMPLATES.PY(bucket, name, path, hashDisplay),
},
{
label: 'CLI',
hl: 'bash',
contents: [
TEMPLATES.CLI_DOWNLOAD(bucket, name, path, hashDisplay),
!path ? TEMPLATES.CLI_UPLOAD(bucket, name) : '',
]
.filter(Boolean)
.join('\n\n'),
},
{
label: 'URI',
contents: PackageUri.stringify({ bucket, name, hash, path }),
},
],
[bucket, name, hashDisplay, hash, path],
)
return <Code {...props}>{code}</Code>
}
39 changes: 6 additions & 33 deletions catalog/app/containers/Bucket/Dir.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { basename, join } from 'path'
import { join } from 'path'

import dedent from 'dedent'
import * as R from 'ramda'
import * as React from 'react'
import * as RRDom from 'react-router-dom'
Expand All @@ -23,7 +22,7 @@ import parseSearch from 'utils/parseSearch'
import * as s3paths from 'utils/s3paths'
import type * as workflows from 'utils/workflows'

import Code from './Code'
import DirCodeSamples from './CodeSamples/Dir'
import * as FileView from './FileView'
import { Item, Listing, PrefixFilter } from './Listing'
import Menu from './Menu'
Expand Down Expand Up @@ -289,35 +288,6 @@ export default function Dir({
const prefs = BucketPreferences.use()
const { prefix } = parseSearch(l.search)
const path = s3paths.decode(encodedPath)
const dest = path ? basename(path) : bucket

const code = React.useMemo(
() => [
{
label: 'Python',
hl: 'python',
contents: dedent`
import quilt3 as q3
b = q3.Bucket("s3://${bucket}")
# list files
b.ls("${path}")
# download
b.fetch("${path}", "./${dest}")
`,
},
{
label: 'CLI',
hl: 'bash',
contents: dedent`
# list files
aws s3 ls "s3://${bucket}/${path}"
# download
aws s3 cp --recursive "s3://${bucket}/${path}" "./${dest}"
`,
},
],
[bucket, path, dest],
)

const [prev, setPrev] = React.useState<requests.BucketListingResult | null>(null)

Expand Down Expand Up @@ -417,7 +387,10 @@ export default function Dir({

{BucketPreferences.Result.match(
{
Ok: ({ ui: { blocks } }) => blocks.code && <Code gutterBottom>{code}</Code>,
Ok: ({ ui: { blocks } }) =>
blocks.code && (
<DirCodeSamples bucket={bucket} path={path} gutterBottom isDirectory />
),
Pending: () => null,
Init: () => null,
},
Expand Down
Loading