Skip to content

Commit

Permalink
fix(use-data-query): correctly infer type from apollo client result f…
Browse files Browse the repository at this point in the history
…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
luvkapur authored Dec 5, 2024
1 parent 5606ae2 commit ca0d17a
Show file tree
Hide file tree
Showing 13 changed files with 464 additions and 46 deletions.
7 changes: 0 additions & 7 deletions .bitmap
Original file line number Diff line number Diff line change
Expand Up @@ -779,13 +779,6 @@
"mainFile": "index.ts",
"rootDir": "scopes/cloud/hooks/use-logout"
},
"hooks/use-schema": {
"name": "hooks/use-schema",
"scope": "teambit.api-reference",
"version": "0.0.35",
"mainFile": "index.ts",
"rootDir": "scopes/api-reference/hooks/use-schema"
},
"hooks/use-viewed-lane-from-url": {
"name": "hooks/use-viewed-lane-from-url",
"scope": "teambit.lanes",
Expand Down
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ types/*
#distrubution
distribution/

artifacts/
bit-*.tar.gz
tmp/
bin/node
Expand Down
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 components/ui/artifacts/artifacts-tree/artifacts-tree.module.scss
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 components/ui/artifacts/artifacts-tree/artifacts-tree.tsx
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} />;
};
}
2 changes: 2 additions & 0 deletions components/ui/artifacts/artifacts-tree/index.ts
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;
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,
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export {
mapToArtifacts,
ArtifactFile,
Artifact,
ComponentArtifactsGQLResponse,
getArtifactFileDetailsFromUrl,
} from './component-artifacts.model';
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { useComponentArtifacts, useComponentArtifactFileContent } from './use-component-artifacts';
Loading

0 comments on commit ca0d17a

Please sign in to comment.