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

[AXON 95] Fix file attachment to jira tickets #93

Merged
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
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
49 changes: 36 additions & 13 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -1426,7 +1426,7 @@
"fast-deep-equal": "^3.1.1",
"filesize": "^4.1.2",
"flatten-anything": "^2.0.1",
"form-data": "^2.5.1",
"form-data": "^2.5.2",
"git-url-parse": "^15.0.0",
"ini": "^1.3.8",
"jwt-decode": "^3.1.2",
Expand Down
21 changes: 21 additions & 0 deletions src/util/bufferTransformers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
export function arrayBufferToBase64(buffer: ArrayBuffer | null | undefined): string | undefined {
marcomura marked this conversation as resolved.
Show resolved Hide resolved
if (!buffer) {
return undefined;
}
let binary = '';
const bytes = new Uint8Array(buffer);
const len = bytes.byteLength;
for (let i = 0; i < len; ++i) {
binary += String.fromCharCode(bytes[i]);
}
return btoa(binary);
}

export function base64ToBuffer(data: string): Buffer {
let binaryString = atob(data);
let bytes = new Uint8Array(binaryString.length);
for (let i = 0; i < binaryString.length; ++i) {
bytes[i] = binaryString.charCodeAt(i);
}
return Buffer.from(bytes);
}
25 changes: 25 additions & 0 deletions src/util/files.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { arrayBufferToBase64 } from './bufferTransformers';

export interface FileWithContent extends File {
/** base64-encoded file content */
fileContent: string | undefined;
}

export function readFilesContentAsync(files: File[]): Promise<FileWithContent[]> {
const promise = new Promise<any>((resolve) => {
let doneCount = 0;
for (let i = 0; i < files.length; ++i) {
const index = i;
marcomura marked this conversation as resolved.
Show resolved Hide resolved
const reader = new FileReader();
reader.onloadend = (event) => {
(files[index] as FileWithContent).fileContent = arrayBufferToBase64(reader.result as ArrayBuffer);
if (++doneCount === files.length) {
marcomura marked this conversation as resolved.
Show resolved Hide resolved
resolve(files);
}
};
reader.readAsArrayBuffer(files[index]);
}
});

return promise;
}
37 changes: 23 additions & 14 deletions src/webviews/components/issue/CreateIssuePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import Spinner from '@atlaskit/spinner';
import { chain } from '../fieldValidators';
import { AtlascodeErrorBoundary } from 'src/react/atlascode/common/ErrorBoundary';
import { AnalyticsView } from 'src/analyticsTypes';
import { readFilesContentAsync } from '../../../util/files';

type Emit = CommonEditorPageEmit;
type Accept = CommonEditorPageAccept | CreateIssueData;
Expand Down Expand Up @@ -189,6 +190,26 @@ export default class CreateIssuePage extends AbstractIssueEditorPage<Emit, Accep
});
};

protected handleInlineAttachments = async (fieldkey: string, newValue: any) => {
if (Array.isArray(newValue) && newValue.length > 0) {
readFilesContentAsync(newValue).then((filesWithContent) => {
const serFiles = filesWithContent.map((file) => {
return {
lastModified: file.lastModified,
lastModifiedDate: (file as any).lastModifiedDate,
name: file.name,
size: file.size,
type: file.type,
path: (file as any).path,
fileContent: file.fileContent,
};
});

this.setState({ fieldValues: { ...this.state.fieldValues, ...{ [fieldkey]: serFiles } } });
});
}
};

protected handleInlineEdit = async (field: FieldUI, newValue: any) => {
let typedVal = newValue;
let fieldkey = field.key;
Expand All @@ -214,20 +235,8 @@ export default class CreateIssuePage extends AbstractIssueEditorPage<Emit, Accep
}

if (field.uiType === UIType.Attachment) {
if (Array.isArray(newValue) && newValue.length > 0) {
const serFiles = newValue.map((file: any) => {
return {
lastModified: file.lastModified,
lastModifiedDate: file.lastModifiedDate,
name: file.name,
size: file.size,
type: file.type,
path: file.path,
};
});

typedVal = serFiles;
}
await this.handleInlineAttachments(fieldkey, newValue);
return;
}

this.setState({ fieldValues: { ...this.state.fieldValues, ...{ [fieldkey]: typedVal } } });
Expand Down
45 changes: 28 additions & 17 deletions src/webviews/components/issue/JiraIssuePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import WorklogForm from './WorklogForm';
import Worklogs from './Worklogs';
import { AtlascodeErrorBoundary } from 'src/react/atlascode/common/ErrorBoundary';
import { AnalyticsView } from 'src/analyticsTypes';
import { readFilesContentAsync } from '../../../util/files';

type Emit = CommonEditorPageEmit | EditIssueAction | IssueCommentAction;
type Accept = CommonEditorPageAccept | EditIssueData;
Expand All @@ -68,6 +69,7 @@ const emptyState: ViewState = {
export default class JiraIssuePage extends AbstractIssueEditorPage<Emit, Accept, {}, ViewState> {
private advancedSidebarFields: FieldUI[] = [];
private advancedMainFields: FieldUI[] = [];
private attachingInProgress: boolean;

constructor(props: any) {
super(props);
Expand Down Expand Up @@ -384,23 +386,32 @@ export default class JiraIssuePage extends AbstractIssueEditorPage<Emit, Accept,
this.postMessage({ action: 'removeVote', site: this.state.siteDetails, issueKey: this.state.key, voter: user });
};

handleAddAttachments = (files: any[]) => {
this.setState({ currentInlineDialog: '', isSomethingLoading: false, loadingField: 'attachment' });
const serFiles = files.map((file: any) => {
return {
lastModified: file.lastModified,
lastModifiedDate: file.lastModifiedDate,
name: file.name,
size: file.size,
type: file.type,
path: file.path,
};
});
this.postMessage({
action: 'addAttachments',
site: this.state.siteDetails,
issueKey: this.state.key,
files: serFiles,
handleAddAttachments = (files: File[]) => {
if (this.attachingInProgress) {
marcomura marked this conversation as resolved.
Show resolved Hide resolved
return;
}
this.attachingInProgress = true;

readFilesContentAsync(files).then((filesWithContent) => {
this.setState({ currentInlineDialog: '', isSomethingLoading: false, loadingField: 'attachment' });
const serFiles = filesWithContent.map((file) => {
return {
lastModified: file.lastModified,
lastModifiedDate: (file as any).lastModifiedDate,
name: file.name,
size: file.size,
type: file.type,
path: (file as any).path,
fileContent: file.fileContent,
};
});
this.postMessage({
action: 'addAttachments',
site: this.state.siteDetails,
issueKey: this.state.key,
files: serFiles,
});
this.attachingInProgress = false;
});
};

Expand Down
8 changes: 5 additions & 3 deletions src/webviews/createIssueWebview.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import * as fs from 'fs';

import { Action, onlineStatus } from '../ipc/messaging';
import {
CreateIssueAction,
Expand All @@ -25,6 +23,7 @@ import { configuration } from '../config/configuration';
import { fetchCreateIssueUI } from '../jira/fetchIssue';
import { format } from 'date-fns';
import { issueCreatedEvent } from '../analytics';
import { base64ToBuffer } from '../util/bufferTransformers';

export interface PartialIssue {
uri?: Uri;
Expand Down Expand Up @@ -465,7 +464,10 @@ export class CreateIssueWebview
if (attachments && attachments.length > 0) {
let formData = new FormData();
attachments.forEach((file: any) => {
formData.append('file', fs.createReadStream(file.path), {
if (!file.fileContent) {
throw new Error(`Unable to read the file '${file.name}'`);
}
formData.append('file', base64ToBuffer(file.fileContent), {
filename: file.name,
contentType: file.type,
});
Expand Down
10 changes: 6 additions & 4 deletions src/webviews/jiraIssueWebview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import {
} from '@atlassianlabs/jira-pi-common-models';
import { FieldValues, ValueType } from '@atlassianlabs/jira-pi-meta-models';
import FormData from 'form-data';
import * as fs from 'fs';
import { commands, env } from 'vscode';
import { issueCreatedEvent, issueUpdatedEvent, issueUrlCopiedEvent } from '../analytics';
import { DetailedSiteInfo, emptySiteInfo, Product, ProductJira } from '../atlclients/authInfo';
Expand Down Expand Up @@ -47,6 +46,7 @@ import { Logger } from '../logger';
import { iconSet, Resources } from '../resources';
import { AbstractIssueEditorWebview } from './abstractIssueEditorWebview';
import { InitializingWebview } from './abstractWebview';
import { base64ToBuffer } from '../util/bufferTransformers';

export class JiraIssueWebview
extends AbstractIssueEditorWebview
Expand Down Expand Up @@ -750,16 +750,18 @@ export class JiraIssueWebview
if (isAddAttachmentsAction(msg)) {
handled = true;
try {
let client = await Container.clientManager.jiraClient(msg.site);

let formData = new FormData();
msg.files.forEach((file: any) => {
formData.append('file', fs.createReadStream(file.path), {
if (!file.fileContent) {
throw new Error(`Unable to read the file '${file.name}'`);
}
formData.append('file', base64ToBuffer(file.fileContent), {
filename: file.name,
contentType: file.type,
});
});

const client = await Container.clientManager.jiraClient(msg.site);
const resp = await client.addAttachments(msg.issueKey, formData);

if (
Expand Down