diff --git a/web/config/moduleFederation.config.js b/web/config/moduleFederation.config.js index e7ddc8b09..74ba9bb6f 100644 --- a/web/config/moduleFederation.config.js +++ b/web/config/moduleFederation.config.js @@ -16,7 +16,7 @@ const ExactSharedPackages = [ '@harness/monaco-yaml', 'monaco-editor', 'monaco-editor-core', - 'monaco-languages', + // 'monaco-languages', 'monaco-plugin-helpers', 'react-monaco-editor' ] diff --git a/web/package.json b/web/package.json index 5c5aabed9..d57058c76 100644 --- a/web/package.json +++ b/web/package.json @@ -37,10 +37,10 @@ "@blueprintjs/datetime": "3.13.0", "@blueprintjs/select": "3.12.3", "@harness/design-system": "1.4.0", - "@harness/icons": "1.95.1", + "@harness/icons": "1.101.1", "@harness/ng-tooltip": ">=1.31.25", "@harness/telemetry": ">=1.0.42", - "@harness/uicore": "3.95.1", + "@harness/uicore": "3.106.3", "@harness/use-modal": "1.3.0", "@popperjs/core": "^2.4.2", "@uiw/react-markdown-editor": "^5.10.1", diff --git a/web/src/components/Changes/Changes.tsx b/web/src/components/Changes/Changes.tsx index 8d4311dbc..510253aab 100644 --- a/web/src/components/Changes/Changes.tsx +++ b/web/src/components/Changes/Changes.tsx @@ -49,6 +49,7 @@ interface ChangesProps extends Pick { emptyMessage: string pullRequestMetadata?: TypesPullReq className?: string + onCommentUpdate: () => void } export const Changes: React.FC = ({ @@ -59,6 +60,7 @@ export const Changes: React.FC = ({ emptyTitle, emptyMessage, pullRequestMetadata, + onCommentUpdate, className }) => { const { getString } = useStrings() @@ -72,7 +74,9 @@ export const Changes: React.FC = ({ loading, refetch } = useGet({ - path: `/api/v1/repos/${repoMetadata?.path}/+/compare/${targetBranch}...${sourceBranch}`, + path: `/api/v1/repos/${repoMetadata?.path}/+/${ + pullRequestMetadata ? `pullreq/${pullRequestMetadata.number}/diff` : `compare/${targetBranch}...${sourceBranch}` + }`, lazy: !targetBranch || !sourceBranch }) const { @@ -214,6 +218,7 @@ export const Changes: React.FC = ({ stickyTopPosition={STICKY_TOP_POSITION} repoMetadata={repoMetadata} pullRequestMetadata={pullRequestMetadata} + onCommentUpdate={onCommentUpdate} /> ))} diff --git a/web/src/components/CommentBox/CommentBox.tsx b/web/src/components/CommentBox/CommentBox.tsx index da8359941..182b7eabc 100644 --- a/web/src/components/CommentBox/CommentBox.tsx +++ b/web/src/components/CommentBox/CommentBox.tsx @@ -225,20 +225,20 @@ const CommentsThread = ({ title={ - + - {commentItem.author} + {commentItem?.author} - + - + <> - {getString(commentItem.deleted ? 'deleted' : 'edited')} + {getString(commentItem?.deleted ? 'deleted' : 'edited')} @@ -249,7 +249,7 @@ const CommentsThread = ({ icon="Options" iconProps={{ size: 14 }} style={{ padding: '5px' }} - disabled={!!commentItem.deleted} + disabled={!!commentItem?.deleted} width="100px" items={[ { @@ -258,7 +258,7 @@ const CommentsThread = ({ }, { text: getString('quote'), - onClick: () => onQuote(commentItem.content) + onClick: () => onQuote(commentItem?.content) }, '-', { @@ -309,12 +309,12 @@ const CommentsThread = ({ - + {getString('commentDeleted')} - + diff --git a/web/src/components/CreatePullRequestModal/CreatePullRequestModal.module.scss b/web/src/components/CreatePullRequestModal/CreatePullRequestModal.module.scss index 647f15d62..a3973398b 100644 --- a/web/src/components/CreatePullRequestModal/CreatePullRequestModal.module.scss +++ b/web/src/components/CreatePullRequestModal/CreatePullRequestModal.module.scss @@ -6,4 +6,11 @@ .description textarea { height: 300px !important; } + + .checkbox { + color: var(--grey-600); + font-size: var(--form-input-font-size) !important; + font-weight: 500 !important; + display: inline-flex !important; + } } diff --git a/web/src/components/CreatePullRequestModal/CreatePullRequestModal.module.scss.d.ts b/web/src/components/CreatePullRequestModal/CreatePullRequestModal.module.scss.d.ts index c66f984ee..a1519c672 100644 --- a/web/src/components/CreatePullRequestModal/CreatePullRequestModal.module.scss.d.ts +++ b/web/src/components/CreatePullRequestModal/CreatePullRequestModal.module.scss.d.ts @@ -4,5 +4,6 @@ declare const styles: { readonly main: string readonly title: string readonly description: string + readonly checkbox: string } export default styles diff --git a/web/src/components/CreatePullRequestModal/CreatePullRequestModal.tsx b/web/src/components/CreatePullRequestModal/CreatePullRequestModal.tsx index 55fb0ac10..1c90157f2 100644 --- a/web/src/components/CreatePullRequestModal/CreatePullRequestModal.tsx +++ b/web/src/components/CreatePullRequestModal/CreatePullRequestModal.tsx @@ -8,6 +8,7 @@ import React from 'react' import { Dialog, Intent } from '@blueprintjs/core' import * as yup from 'yup' +import { Render } from 'react-jsx-match' import { Button, ButtonProps, @@ -35,6 +36,7 @@ import css from './CreatePullRequestModal.module.scss' interface FormData { title: string description: string + draft: boolean } interface CreatePullRequestModalProps extends Pick { @@ -65,7 +67,8 @@ export function useCreatePullRequestModal({ target_branch: targetGitRef, source_branch: sourceGitRef, title: title, - description: description + description: description, + is_draft: formData.draft } try { @@ -97,7 +100,8 @@ export function useCreatePullRequestModal({ initialValues={{ title: '', - description: '' + description: '', + draft: false }} formName="createPullRequest" enableReinitialize={true} @@ -129,6 +133,7 @@ export function useCreatePullRequestModal({ className={css.description} maxLength={1024 * 50} /> + - {loading && } + + + diff --git a/web/src/components/DiffViewer/DiffViewer.tsx b/web/src/components/DiffViewer/DiffViewer.tsx index 013c39d55..feeda98dd 100644 --- a/web/src/components/DiffViewer/DiffViewer.tsx +++ b/web/src/components/DiffViewer/DiffViewer.tsx @@ -49,6 +49,7 @@ interface DiffViewerProps extends Pick { stickyTopPosition?: number readOnly?: boolean pullRequestMetadata?: TypesPullReq + onCommentUpdate: () => void } // @@ -62,7 +63,8 @@ export const DiffViewer: React.FC = ({ stickyTopPosition = 0, readOnly, repoMetadata, - pullRequestMetadata + pullRequestMetadata, + onCommentUpdate }) => { const { getString } = useStrings() const [viewed, setViewed] = useState(false) @@ -379,6 +381,10 @@ export const DiffViewer: React.FC = ({ } } + if (result) { + onCommentUpdate() + } + return [result, updatedItem] }} />, diff --git a/web/src/components/DiffViewer/DiffViewerUtils.tsx b/web/src/components/DiffViewer/DiffViewerUtils.tsx index 11ff09e73..95dd4237c 100644 --- a/web/src/components/DiffViewer/DiffViewerUtils.tsx +++ b/web/src/components/DiffViewer/DiffViewerUtils.tsx @@ -16,7 +16,9 @@ export enum CommentType { CODE_COMMENT = 'code-comment', TITLE_CHANGE = 'title-change', REVIEW_SUBMIT = 'review-submit', - MERGE = 'merge' + MERGE = 'merge', + BRANCH_UPDATE = 'branch-update', + STATE_CHANGE = 'state-change' } export const PR_CODE_COMMENT_PAYLOAD_VERSION = '0.1' @@ -194,7 +196,7 @@ export const activityToCommentItem = (activity: TypesPullReqActivity): CommentIt created: activity.created as number, updated: activity.edited as number, deleted: activity.deleted as number, - content: (activity.text || activity.payload?.Message) as string, + content: (activity.text || (activity.payload as Unknown)?.Message) as string, payload: activity }) diff --git a/web/src/components/MarkdownEditorWithPreview/MarkdownEditorWithPreview.module.scss b/web/src/components/MarkdownEditorWithPreview/MarkdownEditorWithPreview.module.scss index baa712e43..4ad575611 100644 --- a/web/src/components/MarkdownEditorWithPreview/MarkdownEditorWithPreview.module.scss +++ b/web/src/components/MarkdownEditorWithPreview/MarkdownEditorWithPreview.module.scss @@ -85,8 +85,14 @@ overflow: auto; :global { - .wmde-markdown .anchor { - display: none; + .wmde-markdown { + .anchor { + display: none; + } + + pre { + position: relative; + } } } } diff --git a/web/src/components/NoResultCard/NoResultCard.tsx b/web/src/components/NoResultCard/NoResultCard.tsx index 8c84665b9..3d594b984 100644 --- a/web/src/components/NoResultCard/NoResultCard.tsx +++ b/web/src/components/NoResultCard/NoResultCard.tsx @@ -3,11 +3,11 @@ import { Button, Container, ButtonVariation, NoDataCard, IconName } from '@harne import { noop } from 'lodash-es' import { CodeIcon } from 'utils/GitUtils' import { useStrings } from 'framework/strings' -import emptyStateImage from 'images/empty-state.svg' +import { Images } from 'images' import css from './NoResultCard.module.scss' interface NoResultCardProps { - showWhen: () => boolean + showWhen?: () => boolean forSearch: boolean title?: string message?: string @@ -18,7 +18,7 @@ interface NoResultCardProps { } export const NoResultCard: React.FC = ({ - showWhen, + showWhen = () => true, forSearch, title, message, @@ -36,7 +36,7 @@ export const NoResultCard: React.FC = ({ return ( = ({ state }) => { - const { getString } = useStrings() - - let color = Color.GREEN_700 - let icon: IconName = CodeIcon.PullRequest - let clazz: typeof css | string = '' - - switch (state) { - case PullRequestState.MERGED: - color = Color.PURPLE_700 - icon = CodeIcon.PullRequest - clazz = css.merged - break - case PullRequestState.CLOSED: - color = Color.GREY_600 - icon = CodeIcon.PullRequest - clazz = css.closed - break - case PullRequestState.REJECTED: - color = Color.RED_600 - icon = CodeIcon.PullRequestRejected - clazz = css.rejected - break - default: - break - } - - return ( - - - - ) -} diff --git a/web/src/components/PullRequestStateLabel/PullRequestStateLabel.module.scss b/web/src/components/PullRequestStateLabel/PullRequestStateLabel.module.scss new file mode 100644 index 000000000..af555dbe1 --- /dev/null +++ b/web/src/components/PullRequestStateLabel/PullRequestStateLabel.module.scss @@ -0,0 +1,58 @@ +.prStatus { + --fg: var(--green-500); + --bg: var(--green-50); + + display: inline-flex !important; + align-items: center !important; + justify-content: center; + border-radius: 4px; + padding: 5px 10px !important; + font-weight: 600 !important; + font-size: 12px !important; + line-height: 12px !important; + + color: var(--fg) !important; + background-color: var(--bg) !important; + + > span { + padding-right: 5px !important; + } + + &.iconOnly { + padding: 5px !important; + + > span { + padding-right: 0 !important; + } + } + + svg { + path { + fill: var(--fg) !important; + } + + g { + stroke: var(--fg) !important; + } + } + + &.open { + --fg: var(--green-700); + --bg: var(--green-50); + } + + &.merged { + --fg: var(--blue-800); + --bg: var(--blue-50); + } + + &.closed { + --fg: var(--grey-600); + --bg: var(--grey-100); + } + + &.draft { + --fg: var(--orange-900); + --bg: var(--orange-100); + } +} diff --git a/web/src/components/PRStateLabel/PRStateLabel.module.scss.d.ts b/web/src/components/PullRequestStateLabel/PullRequestStateLabel.module.scss.d.ts similarity index 59% rename from web/src/components/PRStateLabel/PRStateLabel.module.scss.d.ts rename to web/src/components/PullRequestStateLabel/PullRequestStateLabel.module.scss.d.ts index a0f561017..8f5808740 100644 --- a/web/src/components/PRStateLabel/PRStateLabel.module.scss.d.ts +++ b/web/src/components/PullRequestStateLabel/PullRequestStateLabel.module.scss.d.ts @@ -1,9 +1,11 @@ /* eslint-disable */ // this is an auto-generated file declare const styles: { - readonly state: string + readonly prStatus: string + readonly iconOnly: string + readonly open: string readonly merged: string readonly closed: string - readonly rejected: string + readonly draft: string } export default styles diff --git a/web/src/components/PullRequestStateLabel/PullRequestStateLabel.tsx b/web/src/components/PullRequestStateLabel/PullRequestStateLabel.tsx new file mode 100644 index 000000000..de16de161 --- /dev/null +++ b/web/src/components/PullRequestStateLabel/PullRequestStateLabel.tsx @@ -0,0 +1,50 @@ +import React from 'react' +import { Text, StringSubstitute, IconName } from '@harness/uicore' +import cx from 'classnames' +import { CodeIcon } from 'utils/GitUtils' +import { useStrings } from 'framework/strings' +import type { TypesPullReq } from 'services/code' +import css from './PullRequestStateLabel.module.scss' + +export const PullRequestStateLabel: React.FC<{ data: TypesPullReq; iconSize?: number; iconOnly?: boolean }> = ({ + data, + iconSize = 20, + iconOnly = false +}) => { + const { getString } = useStrings() + const maps = { + open: { + icon: CodeIcon.PullRequest, + css: css.open + }, + merged: { + icon: CodeIcon.Merged, + css: css.merged + }, + closed: { + icon: CodeIcon.Merged, + css: css.closed + }, + draft: { + icon: CodeIcon.Draft, + css: css.draft + }, + unknown: { + icon: CodeIcon.PullRequest, + css: css.open + } + } + const map = data.is_draft ? maps.draft : maps[data.state || 'unknown'] + + return ( + + {!iconOnly && ( + + )} + + ) +} diff --git a/web/src/components/SourceCodeEditor/MonacoSourceCodeEditor.tsx b/web/src/components/SourceCodeEditor/MonacoSourceCodeEditor.tsx index 03572a099..9abc286c3 100644 --- a/web/src/components/SourceCodeEditor/MonacoSourceCodeEditor.tsx +++ b/web/src/components/SourceCodeEditor/MonacoSourceCodeEditor.tsx @@ -67,9 +67,9 @@ export default function MonacoSourceCodeEditor({ const scrollbar = autoHeight ? 'hidden' : 'auto' useEffect(() => { - monaco.languages.typescript.typescriptDefaults.setDiagnosticsOptions?.(diagnosticsOptions) - monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions?.(diagnosticsOptions) - monaco.languages.typescript.typescriptDefaults.setCompilerOptions(compilerOptions) + monaco.languages.typescript?.typescriptDefaults?.setDiagnosticsOptions?.(diagnosticsOptions) + monaco.languages.typescript?.javascriptDefaults?.setDiagnosticsOptions?.(diagnosticsOptions) + monaco.languages.typescript?.typescriptDefaults?.setCompilerOptions?.(compilerOptions) }, []) return ( diff --git a/web/src/components/SourceCodeViewer/SourceCodeViewer.module.scss b/web/src/components/SourceCodeViewer/SourceCodeViewer.module.scss new file mode 100644 index 000000000..9f51b1099 --- /dev/null +++ b/web/src/components/SourceCodeViewer/SourceCodeViewer.module.scss @@ -0,0 +1,9 @@ +.main { + :global { + .wmde-markdown { + pre { + position: relative; + } + } + } +} diff --git a/web/src/components/SourceCodeViewer/SourceCodeViewer.module.scss.d.ts b/web/src/components/SourceCodeViewer/SourceCodeViewer.module.scss.d.ts new file mode 100644 index 000000000..9e614bf2d --- /dev/null +++ b/web/src/components/SourceCodeViewer/SourceCodeViewer.module.scss.d.ts @@ -0,0 +1,6 @@ +/* eslint-disable */ +// this is an auto-generated file +declare const styles: { + readonly main: string +} +export default styles diff --git a/web/src/components/SourceCodeViewer/SourceCodeViewer.tsx b/web/src/components/SourceCodeViewer/SourceCodeViewer.tsx index b61450e48..7d48da0de 100644 --- a/web/src/components/SourceCodeViewer/SourceCodeViewer.tsx +++ b/web/src/components/SourceCodeViewer/SourceCodeViewer.tsx @@ -4,6 +4,7 @@ import MarkdownEditor from '@uiw/react-markdown-editor' import { useStrings } from 'framework/strings' import { SourceCodeEditor } from 'components/SourceCodeEditor/SourceCodeEditor' import type { SourceCodeEditorProps } from 'utils/Utils' +import css from './SourceCodeViewer.module.scss' interface MarkdownViewerProps { source: string @@ -13,7 +14,7 @@ export function MarkdownViewer({ source }: MarkdownViewerProps) { const { getString } = useStrings() return ( - + {getString('loading')}}> - + )} @@ -120,6 +121,7 @@ export default function Compare() { sourceBranch={sourceGitRef} emptyTitle={getString('noChanges')} emptyMessage={getString('noChangesCompare')} + onCommentUpdate={noop} // TODO: Update tab stats /> ) diff --git a/web/src/pages/PullRequest/Conversation/Conversation.module.scss b/web/src/pages/PullRequest/Conversation/Conversation.module.scss index f1e080a53..486f126f7 100644 --- a/web/src/pages/PullRequest/Conversation/Conversation.module.scss +++ b/web/src/pages/PullRequest/Conversation/Conversation.module.scss @@ -4,8 +4,15 @@ box-shadow: 0px 0px 1px rgba(40, 41, 61, 0.08), 0px 0.5px 2px rgba(96, 97, 112, 0.16), 0px 0px 1px rgba(40, 41, 61, 0.08), 0px 0.5px 2px rgba(96, 97, 112, 0.16); - border-radius: 5px; - padding: var(--spacing-xlarge) !important; + border-radius: 4px; + padding: var(--spacing-xlarge) var(--spacing-large) !important; +} + +/* TODO: This styling per design is too white */ +.descBox { + box-shadow: 0px 0px 1px rgba(40, 41, 61, 0.08), 0px 0.5px 2px rgba(96, 97, 112, 0.16); + border-radius: 4px; + padding: var(--spacing-xlarge) var(--spacing-large) !important; } .snapshot { @@ -70,7 +77,7 @@ .newCommentCreated { box-shadow: 0px 0px 5px rgb(37 41 192); border-radius: 4px; - transition: box-shadow 1s ease-in-out; + transition: box-shadow 0.5s ease-in-out; &.clear { box-shadow: none; diff --git a/web/src/pages/PullRequest/Conversation/Conversation.module.scss.d.ts b/web/src/pages/PullRequest/Conversation/Conversation.module.scss.d.ts index 6e0b1ac48..8f3eb14d5 100644 --- a/web/src/pages/PullRequest/Conversation/Conversation.module.scss.d.ts +++ b/web/src/pages/PullRequest/Conversation/Conversation.module.scss.d.ts @@ -2,6 +2,7 @@ // this is an auto-generated file declare const styles: { readonly box: string + readonly descBox: string readonly snapshot: string readonly title: string readonly fname: string diff --git a/web/src/pages/PullRequest/Conversation/Conversation.tsx b/web/src/pages/PullRequest/Conversation/Conversation.tsx index e96de3c3f..2d138339a 100644 --- a/web/src/pages/PullRequest/Conversation/Conversation.tsx +++ b/web/src/pages/PullRequest/Conversation/Conversation.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useMemo, useState } from 'react' +import React, { useCallback, useEffect, useMemo, useState } from 'react' import { Avatar, Color, @@ -20,9 +20,8 @@ import { CodeIcon, GitInfoProps } from 'utils/GitUtils' import { MarkdownViewer } from 'components/SourceCodeViewer/SourceCodeViewer' import { useStrings } from 'framework/strings' import { useAppContext } from 'AppContext' -import type { TypesPullReqActivity } from 'services/code' +import type { OpenapiUpdatePullReqRequest, TypesPullReqActivity } from 'services/code' import { CommentAction, CommentBox, CommentBoxOutletPosition, CommentItem } from 'components/CommentBox/CommentBox' -import { PipeSeparator } from 'components/PipeSeparator/PipeSeparator' import { OptionsMenuButton } from 'components/OptionsMenuButton/OptionsMenuButton' import { MarkdownEditorWithPreview } from 'components/MarkdownEditorWithPreview/MarkdownEditorWithPreview' import { useConfirmAct } from 'hooks/useConfirmAction' @@ -33,18 +32,14 @@ import { PullRequestCodeCommentPayload } from 'components/DiffViewer/DiffViewerUtils' import { PullRequestTabContentWrapper } from '../PullRequestTabContentWrapper' -import { PullRequestStatusInfo } from './PullRequestStatusInfo/PullRequestStatusInfo' +import { PullRequestActionsBox } from './PullRequestActionsBox/PullRequestActionsBox' import css from './Conversation.module.scss' interface ConversationProps extends Pick { - refreshPullRequestMetadata: () => void + onCommentUpdate: () => void } -export const Conversation: React.FC = ({ - repoMetadata, - pullRequestMetadata, - refreshPullRequestMetadata -}) => { +export const Conversation: React.FC = ({ repoMetadata, pullRequestMetadata, onCommentUpdate }) => { const { getString } = useStrings() const { currentUser } = useAppContext() const { @@ -62,12 +57,8 @@ export const Conversation: React.FC = ({ // contains a parent comment and multiple replied comments const blocks: CommentItem[][] = [] - if (newComments.length) { - blocks.push(orderBy(newComments, 'edited', 'desc').map(activityToCommentItem)) - } - // Determine all parent activities - const parentActivities = orderBy(activities?.filter(activity => !activity.parent_id) || [], 'edited', 'desc').map( + const parentActivities = orderBy(activities?.filter(activity => !activity.parent_id) || [], 'edited', 'asc').map( _comment => [_comment] ) @@ -84,20 +75,26 @@ export const Conversation: React.FC = ({ blocks.push(parentActivity.map(activityToCommentItem)) }) + if (newComments.length) { + blocks.push(orderBy(newComments, 'edited', 'asc').map(activityToCommentItem)) + } + // Group title-change events into one single block - const titleChangeItems = - blocks.filter( - _activities => isSystemComment(_activities) && _activities[0].payload?.type === CommentType.TITLE_CHANGE - ) || [] + // Disabled for now, @see https://harness.atlassian.net/browse/SCM-79 + // const titleChangeItems = + // blocks.filter( + // _activities => isSystemComment(_activities) && _activities[0].payload?.type === CommentType.TITLE_CHANGE + // ) || [] - titleChangeItems.forEach((value, index) => { - if (index > 0) { - titleChangeItems[0].push(...value) - } - }) - titleChangeItems.shift() + // titleChangeItems.forEach((value, index) => { + // if (index > 0) { + // titleChangeItems[0].push(...value) + // } + // }) + // titleChangeItems.shift() + // return blocks.filter(_activities => !titleChangeItems.includes(_activities)) - return blocks.filter(_activities => !titleChangeItems.includes(_activities)) + return blocks }, [activities, newComments]) const path = useMemo( () => `/api/v1/repos/${repoMetadata.path}/+/pullreq/${pullRequestMetadata.number}/comments`, @@ -108,6 +105,10 @@ export const Conversation: React.FC = ({ const { mutate: deleteComment } = useMutate({ verb: 'DELETE', path: ({ id }) => `${path}/${id}` }) const confirmAct = useConfirmAct() const [commentCreated, setCommentCreated] = useState(false) + const refreshPR = useCallback(() => { + onCommentUpdate() + refetchActivities() + }, [onCommentUpdate, refetchActivities]) useAnimateNewCommentBox(commentCreated, setCommentCreated) @@ -115,20 +116,17 @@ export const Conversation: React.FC = ({ - { - refreshPullRequestMetadata() - refetchActivities() - }} + onPRStateChanged={refreshPR} /> {activityBlocks?.map((blocks, index) => { @@ -145,7 +143,7 @@ export const Conversation: React.FC = ({ = ({ break } + if (result) { + onCommentUpdate() + } + return [result, updatedItem] }} outlets={{ @@ -227,6 +229,11 @@ export const Conversation: React.FC = ({ result = false showError(getErrorMessage(exception), 0) }) + + if (result) { + onCommentUpdate() + } + return [result, updatedItem] }} /> @@ -241,10 +248,10 @@ export const Conversation: React.FC = ({ const DescriptionBox: React.FC = ({ repoMetadata, pullRequestMetadata, - refreshPullRequestMetadata + onCommentUpdate: refreshPullRequestMetadata }) => { const [edit, setEdit] = useState(false) - const [updated, setUpdated] = useState(pullRequestMetadata.edited as number) + // const [updated, setUpdated] = useState(pullRequestMetadata.edited as number) const [originalContent, setOriginalContent] = useState(pullRequestMetadata.description as string) const [content, setContent] = useState(originalContent) const { getString } = useStrings() @@ -259,15 +266,29 @@ const DescriptionBox: React.FC = ({ - - - - {name} - - + + + + + {name} + + + ), + time: ( + + + + ) + }} + /> + + {/* - - + */} = ({ { - mutate({ + const payload: OpenapiUpdatePullReqRequest = { title: pullRequestMetadata.title, description: value - }) + } + mutate(payload) .then(() => { setContent(value) setOriginalContent(value) setEdit(false) - setUpdated(Date.now()) + // setUpdated(Date.now()) refreshPullRequestMetadata() }) .catch(exception => showError(getErrorMessage(exception), 0, getString('pr.failedToUpdate'))) @@ -322,7 +344,7 @@ const DescriptionBox: React.FC = ({ } function isCodeComment(commentItems: CommentItem[]) { - return commentItems[0]?.payload?.payload?.type === CommentType.CODE_COMMENT + return (commentItems[0]?.payload?.payload as Unknown)?.type === CommentType.CODE_COMMENT } interface CodeCommentHeaderProps { @@ -400,6 +422,68 @@ const SystemBox: React.FC = ({ pullRequestMetadata, commentItems ) } + case CommentType.BRANCH_UPDATE: { + return ( + + + + + {payload?.author?.display_name}, + commit: {(payload?.payload as Unknown)?.new} + }} + /> + + + + + + + + ) + } + + case CommentType.STATE_CHANGE: { + const openFromDraft = + (payload?.payload as Unknown)?.old === (payload?.payload as Unknown)?.new && + (payload?.payload as Unknown)?.new === 'open' && + (payload?.payload as Unknown)?.is_draft === false + + return ( + + + + + {payload?.author?.display_name}, + old: {(payload?.payload as Unknown)?.old}, + new: {(payload?.payload as Unknown)?.new} + }} + /> + + + + + + + + ) + } + case CommentType.TITLE_CHANGE: { return ( @@ -412,15 +496,20 @@ const SystemBox: React.FC = ({ pullRequestMetadata, commentItems user: {payload?.author?.display_name}, old: ( - {payload?.payload?.old} + {(payload?.payload as Unknown)?.old} ), - new: {payload?.payload?.new} + new: {(payload?.payload as Unknown)?.new} }} /> - + @@ -435,8 +524,8 @@ const SystemBox: React.FC = ({ pullRequestMetadata, commentItems .filter((_, index) => index > 0) .map( item => - `|${item.author}|${item.payload?.payload?.old}|${ - item.payload?.payload?.new + `|${item.author}|${(item.payload?.payload as Unknown)?.old}|${ + (item.payload?.payload as Unknown)?.new }|${formatDate(item.updated)} ${formatTime(item.updated)}|` ) ) diff --git a/web/src/pages/PullRequest/Conversation/PullRequestActionsBox/PullRequestActionsBox.module.scss b/web/src/pages/PullRequest/Conversation/PullRequestActionsBox/PullRequestActionsBox.module.scss new file mode 100644 index 000000000..568e4ce99 --- /dev/null +++ b/web/src/pages/PullRequest/Conversation/PullRequestActionsBox/PullRequestActionsBox.module.scss @@ -0,0 +1,70 @@ +.main { + --bar-height: 60px; + + background-color: var(--green-50) !important; // #f6fff2 + margin: -24px -24px 0 !important; + position: sticky; + top: 0; + z-index: 2; + + &.merged { + border-color: transparent !important; + background: var(--purple-50) !important; + } + + .layout { + height: var(--bar-height); + padding: 0 var(--spacing-xlarge) !important; + + .secondaryButton, + [class*='Button--variation-tertiary'] { + --box-shadow: 0px 0px 1px rgba(40, 41, 61, 0.04), 0px 2px 4px rgba(96, 97, 112, 0.16) !important; + } + } + + .btn { + background-color: var(--green-800) !important; + color: var(--white) !important; + } + + .heading { + font-weight: 600 !important; + font-size: 16px !important; + line-height: 24px !important; + color: var(--grey-700) !important; + } + + .sub { + font-weight: 500 !important; + font-size: 13px !important; + line-height: 20px !important; + color: var(--grey-500) !important; + } +} + +.popover { + transform: translateY(5px) !important; + + .menuItem { + strong { + display: inline-block; + margin-left: 10px; + } + + p { + font-size: 12px; + padding-left: 27px; + line-height: 16px; + margin: 5px 0; + max-width: 320px; + } + } +} + +.btnWrapper { + button { + --background-color: var(--green-800) !important; + --background-color-hover: var(--green-900) !important; + --background-color-active: var(--green-900) !important; + } +} diff --git a/web/src/pages/PullRequest/Conversation/PullRequestStatusInfo/PullRequestStatusInfo.module.scss.d.ts b/web/src/pages/PullRequest/Conversation/PullRequestActionsBox/PullRequestActionsBox.module.scss.d.ts similarity index 60% rename from web/src/pages/PullRequest/Conversation/PullRequestStatusInfo/PullRequestStatusInfo.module.scss.d.ts rename to web/src/pages/PullRequest/Conversation/PullRequestActionsBox/PullRequestActionsBox.module.scss.d.ts index 825b3d2d1..1a0dd7018 100644 --- a/web/src/pages/PullRequest/Conversation/PullRequestStatusInfo/PullRequestStatusInfo.module.scss.d.ts +++ b/web/src/pages/PullRequest/Conversation/PullRequestActionsBox/PullRequestActionsBox.module.scss.d.ts @@ -3,8 +3,13 @@ declare const styles: { readonly main: string readonly merged: string + readonly layout: string + readonly secondaryButton: string readonly btn: string readonly heading: string readonly sub: string + readonly popover: string + readonly menuItem: string + readonly btnWrapper: string } export default styles diff --git a/web/src/pages/PullRequest/Conversation/PullRequestActionsBox/PullRequestActionsBox.tsx b/web/src/pages/PullRequest/Conversation/PullRequestActionsBox/PullRequestActionsBox.tsx new file mode 100644 index 000000000..075d4e741 --- /dev/null +++ b/web/src/pages/PullRequest/Conversation/PullRequestActionsBox/PullRequestActionsBox.tsx @@ -0,0 +1,244 @@ +import React, { useState } from 'react' +import { + Button, + ButtonVariation, + Color, + Container, + FlexExpander, + Icon, + Layout, + SplitButton, + StringSubstitute, + Text, + useToaster +} from '@harness/uicore' +import { useMutate } from 'restful-react' +import { Case, Else, Match, Render, Truthy } from 'react-jsx-match' +import { Menu, PopoverPosition, Icon as BIcon } from '@blueprintjs/core' +import cx from 'classnames' +import ReactTimeago from 'react-timeago' +import type { EnumMergeMethod, OpenapiMergePullReq, OpenapiStatePullReqRequest, TypesPullReq } from 'services/code' +import { useStrings } from 'framework/strings' +import { CodeIcon, GitInfoProps, PullRequestFilterOption, PullRequestState } from 'utils/GitUtils' +import { getErrorMessage } from 'utils/Utils' +import css from './PullRequestActionsBox.module.scss' + +interface PullRequestActionsBoxProps extends Pick { + onPRStateChanged: () => void +} + +interface PRMergeOption { + method: EnumMergeMethod | 'close' + title: string + desc: string +} + +export const PullRequestActionsBox: React.FC = ({ + repoMetadata, + pullRequestMetadata, + onPRStateChanged +}) => { + const { getString } = useStrings() + const { showError } = useToaster() + const { mutate: mergePR, loading } = useMutate({ + verb: 'POST', + path: `/api/v1/repos/${repoMetadata.path}/+/pullreq/${pullRequestMetadata.number}/merge` + }) + const { mutate: updatePRState, loading: loadingState } = useMutate({ + verb: 'POST', + path: `/api/v1/repos/${repoMetadata.path}/+/pullreq/${pullRequestMetadata.number}/state` + }) + + const isDraft = pullRequestMetadata.is_draft + const mergeOptions: PRMergeOption[] = [ + { + method: 'squash', + title: getString('pr.mergeOptions.squashAndMerge'), + desc: getString('pr.mergeOptions.squashAndMergeDesc') + }, + { + method: 'merge', + title: getString('pr.mergeOptions.createMergeCommit'), + desc: getString('pr.mergeOptions.createMergeCommitDesc') + }, + { + method: 'rebase', + title: getString('pr.mergeOptions.rebaseAndMerge'), + desc: getString('pr.mergeOptions.rebaseAndMergeDesc') + }, + { + method: 'close', + title: getString('pr.mergeOptions.close'), + desc: getString('pr.mergeOptions.closeDesc') + } + ] + const [mergeOption, setMergeOption] = useState(mergeOptions[0]) + + if (pullRequestMetadata.state === PullRequestFilterOption.MERGED) { + return + } + + return ( + + + + + + {getString(isDraft ? 'prState.draftHeading' : 'pr.branchHasNoConflicts')} + + + + + + +