Skip to content
This repository was archived by the owner on Jan 22, 2019. It is now read-only.

feat(review-board): add review board support #98

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 18 additions & 4 deletions app/components/CodeIntelStatusIndicator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ export class CodeIntelStatusIndicator extends React.Component<
<>
<h3>No language server connected</h3>
Check{' '}
<a href="http://langserver.org/" target="_blank">
<a className="text-primary" href="http://langserver.org/" target="_blank">
langserver.org
</a>{' '}
for {language} language servers
Expand Down Expand Up @@ -248,7 +248,11 @@ export class CodeIntelStatusIndicator extends React.Component<
<>
<h3>
Connected to the <wbr />
<a href={this.state.langServerOrError.homepageURL} target="_blank">
<a
className="text-primary"
href={this.state.langServerOrError.homepageURL}
target="_blank"
>
{this.state.langServerOrError.displayName || language} language server
</a>
</h3>
Expand Down Expand Up @@ -307,18 +311,28 @@ export class CodeIntelStatusIndicator extends React.Component<
)}
{this.props.userIsSiteAdmin && (
<p className="mt-2 mb-0">
<a href={`${sourcegraphUrl}/site-admin/code-intelligence`}>Manage</a>
<a
className="text-primary"
href={`${sourcegraphUrl}/site-admin/code-intelligence`}
>
Manage
</a>
</p>
)}
{this.state.langServerOrError.issuesURL && (
<p className="mt-2 mb-0">
<a href={this.state.langServerOrError.issuesURL} target="_blank">
<a
className="text-primary"
href={this.state.langServerOrError.issuesURL}
target="_blank"
>
Report issue
</a>
</p>
)}
<p className="mt-2 mb-0">
<a
className="text-primary"
onClick={this.toggleCodeIntelligence}
style={{ cursor: 'pointer' }}
target="_blank"
Expand Down
1 change: 1 addition & 0 deletions app/components/codeIntelStatusIndicator.scss
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

&__unstyled-list {
list-style-type: none;
padding: 0;
}
}

Expand Down
5 changes: 2 additions & 3 deletions app/repo/backend.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ export const resolveRepo = memoizeObservable(
getContext({ repoKey: ctx.repoPath }),
`query ResolveRepo($repoPath: String!) {
repository(uri: $repoPath) {
url
name
}
}`,
{ ...ctx }
Expand All @@ -109,8 +109,7 @@ export const resolveRepo = memoizeObservable(
if (!result.data || !result.data.repository) {
throw new RepoNotFoundError(ctx.repoPath)
}

return result.data.repository.url
return result.data.repository.name
}, catchError((err, caught) => caught))
),
makeRepoURI
Expand Down
19 changes: 19 additions & 0 deletions app/repo/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,25 @@ export function makeRepoURI(parsed: ParsedRepoURI): RepoURI {
return uri
}

export function normalizeRepoPath(origin: string): string {
let repoPath = origin
repoPath = repoPath.replace('\\', '')
if (origin.startsWith('git@')) {
repoPath = origin.substr('git@'.length)
repoPath = repoPath.replace(':', '/')
} else if (origin.startsWith('git://')) {
repoPath = origin.substr('git://'.length)
} else if (origin.startsWith('https://')) {
repoPath = origin.substr('https://'.length)
} else if (origin.includes('@')) {
// Assume the origin looks like `username@host:repo/path`
const split = origin.split('@')
repoPath = split[1]
repoPath = repoPath.replace(':', '/')
}
return repoPath.replace(/.git$/, '')
}

/**
* A file at an exact commit of a known programming language
*/
Expand Down
19 changes: 19 additions & 0 deletions app/review-board/backend/fetch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { from, Observable } from 'rxjs'
import { ReviewBoardRepository } from '../util'

function getRepository(repoID: number): Promise<ReviewBoardRepository> {
return new Promise((resolve, reject) => {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You shouldn't use the Promise constructor here, fetch already returns a Promise.

fetch(`${window.location.origin}/api/repositories/${repoID}/`, {
method: 'GET',
credentials: 'include',
headers: new Headers({ Accept: 'application/json' }),
})
.then(resp => resp.json())
.then(resp => resolve(resp.repository))
.catch(err => reject(err))
})
}

export function getRepositoryFromReviewBoardAPI(repoID: number): Observable<ReviewBoardRepository> {
return from(getRepository(repoID))
}
119 changes: 119 additions & 0 deletions app/review-board/dom/util.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import { DiffPart, DOMFunctions } from '@sourcegraph/codeintellify'

/**
* Gets the `<td>` element for a target that contains the code
*/
const getCodeCellFromTarget = (target: HTMLElement): HTMLElement | null => {
if (target.nodeName === 'PRE') {
return null
}
const pre = target.closest('pre') as HTMLElement
if (!pre) {
return null
}
if (target.innerText.trim().length === 0) {
return null
}
const closest = (target.closest('.l') || target.closest('.r')) as HTMLElement
if (!closest.classList.contains('trimmed') && closest.classList.contains('annotated')) {
const firstChild = closest.firstElementChild
if (
firstChild &&
firstChild.nodeName === 'SPAN' &&
firstChild.textContent &&
firstChild.innerHTML.trim().length === 0
) {
const newElement = document.createElement('span')
newElement.innerHTML = ' '
closest.replaceChild(newElement, firstChild)
}
closest.classList.add('trimmed')
}
return closest
}

const getBlobCodeInner = (codeCell: HTMLElement) => {
if (codeCell.classList.contains('l') || codeCell.classList.contains('r')) {
return codeCell
}
return (codeCell.closest('.l') || codeCell.closest('.r')) as HTMLElement
}

/**
* Gets the line number for a given code element on unified diff, split diff and blob views
*/
const getLineNumberFromCodeElement = (codeElement: HTMLElement): number => {
// In diff views, the code element is the `<span>` inside the cell
// On blob views, the code element is the `<td>` itself, so `closest()` will simply return it
// Walk all previous sibling cells until we find one with the line number
let cell = codeElement.closest('td') as HTMLElement
while (cell) {
if (cell.nodeName === 'TH') {
return parseInt(cell.innerText, 10)
}
cell = cell.previousElementSibling as HTMLTableRowElement
}

cell = codeElement.closest('tr')! as HTMLTableRowElement
if (cell.getAttribute('line')) {
return parseInt(cell.getAttribute('line')!, 10)
}
throw new Error('Could not find a line number in any cell')
}

/**
* getDeltaFileName returns the path of the file container. Reviewboard will always be a diff.
*/
export function getDeltaFileName(container: HTMLElement): { headFilePath: string; baseFilePath: string } {
const info = container.querySelector('.filename-row') as HTMLElement
if (!info) {
throw new Error(`Unable to getDeltaFileName for container: ${container}`)
}
return { headFilePath: info.innerText, baseFilePath: info.innerText }
}

const getDiffCodePart = (codeElement: HTMLElement): DiffPart => {
const td = codeElement.closest('td')!
// If there are more cells on the right, this is the base, otherwise the head
return td.classList.contains('l') ? 'base' : 'head'
}

/**
* Implementations of the DOM functions for diff code views
*/
export const diffDomFunctions: DOMFunctions = {
getCodeElementFromTarget: target => {
const codeCell = getCodeCellFromTarget(target)
return codeCell && getBlobCodeInner(codeCell)
},
getCodeElementFromLineNumber: () => null,
getLineNumberFromCodeElement,
getDiffCodePart,
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would propose to not have these in a util file, but in a file dom_functions like we do for GitHub.


/**
* createBlobAnnotatorMount creates a <div> element and adds it to the DOM
* where the BlobAnnotator component should be mounted.
*/
export function createBlobAnnotatorMount(fileContainer: HTMLElement, isBase?: boolean): HTMLElement | null {
const className = 'sourcegraph-app-annotator' + (isBase ? '-base' : '')
const existingMount = fileContainer.querySelector('.' + className) as HTMLElement
if (existingMount) {
return existingMount
}

const mountEl = document.createElement('div')
mountEl.style.display = 'inline-flex'
mountEl.style.verticalAlign = 'middle'
mountEl.style.alignItems = 'center'
mountEl.className = className
mountEl.style.cssFloat = 'right'

const fileActions = fileContainer.querySelector('.filename-row') as HTMLElement
if (!fileActions || !fileActions.firstElementChild) {
return null
}
;(fileActions.firstElementChild as HTMLElement).style.overflow = 'visible'
fileActions.firstElementChild!.appendChild(mountEl)
return mountEl
}
Loading