Merge branch 'main' of github.com:harness/gitness

This commit is contained in:
Enver Bisevac 2022-11-09 00:11:57 +01:00
commit df76498bf5
12 changed files with 266 additions and 185 deletions

View File

@ -1,17 +1,17 @@
const path = require('path'); const path = require('path')
const webpack = require('webpack') const webpack = require('webpack')
const { const {
container: { ModuleFederationPlugin }, container: { ModuleFederationPlugin },
DefinePlugin DefinePlugin
} = require('webpack'); } = require('webpack')
const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin'); const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin')
const GenerateStringTypesPlugin = require('../scripts/webpack/GenerateStringTypesPlugin').GenerateStringTypesPlugin const GenerateStringTypesPlugin = require('../scripts/webpack/GenerateStringTypesPlugin').GenerateStringTypesPlugin
const { RetryChunkLoadPlugin } = require('webpack-retry-chunk-load-plugin') const { RetryChunkLoadPlugin } = require('webpack-retry-chunk-load-plugin')
const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin')
const moduleFederationConfig = require('./moduleFederation.config'); const moduleFederationConfig = require('./moduleFederation.config')
const CONTEXT = process.cwd(); const CONTEXT = process.cwd()
const DEV = process.env.NODE_ENV === 'development' const DEV = process.env.NODE_ENV === 'development'
const ON_PREM = `${process.env.ON_PREM}` === 'true' const ON_PREM = `${process.env.ON_PREM}` === 'true'
@ -150,8 +150,7 @@ module.exports = {
}, },
resolve: { resolve: {
extensions: ['.mjs', '.js', '.ts', '.tsx', '.json', '.ttf', '.scss'], extensions: ['.mjs', '.js', '.ts', '.tsx', '.json', '.ttf', '.scss'],
plugins: [ plugins: [new TsconfigPathsPlugin()]
new TsconfigPathsPlugin()]
}, },
plugins: [ plugins: [
new ModuleFederationPlugin(moduleFederationConfig), new ModuleFederationPlugin(moduleFederationConfig),
@ -164,5 +163,71 @@ module.exports = {
new RetryChunkLoadPlugin({ new RetryChunkLoadPlugin({
maxRetries: 2 maxRetries: 2
}), }),
new MonacoWebpackPlugin({
// available options are documented at https://github.com/Microsoft/monaco-editor-webpack-plugin#options
languages: [
'abap',
'apex',
'azcli',
'bat',
'cameligo',
'clojure',
'coffee',
'cpp',
'csharp',
'csp',
'css',
'dockerfile',
'fsharp',
'go',
'graphql',
'handlebars',
'html',
'ini',
'java',
'javascript',
'json',
'kotlin',
'less',
'lua',
'markdown',
'mips',
'msdax',
'mysql',
'objective-c',
'pascal',
'pascaligo',
'perl',
'pgsql',
'php',
'postiats',
'powerquery',
'powershell',
'pug',
'python',
'r',
'razor',
'redis',
'redshift',
'restructuredtext',
'ruby',
'rust',
'sb',
'scheme',
'scss',
'shell',
'solidity',
'sophia',
'sql',
'st',
'swift',
'tcl',
'twig',
'typescript',
'vb',
'xml',
'yaml'
] ]
}; })
]
}

View File

@ -12,7 +12,6 @@ const {
WatchIgnorePlugin, WatchIgnorePlugin,
container: { ModuleFederationPlugin } container: { ModuleFederationPlugin }
} = require('webpack') } = require('webpack')
const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin')
const commonConfig = require('./webpack.common') const commonConfig = require('./webpack.common')
const baseUrl = process.env.BASE_URL ?? 'https://qa.harness.io/gateway' const baseUrl = process.env.BASE_URL ?? 'https://qa.harness.io/gateway'
@ -61,72 +60,6 @@ const devConfig = {
new DefinePlugin({ new DefinePlugin({
'process.env': '{}', // required for @blueprintjs/core 'process.env': '{}', // required for @blueprintjs/core
__DEV__: DEV __DEV__: DEV
}),
new MonacoWebpackPlugin({
// available options are documented at https://github.com/Microsoft/monaco-editor-webpack-plugin#options
languages: [
'abap',
'apex',
'azcli',
'bat',
'cameligo',
'clojure',
'coffee',
'cpp',
'csharp',
'csp',
'css',
'dockerfile',
'fsharp',
'go',
'graphql',
'handlebars',
'html',
'ini',
'java',
'javascript',
'json',
'kotlin',
'less',
'lua',
'markdown',
'mips',
'msdax',
'mysql',
'objective-c',
'pascal',
'pascaligo',
'perl',
'pgsql',
'php',
'postiats',
'powerquery',
'powershell',
'pug',
'python',
'r',
'razor',
'redis',
'redshift',
'restructuredtext',
'ruby',
'rust',
'sb',
'scheme',
'scss',
'shell',
'solidity',
'sophia',
'sql',
'st',
'swift',
'tcl',
'twig',
'typescript',
'vb',
'xml',
'yaml'
]
}) })
// new ForkTsCheckerWebpackPlugin() // new ForkTsCheckerWebpackPlugin()
// new WatchIgnorePlugin({ // new WatchIgnorePlugin({

View File

@ -0,0 +1,102 @@
import React, { useEffect } from 'react'
import { Container } from '@harness/uicore'
import type monacoEditor from 'monaco-editor/esm/vs/editor/editor.api'
import MonacoEditor from 'react-monaco-editor'
import { noop } from 'lodash-es'
import type { SourceCodeEditorProps } from 'utils/Utils'
export const MonacoEditorOptions = {
ignoreTrimWhitespace: true,
minimap: { enabled: false },
codeLens: false,
scrollBeyondLastLine: false,
smartSelect: false,
tabSize: 2,
insertSpaces: true,
overviewRulerBorder: false,
automaticLayout: true
}
const diagnosticsOptions = {
noSemanticValidation: true,
noSyntaxValidation: true
}
const compilerOptions = {
jsx: 'react',
noLib: true,
allowNonTsExtensions: true
}
function autoAdjustEditorHeight(editor: monacoEditor.editor.IStandaloneCodeEditor) {
// Adjust editor height based on its content
// https://github.com/microsoft/monaco-editor/issues/794#issuecomment-427092969
const LINE_HEIGHT = 18
const CONTAINER_GUTTER = 10
const editorNode = editor.getDomNode() as HTMLElement
const codeContainer = editorNode.getElementsByClassName('view-lines')[0]
let prevLineCount = 0
const adjustHeight = (): void => {
const _height =
codeContainer.childElementCount > prevLineCount
? (codeContainer as HTMLElement).offsetHeight // unfold
: codeContainer.childElementCount * LINE_HEIGHT + CONTAINER_GUTTER // fold
prevLineCount = codeContainer.childElementCount
editorNode.style.height = Math.max(_height, 100) + 'px'
editor.layout()
}
setTimeout(adjustHeight, 0)
editor.onDidChangeModelDecorations(() => setTimeout(adjustHeight, 0))
}
const toOnOff = (flag: boolean) => (flag ? 'on' : 'off')
export default function MonacoSourceCodeEditor({
source,
language = 'plaintext',
lineNumbers = true,
readOnly = false,
className,
height,
autoHeight,
wordWrap = true,
onChange = noop
}: SourceCodeEditorProps) {
const scrollbar = autoHeight ? 'hidden' : 'auto'
useEffect(() => {
monaco.languages.typescript.typescriptDefaults.setDiagnosticsOptions?.(diagnosticsOptions)
monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions?.(diagnosticsOptions)
monaco.languages.typescript.typescriptDefaults.setCompilerOptions(compilerOptions)
}, [])
return (
<Container className={className}>
<MonacoEditor
language={language}
theme="vs-light"
value={source}
height={height}
options={{
...MonacoEditorOptions,
readOnly,
wordWrap: toOnOff(wordWrap),
lineNumbers: toOnOff(lineNumbers),
scrollbar: {
vertical: scrollbar,
horizontal: scrollbar,
alwaysConsumeMouseWheel: false
}
}}
editorDidMount={editor => {
if (autoHeight) {
autoAdjustEditorHeight(editor)
}
}}
onChange={onChange}
/>
</Container>
)
}

View File

@ -1,79 +1,17 @@
import React, { useRef } from 'react' import React, { lazy, Suspense } from 'react'
import { Container } from '@harness/uicore' import { Text } from '@harness/uicore'
import MonacoEditor from 'react-monaco-editor' import type { SourceCodeEditorProps } from 'utils/Utils'
import { MonacoEditorOptions } from 'utils/Utils' import { useStrings } from 'framework/strings'
export interface SourceCodeEditorProps { function Editor(props: SourceCodeEditorProps) {
source: string const { getString } = useStrings()
language?: string const MonacoSourceCodeEditor = lazy(() => import('./MonacoSourceCodeEditor'))
lineNumbers?: boolean
readOnly?: boolean
highlightLines?: string // i.e: {1,3-4}, TODO: not yet supported
className?: string
height?: number | string
autoHeight?: boolean
}
function MonacoSourceCodeEditor({
source,
language = 'plaintext',
lineNumbers = true,
readOnly = false,
className,
height,
autoHeight
}: SourceCodeEditorProps) {
const inputContainerRef = useRef<HTMLDivElement>(null)
const scrollbar = autoHeight ? 'hidden' : 'auto'
// return <Container ref={inputContainerRef} className={className}> </Container>
return ( return (
<Container ref={inputContainerRef} className={className}> <Suspense fallback={<Text>{getString('loading')}</Text>}>
<MonacoEditor <MonacoSourceCodeEditor {...props} />
language={language} </Suspense>
theme="vs-light"
value={source}
height={height}
options={{
...MonacoEditorOptions,
...(autoHeight ? {} : { scrollBeyondLastLine: false }),
automaticLayout: true,
readOnly,
wordWrap: 'on',
lineNumbers: lineNumbers ? 'on' : 'off',
scrollbar: {
vertical: scrollbar,
horizontal: scrollbar,
alwaysConsumeMouseWheel: false
}
}}
editorDidMount={editor => {
if (autoHeight) {
// Aadjust editor height based on content
// https://github.com/microsoft/monaco-editor/issues/794#issuecomment-427092969
const LINE_HEIGHT = 18
const CONTAINER_GUTTER = 10
const editorNode = editor.getDomNode() as HTMLElement
const codeContainer = editorNode.getElementsByClassName('view-lines')[0]
let prevLineCount = 0
const adjustHeight = (): void => {
const _height =
codeContainer.childElementCount > prevLineCount
? (codeContainer as HTMLElement).offsetHeight // unfold
: codeContainer.childElementCount * LINE_HEIGHT + CONTAINER_GUTTER // fold
prevLineCount = codeContainer.childElementCount
editorNode.style.height = Math.max(_height, 100) + 'px'
editor.layout()
}
setTimeout(adjustHeight, 0)
editor.onDidChangeModelDecorations(() => setTimeout(adjustHeight, 0))
}
}}
/>
</Container>
) )
} }
export const SourceCodeEditor = React.memo(MonacoSourceCodeEditor) export const SourceCodeEditor = React.memo(Editor)

View File

@ -36,9 +36,12 @@ export interface StringsMap {
failedToCreateRepo: string failedToCreateRepo: string
files: string files: string
history: string history: string
identifier: string
in: string
inactiveBranches: string inactiveBranches: string
loading: string loading: string
name: string name: string
nameYourFile: string
newFile: string newFile: string
newFolder: string newFolder: string
newRepo: string newRepo: string

View File

@ -32,6 +32,7 @@ create: Create
clone: Clone clone: Clone
copy: Copy copy: Copy
defaultBranch: Default defaultBranch: Default
in: in
ok: OK ok: OK
loading: Loading... loading: Loading...
addGitIgnore: Add a .gitignore addGitIgnore: Add a .gitignore
@ -51,6 +52,7 @@ createBranch: + Create Branch
searchBranches: Search branches searchBranches: Search branches
updated: Updated updated: Updated
cloneHTTPS: Clone with HTTPS cloneHTTPS: Clone with HTTPS
nameYourFile: Name your file...
repos: repos:
name: Repo Name name: Repo Name
data: Repo Data data: Repo Data

View File

@ -129,9 +129,7 @@ export function ContentHeader({ repoMetadata, gitRef, resourcePath = '' }: Conte
variation={ButtonVariation.SECONDARY} variation={ButtonVariation.SECONDARY}
icon="main-clone" icon="main-clone"
iconProps={{ size: 10 }} iconProps={{ size: 10 }}
tooltip={ tooltip={<CloneButtonTooltip httpsURL={`http://localhost:3000/${repoMetadata.path}.git`} />}
<CloneButtonTooltip httpsURL={'https://localhost:8181/kmpySmUISimoRrJL6NL73w/default/scm1/policy-mgmt'} />
}
tooltipProps={{ tooltipProps={{
interactionKind: 'click', interactionKind: 'click',
minimal: true, minimal: true,

View File

@ -7,13 +7,28 @@
overflow: hidden; overflow: hidden;
.heading { .heading {
border-top-left-radius: 4px; // border-top-left-radius: 4px;
border-top-right-radius: 4px; // border-top-right-radius: 4px;
align-items: center; align-items: center;
padding: 0 var(--spacing-xlarge) !important; padding: 0 var(--spacing-xlarge) !important;
height: 52px; height: 52px;
background-color: var(--grey-100); background-color: var(--grey-100);
box-shadow: 0px 0px 1px rgba(40, 41, 61, 0.08), 0px 0.5px 2px rgba(96, 97, 112, 0.16); box-shadow: 0px 0px 1px rgba(40, 41, 61, 0.08), 0px 0.5px 2px rgba(96, 97, 112, 0.16);
border-bottom: 1px solid var(--grey-200);
.path {
align-items: center;
.inputContainer {
margin-left: var(--spacing-small) !important;
margin-bottom: 0;
input {
padding: var(--spacing-xsmall) var(--spacing-small);
height: 28px;
}
}
}
} }
.content { .content {
@ -21,8 +36,7 @@
overflow: hidden; overflow: hidden;
.editorContainer { .editorContainer {
height: calc(100vh - 95px - 52px); height: calc(100vh - 96px - 52px);
background-color: red;
overflow: hidden; overflow: hidden;
} }
} }

View File

@ -3,6 +3,8 @@
declare const styles: { declare const styles: {
readonly container: string readonly container: string
readonly heading: string readonly heading: string
readonly path: string
readonly inputContainer: string
readonly content: string readonly content: string
readonly editorContainer: string readonly editorContainer: string
} }

View File

@ -8,10 +8,12 @@ import {
FlexExpander, FlexExpander,
Icon, Icon,
Layout, Layout,
Text Text,
TextInput
} from '@harness/uicore' } from '@harness/uicore'
import { Link } from 'react-router-dom' import { Link } from 'react-router-dom'
import ReactJoin from 'react-join' import ReactJoin from 'react-join'
import cx from 'classnames'
import { SourceCodeEditor } from 'components/SourceCodeEditor/SourceCodeEditor' import { SourceCodeEditor } from 'components/SourceCodeEditor/SourceCodeEditor'
import type { OpenapiGetContentOutput, RepoFileContent, TypesRepository } from 'services/scm' import type { OpenapiGetContentOutput, RepoFileContent, TypesRepository } from 'services/scm'
import { useAppContext } from 'AppContext' import { useAppContext } from 'AppContext'
@ -22,7 +24,7 @@ import css from './FileEditor.module.scss'
interface FileEditorProps { interface FileEditorProps {
repoMetadata: TypesRepository repoMetadata: TypesRepository
gitRef?: string gitRef: string
resourcePath?: string resourcePath?: string
contentInfo: OpenapiGetContentOutput contentInfo: OpenapiGetContentOutput
} }
@ -30,12 +32,13 @@ interface FileEditorProps {
export function FileEditor({ contentInfo, repoMetadata, gitRef, resourcePath = '' }: FileEditorProps) { export function FileEditor({ contentInfo, repoMetadata, gitRef, resourcePath = '' }: FileEditorProps) {
const { getString } = useStrings() const { getString } = useStrings()
const { routes } = useAppContext() const { routes } = useAppContext()
const language = filenameToLanguage(contentInfo?.name)
return ( return (
<Container className={css.container}> <Container className={css.container}>
<Layout.Horizontal className={css.heading}> <Layout.Horizontal className={css.heading}>
<Container> <Container>
<Layout.Horizontal spacing="small"> <Layout.Horizontal spacing="small" className={css.path}>
<Link to={routes.toSCMRepository({ repoPath: repoMetadata.path as string, gitRef })}> <Link to={routes.toSCMRepository({ repoPath: repoMetadata.path as string, gitRef })}>
<Icon name="main-folder" /> <Icon name="main-folder" />
</Link> </Link>
@ -44,7 +47,7 @@ export function FileEditor({ contentInfo, repoMetadata, gitRef, resourcePath = '
{resourcePath.split('/').map((_path, index, paths) => { {resourcePath.split('/').map((_path, index, paths) => {
const pathAtIndex = paths.slice(0, index + 1).join('/') const pathAtIndex = paths.slice(0, index + 1).join('/')
return ( return index < paths.length - 1 ? (
<Link <Link
key={_path + index} key={_path + index}
to={routes.toSCMRepository({ to={routes.toSCMRepository({
@ -54,9 +57,25 @@ export function FileEditor({ contentInfo, repoMetadata, gitRef, resourcePath = '
})}> })}>
<Text color={Color.GREY_900}>{_path}</Text> <Text color={Color.GREY_900}>{_path}</Text>
</Link> </Link>
) : (
<TextInput
key={_path + index}
autoFocus
value={_path || ''}
wrapperClassName={css.inputContainer}
placeholder={getString('nameYourFile')}
/>
) )
})} })}
</ReactJoin> </ReactJoin>
<Text color={Color.GREY_900}>{getString('in')}</Text>
<Link
to={routes.toSCMRepository({
repoPath: repoMetadata.path as string,
gitRef
})}>
{gitRef}
</Link>
</Layout.Horizontal> </Layout.Horizontal>
</Container> </Container>
<FlexExpander /> <FlexExpander />
@ -69,16 +88,14 @@ export function FileEditor({ contentInfo, repoMetadata, gitRef, resourcePath = '
/> />
</Layout.Horizontal> </Layout.Horizontal>
{(contentInfo?.content as RepoFileContent)?.data && ( <Container className={cx(css.content, language)}>
<Container className={css.content}>
<SourceCodeEditor <SourceCodeEditor
className={css.editorContainer} className={css.editorContainer}
height="100%" height="100%"
language={filenameToLanguage(contentInfo?.name)} language={language}
source={window.atob((contentInfo?.content as RepoFileContent)?.data || '')} source={window.atob((contentInfo?.content as RepoFileContent)?.data || '')}
/> />
</Container> </Container>
)}
</Container> </Container>
) )
} }

View File

@ -1,7 +1,6 @@
import React from 'react' import React from 'react'
import { Container } from '@harness/uicore' import { Container } from '@harness/uicore'
import type { TypesRepository } from 'services/scm' import type { TypesRepository } from 'services/scm'
import { isFile } from 'utils/GitUtils'
import { useGetResourceContent } from 'hooks/useGetResourceContent' import { useGetResourceContent } from 'hooks/useGetResourceContent'
import { FileEditor } from '../FileEditor/FileEditor' import { FileEditor } from '../FileEditor/FileEditor'
import css from './RepositoryFileEditContent.module.scss' import css from './RepositoryFileEditContent.module.scss'
@ -19,10 +18,10 @@ export function RepositoryFileEditContent({ repoMetadata, gitRef, resourcePath }
return ( return (
<Container className={css.resourceContent}> <Container className={css.resourceContent}>
{data && isFile(data) && ( {data && (
<FileEditor <FileEditor
repoMetadata={repoMetadata} repoMetadata={repoMetadata}
gitRef={gitRef || repoMetadata.defaultBranch} gitRef={gitRef || (repoMetadata.defaultBranch as string)}
resourcePath={resourcePath} resourcePath={resourcePath}
contentInfo={data} contentInfo={data}
/> />

View File

@ -26,15 +26,17 @@ export function showToaster(message: string, props?: Partial<IToastProps>): IToa
export const getErrorMessage = (error: Unknown): string => export const getErrorMessage = (error: Unknown): string =>
get(error, 'data.error', get(error, 'data.message', error?.message)) get(error, 'data.error', get(error, 'data.message', error?.message))
export const MonacoEditorOptions = { export interface SourceCodeEditorProps {
ignoreTrimWhitespace: true, source: string
minimap: { enabled: false }, language?: string
codeLens: false, lineNumbers?: boolean
scrollBeyondLastLine: false, readOnly?: boolean
smartSelect: false, highlightLines?: string // i.e: {1,3-4}, TODO: not yet supported
tabSize: 2, className?: string
insertSpaces: true, height?: number | string
overviewRulerBorder: false autoHeight?: boolean
wordWrap?: boolean
onChange?: (value: string) => void
} }
// Monaco editor has a bug where when its value is set, the value // Monaco editor has a bug where when its value is set, the value
@ -157,10 +159,16 @@ const MONACO_SUPPORTED_LANGUAGES = [
'yaml' 'yaml'
] ]
const EXTENSION_TO_LANG: Record<string, string> = {
tsx: 'typescript',
jsx: 'typescript'
}
export const filenameToLanguage = (name?: string): string | undefined => { export const filenameToLanguage = (name?: string): string | undefined => {
const map = langMap.languages(name?.split('.').pop() || '') const extension = name?.split('.').pop() || ''
const map = langMap.languages(extension)
if (map?.length) { if (map?.length) {
return MONACO_SUPPORTED_LANGUAGES.find(lang => map.includes(lang)) return MONACO_SUPPORTED_LANGUAGES.find(lang => map.includes(lang)) || EXTENSION_TO_LANG[extension]
} }
} }