diff --git a/resources/html/reactView.html b/resources/html/reactView.html index 3e0803df5..6a3cd2169 100644 --- a/resources/html/reactView.html +++ b/resources/html/reactView.html @@ -11,7 +11,7 @@ diff --git a/resources/html/reactWebview.html b/resources/html/reactWebview.html index af6eb1fc4..010457e56 100644 --- a/resources/html/reactWebview.html +++ b/resources/html/reactWebview.html @@ -10,7 +10,7 @@ diff --git a/src/atlclients/clientManager.ts b/src/atlclients/clientManager.ts index d3f6dbd40..e8b0206a6 100644 --- a/src/atlclients/clientManager.ts +++ b/src/atlclients/clientManager.ts @@ -153,6 +153,7 @@ export class ClientManager implements Disposable { if (isOAuthInfo(info)) { Logger.debug(`${tag}: creating client for ${site.baseApiUrl}`); + Logger.debug(`${tag}: Sites123: ${JSON.stringify(site)}`); client = new JiraCloudClient( site, oauthJiraTransportFactory(site), diff --git a/src/atlclients/strategyData.ts b/src/atlclients/strategyData.ts index 64342b527..c62495c30 100644 --- a/src/atlclients/strategyData.ts +++ b/src/atlclients/strategyData.ts @@ -117,7 +117,7 @@ export class OAuthStrategyData { accessibleResourcesURL: 'https://api.atlassian.com/oauth/token/accessible-resources', callbackURL: remoteAuthConfig.callbackURL, apiURL: 'api.atlassian.com', - scope: 'read:jira-user read:jira-work write:jira-work offline_access manage:jira-project', + scope: 'read:jira-user read:jira-work write:jira-work offline_access manage:jira-project manage:jira-configuration manage:jira-webhook read:application-role:jira read:audit-log:jira read:avatar:jira write:avatar:jira delete:avatar:jira read:project.avatar:jira write:project.avatar:jira delete:project.avatar:jira read:dashboard:jira write:dashboard:jira delete:dashboard:jira read:dashboard.property:jira write:dashboard.property:jira delete:dashboard.property:jira read:filter:jira write:filter:jira delete:filter:jira read:filter.column:jira write:filter.column:jira delete:filter.column:jira read:filter.default-share-scope:jira write:filter.default-share-scope:jira read:group:jira write:group:jira delete:group:jira read:license:jira read:issue:jira write:issue:jira delete:issue:jira read:issue-meta:jira send:notification:jira read:attachment:jira write:attachment:jira delete:attachment:jira read:comment:jira write:comment:jira delete:comment:jira read:comment.property:jira write:comment.property:jira delete:comment.property:jira read:field:jira write:field:jira delete:field:jira read:field.default-value:jira write:field.default-value:jira read:field.option:jira write:field.option:jira delete:field.option:jira read:field-configuration-scheme:jira write:field-configuration-scheme:jira delete:field-configuration-scheme:jira read:custom-field-contextual-configuration:jira write:custom-field-contextual-configuration:jira read:field-configuration:jira write:field-configuration:jira delete:field-configuration:jira read:field.options:jira read:issue-link:jira write:issue-link:jira delete:issue-link:jira read:issue-link-type:jira write:issue-link-type:jira delete:issue-link-type:jira read:notification-scheme:jira read:priority:jira read:issue.property:jira write:issue.property:jira delete:issue.property:jira read:issue.remote-link:jira write:issue.remote-link:jira delete:issue.remote-link:jira read:resolution:jira read:issue-details:jira read:issue-security-scheme:jira read:issue-type:jira write:issue-type:jira delete:issue-type:jira read:issue-type-scheme:jira write:issue-type-scheme:jira delete:issue-type-scheme:jira read:issue-type-screen-scheme:jira write:issue-type-screen-scheme:jira delete:issue-type-screen-scheme:jira read:issue-type.property:jira write:issue-type.property:jira delete:issue-type.property:jira read:issue.watcher:jira write:issue.watcher:jira read:issue-worklog:jira write:issue-worklog:jira delete:issue-worklog:jira read:issue-worklog.property:jira write:issue-worklog.property:jira delete:issue-worklog.property:jira read:issue-field-values:jira read:issue-security-level:jira read:issue-status:jira read:issue-type-hierarchy:jira read:issue-type-transition:jira read:issue.changelog:jira read:issue.transition:jira write:issue.vote:jira read:issue-event:jira read:jira-expressions:jira read:user:jira read:user.columns:jira read:label:jira read:permission:jira write:permission:jira delete:permission:jira read:permission-scheme:jira write:permission-scheme:jira delete:permission-scheme:jira read:project:jira write:project:jira delete:project:jira read:project-category:jira write:project-category:jira delete:project-category:jira read:project.component:jira write:project.component:jira delete:project.component:jira read:project.property:jira write:project.property:jira delete:project.property:jira read:project-role:jira write:project-role:jira delete:project-role:jira read:project-version:jira write:project-version:jira delete:project-version:jira read:project.feature:jira write:project.feature:jira read:screen:jira write:screen:jira delete:screen:jira read:screen-scheme:jira write:screen-scheme:jira delete:screen-scheme:jira read:screen-field:jira read:screen-tab:jira write:screen-tab:jira delete:screen-tab:jira read:screenable-field:jira write:screenable-field:jira delete:screenable-field:jira read:issue.time-tracking:jira write:issue.time-tracking:jira read:user.property:jira write:user.property:jira delete:user.property:jira read:webhook:jira write:webhook:jira delete:webhook:jira read:workflow:jira write:workflow:jira delete:workflow:jira read:workflow-scheme:jira write:workflow-scheme:jira delete:workflow-scheme:jira read:status:jira read:workflow.property:jira write:workflow.property:jira delete:workflow.property:jira delete:async-task:jira read:instance-configuration:jira write:instance-configuration:jira read:jql:jira validate:jql:jira read:project-type:jira read:project.email:jira write:project.email:jira read:role:jira read:user-configuration:jira write:user-configuration:jira delete:user-configuration:jira read:email-address:jira', authParams: { audience: 'api.atlassian.com', prompt: 'consent', @@ -135,7 +135,7 @@ export class OAuthStrategyData { accessibleResourcesURL: 'https://api.atlassian.com/oauth/token/accessible-resources', callbackURL: 'http://127.0.0.1:31415/' + OAuthProvider.JiraCloud, apiURL: 'api.atlassian.com', - scope: 'read:jira-user read:jira-work write:jira-work offline_access manage:jira-project', + scope: 'read:jira-user read:jira-work write:jira-work offline_access manage:jira-project manage:jira-configuration manage:jira-webhook read:application-role:jira read:audit-log:jira read:avatar:jira write:avatar:jira delete:avatar:jira read:project.avatar:jira write:project.avatar:jira delete:project.avatar:jira read:dashboard:jira write:dashboard:jira delete:dashboard:jira read:dashboard.property:jira write:dashboard.property:jira delete:dashboard.property:jira read:filter:jira write:filter:jira delete:filter:jira read:filter.column:jira write:filter.column:jira delete:filter.column:jira read:filter.default-share-scope:jira write:filter.default-share-scope:jira read:group:jira write:group:jira delete:group:jira read:license:jira read:issue:jira write:issue:jira delete:issue:jira read:issue-meta:jira send:notification:jira read:attachment:jira write:attachment:jira delete:attachment:jira read:comment:jira write:comment:jira delete:comment:jira read:comment.property:jira write:comment.property:jira delete:comment.property:jira read:field:jira write:field:jira delete:field:jira read:field.default-value:jira write:field.default-value:jira read:field.option:jira write:field.option:jira delete:field.option:jira read:field-configuration-scheme:jira write:field-configuration-scheme:jira delete:field-configuration-scheme:jira read:custom-field-contextual-configuration:jira write:custom-field-contextual-configuration:jira read:field-configuration:jira write:field-configuration:jira delete:field-configuration:jira read:field.options:jira read:issue-link:jira write:issue-link:jira delete:issue-link:jira read:issue-link-type:jira write:issue-link-type:jira delete:issue-link-type:jira read:notification-scheme:jira read:priority:jira read:issue.property:jira write:issue.property:jira delete:issue.property:jira read:issue.remote-link:jira write:issue.remote-link:jira delete:issue.remote-link:jira read:resolution:jira read:issue-details:jira read:issue-security-scheme:jira read:issue-type:jira write:issue-type:jira delete:issue-type:jira read:issue-type-scheme:jira write:issue-type-scheme:jira delete:issue-type-scheme:jira read:issue-type-screen-scheme:jira write:issue-type-screen-scheme:jira delete:issue-type-screen-scheme:jira read:issue-type.property:jira write:issue-type.property:jira delete:issue-type.property:jira read:issue.watcher:jira write:issue.watcher:jira read:issue-worklog:jira write:issue-worklog:jira delete:issue-worklog:jira read:issue-worklog.property:jira write:issue-worklog.property:jira delete:issue-worklog.property:jira read:issue-field-values:jira read:issue-security-level:jira read:issue-status:jira read:issue-type-hierarchy:jira read:issue-type-transition:jira read:issue.changelog:jira read:issue.transition:jira write:issue.vote:jira read:issue-event:jira read:jira-expressions:jira read:user:jira read:user.columns:jira read:label:jira read:permission:jira write:permission:jira delete:permission:jira read:permission-scheme:jira write:permission-scheme:jira delete:permission-scheme:jira read:project:jira write:project:jira delete:project:jira read:project-category:jira write:project-category:jira delete:project-category:jira read:project.component:jira write:project.component:jira delete:project.component:jira read:project.property:jira write:project.property:jira delete:project.property:jira read:project-role:jira write:project-role:jira delete:project-role:jira read:project-version:jira write:project-version:jira delete:project-version:jira read:project.feature:jira write:project.feature:jira read:screen:jira write:screen:jira delete:screen:jira read:screen-scheme:jira write:screen-scheme:jira delete:screen-scheme:jira read:screen-field:jira read:screen-tab:jira write:screen-tab:jira delete:screen-tab:jira read:screenable-field:jira write:screenable-field:jira delete:screenable-field:jira read:issue.time-tracking:jira write:issue.time-tracking:jira read:user.property:jira write:user.property:jira delete:user.property:jira read:webhook:jira write:webhook:jira delete:webhook:jira read:workflow:jira write:workflow:jira delete:workflow:jira read:workflow-scheme:jira write:workflow-scheme:jira delete:workflow-scheme:jira read:status:jira read:workflow.property:jira write:workflow.property:jira delete:workflow.property:jira delete:async-task:jira read:instance-configuration:jira write:instance-configuration:jira read:jql:jira validate:jql:jira read:project-type:jira read:project.email:jira write:project.email:jira read:role:jira read:user-configuration:jira write:user-configuration:jira delete:user-configuration:jira read:email-address:jira', authParams: { audience: 'api.atlassian.com', prompt: 'consent', @@ -153,7 +153,7 @@ export class OAuthStrategyData { accessibleResourcesURL: 'https://api.stg.atlassian.com/oauth/token/accessible-resources', callbackURL: 'http://127.0.0.1:31415/' + OAuthProvider.JiraCloudStaging, apiURL: 'api.stg.atlassian.com', - scope: 'read:jira-user read:jira-work write:jira-work offline_access manage:jira-project', + scope: 'read:jira-user read:jira-work write:jira-work offline_access manage:jira-project manage:jira-configuration manage:jira-webhook read:application-role:jira read:audit-log:jira read:avatar:jira write:avatar:jira delete:avatar:jira read:project.avatar:jira write:project.avatar:jira delete:project.avatar:jira read:dashboard:jira write:dashboard:jira delete:dashboard:jira read:dashboard.property:jira write:dashboard.property:jira delete:dashboard.property:jira read:filter:jira write:filter:jira delete:filter:jira read:filter.column:jira write:filter.column:jira delete:filter.column:jira read:filter.default-share-scope:jira write:filter.default-share-scope:jira read:group:jira write:group:jira delete:group:jira read:license:jira read:issue:jira write:issue:jira delete:issue:jira read:issue-meta:jira send:notification:jira read:attachment:jira write:attachment:jira delete:attachment:jira read:comment:jira write:comment:jira delete:comment:jira read:comment.property:jira write:comment.property:jira delete:comment.property:jira read:field:jira write:field:jira delete:field:jira read:field.default-value:jira write:field.default-value:jira read:field.option:jira write:field.option:jira delete:field.option:jira read:field-configuration-scheme:jira write:field-configuration-scheme:jira delete:field-configuration-scheme:jira read:custom-field-contextual-configuration:jira write:custom-field-contextual-configuration:jira read:field-configuration:jira write:field-configuration:jira delete:field-configuration:jira read:field.options:jira read:issue-link:jira write:issue-link:jira delete:issue-link:jira read:issue-link-type:jira write:issue-link-type:jira delete:issue-link-type:jira read:notification-scheme:jira read:priority:jira read:issue.property:jira write:issue.property:jira delete:issue.property:jira read:issue.remote-link:jira write:issue.remote-link:jira delete:issue.remote-link:jira read:resolution:jira read:issue-details:jira read:issue-security-scheme:jira read:issue-type:jira write:issue-type:jira delete:issue-type:jira read:issue-type-scheme:jira write:issue-type-scheme:jira delete:issue-type-scheme:jira read:issue-type-screen-scheme:jira write:issue-type-screen-scheme:jira delete:issue-type-screen-scheme:jira read:issue-type.property:jira write:issue-type.property:jira delete:issue-type.property:jira read:issue.watcher:jira write:issue.watcher:jira read:issue-worklog:jira write:issue-worklog:jira delete:issue-worklog:jira read:issue-worklog.property:jira write:issue-worklog.property:jira delete:issue-worklog.property:jira read:issue-field-values:jira read:issue-security-level:jira read:issue-status:jira read:issue-type-hierarchy:jira read:issue-type-transition:jira read:issue.changelog:jira read:issue.transition:jira write:issue.vote:jira read:issue-event:jira read:jira-expressions:jira read:user:jira read:user.columns:jira read:label:jira read:permission:jira write:permission:jira delete:permission:jira read:permission-scheme:jira write:permission-scheme:jira delete:permission-scheme:jira read:project:jira write:project:jira delete:project:jira read:project-category:jira write:project-category:jira delete:project-category:jira read:project.component:jira write:project.component:jira delete:project.component:jira read:project.property:jira write:project.property:jira delete:project.property:jira read:project-role:jira write:project-role:jira delete:project-role:jira read:project-version:jira write:project-version:jira delete:project-version:jira read:project.feature:jira write:project.feature:jira read:screen:jira write:screen:jira delete:screen:jira read:screen-scheme:jira write:screen-scheme:jira delete:screen-scheme:jira read:screen-field:jira read:screen-tab:jira write:screen-tab:jira delete:screen-tab:jira read:screenable-field:jira write:screenable-field:jira delete:screenable-field:jira read:issue.time-tracking:jira write:issue.time-tracking:jira read:user.property:jira write:user.property:jira delete:user.property:jira read:webhook:jira write:webhook:jira delete:webhook:jira read:workflow:jira write:workflow:jira delete:workflow:jira read:workflow-scheme:jira write:workflow-scheme:jira delete:workflow-scheme:jira read:status:jira read:workflow.property:jira write:workflow.property:jira delete:workflow.property:jira delete:async-task:jira read:instance-configuration:jira write:instance-configuration:jira read:jql:jira validate:jql:jira read:project-type:jira read:project.email:jira write:project.email:jira read:role:jira read:user-configuration:jira write:user-configuration:jira delete:user-configuration:jira read:email-address:jira', authParams: { audience: 'api.stg.atlassian.com', prompt: 'consent', diff --git a/src/jira/jira-client/providers.ts b/src/jira/jira-client/providers.ts index 0113adb76..34ce06d1a 100644 --- a/src/jira/jira-client/providers.ts +++ b/src/jira/jira-client/providers.ts @@ -47,6 +47,7 @@ export function basicJiraTransportFactory(site: DetailedSiteInfo): TransportFact } export const jiraTokenAuthProvider = (token: string): AuthorizationProvider => { + console.debug(`Bearer ${token}`); return (method: string, url: string) => { return Promise.resolve(`Bearer ${token}`); }; diff --git a/src/webviews/components/issue/common/AtlaskitEditor/AtlaskitEditor.tsx b/src/webviews/components/issue/common/AtlaskitEditor/AtlaskitEditor.tsx index 186627232..3f06815b8 100644 --- a/src/webviews/components/issue/common/AtlaskitEditor/AtlaskitEditor.tsx +++ b/src/webviews/components/issue/common/AtlaskitEditor/AtlaskitEditor.tsx @@ -3,8 +3,12 @@ import './AtlaskitEditor.css'; import { ComposableEditor, EditorNextProps } from '@atlaskit/editor-core/composable-editor'; import { createDefaultPreset } from '@atlaskit/editor-core/preset-default'; import { usePreset } from '@atlaskit/editor-core/use-preset'; +// eslint-disable-next-line import/no-extraneous-dependencies +import { gridPlugin } from '@atlaskit/editor-plugin-grid'; import { insertBlockPlugin } from '@atlaskit/editor-plugin-insert-block'; import { listPlugin } from '@atlaskit/editor-plugin-list'; +// eslint-disable-next-line import/no-extraneous-dependencies +import { mediaPlugin } from '@atlaskit/editor-plugin-media'; import { mentionsPlugin } from '@atlaskit/editor-plugin-mentions'; import { textColorPlugin } from '@atlaskit/editor-plugin-text-color'; import { toolbarListsIndentationPlugin } from '@atlaskit/editor-plugin-toolbar-lists-indentation'; @@ -19,6 +23,8 @@ interface AtlaskitEditorProps extends Omit, 'onChange' onContentChange?: (content: string) => void; onChange?: (content: string) => void; onBlur?: (content: string) => void; + getMediaAuth?: () => Promise<{ token: string; clientId: string; baseUrl?: string; collectionName?: string }>; + issueKey?: string; } const AtlaskitEditor: React.FC = (props: AtlaskitEditorProps) => { @@ -31,8 +37,68 @@ const AtlaskitEditor: React.FC = (props: AtlaskitEditorProp onBlur, onContentChange, mentionProvider, + getMediaAuth, + issueKey, } = props; + // Media plugin options with Jira API token provider + const mediaOptions = React.useMemo(() => { + const base: any = { + allowMediaSingle: true, + allowMediaGroup: true, + allowResizing: true, + allowAnnotation: true, + allowLinking: true, + allowResizingInTables: true, + allowMediaInline: true, + allowCaptions: true, + allowAltTextOnImages: true, + featureFlags: { mediaInline: true }, + }; + + // Only provide media provider if we have auth function + if (!getMediaAuth) { + return base; + } + + const provider = Promise.resolve({ + uploadMediaClientConfig: { + authProvider: async () => { + try { + const auth = await getMediaAuth(); + return { + token: auth.token, + clientId: auth.clientId, + baseUrl: auth.baseUrl ?? 'https://api.media.atlassian.com', + } as any; + } catch (error) { + console.error('Error getting media auth for upload:', error); + throw error; + } + }, + }, + viewMediaClientConfig: { + authProvider: async () => { + try { + const auth = await getMediaAuth(); + return { + token: auth.token, + clientId: auth.clientId, + baseUrl: auth.baseUrl ?? 'https://api.media.atlassian.com', + } as any; + } catch (error) { + console.error('Error getting media auth for view:', error); + throw error; + } + }, + }, + uploadParams: { + collection: issueKey ?? 'atlascode', + }, + }); + return { ...base, provider }; + }, [getMediaAuth, issueKey]); + const { preset, editorApi } = usePreset(() => { return ( createDefaultPreset({ @@ -46,20 +112,22 @@ const AtlaskitEditor: React.FC = (props: AtlaskitEditorProp platform: 'web', }, }) - // You can extend this with other plugins if you need them + // Add additional plugins + .add(gridPlugin) .add(listPlugin) .add([ toolbarListsIndentationPlugin, { showIndentationButtons: false, allowHeadingAndParagraphIndentation: false }, ]) .add(textColorPlugin) + .add([mediaPlugin, mediaOptions]) .add([ insertBlockPlugin, - { toolbarShowPlusInsertOnly: true, appearance: appearance, allowExpand: true }, + { toolbarShowPlusInsertOnly: false, appearance: appearance, allowExpand: true }, ]) - .add(mentionsPlugin) + .add([mentionsPlugin, { mentionProvider }]) ); - }, []); + }, [mediaOptions, mentionProvider]); // Helper function to get current document content const getCurrentContent = React.useCallback(async (): Promise => { try { diff --git a/src/webviews/components/issue/view-issue-screen/JiraIssuePage.tsx b/src/webviews/components/issue/view-issue-screen/JiraIssuePage.tsx index 56e0d3092..586b0394a 100644 --- a/src/webviews/components/issue/view-issue-screen/JiraIssuePage.tsx +++ b/src/webviews/components/issue/view-issue-screen/JiraIssuePage.tsx @@ -630,6 +630,16 @@ export default class JiraIssuePage extends AbstractIssueEditorPage { + const nonce = v4(); + return (await this.postMessageWithEventPromise( + { action: 'getMediaAuth', site: this.state.siteDetails, issueKey: this.state.key, nonce }, + 'mediaAuth', + 15000, + nonce, + )) as any; + }} + issueKey={this.state.key} /> {this.advancedMain()} {this.state.fields['comment'] && ( @@ -654,6 +664,21 @@ export default class JiraIssuePage extends AbstractIssueEditorPage { + const nonce = v4(); + return (await this.postMessageWithEventPromise( + { + action: 'getMediaAuth', + site: this.state.siteDetails, + issueKey: this.state.key, + nonce, + }, + 'mediaAuth', + 15000, + nonce, + )) as any; + }} + issueKey={this.state.key} /> )} diff --git a/src/webviews/components/issue/view-issue-screen/mainpanel/IssueCommentComponent.tsx b/src/webviews/components/issue/view-issue-screen/mainpanel/IssueCommentComponent.tsx index b29456d18..a15a15180 100644 --- a/src/webviews/components/issue/view-issue-screen/mainpanel/IssueCommentComponent.tsx +++ b/src/webviews/components/issue/view-issue-screen/mainpanel/IssueCommentComponent.tsx @@ -36,6 +36,8 @@ export type IssueCommentComponentProps = { onEditingCommentChange: (editing: boolean) => void; isAtlaskitEditorEnabled?: boolean; mentionProvider: AtlascodeMentionProvider; + getMediaAuth?: () => Promise<{ token: string; clientId: string; baseUrl?: string; collectionName?: string }>; + issueKey?: string; }; const CommentComponent: React.FC<{ siteDetails: DetailedSiteInfo; @@ -47,6 +49,8 @@ const CommentComponent: React.FC<{ isServiceDeskProject?: boolean; isAtlaskitEditorEnabled?: boolean; mentionProvider: AtlascodeMentionProvider; + getMediaAuth?: () => Promise<{ token: string; clientId: string; baseUrl?: string; collectionName?: string }>; + issueKey?: string; }> = ({ siteDetails, comment, @@ -57,6 +61,8 @@ const CommentComponent: React.FC<{ isServiceDeskProject, isAtlaskitEditorEnabled, mentionProvider, + getMediaAuth, + issueKey, }) => { const { openEditor, closeEditor, isEditorActive } = useEditorState(); const editorId = `edit-comment-${comment.id}` as const; @@ -135,6 +141,8 @@ const CommentComponent: React.FC<{ isAtlaskitEditorEnabled ? ( { setIsSaving(true); closeEditorHandler(); @@ -201,6 +209,8 @@ const AddCommentComponent: React.FC<{ isEditing: boolean; setIsEditing: (editing: boolean) => void; mentionProvider: AtlascodeMentionProvider; + getMediaAuth?: () => Promise<{ token: string; clientId: string; baseUrl?: string; collectionName?: string }>; + issueKey?: string; }> = ({ fetchUsers, user, @@ -212,6 +222,8 @@ const AddCommentComponent: React.FC<{ isEditing, setIsEditing, mentionProvider, + getMediaAuth, + issueKey, }) => { const { openEditor, closeEditor } = useEditorState(); @@ -278,6 +290,8 @@ const AddCommentComponent: React.FC<{ { if (content && content.trim() !== '') { onCreate(content, undefined); @@ -340,6 +354,8 @@ export const IssueCommentComponent: React.FC = ({ onEditingCommentChange, isAtlaskitEditorEnabled, mentionProvider, + getMediaAuth, + issueKey, }) => { return ( = ({ isEditing={isEditingComment} setIsEditing={onEditingCommentChange} mentionProvider={mentionProvider} + getMediaAuth={getMediaAuth} + issueKey={issueKey} /> {comments .sort((a, b) => (a.created > b.created ? -1 : 1)) @@ -372,6 +390,8 @@ export const IssueCommentComponent: React.FC = ({ isServiceDeskProject={isServiceDeskProject} isAtlaskitEditorEnabled={isAtlaskitEditorEnabled} mentionProvider={mentionProvider} + getMediaAuth={getMediaAuth} + issueKey={issueKey} /> ))} diff --git a/src/webviews/components/issue/view-issue-screen/mainpanel/IssueMainPanel.tsx b/src/webviews/components/issue/view-issue-screen/mainpanel/IssueMainPanel.tsx index d8e5ccfe4..1406fdb79 100644 --- a/src/webviews/components/issue/view-issue-screen/mainpanel/IssueMainPanel.tsx +++ b/src/webviews/components/issue/view-issue-screen/mainpanel/IssueMainPanel.tsx @@ -43,6 +43,8 @@ type Props = { onIssueUpdate?: (issueKey: string, fieldKey: string, newValue: any) => void; isAtlaskitEditorEnabled?: boolean; mentionProvider: AtlascodeMentionProvider; + getMediaAuth?: () => Promise<{ token: string; clientId: string; baseUrl?: string; collectionName?: string }>; + issueKey?: string; }; const IssueMainPanel: React.FC = ({ @@ -66,6 +68,8 @@ const IssueMainPanel: React.FC = ({ onIssueUpdate, isAtlaskitEditorEnabled, mentionProvider, + getMediaAuth, + issueKey, }) => { const attachments = fields['attachment'] && fieldValues['attachment'] ? fieldValues['attachment'] : undefined; const subtasks = @@ -214,6 +218,8 @@ const IssueMainPanel: React.FC = ({ isAtlaskitEditorEnabled ? ( { handleInlineEdit(fields['description'], content); closeEditorHandler(); diff --git a/src/webviews/jiraIssueWebview.ts b/src/webviews/jiraIssueWebview.ts index ddca5e65e..072ccffc6 100644 --- a/src/webviews/jiraIssueWebview.ts +++ b/src/webviews/jiraIssueWebview.ts @@ -1209,6 +1209,57 @@ export class JiraIssueWebview } break; } + case 'getMediaAuth': { + handled = true; + try { + const site = (msg as any).site; + const issueKey = (msg as any).issueKey; + + if (!issueKey) { + throw new Error('Issue key is required for media upload credentials'); + } + + const client = await Container.clientManager.jiraClient(site); + + // Use Jira's public REST API to get upload credentials for media service + // This endpoint generates an upload token with 20 min TTL, same as Jira Web + const credentialsUrl = `${client.baseUrl}/rest/api/2/attachment/upload/issue/${issueKey}/credentials`; + + const response = await client.transportFactory().get(credentialsUrl, { + method: 'GET', + headers: { + Authorization: await client.authorizationProvider('GET', credentialsUrl), + }, + }); + console.log(credentialsUrl, 'credentialsUrl'); + console.log('Authorization header:', await client.authorizationProvider('GET', credentialsUrl)); + console.log(response, 'response'); + + if (!response || !response.data) { + throw new Error('Failed to get media upload credentials from Jira API'); + } + + const credentials = response.data; + + // Extract the necessary fields from Jira's response + await this.postMessage({ + type: 'mediaAuth', + token: credentials.token || credentials.uploadToken, + clientId: credentials.clientId, + baseUrl: credentials.baseUrl || 'https://api.media.atlassian.com', + collectionName: credentials.collectionName || issueKey, + nonce: (msg as any).nonce, + }); + } catch (e) { + Logger.error(e, 'Error getting media upload credentials'); + await this.postMessage({ + type: 'error', + reason: this.formatErrorReason(e, 'Error getting media upload credentials'), + nonce: (msg as any).nonce, + }); + } + break; + } } } diff --git a/webpack.react.dev.js b/webpack.react.dev.js index ea99a2fee..92083a6b2 100644 --- a/webpack.react.dev.js +++ b/webpack.react.dev.js @@ -58,6 +58,7 @@ module.exports = { fallback: { path: require.resolve('path-browserify'), process: false, + buffer: require.resolve('buffer'), }, alias: { // Resolve ProseMirror conflicts by using unified versions @@ -113,6 +114,7 @@ module.exports = { }), new webpack.ProvidePlugin({ process: 'process/browser', + Buffer: ['buffer', 'Buffer'], }), ], module: { diff --git a/webpack.react.prod.js b/webpack.react.prod.js index 6fcefe51a..c3f0c056d 100644 --- a/webpack.react.prod.js +++ b/webpack.react.prod.js @@ -83,6 +83,7 @@ module.exports = { plugins: [new TsconfigPathsPlugin({ configFile: resolveApp('./tsconfig.notest.json') })], fallback: { path: require.resolve('path-browserify'), + buffer: require.resolve('buffer'), }, alias: { // Resolve ProseMirror conflicts by using unified versions @@ -145,6 +146,7 @@ module.exports = { }), new webpack.ProvidePlugin({ process: 'process/browser', + Buffer: ['buffer', 'Buffer'], }), ], performance: {