-
Notifications
You must be signed in to change notification settings - Fork 940
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(use-data-query): correctly infer type from apollo client result f…
…or masked result (#9353) This PR updates the `@teambit/ui-foundation.ui.hooks.use-data-query` package which fixes the build issue with apollo client incorrectly inferring result type because of the introduction of MaskedType as part of their latest `3.12.0` releases.
- Loading branch information
Showing
13 changed files
with
464 additions
and
46 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -64,7 +64,6 @@ types/* | |
#distrubution | ||
distribution/ | ||
|
||
artifacts/ | ||
bit-*.tar.gz | ||
tmp/ | ||
bin/node | ||
|
27 changes: 27 additions & 0 deletions
27
components/ui/artifacts/artifacts-tree/artifact-file-node-clicked.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import { ArtifactFile } from '@teambit/component.ui.artifacts.models.component-artifacts-model'; | ||
|
||
export const fileNodeClicked = | ||
(files: (ArtifactFile & { id: string })[], opts: 'download' | 'new tab') => (e, node) => { | ||
const { id } = node; | ||
const artifactFile = files.find((file) => file.id === id); | ||
|
||
if (artifactFile?.downloadUrl) { | ||
fetch(artifactFile.downloadUrl, { method: 'GET' }) | ||
.then((res) => res.blob()) | ||
.then((blob) => { | ||
// create blob link to download | ||
const url = window.URL.createObjectURL(new Blob([blob])); | ||
const link = document.createElement('a'); | ||
link.href = url; | ||
if (opts === 'download') link.setAttribute('download', artifactFile.path); | ||
if (opts === 'new tab') link.setAttribute('target', '_blank'); | ||
// append to html page | ||
document.body.appendChild(link); | ||
// force download | ||
link.click(); | ||
// clean up and remove the link | ||
link.parentNode?.removeChild(link); | ||
}) | ||
.catch(() => {}); | ||
} | ||
}; |
75 changes: 75 additions & 0 deletions
75
components/ui/artifacts/artifacts-tree/artifacts-tree.module.scss
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
.artifactsPanel { | ||
display: flex; | ||
flex-direction: column; | ||
overflow-y: auto; | ||
height: 100%; | ||
border-right: 2px solid #ededed; | ||
background: #fafafa; | ||
font-size: var(--bit-p-xs); | ||
} | ||
|
||
.artifactsPanelCodeTabDrawer { | ||
// drawer name | ||
> div:first-child { | ||
border: none; | ||
border-bottom: 1px solid var(--bit-border-color-lightest, #ededed); | ||
} | ||
} | ||
.openDrawer { | ||
flex: 1; | ||
height: 100%; | ||
} | ||
.artifactsPanelCodeDrawerContent { | ||
overflow-y: auto; | ||
} | ||
|
||
.drawerIcon { | ||
margin-right: 8px; | ||
font-size: var(--bit-p-xs); | ||
} | ||
|
||
.label { | ||
font-size: var(--bit-p-xxs); | ||
font-weight: unset; | ||
padding: 4px; | ||
} | ||
|
||
.icon { | ||
font-size: var(--bit-p-xxs); | ||
padding: 4px; | ||
color: var(--bit-text-color-heavy, #2b2b2b); | ||
pointer-events: auto; | ||
} | ||
|
||
.artifactIconLink { | ||
text-decoration: none; | ||
} | ||
|
||
.link { | ||
color: var(--bit-accent-color, #6c5ce7); | ||
} | ||
|
||
.node { | ||
pointer-events: none; | ||
> div { | ||
pointer-events: auto; | ||
} | ||
} | ||
|
||
.artifactWidgets { | ||
display: flex; | ||
} | ||
|
||
.size { | ||
margin-right: 8px; | ||
padding: 4px; | ||
font-size: var(--bit-p-xxs, '12px'); | ||
line-height: 1em; | ||
border-radius: 4px; | ||
background-color: var(--bit-accent-bg, #edebfc); | ||
color: var(--bit-accent-text, #6c5ce7); | ||
|
||
&.selected { | ||
border: 1px solid var(--bit-accent-text, #6c5ce7); | ||
} | ||
} |
189 changes: 189 additions & 0 deletions
189
components/ui/artifacts/artifacts-tree/artifacts-tree.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,189 @@ | ||
import React, { HTMLAttributes, useMemo, useContext, useCallback } from 'react'; | ||
import classNames from 'classnames'; | ||
import { Icon } from '@teambit/evangelist.elements.icon'; | ||
import { WidgetProps, TreeNode as Node } from '@teambit/ui-foundation.ui.tree.tree-node'; | ||
import { DrawerUI } from '@teambit/ui-foundation.ui.tree.drawer'; | ||
import { FileTree, useFileTreeContext } from '@teambit/ui-foundation.ui.tree.file-tree'; | ||
import { | ||
ArtifactFile, | ||
getArtifactFileDetailsFromUrl, | ||
} from '@teambit/component.ui.artifacts.models.component-artifacts-model'; | ||
import { TreeNode, TreeNodeProps } from '@teambit/design.ui.tree'; | ||
import { TreeContext } from '@teambit/base-ui.graph.tree.tree-context'; | ||
import { ComponentTreeLoader } from '@teambit/design.ui.skeletons.sidebar-loader'; | ||
import isBinaryPath from 'is-binary-path'; | ||
import { FolderTreeNode } from '@teambit/ui-foundation.ui.tree.folder-tree-node'; | ||
import { useCodeParams } from '@teambit/code.ui.hooks.use-code-params'; | ||
import { affix } from '@teambit/base-ui.utils.string.affix'; | ||
import { ComponentContext } from '@teambit/component'; | ||
import { useComponentArtifacts } from '@teambit/component.ui.artifacts.queries.use-component-artifacts'; | ||
import prettyBytes from 'pretty-bytes'; | ||
import { fileNodeClicked } from './artifact-file-node-clicked'; | ||
import { FILE_SIZE_THRESHOLD } from '.'; | ||
|
||
import styles from './artifacts-tree.module.scss'; | ||
|
||
export type ArtifactsTreeProps = { | ||
getIcon?: (node: TreeNode) => string | undefined; | ||
drawerOpen: boolean; | ||
onToggleDrawer: () => void; | ||
drawerName: string; | ||
host: string; | ||
} & HTMLAttributes<HTMLDivElement>; | ||
|
||
export function ArtifactsTree({ getIcon, drawerName, drawerOpen, onToggleDrawer, host }: ArtifactsTreeProps) { | ||
const urlParams = useCodeParams(); | ||
const component = useContext(ComponentContext); | ||
|
||
const { data: artifacts = [], loading } = useComponentArtifacts(host, component.id.toString()); | ||
|
||
const [artifactFiles, artifactFilesTree] = useMemo(() => { | ||
const files = | ||
(artifacts.length > 0 && | ||
artifacts.flatMap((artifact) => | ||
artifact.files.map((file) => ({ ...file, id: `${artifact.taskName}/${artifact.name}/${file.path}` })) | ||
)) || | ||
[]; | ||
|
||
const _artifactFilesTree = files.map((file) => file.id); | ||
return [files, _artifactFilesTree]; | ||
}, [loading]); | ||
|
||
const hasArtifacts = artifacts.length > 0; | ||
const artifactDetailsFromUrl = getArtifactFileDetailsFromUrl(artifacts, urlParams.file); | ||
const selected = | ||
artifactDetailsFromUrl && | ||
`${artifactDetailsFromUrl.taskName}/${artifactDetailsFromUrl.artifactName}/${artifactDetailsFromUrl.artifactFile.path}`; | ||
|
||
const payloadMap = useMemo(() => { | ||
const _payloadMap = | ||
(hasArtifacts && | ||
artifacts.reduce((accum, next) => { | ||
if (!accum.has(next.taskName)) accum.set(`${next.taskName}/`, { open: false }); | ||
return accum; | ||
}, new Map<string, { open?: boolean }>())) || | ||
new Map<string, { open?: boolean }>(); | ||
|
||
const { taskName, artifactName, artifactFile } = { | ||
taskName: artifactDetailsFromUrl?.taskName, | ||
artifactName: artifactDetailsFromUrl?.artifactName, | ||
artifactFile: artifactDetailsFromUrl?.artifactFile, | ||
}; | ||
|
||
if (taskName && artifactName && artifactFile) { | ||
_payloadMap.set(`${taskName}/`, { open: true }); | ||
_payloadMap.set(`${taskName}/${artifactName}/`, { open: true }); | ||
_payloadMap.set(`${taskName}/${artifactName}/${artifactFile.path}`, { open: true }); | ||
} | ||
|
||
return _payloadMap; | ||
}, [loading, selected]); | ||
|
||
const getHref = useCallback( | ||
(node) => { | ||
return `~artifact/${node.id}${affix('?version=', urlParams.version)}`; | ||
}, | ||
[loading] | ||
); | ||
|
||
const widgets = useMemo(() => [generateWidget(artifactFiles || [], selected)], [loading]); | ||
|
||
if (!hasArtifacts) return null; | ||
|
||
return ( | ||
<DrawerUI | ||
isOpen={drawerOpen} | ||
onToggle={onToggleDrawer} | ||
name={drawerName} | ||
contentClass={styles.artifactsPanelCodeDrawerContent} | ||
className={classNames(styles.artifactsPanelCodeTabDrawer, drawerOpen && styles.openDrawer)} | ||
> | ||
{loading && <ComponentTreeLoader />} | ||
{loading || ( | ||
<FileTree | ||
getIcon={getIcon} | ||
getHref={getHref} | ||
files={artifactFilesTree} | ||
widgets={widgets} | ||
payloadMap={payloadMap} | ||
TreeNode={fileTreeNodeWithArtifactFiles(artifactFiles)} | ||
selected={selected} | ||
onTreeNodeSelected={(id: string, e) => { | ||
const matchingArtifactFile = artifactFiles.find((artifactFile) => artifactFile.id === id); | ||
if (!matchingArtifactFile) return; | ||
const fileName = getFileNameFromNode(id); | ||
if (isBinaryPath(fileName) || matchingArtifactFile.size > FILE_SIZE_THRESHOLD) { | ||
fileNodeClicked(artifactFiles, 'download')(e, { id }); | ||
} | ||
}} | ||
/> | ||
)} | ||
</DrawerUI> | ||
); | ||
} | ||
|
||
function getFileNameFromNode(node: string) { | ||
const lastIndex = node.lastIndexOf('/'); | ||
return node.slice(lastIndex + 1); | ||
} | ||
|
||
function generateWidget(files: (ArtifactFile & { id: string })[], selected?: string) { | ||
return function Widget({ node }: WidgetProps<any>) { | ||
const id = node.id; | ||
const artifactFile = files.find((file) => file.id === id); | ||
const path = getFileNameFromNode(id); | ||
const isBinary = isBinaryPath(path); | ||
const isSelected = selected === id; | ||
|
||
if (artifactFile) { | ||
return ( | ||
<div className={styles.artifactWidgets}> | ||
<div className={classNames(styles.size, isSelected && styles.selected)}>{prettyBytes(artifactFile.size)}</div> | ||
{!isBinary && artifactFile.size <= FILE_SIZE_THRESHOLD && ( | ||
<Icon className={styles.icon} of="open-tab" onClick={(e) => fileNodeClicked(files, 'new tab')(e, node)} /> | ||
)} | ||
<Icon | ||
className={styles.icon} | ||
of="download" | ||
onClick={(e) => { | ||
fileNodeClicked(files, 'download')(e, node); | ||
}} | ||
/> | ||
</div> | ||
); | ||
} | ||
return null; | ||
}; | ||
} | ||
|
||
function fileTreeNodeWithArtifactFiles(artifactFiles: Array<ArtifactFile & { id: string }>) { | ||
return function FileTreeNode(props: TreeNodeProps<any>) { | ||
const { node } = props; | ||
const { id } = node; | ||
const fileTreeContext = useFileTreeContext(); | ||
const { selected, onSelect } = useContext(TreeContext); | ||
|
||
const href = fileTreeContext?.getHref?.(node); | ||
const widgets = fileTreeContext?.widgets; | ||
const icon = fileTreeContext?.getIcon?.(node); | ||
const path = getFileNameFromNode(id); | ||
const isBinary = isBinaryPath(path); | ||
const matchingArtifactFile = artifactFiles.find((artifactFile) => artifactFile.id === node.id); | ||
const isLink = isBinary || (matchingArtifactFile?.size ?? 0) > FILE_SIZE_THRESHOLD; | ||
|
||
if (!node?.children) { | ||
return ( | ||
<Node | ||
{...props} | ||
className={classNames(styles.node, isLink && styles.link)} | ||
onClick={onSelect && ((e) => onSelect(node.id, e))} | ||
href={href} | ||
isActive={node?.id === selected} | ||
icon={icon} | ||
widgets={widgets} | ||
/> | ||
); | ||
} | ||
return <FolderTreeNode {...props} />; | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export { ArtifactsTree, ArtifactsTreeProps } from './artifacts-tree'; | ||
export const FILE_SIZE_THRESHOLD = 10000; |
55 changes: 55 additions & 0 deletions
55
components/ui/artifacts/models/component-artifacts-model/component-artifacts.model.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
export type ArtifactFile = { | ||
name: string; | ||
path: string; | ||
content?: string; | ||
downloadUrl?: string; | ||
size: number; | ||
}; | ||
|
||
export type Artifact = { | ||
name: string; | ||
taskId: string; | ||
taskName: string; | ||
description?: string; | ||
files: Array<ArtifactFile>; | ||
}; | ||
|
||
export type ComponentArtifactsGQLResponse = Array<{ | ||
taskId: string; | ||
taskName: string; | ||
artifact: Artifact; | ||
}>; | ||
|
||
export function mapToArtifacts(gqlResponse: ComponentArtifactsGQLResponse): Artifact[] { | ||
return gqlResponse | ||
.filter((task) => task.artifact) | ||
.map((task) => ({ | ||
...task.artifact, | ||
taskId: task.taskId, | ||
taskName: task.taskName, | ||
})); | ||
} | ||
|
||
export function getArtifactFileDetailsFromUrl( | ||
artifacts: Array<Artifact>, | ||
fileFromUrl?: string | ||
): { taskName: string; artifactName: string; artifactFile: ArtifactFile; taskId: string } | undefined { | ||
if (!fileFromUrl || !fileFromUrl.startsWith('~artifact/')) return undefined; | ||
const [, fileFromUrlParsed] = fileFromUrl.split('~artifact/'); | ||
const [taskName, ...artifactNameAndPath] = fileFromUrlParsed.split('/'); | ||
const [artifactName, ...path] = artifactNameAndPath; | ||
const filePath = path.join('/'); | ||
const matchingArtifact = artifacts.find( | ||
(artifact) => artifact.taskName === taskName && artifact.name === artifactName | ||
); | ||
const matchingArtifactFile = matchingArtifact?.files.find((artifactFile) => artifactFile.path === filePath); | ||
|
||
if (!matchingArtifact || !matchingArtifactFile) return undefined; | ||
|
||
return { | ||
taskName, | ||
artifactName, | ||
artifactFile: { ...matchingArtifactFile }, | ||
taskId: matchingArtifact.taskId, | ||
}; | ||
} |
7 changes: 7 additions & 0 deletions
7
components/ui/artifacts/models/component-artifacts-model/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
export { | ||
mapToArtifacts, | ||
ArtifactFile, | ||
Artifact, | ||
ComponentArtifactsGQLResponse, | ||
getArtifactFileDetailsFromUrl, | ||
} from './component-artifacts.model'; |
1 change: 1 addition & 0 deletions
1
components/ui/artifacts/queries/use-component-artifacts/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { useComponentArtifacts, useComponentArtifactFileContent } from './use-component-artifacts'; |
Oops, something went wrong.