Skip to content

Commit d9493f4

Browse files
committed
refactor based on code review, check for missing configs and handle exceptions better
1 parent f1d6a4b commit d9493f4

File tree

3 files changed

+124
-82
lines changed

3 files changed

+124
-82
lines changed

src/extension.ts

Lines changed: 42 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
import { from } from 'rxjs'
22
import { filter, switchMap } from 'rxjs/operators'
33
import * as sourcegraph from 'sourcegraph'
4-
import { getParamsFromUriPath, matchSentryProject } from './handler'
4+
import {
5+
checkMissingConfig,
6+
createDecoration,
7+
getParamsFromUriPath,
8+
isFileMatched,
9+
matchSentryProject,
10+
} from './handler'
511
import { resolveSettings, Settings } from './settings'
612

713
/**
@@ -12,11 +18,6 @@ interface Params {
1218
file: string | null
1319
}
1420

15-
interface LineDecorationText {
16-
content: string
17-
hover: string
18-
}
19-
2021
const DECORATION_TYPE = sourcegraph.app.createDecorationType()
2122
const SETTINGSCONFIG = resolveSettings(sourcegraph.configuration.get<Settings>().value)
2223
const SENTRYORGANIZATION = SETTINGSCONFIG['sentry.organization']
@@ -31,14 +32,27 @@ const COMMON_ERRORLOG_PATTERNS = [
3132
/log\.(Printf|Print|Println)\(['"]([^'"]+)['"]\)/gi,
3233
]
3334

35+
// TODO: Refactor to use activeEditor
3436
export function activate(context: sourcegraph.ExtensionContext): void {
3537
sourcegraph.workspace.onDidOpenTextDocument.subscribe(textDocument => {
3638
const params: Params = getParamsFromUriPath(textDocument.uri)
3739
const sentryProjects = SETTINGSCONFIG['sentry.projects']
3840

3941
// Retrieve the Sentry project that this document reports to.
40-
const sentryProject = sentryProjects ? matchSentryProject(params, sentryProjects) : null
42+
// TODO: Move this outside of activate() and into a separate, testable function.
43+
const sentryProject = sentryProjects && matchSentryProject(params, sentryProjects)
44+
let missingConfigData: string[] = []
45+
let fileMatched: boolean | null
4146

47+
if (sentryProject) {
48+
missingConfigData = checkMissingConfig(sentryProject)
49+
fileMatched = isFileMatched(params, sentryProject)
50+
// Do not decorate lines if the document file format does not match the
51+
// file matching patterns listed in the Sentry extension configurations.
52+
if (fileMatched === false) {
53+
return
54+
}
55+
}
4256
if (sourcegraph.app.activeWindowChanges) {
4357
const activeEditor = from(sourcegraph.app.activeWindowChanges).pipe(
4458
filter((window): window is sourcegraph.Window => window !== undefined),
@@ -51,39 +65,36 @@ export function activate(context: sourcegraph.ExtensionContext): void {
5165
sentryProject
5266
? decorateEditor(
5367
editor,
68+
missingConfigData,
5469
sentryProject.projectId,
55-
sentryProject.lineMatches,
56-
sentryProject.fileMatch
70+
sentryProject.patternProperties.lineMatches
5771
)
58-
: decorateEditor(editor)
72+
: decorateEditor(editor, missingConfigData)
5973
})
6074
)
6175
}
6276
})
6377
}
6478

79+
// TODO: Refactor so that it calls a new function that returns TextDocumentDecoration[],
80+
// and add tests for that new function (kind of like getBlameDecorations())
6581
function decorateEditor(
6682
editor: sourcegraph.CodeEditor,
83+
missingConfigData: string[],
6784
sentryProjectId?: string,
68-
lineMatches?: RegExp[],
69-
fileMatch?: boolean | null
85+
lineMatches?: RegExp[]
7086
): void {
71-
// Do not decorate lines if the document file format does not match the
72-
// file matching patterns listed in the Sentry extension configurations.
73-
if (fileMatch === false) {
74-
return
75-
}
7687
const decorations: sourcegraph.TextDocumentDecoration[] = []
77-
for (const [i, line] of editor.document.text!.split('\n').entries()) {
78-
let m: RegExpExecArray | null
88+
for (const [index, line] of editor.document.text!.split('\n').entries()) {
89+
let match: RegExpExecArray | null
7990
for (let pattern of lineMatches ? lineMatches : COMMON_ERRORLOG_PATTERNS) {
8091
pattern = new RegExp(pattern, 'gi')
8192
do {
82-
m = pattern.exec(line)
83-
if (m) {
84-
decorations.push(decorateLine(i, m, sentryProjectId, fileMatch))
93+
match = pattern.exec(line)
94+
if (match) {
95+
decorations.push(decorateLine(index, match, missingConfigData, sentryProjectId))
8596
}
86-
} while (m)
97+
} while (match)
8798
pattern.lastIndex = 0 // reset
8899
}
89100
}
@@ -100,19 +111,22 @@ function decorateEditor(
100111
export function decorateLine(
101112
index: number,
102113
match: RegExpExecArray,
103-
sentryProjectId?: string,
104-
fileMatch?: boolean | null
114+
missingConfigData: string[],
115+
sentryProjectId?: string
105116
): sourcegraph.TextDocumentDecoration {
106-
const lineDecorationText = setLineDecorationText(sentryProjectId, fileMatch)
117+
const lineDecorationText = createDecoration(missingConfigData, sentryProjectId)
107118
const decoration: sourcegraph.TextDocumentDecoration = {
108119
range: new sourcegraph.Range(index, 0, index, 0),
109120
isWholeLine: true,
110121
after: {
111-
backgroundColor: '#e03e2f',
122+
backgroundColor: missingConfigData.length === 0 ? '#e03e2f' : '#f2736d',
112123
color: 'rgba(255, 255, 255, 0.8)',
113124
contentText: lineDecorationText.content,
114125
hoverMessage: lineDecorationText.hover,
115126
// Depending on the line matching pattern the query m is indexed in position 1 or 2.
127+
// TODO: Specify which capture group should be used through configuration.
128+
// TODO: If !SENTRYORGANIZATION is missing in config, link to $USER/settings and hint
129+
// user to fill it out.
116130
linkURL: !SENTRYORGANIZATION
117131
? ''
118132
: sentryProjectId
@@ -123,33 +137,13 @@ export function decorateLine(
123137
return decoration
124138
}
125139

126-
export function setLineDecorationText(sentryProjectId?: string, fileMatch?: boolean | null): LineDecorationText {
127-
let contentText = ' View logs in Sentry » '
128-
let hoverText = ' View logs in Sentry » '
129-
130-
if (!SENTRYORGANIZATION) {
131-
contentText = ' Configure the Sentry extension to view logs. '
132-
hoverText = ' Configure the Sentry extension to view logs in Sentry. '
133-
} else if (!sentryProjectId) {
134-
contentText = ' View logs in Sentry (❕)» '
135-
hoverText = ' Add Sentry projects to your Sentry extension settings for project matching.'
136-
} else if (!fileMatch) {
137-
// If fileMatch is null (= not specified in the Sentry extension settings), suggest adding file matching
138-
contentText = ' View logs in Sentry (❕)» '
139-
hoverText = ' Add Sentry file matching regexes to your Sentry extension settings for file matching.'
140-
}
141-
return {
142-
content: contentText,
143-
hover: hoverText,
144-
}
145-
}
146-
147140
/**
148141
* Build URL to the Sentry issues stream page with the Sentry Org, query and, if available, Sentry project ID.
149142
* @param errorQuery extracted from the error handling code matching the config matching pattern.
150143
* @param sentryProjectId from the associated Sentry project receiving logs from the document's repo.
151144
* @return URL to the Sentry unresolved issues stream page for this kind of query.
152145
*/
146+
// TODO: Use URLSearchParams instead of encodeURIComponent
153147
function buildUrl(errorQuery: string, sentryProjectId?: string): URL {
154148
const url = new URL(
155149
'https://sentry.io/organizations/' +

src/handler.ts

Lines changed: 65 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
1-
import { Settings } from './settings'
1+
import * as sourcegraph from 'sourcegraph'
2+
import { resolveSettings, SentryProject, Settings } from './settings'
23

34
interface Params {
45
repo: string | null
56
file: string | null
67
}
78

8-
export interface SentryProject {
9-
projectId: string
10-
lineMatches: RegExp[]
11-
fileMatch: boolean | null
9+
interface LineDecorationText {
10+
content: string
11+
hover: string
12+
backgroundColor: string
1213
}
1314

15+
const SETTINGSCONFIG = resolveSettings(sourcegraph.configuration.get<Settings>().value)
16+
const SENTRYORGANIZATION = SETTINGSCONFIG['sentry.organization']
17+
1418
/**
1519
* Extract Sentry params from document URI necessary to
1620
* check if the current document sends log events to Sentry and
@@ -20,6 +24,7 @@ export interface SentryProject {
2024
*/
2125
export function getParamsFromUriPath(textDocument: string): Params {
2226
// TODO: Support more than just GitHub.
27+
// TODO: Safeguard for cases where repo/fileMatch are null.
2328
const repoPattern = /github\.com\/([^\?\#\/]+\/[^\?\#\/]*)/gi
2429
const filePattern = /#([^\?\#\/]+)\/.*\.?$/gi
2530
const repoMatch = repoPattern.exec(textDocument)
@@ -38,28 +43,71 @@ export function getParamsFromUriPath(textDocument: string): Params {
3843
* @param projects Sentry extension projects configurations.
3944
* @return Sentry projectID this document reports to.
4045
*/
41-
export function matchSentryProject(
42-
params: Params,
43-
projects: Settings['sentry.projects'] | null
44-
): SentryProject | undefined {
46+
export function matchSentryProject(params: Params, projects: SentryProject[]): SentryProject | undefined {
4547
if (!projects || !params.repo || !params.file) {
4648
return
4749
}
4850
// Check if a Sentry project is associated with this document's repo and retrieve the project.
51+
// TODO: Handle the null case instead of using a non-null assertion !
4952
const project = projects.find(p => !!new RegExp(p.patternProperties.repoMatch).exec(params.repo!))
5053
if (!project) {
5154
return
5255
}
56+
return project
57+
}
58+
59+
// Check if document file format matches the file pattern set of the project
60+
export function isFileMatched(params: Params, project: SentryProject): boolean | null {
61+
// TODO: Handle edge case of when project.patternProperties.fileMatches is falsy and add a unit test for it.
62+
return project.patternProperties.fileMatches && project.patternProperties.fileMatches.length > 0
63+
? project.patternProperties.fileMatches.some(pattern => !!new RegExp(pattern).exec(params.file!))
64+
: null
65+
}
66+
67+
/**
68+
* Check for missing configurations in the Sentry extension settings
69+
* @param settings
70+
*/
71+
export function checkMissingConfig(settings: SentryProject): string[] {
72+
if (!settings) {
73+
return []
74+
}
75+
const missingConfig: string[] = []
76+
77+
for (const [key, value] of Object.entries(settings)) {
78+
if (value instanceof Object) {
79+
for (const [k, v] of Object.entries(value)) {
80+
if (!v || Object.keys(v).length === 0) {
81+
missingConfig.push(k)
82+
console.log('object: ', k, v)
83+
}
84+
}
85+
} else if (!value) {
86+
missingConfig.push(key)
87+
}
88+
}
89+
return missingConfig
90+
}
5391

54-
// Check if document file format matches the file pattern set of the project.
55-
const fileMatched =
56-
project.patternProperties.fileMatches && project.patternProperties.fileMatches.length > 0
57-
? project.patternProperties.fileMatches.some(pattern => !!new RegExp(pattern).exec(params.file!))
58-
: null
92+
export function createDecoration(missingConfigData: string[], sentryProjectId?: string): LineDecorationText {
93+
let contentText = ' View logs in Sentry » '
94+
let hoverText = ' View logs in Sentry » '
95+
const color = '#e03e2f'
5996

97+
if (!SENTRYORGANIZATION) {
98+
contentText = ' Configure the Sentry extension to view logs. '
99+
hoverText = ' Configure the Sentry extension to view logs in Sentry. '
100+
} else if (!sentryProjectId) {
101+
contentText = ' View logs in Sentry (❕)» '
102+
hoverText = ' Add Sentry projects to your Sentry extension settings for project matching.'
103+
} else if (missingConfigData.length > 0) {
104+
contentText = ' View logs in Sentry (❕)» '
105+
hoverText =
106+
' Please fill out the following configurations in your Sentry extension settings: ' + missingConfigData
107+
}
60108
return {
61-
projectId: project.projectId,
62-
lineMatches: project.patternProperties.lineMatches,
63-
fileMatch: fileMatched,
109+
content: contentText,
110+
hover: hoverText,
111+
backgroundColor: color,
64112
}
65113
}

src/settings.ts

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,23 @@
66
*/
77
export interface Settings {
88
['sentry.organization']?: string
9-
['sentry.projects']?: [
10-
{
11-
name: string
12-
projectId: string
13-
patternProperties: {
14-
repoMatch: RegExp
15-
fileMatches: RegExp[]
16-
lineMatches: RegExp[]
17-
}
18-
// TODO: Add these to v1.
19-
additionalProperties?: {
20-
contentText?: string // e.g. "View sourcegraph/sourcegraph_dot_com errors"
21-
hoverMessage?: string // e.g. "View errors matching '$1' in Sentry"
22-
query?: string // e.g. "$1"
23-
}
24-
}
25-
]
9+
['sentry.projects']?: [SentryProject]
10+
}
11+
12+
export interface SentryProject {
13+
name: string
14+
projectId: string
15+
patternProperties: {
16+
repoMatch: RegExp
17+
fileMatches: RegExp[]
18+
lineMatches: RegExp[]
19+
}
20+
// TODO: Add these to v1.
21+
additionalProperties?: {
22+
contentText?: string // e.g. "View sourcegraph/sourcegraph_dot_com errors"
23+
hoverMessage?: string // e.g. "View errors matching '$1' in Sentry"
24+
query?: string // e.g. "$1"
25+
}
2626
}
2727

2828
/** Returns a copy of the extension settings with values normalized and defaults applied. */

0 commit comments

Comments
 (0)