1
1
import { from } from 'rxjs'
2
2
import { filter , switchMap } from 'rxjs/operators'
3
3
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'
5
11
import { resolveSettings , Settings } from './settings'
6
12
7
13
/**
@@ -12,11 +18,6 @@ interface Params {
12
18
file : string | null
13
19
}
14
20
15
- interface LineDecorationText {
16
- content : string
17
- hover : string
18
- }
19
-
20
21
const DECORATION_TYPE = sourcegraph . app . createDecorationType ( )
21
22
const SETTINGSCONFIG = resolveSettings ( sourcegraph . configuration . get < Settings > ( ) . value )
22
23
const SENTRYORGANIZATION = SETTINGSCONFIG [ 'sentry.organization' ]
@@ -31,14 +32,27 @@ const COMMON_ERRORLOG_PATTERNS = [
31
32
/ l o g \. ( P r i n t f | P r i n t | P r i n t l n ) \( [ ' " ] ( [ ^ ' " ] + ) [ ' " ] \) / gi,
32
33
]
33
34
35
+ // TODO: Refactor to use activeEditor
34
36
export function activate ( context : sourcegraph . ExtensionContext ) : void {
35
37
sourcegraph . workspace . onDidOpenTextDocument . subscribe ( textDocument => {
36
38
const params : Params = getParamsFromUriPath ( textDocument . uri )
37
39
const sentryProjects = SETTINGSCONFIG [ 'sentry.projects' ]
38
40
39
41
// 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
41
46
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
+ }
42
56
if ( sourcegraph . app . activeWindowChanges ) {
43
57
const activeEditor = from ( sourcegraph . app . activeWindowChanges ) . pipe (
44
58
filter ( ( window ) : window is sourcegraph . Window => window !== undefined ) ,
@@ -51,39 +65,36 @@ export function activate(context: sourcegraph.ExtensionContext): void {
51
65
sentryProject
52
66
? decorateEditor (
53
67
editor ,
68
+ missingConfigData ,
54
69
sentryProject . projectId ,
55
- sentryProject . lineMatches ,
56
- sentryProject . fileMatch
70
+ sentryProject . patternProperties . lineMatches
57
71
)
58
- : decorateEditor ( editor )
72
+ : decorateEditor ( editor , missingConfigData )
59
73
} )
60
74
)
61
75
}
62
76
} )
63
77
}
64
78
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())
65
81
function decorateEditor (
66
82
editor : sourcegraph . CodeEditor ,
83
+ missingConfigData : string [ ] ,
67
84
sentryProjectId ?: string ,
68
- lineMatches ?: RegExp [ ] ,
69
- fileMatch ?: boolean | null
85
+ lineMatches ?: RegExp [ ]
70
86
) : 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
- }
76
87
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
79
90
for ( let pattern of lineMatches ? lineMatches : COMMON_ERRORLOG_PATTERNS ) {
80
91
pattern = new RegExp ( pattern , 'gi' )
81
92
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 ) )
85
96
}
86
- } while ( m )
97
+ } while ( match )
87
98
pattern . lastIndex = 0 // reset
88
99
}
89
100
}
@@ -100,19 +111,22 @@ function decorateEditor(
100
111
export function decorateLine (
101
112
index : number ,
102
113
match : RegExpExecArray ,
103
- sentryProjectId ? : string ,
104
- fileMatch ?: boolean | null
114
+ missingConfigData : string [ ] ,
115
+ sentryProjectId ?: string
105
116
) : sourcegraph . TextDocumentDecoration {
106
- const lineDecorationText = setLineDecorationText ( sentryProjectId , fileMatch )
117
+ const lineDecorationText = createDecoration ( missingConfigData , sentryProjectId )
107
118
const decoration : sourcegraph . TextDocumentDecoration = {
108
119
range : new sourcegraph . Range ( index , 0 , index , 0 ) ,
109
120
isWholeLine : true ,
110
121
after : {
111
- backgroundColor : '#e03e2f' ,
122
+ backgroundColor : missingConfigData . length === 0 ? '#e03e2f' : '#f2736d ',
112
123
color : 'rgba(255, 255, 255, 0.8)' ,
113
124
contentText : lineDecorationText . content ,
114
125
hoverMessage : lineDecorationText . hover ,
115
126
// 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.
116
130
linkURL : ! SENTRYORGANIZATION
117
131
? ''
118
132
: sentryProjectId
@@ -123,33 +137,13 @@ export function decorateLine(
123
137
return decoration
124
138
}
125
139
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
-
147
140
/**
148
141
* Build URL to the Sentry issues stream page with the Sentry Org, query and, if available, Sentry project ID.
149
142
* @param errorQuery extracted from the error handling code matching the config matching pattern.
150
143
* @param sentryProjectId from the associated Sentry project receiving logs from the document's repo.
151
144
* @return URL to the Sentry unresolved issues stream page for this kind of query.
152
145
*/
146
+ // TODO: Use URLSearchParams instead of encodeURIComponent
153
147
function buildUrl ( errorQuery : string , sentryProjectId ?: string ) : URL {
154
148
const url = new URL (
155
149
'https://sentry.io/organizations/' +
0 commit comments