Added: [code-1533] feedback and settings framework for AIDA PR Description (#1087)

This commit is contained in:
Ritik Kapoor 2024-04-16 09:51:42 +00:00 committed by Harness
parent 89dcee0b43
commit bcc1845f0f
11 changed files with 87 additions and 10 deletions

View File

@ -26,7 +26,7 @@ import { currentUserAtom } from 'atoms/currentUser'
import { newCacheStrategy } from 'utils/CacheStrategy' import { newCacheStrategy } from 'utils/CacheStrategy'
import { useGetSettingValue } from 'hooks/useGetSettingValue' import { useGetSettingValue } from 'hooks/useGetSettingValue'
import { useFeatureFlags } from 'hooks/useFeatureFlag' import { useFeatureFlags } from 'hooks/useFeatureFlag'
import { defaultUsefulOrNot } from 'utils/componentMocks/UsefulOrNot' import { defaultUsefulOrNot } from 'components/DefaultUsefulOrNot/UsefulOrNot'
interface AppContextProps extends AppProps { interface AppContextProps extends AppProps {
setAppContext: (value: Partial<AppProps>) => void setAppContext: (value: Partial<AppProps>) => void

View File

@ -21,7 +21,7 @@ import { routes } from 'RouteDefinitions'
import { defaultCurrentUser } from 'AppContext' import { defaultCurrentUser } from 'AppContext'
import { useFeatureFlags } from 'hooks/useFeatureFlag' import { useFeatureFlags } from 'hooks/useFeatureFlag'
import { useGetSettingValue } from 'hooks/useGetSettingValue' import { useGetSettingValue } from 'hooks/useGetSettingValue'
import { defaultUsefulOrNot } from 'utils/componentMocks/UsefulOrNot' import { defaultUsefulOrNot } from 'components/DefaultUsefulOrNot/UsefulOrNot'
import App from './App' import App from './App'
import './bootstrap.scss' import './bootstrap.scss'

View File

@ -280,3 +280,11 @@
background: var(--ai-purple-800) !important; background: var(--ai-purple-800) !important;
font-weight: bold !important; font-weight: bold !important;
} }
.aidaFeedback {
position: absolute !important;
width: 100%;
right: -4.5px;
padding: 5px 10px !important;
margin: 10px 5px !important;
}

View File

@ -16,6 +16,7 @@
/* eslint-disable */ /* eslint-disable */
// This is an auto-generated file // This is an auto-generated file
export declare const aidaFeedback: string
export declare const aidaGenText: string export declare const aidaGenText: string
export declare const buttonsBar: string export declare const buttonsBar: string
export declare const container: string export declare const container: string

View File

@ -32,8 +32,9 @@ import cx from 'classnames'
import { DecorationSet, EditorView, Decoration, keymap } from '@codemirror/view' import { DecorationSet, EditorView, Decoration, keymap } from '@codemirror/view'
import { undo, redo, history } from '@codemirror/commands' import { undo, redo, history } from '@codemirror/commands'
import { EditorSelection, StateEffect, StateField } from '@codemirror/state' import { EditorSelection, StateEffect, StateField } from '@codemirror/state'
import { isEmpty } from 'lodash-es' import { isEmpty, isString } from 'lodash-es'
import { useMutate } from 'restful-react' import { useMutate } from 'restful-react'
import { useAppContext } from 'AppContext'
import { Editor } from 'components/Editor/Editor' import { Editor } from 'components/Editor/Editor'
import { MarkdownViewer } from 'components/MarkdownViewer/MarkdownViewer' import { MarkdownViewer } from 'components/MarkdownViewer/MarkdownViewer'
import { useStrings } from 'framework/strings' import { useStrings } from 'framework/strings'
@ -46,6 +47,8 @@ import {
removeSpecificTextOptimized removeSpecificTextOptimized
} from 'utils/Utils' } from 'utils/Utils'
import { decodeGitContent, handleUpload, normalizeGitRef } from 'utils/GitUtils' import { decodeGitContent, handleUpload, normalizeGitRef } from 'utils/GitUtils'
import { defaultUsefulOrNot } from 'components/DefaultUsefulOrNot/UsefulOrNot'
import { AidaClient } from 'utils/types'
import type { TypesRepository } from 'services/code' import type { TypesRepository } from 'services/code'
import { useEventListener } from 'hooks/useEventListener' import { useEventListener } from 'hooks/useEventListener'
import css from './MarkdownEditorWithPreview.module.scss' import css from './MarkdownEditorWithPreview.module.scss'
@ -179,6 +182,8 @@ export function MarkdownEditorWithPreview({
const [file, setFile] = useState<File>() const [file, setFile] = useState<File>()
const { showError } = useToaster() const { showError } = useToaster()
const [markdownContent, setMarkdownContent] = useState('') const [markdownContent, setMarkdownContent] = useState('')
const { customComponents } = useAppContext()
const AIDAFeedback = customComponents?.UsefulOrNot ? customComponents.UsefulOrNot : defaultUsefulOrNot
const { mutate } = useMutate({ const { mutate } = useMutate({
verb: 'POST', verb: 'POST',
path: `/api/v1/repos/${repoMetadata?.path}/+/genai/change-summary` path: `/api/v1/repos/${repoMetadata?.path}/+/genai/change-summary`
@ -224,7 +229,7 @@ export function MarkdownEditorWithPreview({
) )
useEventListener('mousedown', handleMouseDown) useEventListener('mousedown', handleMouseDown)
const [generating, setGenerating] = useState<boolean>(false)
const dispatchContent = (content: string, userEvent: boolean, decoration = false) => { const dispatchContent = (content: string, userEvent: boolean, decoration = false) => {
const view = viewRef.current const view = viewRef.current
const { from, to } = view?.state.selection.main ?? { from: 0, to: 0 } const { from, to } = view?.state.selection.main ?? { from: 0, to: 0 }
@ -256,12 +261,14 @@ export function MarkdownEditorWithPreview({
useEffect(() => { useEffect(() => {
if (flag) { if (flag) {
if (handleCopilotClick) { if (handleCopilotClick) {
setGenerating(true)
dispatchContent(getString('aidaGenSummary'), false, true) dispatchContent(getString('aidaGenSummary'), false, true)
mutate({ mutate({
head_ref: normalizeGitRef(sourceGitRef), head_ref: normalizeGitRef(sourceGitRef),
base_ref: normalizeGitRef(targetGitRef) base_ref: normalizeGitRef(targetGitRef)
}) })
.then(res => { .then(res => {
setGenerating(false)
setData(res.summary || '') setData(res.summary || '')
}) })
.catch(err => { .catch(err => {
@ -635,6 +642,20 @@ export function MarkdownEditorWithPreview({
</Layout.Horizontal> </Layout.Horizontal>
</Container> </Container>
)} )}
{!isEmpty(data) && !generating && !standalone && (
<AIDAFeedback
className={css.aidaFeedback}
allowCreateTicket={true}
allowFeedback={true}
telemetry={{
aidaClient: AidaClient.CODE_PR_SUMMARY,
metadata: {
query: getString('generateSummary'),
generatedResponse: isString(data) ? data : getString('invalidResponse')
}
}}
/>
)}
</Container> </Container>
) )
} }

View File

@ -259,6 +259,8 @@ export interface StringsMap {
emptyRepoInclude: string emptyRepoInclude: string
emptySpaceText: string emptySpaceText: string
enableAIDAMessage: string enableAIDAMessage: string
enableAIDAPRDescription: string
enableAIDAPRMessange: string
enableAISearch: string enableAISearch: string
enableSSLVerification: string enableSSLVerification: string
enableWebhookContent: string enableWebhookContent: string
@ -344,6 +346,7 @@ export interface StringsMap {
generateCloneCred: string generateCloneCred: string
generateCloneText: string generateCloneText: string
generateHelptext: string generateHelptext: string
generateSummary: string
getMyCloneTitle: string getMyCloneTitle: string
gitIgnore: string gitIgnore: string
gitness: string gitness: string
@ -411,6 +414,7 @@ export interface StringsMap {
'importSpace.title': string 'importSpace.title': string
in: string in: string
inactiveBranches: string inactiveBranches: string
invalidResponse: string
isRequired: string isRequired: string
key: string key: string
'keywordSearch.sampleQueries.searchForClass': string 'keywordSearch.sampleQueries.searchForClass': string

View File

@ -735,6 +735,8 @@ viewFile: View File
searchResult: 'Search Result {count}' searchResult: 'Search Result {count}'
aiSearch: AIDA SEARCH aiSearch: AIDA SEARCH
enableAISearch: Enable AI Search enableAISearch: Enable AI Search
enableAIDAPRDescription: Auto generate PR description
enableAIDAPRMessange: To generate a PR description using Harness AIDA, you need to enable it in the Project Settings.
enableAIDAMessage: In order to use Harness AIDA Search, you will need to enable it in the Project Settings enableAIDAMessage: In order to use Harness AIDA Search, you will need to enable it in the Project Settings
reviewProjectSettings: Review Project Settings reviewProjectSettings: Review Project Settings
turnOnSemanticSearch: Turn on semantic search turnOnSemanticSearch: Turn on semantic search
@ -745,7 +747,6 @@ keywordSearch:
searchForFilesWithCMD: Search for class in files starting with cmd searchForFilesWithCMD: Search for class in files starting with cmd
searchForPattern: Include only results from file paths matching the given search pattern searchForPattern: Include only results from file paths matching the given search pattern
searchForInitialCommit: Search for exact phrase initial commit searchForInitialCommit: Search for exact phrase initial commit
keywordSearchPlaceholder: Search for code or files... keywordSearchPlaceholder: Search for code or files...
codeSearch: Code Search codeSearch: Code Search
codeSearchModal: Begin search by describing what you are looking for codeSearchModal: Begin search by describing what you are looking for
@ -1005,6 +1006,8 @@ reqChanges: 'Request changes'
summary: Summary summary: Summary
prGenSummary: AIDA generate PR summary, insert at the cursor or replace selected text only. prGenSummary: AIDA generate PR summary, insert at the cursor or replace selected text only.
aidaGenSummary: 'AIDA is generating a summary...' aidaGenSummary: 'AIDA is generating a summary...'
generateSummary: Generate PR Summary
invalidResponse: Invalid summary response generated
importFailed: Import Failed importFailed: Import Failed
uploadAFileError: There is no image or video uploaded. Please upload an image or video. uploadAFileError: There is no image or video uploaded. Please upload an image or video.
securitySettings: securitySettings:

View File

@ -33,7 +33,7 @@ import {
import { Color, FontVariation } from '@harnessio/design-system' import { Color, FontVariation } from '@harnessio/design-system'
import { PopoverPosition } from '@blueprintjs/core' import { PopoverPosition } from '@blueprintjs/core'
import cx from 'classnames' import cx from 'classnames'
import { useHistory } from 'react-router-dom' import { Link, useHistory, useParams } from 'react-router-dom'
import { useGet, useMutate } from 'restful-react' import { useGet, useMutate } from 'restful-react'
import { useAppContext } from 'AppContext' import { useAppContext } from 'AppContext'
import { useGetRepositoryMetadata } from 'hooks/useGetRepositoryMetadata' import { useGetRepositoryMetadata } from 'hooks/useGetRepositoryMetadata'
@ -63,10 +63,21 @@ import { CompareContentHeader, PRCreationType } from './CompareContentHeader/Com
import { CompareCommits } from './CompareCommits' import { CompareCommits } from './CompareCommits'
import css from './Compare.module.scss' import css from './Compare.module.scss'
interface Identifier {
accountId: string
orgIdentifier: string
projectIdentifier: string
}
export default function Compare() { export default function Compare() {
const { routes, standalone, hooks, routingId } = useAppContext() const { routes, standalone, hooks, routingId, defaultSettingsURL } = useAppContext()
const [flag, setFlag] = useState(false) const [flag, setFlag] = useState(false)
const { SEMANTIC_SEARCH_ENABLED } = hooks?.useFeatureFlags() const { SEMANTIC_SEARCH_ENABLED } = hooks?.useFeatureFlags()
const { orgIdentifier, projectIdentifier } = useParams<Identifier>()
const { data: aidaSettingResponse, loading: isAidaSettingLoading } = hooks?.useGetSettingValue({
identifier: 'aida',
queryParams: { accountIdentifier: routingId, orgIdentifier, projectIdentifier }
})
const { getString } = useStrings() const { getString } = useStrings()
const history = useHistory() const history = useHistory()
const { repoMetadata, error, loading, diffRefs } = useGetRepositoryMetadata() const { repoMetadata, error, loading, diffRefs } = useGetRepositoryMetadata()
@ -304,7 +315,10 @@ export default function Compare() {
outlets={{ outlets={{
[CommentBoxOutletPosition.START_OF_MARKDOWN_EDITOR_TOOLBAR]: ( [CommentBoxOutletPosition.START_OF_MARKDOWN_EDITOR_TOOLBAR]: (
<> <>
{SEMANTIC_SEARCH_ENABLED && !standalone ? ( {!isAidaSettingLoading &&
aidaSettingResponse?.data?.value == 'true' &&
SEMANTIC_SEARCH_ENABLED &&
!standalone ? (
<Button <Button
size={ButtonSize.SMALL} size={ButtonSize.SMALL}
variation={ButtonVariation.ICON} variation={ButtonVariation.ICON}
@ -338,6 +352,31 @@ export default function Compare() {
) )
}} }}
/> />
{aidaSettingResponse?.data?.value != 'true' &&
SEMANTIC_SEARCH_ENABLED &&
!isAidaSettingLoading && (
<Container
background={Color.AI_PURPLE_50}
padding="small"
margin={{ top: 'xsmall', right: 'small', left: 'xsmall' }}>
<Text
font={{ variation: FontVariation.BODY2 }}
margin={{ bottom: 'small' }}
icon="info-messaging"
iconProps={{ size: 15 }}>
{getString('enableAIDAPRDescription')}
</Text>
<Text
font={{ variation: FontVariation.BODY2_SEMI }}
margin={{ bottom: 'small' }}
color={Color.GREY_450}>
{getString('enableAIDAPRMessange')}
</Text>
<Link to={defaultSettingsURL} color={Color.AI_PURPLE_800}>
{getString('reviewProjectSettings')}
</Link>
</Container>
)}
</Layout.Vertical> </Layout.Vertical>
</Container> </Container>
</Layout.Vertical> </Layout.Vertical>

View File

@ -49,7 +49,7 @@ import KeywordSearchbar from 'components/KeywordSearchbar/KeywordSearchbar'
import { LoadingSpinner } from 'components/LoadingSpinner/LoadingSpinner' import { LoadingSpinner } from 'components/LoadingSpinner/LoadingSpinner'
import { SEARCH_MODE } from 'components/CodeSearch/CodeSearch' import { SEARCH_MODE } from 'components/CodeSearch/CodeSearch'
import CodeSearchBar from 'components/CodeSearchBar/CodeSearchBar' import CodeSearchBar from 'components/CodeSearchBar/CodeSearchBar'
import { defaultUsefulOrNot } from 'utils/componentMocks/UsefulOrNot' import { defaultUsefulOrNot } from 'components/DefaultUsefulOrNot/UsefulOrNot'
import { AidaClient } from 'utils/types' import { AidaClient } from 'utils/types'
import KeywordSearchFilters from './KeywordSearchFilters' import KeywordSearchFilters from './KeywordSearchFilters'
import type { FileMatch, KeywordSearchResponse } from './CodeSearchPage.types' import type { FileMatch, KeywordSearchResponse } from './CodeSearchPage.types'

View File

@ -27,7 +27,8 @@ export interface DiffFileEntry extends DiffFile {
export type FCWithChildren<T> = React.FC<React.PropsWithChildren<T>> export type FCWithChildren<T> = React.FC<React.PropsWithChildren<T>>
export enum AidaClient { export enum AidaClient {
CODE_SEMANTIC_SEARCH = 'CODE_SEMANTIC_SEARCH' CODE_SEMANTIC_SEARCH = 'CODE_SEMANTIC_SEARCH',
CODE_PR_SUMMARY = 'CODE_PR_SUMMARY'
} }
export interface TelemeteryProps { export interface TelemeteryProps {
aidaClient: AidaClient aidaClient: AidaClient