[37m1774[0m [0m >
104 | [37m1775[0m error [37m1775[0m [0m error
105 |
--------------------------------------------------------------------------------
/tests/sentry/7/in.diff:
--------------------------------------------------------------------------------
1 | commit 48d81f61f905d0c92f840d3806b05f19965daddf
2 | Author: k-fish <6111995+k-fish@users.noreply.github.com>
3 | Date: Mon Jun 22 12:28:37 2020 -0700
4 |
5 | fix(discover): Fixed incorrect tags from showing in autocomplete (#19459)
6 |
7 | Fixed incorrect tags from showing in autocomplete
8 |
9 | `count()` for example was showing up in the autocomplete if you had visited discover before visiting the performance landing page. The search bar would then incorrectly show it as a recommendation
10 |
11 | This was caused by assigning into a reused object, I turned it into a proper field and switched field keys to an `Enum` so that it could be used with record to make sure the field tags and values correctly mapped to real field keys.
12 |
13 | * Remove export from enum and switch to generating FIELD_TAGS again
14 | * Replace 'as const' and assert with direct typing
15 |
16 | diff --git a/src/sentry/static/sentry/app/utils/discover/fields.tsx b/src/sentry/static/sentry/app/utils/discover/fields.tsx
17 | index 7c0ea52762..69ae69024d 100644
18 | --- a/src/sentry/static/sentry/app/utils/discover/fields.tsx
19 | +++ b/src/sentry/static/sentry/app/utils/discover/fields.tsx
20 | @@ -261,83 +261,153 @@ export type Aggregation = {
21 | multiPlotType: PlotType;
22 | };
23 |
24 | +enum FieldKey {
25 | + CULPRIT = 'culprit',
26 | + DEVICE_ARCH = 'device.arch',
27 | + DEVICE_BATTERY_LEVEL = 'device.battery_level',
28 | + DEVICE_BRAND = 'device.brand',
29 | + DEVICE_CHARGING = 'device.charging',
30 | + DEVICE_LOCALE = 'device.locale',
31 | + DEVICE_NAME = 'device.name',
32 | + DEVICE_ONLINE = 'device.online',
33 | + DEVICE_ORIENTATION = 'device.orientation',
34 | + DEVICE_SIMULATOR = 'device.simulator',
35 | + DEVICE_UUID = 'device.uuid',
36 | + DIST = 'dist',
37 | + ENVIRONMENT = 'environment',
38 | + ERROR_HANDLED = 'error.handled',
39 | + ERROR_MECHANISM = 'error.mechanism',
40 | + ERROR_TYPE = 'error.type',
41 | + ERROR_VALUE = 'error.value',
42 | + EVENT_TYPE = 'event.type',
43 | + GEO_CITY = 'geo.city',
44 | + GEO_COUNTRY_CODE = 'geo.country_code',
45 | + GEO_REGION = 'geo.region',
46 | + HTTP_METHOD = 'http.method',
47 | + HTTP_URL = 'http.url',
48 | + ID = 'id',
49 | + ISSUE = 'issue',
50 | + LOCATION = 'location',
51 | + MESSAGE = 'message',
52 | + OS_BUILD = 'os.build',
53 | + OS_KERNEL_VERSION = 'os.kernel_version',
54 | + PLATFORM_NAME = 'platform.name',
55 | + PROJECT = 'project',
56 | + RELEASE = 'release',
57 | + SDK_NAME = 'sdk.name',
58 | + SDK_VERSION = 'sdk.version',
59 | + STACK_ABS_PATH = 'stack.abs_path',
60 | + STACK_COLNO = 'stack.colno',
61 | + STACK_FILENAME = 'stack.filename',
62 | + STACK_FUNCTION = 'stack.function',
63 | + STACK_IN_APP = 'stack.in_app',
64 | + STACK_LINENO = 'stack.lineno',
65 | + STACK_MODULE = 'stack.module',
66 | + STACK_PACKAGE = 'stack.package',
67 | + STACK_STACK_LEVEL = 'stack.stack_level',
68 | + TIME = 'time',
69 | + TIMESTAMP = 'timestamp',
70 | + TITLE = 'title',
71 | + TRACE = 'trace',
72 | + TRACE_PARENT_SPAN = 'trace.parent_span',
73 | + TRACE_SPAN = 'trace.span',
74 | + TRANSACTION = 'transaction',
75 | + TRANSACTION_DURATION = 'transaction.duration',
76 | + TRANSACTION_OP = 'transaction.op',
77 | + TRANSACTION_STATUS = 'transaction.status',
78 | + USER = 'user',
79 | + USER_EMAIL = 'user.email',
80 | + USER_ID = 'user.id',
81 | + USER_IP = 'user.ip',
82 | + USER_USERNAME = 'user.username',
83 | +}
84 | +
85 | /**
86 | * Refer to src/sentry/snuba/events.py, search for Columns
87 | */
88 | -export const FIELDS = {
89 | - id: 'string',
90 | +export const FIELDS: Readonly
> = {
91 | + [FieldKey.ID]: 'string',
92 | // issue.id and project.id are omitted on purpose.
93 | // Customers should use `issue` and `project` instead.
94 | - timestamp: 'date',
95 | - time: 'date',
96 | -
97 | - culprit: 'string',
98 | - location: 'string',
99 | - message: 'string',
100 | - 'platform.name': 'string',
101 | - environment: 'string',
102 | - release: 'string',
103 | - dist: 'string',
104 | - title: 'string',
105 | - 'event.type': 'string',
106 | + [FieldKey.TIMESTAMP]: 'date',
107 | + [FieldKey.TIME]: 'date',
108 | +
109 | + [FieldKey.CULPRIT]: 'string',
110 | + [FieldKey.LOCATION]: 'string',
111 | + [FieldKey.MESSAGE]: 'string',
112 | + [FieldKey.PLATFORM_NAME]: 'string',
113 | + [FieldKey.ENVIRONMENT]: 'string',
114 | + [FieldKey.RELEASE]: 'string',
115 | + [FieldKey.DIST]: 'string',
116 | + [FieldKey.TITLE]: 'string',
117 | + [FieldKey.EVENT_TYPE]: 'string',
118 | // tags.key and tags.value are omitted on purpose as well.
119 |
120 | - transaction: 'string',
121 | - user: 'string',
122 | - 'user.id': 'string',
123 | - 'user.email': 'string',
124 | - 'user.username': 'string',
125 | - 'user.ip': 'string',
126 | - 'sdk.name': 'string',
127 | - 'sdk.version': 'string',
128 | - 'http.method': 'string',
129 | - 'http.url': 'string',
130 | - 'os.build': 'string',
131 | - 'os.kernel_version': 'string',
132 | - 'device.name': 'string',
133 | - 'device.brand': 'string',
134 | - 'device.locale': 'string',
135 | - 'device.uuid': 'string',
136 | - 'device.arch': 'string',
137 | - 'device.battery_level': 'number',
138 | - 'device.orientation': 'string',
139 | - 'device.simulator': 'boolean',
140 | - 'device.online': 'boolean',
141 | - 'device.charging': 'boolean',
142 | - 'geo.country_code': 'string',
143 | - 'geo.region': 'string',
144 | - 'geo.city': 'string',
145 | - 'error.type': 'string',
146 | - 'error.value': 'string',
147 | - 'error.mechanism': 'string',
148 | - 'error.handled': 'boolean',
149 | - 'stack.abs_path': 'string',
150 | - 'stack.filename': 'string',
151 | - 'stack.package': 'string',
152 | - 'stack.module': 'string',
153 | - 'stack.function': 'string',
154 | - 'stack.in_app': 'boolean',
155 | - 'stack.colno': 'number',
156 | - 'stack.lineno': 'number',
157 | - 'stack.stack_level': 'number',
158 | + [FieldKey.TRANSACTION]: 'string',
159 | + [FieldKey.USER]: 'string',
160 | + [FieldKey.USER_ID]: 'string',
161 | + [FieldKey.USER_EMAIL]: 'string',
162 | + [FieldKey.USER_USERNAME]: 'string',
163 | + [FieldKey.USER_IP]: 'string',
164 | + [FieldKey.SDK_NAME]: 'string',
165 | + [FieldKey.SDK_VERSION]: 'string',
166 | + [FieldKey.HTTP_METHOD]: 'string',
167 | + [FieldKey.HTTP_URL]: 'string',
168 | + [FieldKey.OS_BUILD]: 'string',
169 | + [FieldKey.OS_KERNEL_VERSION]: 'string',
170 | + [FieldKey.DEVICE_NAME]: 'string',
171 | + [FieldKey.DEVICE_BRAND]: 'string',
172 | + [FieldKey.DEVICE_LOCALE]: 'string',
173 | + [FieldKey.DEVICE_UUID]: 'string',
174 | + [FieldKey.DEVICE_ARCH]: 'string',
175 | + [FieldKey.DEVICE_BATTERY_LEVEL]: 'number',
176 | + [FieldKey.DEVICE_ORIENTATION]: 'string',
177 | + [FieldKey.DEVICE_SIMULATOR]: 'boolean',
178 | + [FieldKey.DEVICE_ONLINE]: 'boolean',
179 | + [FieldKey.DEVICE_CHARGING]: 'boolean',
180 | + [FieldKey.GEO_COUNTRY_CODE]: 'string',
181 | + [FieldKey.GEO_REGION]: 'string',
182 | + [FieldKey.GEO_CITY]: 'string',
183 | + [FieldKey.ERROR_TYPE]: 'string',
184 | + [FieldKey.ERROR_VALUE]: 'string',
185 | + [FieldKey.ERROR_MECHANISM]: 'string',
186 | + [FieldKey.ERROR_HANDLED]: 'boolean',
187 | + [FieldKey.STACK_ABS_PATH]: 'string',
188 | + [FieldKey.STACK_FILENAME]: 'string',
189 | + [FieldKey.STACK_PACKAGE]: 'string',
190 | + [FieldKey.STACK_MODULE]: 'string',
191 | + [FieldKey.STACK_FUNCTION]: 'string',
192 | + [FieldKey.STACK_IN_APP]: 'boolean',
193 | + [FieldKey.STACK_COLNO]: 'number',
194 | + [FieldKey.STACK_LINENO]: 'number',
195 | + [FieldKey.STACK_STACK_LEVEL]: 'number',
196 | // contexts.key and contexts.value omitted on purpose.
197 |
198 | // Transaction event fields.
199 | - 'transaction.duration': 'duration',
200 | - 'transaction.op': 'string',
201 | - 'transaction.status': 'string',
202 | + [FieldKey.TRANSACTION_DURATION]: 'duration',
203 | + [FieldKey.TRANSACTION_OP]: 'string',
204 | + [FieldKey.TRANSACTION_STATUS]: 'string',
205 |
206 | - trace: 'string',
207 | - 'trace.span': 'string',
208 | - 'trace.parent_span': 'string',
209 | + [FieldKey.TRACE]: 'string',
210 | + [FieldKey.TRACE_SPAN]: 'string',
211 | + [FieldKey.TRACE_PARENT_SPAN]: 'string',
212 |
213 | // Field alises defined in src/sentry/api/event_search.py
214 | - project: 'string',
215 | - issue: 'string',
216 | -} as const;
217 | -assert(FIELDS as Readonly<{[key in keyof typeof FIELDS]: ColumnType}>);
218 | + [FieldKey.PROJECT]: 'string',
219 | + [FieldKey.ISSUE]: 'string',
220 | +};
221 | +
222 | +export type FieldTag = {
223 | + key: FieldKey;
224 | + name: FieldKey;
225 | +};
226 | +
227 | +export const FIELD_TAGS = Object.freeze(
228 | + Object.fromEntries(Object.keys(FIELDS).map(item => [item, {key: item, name: item}]))
229 | +);
230 |
231 | -export type FieldKey = keyof typeof FIELDS | string | '';
232 | +// Allows for a less strict field key definition in cases we are returning custom strings as fields
233 | +export type LooseFieldKey = FieldKey | string | '';
234 |
235 | // This list should be removed with the tranaction-events feature flag.
236 | export const TRACING_FIELDS = [
237 | diff --git a/src/sentry/static/sentry/app/views/events/searchBar.tsx b/src/sentry/static/sentry/app/views/events/searchBar.tsx
238 | index 2bee7646e3..10c503474e 100644
239 | --- a/src/sentry/static/sentry/app/views/events/searchBar.tsx
240 | +++ b/src/sentry/static/sentry/app/views/events/searchBar.tsx
241 | @@ -12,7 +12,7 @@ import {defined} from 'app/utils';
242 | import {fetchTagValues} from 'app/actionCreators/tags';
243 | import SentryTypes from 'app/sentryTypes';
244 | import SmartSearchBar, {SearchType} from 'app/components/smartSearchBar';
245 | -import {Field, FIELDS, TRACING_FIELDS} from 'app/utils/discover/fields';
246 | +import {Field, FIELD_TAGS, TRACING_FIELDS} from 'app/utils/discover/fields';
247 | import withApi from 'app/utils/withApi';
248 | import withTags from 'app/utils/withTags';
249 | import {Client} from 'app/api';
250 | @@ -23,10 +23,6 @@ const SEARCH_SPECIAL_CHARS_REGEXP = new RegExp(
251 | 'g'
252 | );
253 |
254 | -const FIELD_TAGS = Object.fromEntries(
255 | - Object.keys(FIELDS).map(item => [item, {key: item, name: item}])
256 | -);
257 | -
258 | type SearchBarProps = Omit, 'tags'> & {
259 | api: Client;
260 | organization: Organization;
261 | @@ -101,7 +97,7 @@ class SearchBar extends React.PureComponent {
262 | : {};
263 |
264 | const fieldTags = organization.features.includes('performance-view')
265 | - ? assign(FIELD_TAGS, functionTags)
266 | + ? Object.assign({}, FIELD_TAGS, functionTags)
267 | : omit(FIELD_TAGS, TRACING_FIELDS);
268 |
269 | const combined = assign({}, tags, fieldTags);
270 | diff --git a/src/sentry/static/sentry/app/views/settings/incidentRules/metricField.tsx b/src/sentry/static/sentry/app/views/settings/incidentRules/metricField.tsx
271 | index 3acb977f8a..9ff83b3f82 100644
272 | --- a/src/sentry/static/sentry/app/views/settings/incidentRules/metricField.tsx
273 | +++ b/src/sentry/static/sentry/app/views/settings/incidentRules/metricField.tsx
274 | @@ -16,7 +16,7 @@ import {
275 | explodeFieldString,
276 | generateFieldAsString,
277 | AggregationKey,
278 | - FieldKey,
279 | + LooseFieldKey,
280 | AGGREGATIONS,
281 | FIELDS,
282 | } from 'app/utils/discover/fields';
283 | @@ -30,7 +30,7 @@ type Props = Omit & {
284 |
285 | type OptionConfig = {
286 | aggregations: AggregationKey[];
287 | - fields: FieldKey[];
288 | + fields: LooseFieldKey[];
289 | };
290 |
291 | const errorFieldConfig: OptionConfig = {
292 |
--------------------------------------------------------------------------------
/tests/sentry/7/out:
--------------------------------------------------------------------------------
1 | [36mcommit 48d81f61f905d0c92f840d3806b05f19965daddf
2 | [0m[36mAuthor: k-fish <6111995+k-fish@users.noreply.github.com>
3 | [0m[36mDate: Mon Jun 22 12:28:37 2020 -0700
4 | [0m[36m
5 | [0m[36m fix(discover): Fixed incorrect tags from showing in autocomplete (#19459)
6 | [0m[36m
7 | [0m[36m Fixed incorrect tags from showing in autocomplete
8 | [0m[36m
9 | [0m[36m `count()` for example was showing up in the autocomplete if you had visited discover before visiting the performance landing page. The search bar would then incorrectly show it as a recommendation
10 | [0m[36m
11 | [0m[36m This was caused by assigning into a reused object, I turned it into a proper field and switched field keys to an `Enum` so that it could be used with record to make sure the field tags and values correctly mapped to real field keys.
12 | [0m[36m
13 | [0m[36m * Remove export from enum and switch to generating FIELD_TAGS again
14 | [0m[36m * Replace 'as const' and assert with direct typing
15 | [0m[36m
16 | [0m[36mdiff --git a/src/sentry/static/sentry/app/utils/discover/fields.tsx b/src/sentry/static/sentry/app/utils/discover/fields.tsx
17 | [0m[36mindex 7c0ea52762..69ae69024d 100644
18 | [0m[33m--- a/src/sentry/static/sentry/app/utils/discover/fields.tsx
19 | [0m[33m+++ b/src/sentry/static/sentry/app/utils/discover/fields.tsx
20 | [0m[37m┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈[0m[37m261[0m multiPlotType: PlotType; [37m261[0m [0m multiPlotType: PlotType;
21 | [37m262[0m }; [37m262[0m [0m};
22 | [37m [0m [37m263[0m [32m[0m
23 | [37m [0m [37m264[0m [32menum FieldKey {[0m
24 | [37m [0m [37m265[0m [32m CULPRIT = 'culprit',[0m
25 | [37m [0m [37m266[0m [32m DEVICE_ARCH = 'device.arch',[0m
26 | [37m [0m [37m267[0m [32m DEVICE_BATTERY_LEVEL = 'device.battery_level',[0m
27 | [37m [0m [37m268[0m [32m DEVICE_BRAND = 'device.brand',[0m
28 | [37m [0m [37m269[0m [32m DEVICE_CHARGING = 'device.charging',[0m
29 | [37m [0m [37m270[0m [32m DEVICE_LOCALE = 'device.locale',[0m
30 | [37m [0m [37m271[0m [32m DEVICE_NAME = 'device.name',[0m
31 | [37m [0m [37m272[0m [32m DEVICE_ONLINE = 'device.online',[0m
32 | [37m [0m [37m273[0m [32m DEVICE_ORIENTATION = 'device.orientation',[0m
33 | [37m [0m [37m274[0m [32m DEVICE_SIMULATOR = 'device.simulator',[0m
34 | [37m [0m [37m275[0m [32m DEVICE_UUID = 'device.uuid',[0m
35 | [37m [0m [37m276[0m [32m DIST = 'dist',[0m
36 | [37m [0m [37m277[0m [32m ENVIRONMENT = 'environment',[0m
37 | [37m [0m [37m278[0m [32m ERROR_HANDLED = 'error.handled',[0m
38 | [37m [0m [37m279[0m [32m ERROR_MECHANISM = 'error.mechanism',[0m
39 | [37m [0m [37m280[0m [32m ERROR_TYPE = 'error.type',[0m
40 | [37m [0m [37m281[0m [32m ERROR_VALUE = 'error.value',[0m
41 | [37m [0m [37m282[0m [32m EVENT_TYPE = 'event.type',[0m
42 | [37m [0m [37m283[0m [32m GEO_CITY = 'geo.city',[0m
43 | [37m [0m [37m284[0m [32m GEO_COUNTRY_CODE = 'geo.country_code',[0m
44 | [37m [0m [37m285[0m [32m GEO_REGION = 'geo.region',[0m
45 | [37m [0m [37m286[0m [32m HTTP_METHOD = 'http.method',[0m
46 | [37m [0m [37m287[0m [32m HTTP_URL = 'http.url',[0m
47 | [37m [0m [37m288[0m [32m ID = 'id',[0m
48 | [37m [0m [37m289[0m [32m ISSUE = 'issue',[0m
49 | [37m [0m [37m290[0m [32m LOCATION = 'location',[0m
50 | [37m [0m [37m291[0m [32m MESSAGE = 'message',[0m
51 | [37m [0m [37m292[0m [32m OS_BUILD = 'os.build',[0m
52 | [37m [0m [37m293[0m [32m OS_KERNEL_VERSION = 'os.kernel_version',[0m
53 | [37m [0m [37m294[0m [32m PLATFORM_NAME = 'platform.name',[0m
54 | [37m [0m [37m295[0m [32m PROJECT = 'project',[0m
55 | [37m [0m [37m296[0m [32m RELEASE = 'release',[0m
56 | [37m [0m [37m297[0m [32m SDK_NAME = 'sdk.name',[0m
57 | [37m [0m [37m298[0m [32m SDK_VERSION = 'sdk.version',[0m
58 | [37m [0m [37m299[0m [32m STACK_ABS_PATH = 'stack.abs_path',[0m
59 | [37m [0m [37m300[0m [32m STACK_COLNO = 'stack.colno',[0m
60 | [37m [0m [37m301[0m [32m STACK_FILENAME = 'stack.filename',[0m
61 | [37m [0m [37m302[0m [32m STACK_FUNCTION = 'stack.function',[0m
62 | [37m [0m [37m303[0m [32m STACK_IN_APP = 'stack.in_app',[0m
63 | [37m [0m [37m304[0m [32m STACK_LINENO = 'stack.lineno',[0m
64 | [37m [0m [37m305[0m [32m STACK_MODULE = 'stack.module',[0m
65 | [37m [0m [37m306[0m [32m STACK_PACKAGE = 'stack.package',[0m
66 | [37m [0m [37m307[0m [32m STACK_STACK_LEVEL = 'stack.stack_level',[0m
67 | [37m [0m [37m308[0m [32m TIME = 'time',[0m
68 | [37m [0m [37m309[0m [32m TIMESTAMP = 'timestamp',[0m
69 | [37m [0m [37m310[0m [32m TITLE = 'title',[0m
70 | [37m [0m [37m311[0m [32m TRACE = 'trace',[0m
71 | [37m [0m [37m312[0m [32m TRACE_PARENT_SPAN = 'trace.parent_span',[0m
72 | [37m [0m [37m313[0m [32m TRACE_SPAN = 'trace.span',[0m
73 | [37m [0m [37m314[0m [32m TRANSACTION = 'transaction',[0m
74 | [37m [0m [37m315[0m [32m TRANSACTION_DURATION = 'transaction.duration',[0m
75 | [37m [0m [37m316[0m [32m TRANSACTION_OP = 'transaction.op',[0m
76 | [37m [0m [37m317[0m [32m TRANSACTION_STATUS = 'transaction.status',[0m
77 | [37m [0m [37m318[0m [32m USER = 'user',[0m
78 | [37m [0m [37m319[0m [32m USER_EMAIL = 'user.email',[0m
79 | [37m [0m [37m320[0m [32m USER_ID = 'user.id',[0m
80 | [37m [0m [37m321[0m [32m USER_IP = 'user.ip',[0m
81 | [37m [0m [37m322[0m [32m USER_USERNAME = 'user.username',[0m
82 | [37m [0m [37m323[0m [32m}[0m
83 | [37m263[0m [37m324[0m [0m
84 | [37m264[0m /** [37m325[0m [0m/**
85 | [37m265[0m * Refer to src/sentry/snuba/events.py, search for Columns [37m326[0m [0m * Refer to src/sentry/snuba/events.py, search for Columns
86 | [37m266[0m */ [37m327[0m [0m */
87 | [37m267[0m [22m[7m[31mexport const FIELDS = {[0m [37m328[0m [22m[7m[32mexport const FIELDS: Readonly> [0m
88 | [37m [0m [37m [0m [22m[7m[32m= {[0m
89 | [37m268[0m [22m[7m[31m id: 'string',[0m [37m329[0m [22m[7m[32m [FieldKey.ID]: 'string',[0m
90 | [37m269[0m // issue.id and project.id are omitted on purpose. [37m330[0m [0m // issue.id and project.id are omitted on purpose.
91 | [37m270[0m // Customers should use `issue` and `project` instead. [37m331[0m [0m // Customers should use `issue` and `project` instead.
92 | [37m271[0m [22m[7m[31m timestamp: 'date',[0m [37m332[0m [22m[7m[32m [FieldKey.TIMESTAMP]: 'date',[0m
93 | [37m272[0m [22m[7m[31m time: 'date',[0m [37m333[0m [22m[7m[32m [FieldKey.TIME]: 'date',[0m
94 | [37m273[0m [37m334[0m [0m
95 | [37m274[0m [22m[7m[31m culprit: 'string',[0m [37m335[0m [22m[7m[32m [FieldKey.CULPRIT]: 'string',[0m
96 | [37m275[0m [22m[7m[31m location: 'string',[0m [37m336[0m [22m[7m[32m [FieldKey.LOCATION]: 'string',[0m
97 | [37m276[0m [22m[7m[31m message: 'string',[0m [37m337[0m [22m[7m[32m [FieldKey.MESSAGE]: 'string',[0m
98 | [37m277[0m [22m[7m[31m 'platform.name': 'string',[0m [37m338[0m [22m[7m[32m [FieldKey.PLATFORM_NAME]: 'string',[0m
99 | [37m278[0m [22m[7m[31m environment: 'string',[0m [37m339[0m [22m[7m[32m [FieldKey.ENVIRONMENT]: 'string',[0m
100 | [37m279[0m [22m[7m[31m release: 'string',[0m [37m340[0m [22m[7m[32m [FieldKey.RELEASE]: 'string',[0m
101 | [37m280[0m [22m[7m[31m dist: 'string',[0m [37m341[0m [22m[7m[32m [FieldKey.DIST]: 'string',[0m
102 | [37m281[0m [22m[7m[31m title: 'string',[0m [37m342[0m [22m[7m[32m [FieldKey.TITLE]: 'string',[0m
103 | [37m282[0m [22m[7m[31m 'event.type': 'string',[0m [37m343[0m [22m[7m[32m [FieldKey.EVENT_TYPE]: 'string',[0m
104 | [37m283[0m // tags.key and tags.value are omitted on purpose as well. [37m344[0m [0m // tags.key and tags.value are omitted on purpose as well.
105 | [37m284[0m [37m345[0m [0m
106 | [37m285[0m [22m[7m[31m transaction: 'string',[0m [37m346[0m [22m[7m[32m [FieldKey.TRANSACTION]: 'string',[0m
107 | [37m286[0m [22m[7m[31m user: 'string',[0m [37m347[0m [22m[7m[32m [FieldKey.USER]: 'string',[0m
108 | [37m287[0m [22m[7m[31m 'user.id': 'string',[0m [37m348[0m [22m[7m[32m [FieldKey.USER_ID]: 'string',[0m
109 | [37m288[0m [22m[7m[31m 'user.email': 'string',[0m [37m349[0m [22m[7m[32m [FieldKey.USER_EMAIL]: 'string',[0m
110 | [37m289[0m [22m[7m[31m 'user.username': 'string',[0m [37m350[0m [22m[7m[32m [FieldKey.USER_USERNAME]: 'string',[0m
111 | [37m290[0m [22m[7m[31m 'user.ip': 'string',[0m [37m351[0m [22m[7m[32m [FieldKey.USER_IP]: 'string',[0m
112 | [37m291[0m [22m[7m[31m 'sdk.name': 'string',[0m [37m352[0m [22m[7m[32m [FieldKey.SDK_NAME]: 'string',[0m
113 | [37m292[0m [22m[7m[31m 'sdk.version': 'string',[0m [37m353[0m [22m[7m[32m [FieldKey.SDK_VERSION]: 'string',[0m
114 | [37m293[0m [22m[7m[31m 'http.method': 'string',[0m [37m354[0m [22m[7m[32m [FieldKey.HTTP_METHOD]: 'string',[0m
115 | [37m294[0m [22m[7m[31m 'http.url': 'string',[0m [37m355[0m [22m[7m[32m [FieldKey.HTTP_URL]: 'string',[0m
116 | [37m295[0m [22m[7m[31m 'os.build': 'string',[0m [37m356[0m [22m[7m[32m [FieldKey.OS_BUILD]: 'string',[0m
117 | [37m296[0m [22m[7m[31m 'os.kernel_version': 'string',[0m [37m357[0m [22m[7m[32m [FieldKey.OS_KERNEL_VERSION]: 'string',[0m
118 | [37m297[0m [22m[7m[31m 'device.name': 'string',[0m [37m358[0m [22m[7m[32m [FieldKey.DEVICE_NAME]: 'string',[0m
119 | [37m298[0m [22m[7m[31m 'device.brand': 'string',[0m [37m359[0m [22m[7m[32m [FieldKey.DEVICE_BRAND]: 'string',[0m
120 | [37m299[0m [22m[7m[31m 'device.locale': 'string',[0m [37m360[0m [22m[7m[32m [FieldKey.DEVICE_LOCALE]: 'string',[0m
121 | [37m300[0m [22m[7m[31m 'device.uuid': 'string',[0m [37m361[0m [22m[7m[32m [FieldKey.DEVICE_UUID]: 'string',[0m
122 | [37m301[0m [22m[7m[31m 'device.arch': 'string',[0m [37m362[0m [22m[7m[32m [FieldKey.DEVICE_ARCH]: 'string',[0m
123 | [37m302[0m [22m[7m[31m 'device.battery_level': 'number',[0m [37m363[0m [22m[7m[32m [FieldKey.DEVICE_BATTERY_LEVEL]: 'number',[0m
124 | [37m303[0m [22m[7m[31m 'device.orientation': 'string',[0m [37m364[0m [22m[7m[32m [FieldKey.DEVICE_ORIENTATION]: 'string',[0m
125 | [37m304[0m [22m[7m[31m 'device.simulator': 'boolean',[0m [37m365[0m [22m[7m[32m [FieldKey.DEVICE_SIMULATOR]: 'boolean',[0m
126 | [37m305[0m [22m[7m[31m 'device.online': 'boolean',[0m [37m366[0m [22m[7m[32m [FieldKey.DEVICE_ONLINE]: 'boolean',[0m
127 | [37m306[0m [22m[7m[31m 'device.charging': 'boolean',[0m [37m367[0m [22m[7m[32m [FieldKey.DEVICE_CHARGING]: 'boolean',[0m
128 | [37m307[0m [22m[7m[31m 'geo.country_code': 'string',[0m [37m368[0m [22m[7m[32m [FieldKey.GEO_COUNTRY_CODE]: 'string',[0m
129 | [37m308[0m [22m[7m[31m 'geo.region': 'string',[0m [37m369[0m [22m[7m[32m [FieldKey.GEO_REGION]: 'string',[0m
130 | [37m309[0m [22m[7m[31m 'geo.city': 'string',[0m [37m370[0m [22m[7m[32m [FieldKey.GEO_CITY]: 'string',[0m
131 | [37m310[0m [22m[7m[31m 'error.type': 'string',[0m [37m371[0m [22m[7m[32m [FieldKey.ERROR_TYPE]: 'string',[0m
132 | [37m311[0m [22m[7m[31m 'error.value': 'string',[0m [37m372[0m [22m[7m[32m [FieldKey.ERROR_VALUE]: 'string',[0m
133 | [37m312[0m [22m[7m[31m 'error.mechanism': 'string',[0m [37m373[0m [22m[7m[32m [FieldKey.ERROR_MECHANISM]: 'string',[0m
134 | [37m313[0m [22m[7m[31m 'error.handled': 'boolean',[0m [37m374[0m [22m[7m[32m [FieldKey.ERROR_HANDLED]: 'boolean',[0m
135 | [37m314[0m [22m[7m[31m 'stack.abs_path': 'string',[0m [37m375[0m [22m[7m[32m [FieldKey.STACK_ABS_PATH]: 'string',[0m
136 | [37m315[0m [22m[7m[31m 'stack.filename': 'string',[0m [37m376[0m [22m[7m[32m [FieldKey.STACK_FILENAME]: 'string',[0m
137 | [37m316[0m [22m[7m[31m 'stack.package': 'string',[0m [37m377[0m [22m[7m[32m [FieldKey.STACK_PACKAGE]: 'string',[0m
138 | [37m317[0m [22m[7m[31m 'stack.module': 'string',[0m [37m378[0m [22m[7m[32m [FieldKey.STACK_MODULE]: 'string',[0m
139 | [37m318[0m [22m[7m[31m 'stack.function': 'string',[0m [37m379[0m [22m[7m[32m [FieldKey.STACK_FUNCTION]: 'string',[0m
140 | [37m319[0m [22m[7m[31m 'stack.in_app': 'boolean',[0m [37m380[0m [22m[7m[32m [FieldKey.STACK_IN_APP]: 'boolean',[0m
141 | [37m320[0m [22m[7m[31m 'stack.colno': 'number',[0m [37m381[0m [22m[7m[32m [FieldKey.STACK_COLNO]: 'number',[0m
142 | [37m321[0m [22m[7m[31m 'stack.lineno': 'number',[0m [37m382[0m [22m[7m[32m [FieldKey.STACK_LINENO]: 'number',[0m
143 | [37m322[0m [22m[7m[31m 'stack.stack_level': 'number',[0m [37m383[0m [22m[7m[32m [FieldKey.STACK_STACK_LEVEL]: 'number',[0m
144 | [37m323[0m // contexts.key and contexts.value omitted on purpose. [37m384[0m [0m // contexts.key and contexts.value omitted on purpose.
145 | [37m324[0m [37m385[0m [0m
146 | [37m325[0m // Transaction event fields. [37m386[0m [0m // Transaction event fields.
147 | [37m326[0m [22m[7m[31m 'transaction.duration': 'duration',[0m [37m387[0m [22m[7m[32m [FieldKey.TRANSACTION_DURATION]: 'duration',[0m
148 | [37m327[0m [22m[7m[31m 'transaction.op': 'string',[0m [37m388[0m [22m[7m[32m [FieldKey.TRANSACTION_OP]: 'string',[0m
149 | [37m328[0m [22m[7m[31m 'transaction.status': 'string',[0m [37m389[0m [22m[7m[32m [FieldKey.TRANSACTION_STATUS]: 'string',[0m
150 | [37m329[0m [37m390[0m [0m
151 | [37m330[0m [22m[7m[31m trace: 'string',[0m [37m391[0m [22m[7m[32m [FieldKey.TRACE]: 'string',[0m
152 | [37m331[0m [22m[7m[31m 'trace.span': 'string',[0m [37m392[0m [22m[7m[32m [FieldKey.TRACE_SPAN]: 'string',[0m
153 | [37m332[0m [22m[7m[31m 'trace.parent_span': 'string',[0m [37m393[0m [22m[7m[32m [FieldKey.TRACE_PARENT_SPAN]: 'string',[0m
154 | [37m333[0m [37m394[0m [0m
155 | [37m334[0m // Field alises defined in src/sentry/api/event_search.py [37m395[0m [0m // Field alises defined in src/sentry/api/event_search.py
156 | [37m335[0m [22m[7m[31m project: 'string',[0m [37m396[0m [22m[7m[32m [FieldKey.PROJECT]: 'string',[0m
157 | [37m336[0m [22m[7m[31m issue: 'string',[0m [37m397[0m [22m[7m[32m [FieldKey.ISSUE]: 'string',[0m
158 | [37m337[0m [22m[7m[31m} as const;[0m [37m398[0m [22m[7m[32m};[0m
159 | [37m338[0m [31massert(FIELDS as Readonly<{[key in keyof typeof FIELDS]: Col[0m [37m [0m
160 | [37m [0m [31mumnType}>);[0m [37m [0m
161 | [37m339[0m [37m399[0m [0m
162 | [37m340[0m [22m[7m[31mexport type FieldKey = keyof typeof FIELDS | string | '';[0m [37m400[0m [22m[7m[32mexport type FieldTag = {[0m
163 | [37m [0m [37m401[0m [32m key: FieldKey;[0m
164 | [37m [0m [37m402[0m [32m name: FieldKey;[0m
165 | [37m [0m [37m403[0m [32m};[0m
166 | [37m [0m [37m404[0m [32m[0m
167 | [37m [0m [37m405[0m [32mexport const FIELD_TAGS = Object.freeze([0m
168 | [37m [0m [37m406[0m [32m Object.fromEntries(Object.keys(FIELDS).map(item => [item, [0m
169 | [37m [0m [37m [0m [32m{key: item, name: item}]))[0m
170 | [37m [0m [37m407[0m [32m);[0m
171 | [37m [0m [37m408[0m [32m[0m
172 | [37m [0m [37m409[0m [32m// Allows for a less strict field key definition in cases we[0m
173 | [37m [0m [37m [0m [32m are returning custom strings as fields[0m
174 | [37m [0m [37m410[0m [32mexport type LooseFieldKey = FieldKey | string | '';[0m
175 | [37m341[0m [37m411[0m [0m
176 | [37m342[0m // This list should be removed with the tranaction-events fe [37m412[0m [0m// This list should be removed with the tranaction-events fe
177 | [37m [0m ature flag. [37m [0m ature flag.
178 | [37m343[0m export const TRACING_FIELDS = [ [37m413[0m [0mexport const TRACING_FIELDS = [
179 | [36mdiff --git a/src/sentry/static/sentry/app/views/events/searchBar.tsx b/src/sentry/static/sentry/app/views/events/searchBar.tsx
180 | [0m[36mindex 2bee7646e3..10c503474e 100644
181 | [0m[33m--- a/src/sentry/static/sentry/app/views/events/searchBar.tsx
182 | [0m[33m+++ b/src/sentry/static/sentry/app/views/events/searchBar.tsx
183 | [0m[37m┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈[0m[37m 12[0m import {fetchTagValues} from 'app/actionCreators/tags'; [37m 12[0m [0mimport {fetchTagValues} from 'app/actionCreators/tags';
184 | [37m 13[0m import SentryTypes from 'app/sentryTypes'; [37m 13[0m [0mimport SentryTypes from 'app/sentryTypes';
185 | [37m 14[0m import SmartSearchBar, {SearchType} from 'app/components/sma [37m 14[0m [0mimport SmartSearchBar, {SearchType} from 'app/components/sma
186 | [37m [0m rtSearchBar'; [37m [0m rtSearchBar';
187 | [37m 15[0m [22mimport {Field, FIELDS, TRACING_FIELDS} from 'app/utils/disco[0m [37m 15[0m [22mimport {Field, FIELD[7m[32m_TAG[0m[22mS, TRACING_FIELDS} from 'app/utils/d[0m
188 | [37m [0m [22mver/fields';[0m [37m [0m [22miscover/fields';[0m
189 | [37m 16[0m import withApi from 'app/utils/withApi'; [37m 16[0m [0mimport withApi from 'app/utils/withApi';
190 | [37m 17[0m import withTags from 'app/utils/withTags'; [37m 17[0m [0mimport withTags from 'app/utils/withTags';
191 | [37m 18[0m import {Client} from 'app/api'; [37m 18[0m [0mimport {Client} from 'app/api';
192 | [37m┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈[0m[37m 23[0m 'g' [37m 23[0m [0m 'g'
193 | [37m 24[0m [31m);[0m [37m [0m
194 | [37m 25[0m [31m[0m [37m [0m
195 | [37m 26[0m [31mconst FIELD_TAGS = Object.fromEntries([0m [37m [0m
196 | [37m 27[0m [31m Object.keys(FIELDS).map(item => [item, {key: item, name: i[0m [37m [0m
197 | [37m [0m [31mtem}])[0m [37m [0m
198 | [37m 28[0m ); [37m 24[0m [0m);
199 | [37m 29[0m [37m 25[0m [0m
200 | [37m 30[0m type SearchBarProps = Omit, 'tags'> & { [37m [0m SearchBar>, 'tags'> & {
202 | [37m 31[0m api: Client; [37m 27[0m [0m api: Client;
203 | [37m 32[0m organization: Organization; [37m 28[0m [0m organization: Organization;
204 | [37m┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈[0m[37m101[0m : {}; [37m 97[0m [0m : {};
205 | [37m102[0m [37m 98[0m [0m
206 | [37m103[0m const fieldTags = organization.features.includes('perfor [37m 99[0m [0m const fieldTags = organization.features.includes('perfor
207 | [37m [0m mance-view') [37m [0m mance-view')
208 | [37m104[0m [22m ? assign(FIELD_TAGS, functionTags)[0m [37m100[0m [22m ? [7m[32mObject.[0m[22massign([7m[32m{}, [0m[22mFIELD_TAGS, functionTags)[0m
209 | [37m105[0m : omit(FIELD_TAGS, TRACING_FIELDS); [37m101[0m [0m : omit(FIELD_TAGS, TRACING_FIELDS);
210 | [37m106[0m [37m102[0m [0m
211 | [37m107[0m const combined = assign({}, tags, fieldTags); [37m103[0m [0m const combined = assign({}, tags, fieldTags);
212 | [36mdiff --git a/src/sentry/static/sentry/app/views/settings/incidentRules/metricField.tsx b/src/sentry/static/sentry/app/views/settings/incidentRules/metricField.tsx
213 | [0m[36mindex 3acb977f8a..9ff83b3f82 100644
214 | [0m[33m--- a/src/sentry/static/sentry/app/views/settings/incidentRules/metricField.tsx
215 | [0m[33m+++ b/src/sentry/static/sentry/app/views/settings/incidentRules/metricField.tsx
216 | [0m[37m┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈[0m[37m16[0m explodeFieldString, [37m16[0m [0m explodeFieldString,
217 | [37m17[0m generateFieldAsString, [37m17[0m [0m generateFieldAsString,
218 | [37m18[0m AggregationKey, [37m18[0m [0m AggregationKey,
219 | [37m19[0m [22m FieldKey,[0m [37m19[0m [22m [7m[32mLoose[0m[22mFieldKey,[0m
220 | [37m20[0m AGGREGATIONS, [37m20[0m [0m AGGREGATIONS,
221 | [37m21[0m FIELDS, [37m21[0m [0m FIELDS,
222 | [37m22[0m } from 'app/utils/discover/fields'; [37m22[0m [0m} from 'app/utils/discover/fields';
223 | [37m┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈[0m[37m30[0m [37m30[0m [0m
224 | [37m31[0m type OptionConfig = { [37m31[0m [0mtype OptionConfig = {
225 | [37m32[0m aggregations: AggregationKey[]; [37m32[0m [0m aggregations: AggregationKey[];
226 | [37m33[0m [22m fields: FieldKey[];[0m [37m33[0m [22m fields: [7m[32mLoose[0m[22mFieldKey[];[0m
227 | [37m34[0m }; [37m34[0m [0m};
228 | [37m35[0m [37m35[0m [0m
229 | [37m36[0m const errorFieldConfig: OptionConfig = { [37m36[0m [0mconst errorFieldConfig: OptionConfig = {
230 |
--------------------------------------------------------------------------------
/tests/sentry/8/in.diff:
--------------------------------------------------------------------------------
1 | commit b3e365e5db58046244d52cad596d0dd390d9c59c
2 | Author: k-fish <6111995+k-fish@users.noreply.github.com>
3 | Date: Mon Jun 22 11:53:59 2020 -0700
4 |
5 | ref(ts): Convert `scoreBar` to typescript (#19434)
6 |
7 | This converts the `scoreBar` component to typescript
8 |
9 | * ref(ts): Convert `scoreBar` to typescript
10 | * Switch ScoreBar to be a functional component
11 |
12 | diff --git a/src/sentry/static/sentry/app/components/scoreBar.jsx b/src/sentry/static/sentry/app/components/scoreBar.jsx
13 | deleted file mode 100644
14 | index 049556bf76..0000000000
15 | --- a/src/sentry/static/sentry/app/components/scoreBar.jsx
16 | +++ /dev/null
17 | @@ -1,77 +0,0 @@
18 | -import PropTypes from 'prop-types';
19 | -import React from 'react';
20 | -import styled from '@emotion/styled';
21 | -
22 | -import theme from 'app/utils/theme';
23 | -
24 | -class ScoreBar extends React.Component {
25 | - static propTypes = {
26 | - vertical: PropTypes.bool,
27 | - score: PropTypes.number.isRequired,
28 | - /** Array of strings */
29 | - palette: PropTypes.arrayOf(PropTypes.string),
30 | - /** Array of classNames whose index maps to score */
31 | - paletteClassNames: PropTypes.arrayOf(PropTypes.string),
32 | - size: PropTypes.number,
33 | - thickness: PropTypes.number,
34 | - radius: PropTypes.number,
35 | - };
36 | -
37 | - static defaultProps = {
38 | - size: 40,
39 | - thickness: 4,
40 | - radius: 3,
41 | - palette: theme.similarity.colors,
42 | - };
43 | -
44 | - render() {
45 | - const {className, vertical, palette, score, size, thickness, radius} = this.props;
46 | - const maxScore = palette.length;
47 | -
48 | - // Make sure score is between 0 and maxScore
49 | - const scoreInBounds = score >= maxScore ? maxScore : score <= 0 ? 0 : score;
50 | - // Make sure paletteIndex is 0 based
51 | - const paletteIndex = scoreInBounds - 1;
52 | -
53 | - // Size of bar, depends on orientation, although we could just apply a transformation via css
54 | - const barProps = {
55 | - vertical,
56 | - thickness,
57 | - size,
58 | - radius,
59 | - };
60 | -
61 | - return (
62 | -
63 | - {[...Array(scoreInBounds)].map((_j, i) => (
64 | -
65 | - ))}
66 | - {[...Array(maxScore - scoreInBounds)].map((_j, i) => (
67 | -
68 | - ))}
69 | -
70 | - );
71 | - }
72 | -}
73 | -
74 | -const StyledScoreBar = styled(ScoreBar)`
75 | - display: flex;
76 | -
77 | - ${p =>
78 | - p.vertical
79 | - ? `flex-direction: column-reverse;
80 | - justify-content: flex-end;`
81 | - : 'min-width: 80px;'};
82 | -`;
83 | -
84 | -const Bar = styled('div')`
85 | - border-radius: ${p => p.radius}px;
86 | - margin: 2px;
87 | - ${p => p.empty && `background-color: ${p.theme.similarity.empty};`};
88 | - ${p => p.color && `background-color: ${p.color};`};
89 | -
90 | - width: ${p => (!p.vertical ? p.thickness : p.size)}px;
91 | - height: ${p => (!p.vertical ? p.size : p.thickness)}px;
92 | -`;
93 | -
94 | -export default StyledScoreBar;
95 | diff --git a/src/sentry/static/sentry/app/components/scoreBar.tsx b/src/sentry/static/sentry/app/components/scoreBar.tsx
96 | new file mode 100644
97 | index 0000000000..968ac64120
98 | --- /dev/null
99 | +++ b/src/sentry/static/sentry/app/components/scoreBar.tsx
100 | @@ -0,0 +1,95 @@
101 | +import PropTypes from 'prop-types';
102 | +import React from 'react';
103 | +import styled from '@emotion/styled';
104 | +
105 | +import theme from 'app/utils/theme';
106 | +
107 | +type Props = {
108 | + score: number;
109 | + size?: number;
110 | + thickness?: number;
111 | + radius?: number;
112 | + palette?: Readonly;
113 | + className?: string;
114 | + paletteClassNames?: string[];
115 | + vertical?: boolean;
116 | +};
117 | +
118 | +const ScoreBar = ({
119 | + score,
120 | + className,
121 | + vertical,
122 | + size = 40,
123 | + thickness = 4,
124 | + radius = 3,
125 | + palette = theme.similarity.colors,
126 | +}: Props) => {
127 | + const maxScore = palette.length;
128 | +
129 | + // Make sure score is between 0 and maxScore
130 | + const scoreInBounds = score >= maxScore ? maxScore : score <= 0 ? 0 : score;
131 | + // Make sure paletteIndex is 0 based
132 | + const paletteIndex = scoreInBounds - 1;
133 | +
134 | + // Size of bar, depends on orientation, although we could just apply a transformation via css
135 | + const barProps = {
136 | + vertical,
137 | + thickness,
138 | + size,
139 | + radius,
140 | + };
141 | +
142 | + return (
143 | +
144 | + {[...Array(scoreInBounds)].map((_j, i) => (
145 | +
146 | + ))}
147 | + {[...Array(maxScore - scoreInBounds)].map((_j, i) => (
148 | +
149 | + ))}
150 | +
151 | + );
152 | +};
153 | +
154 | +ScoreBar.propTypes = {
155 | + vertical: PropTypes.bool,
156 | + score: PropTypes.number.isRequired,
157 | + /** Array of strings */
158 | + palette: PropTypes.arrayOf(PropTypes.string),
159 | + /** Array of classNames whose index maps to score */
160 | + paletteClassNames: PropTypes.arrayOf(PropTypes.string),
161 | + size: PropTypes.number,
162 | + thickness: PropTypes.number,
163 | + radius: PropTypes.number,
164 | +};
165 | +
166 | +const StyledScoreBar = styled(ScoreBar)`
167 | + display: flex;
168 | +
169 | + ${p =>
170 | + p.vertical
171 | + ? `flex-direction: column-reverse;
172 | + justify-content: flex-end;`
173 | + : 'min-width: 80px;'};
174 | +`;
175 | +
176 | +type BarProps = {
177 | + radius: number;
178 | + size: number;
179 | + thickness: number;
180 | + color?: string;
181 | + empty?: boolean;
182 | + vertical?: boolean;
183 | +};
184 | +
185 | +const Bar = styled('div')`
186 | + border-radius: ${p => p.radius}px;
187 | + margin: 2px;
188 | + ${p => p.empty && `background-color: ${p.theme.similarity.empty};`};
189 | + ${p => p.color && `background-color: ${p.color};`};
190 | +
191 | + width: ${p => (!p.vertical ? p.thickness : p.size)}px;
192 | + height: ${p => (!p.vertical ? p.size : p.thickness)}px;
193 | +`;
194 | +
195 | +export default StyledScoreBar;
196 | diff --git a/tests/js/spec/views/organizationGroupDetails/__snapshots__/groupSimilar.spec.jsx.snap b/tests/js/spec/views/organizationGroupDetails/__snapshots__/groupSimilar.spec.jsx.snap
197 | index be35bc42ae..7b7e51ca00 100644
198 | --- a/tests/js/spec/views/organizationGroupDetails/__snapshots__/groupSimilar.spec.jsx.snap
199 | +++ b/tests/js/spec/views/organizationGroupDetails/__snapshots__/groupSimilar.spec.jsx.snap
200 | @@ -1669,40 +1669,16 @@ exports[`Issues Similar View renders with mocked data 1`] = `
201 | onMouseLeave={[Function]}
202 | >
203 |
219 |
237 |
241 |
246 |
255 |
264 |
273 |
282 |
288 | @@ -1807,40 +1783,16 @@ exports[`Issues Similar View renders with mocked data 1`] = `
289 | onMouseLeave={[Function]}
290 | >
291 |
307 |
325 |
329 |
334 |
340 | @@ -1865,7 +1817,7 @@ exports[`Issues Similar View renders with mocked data 1`] = `
341 | vertical={true}
342 | >
343 |
349 | @@ -1879,7 +1831,7 @@ exports[`Issues Similar View renders with mocked data 1`] = `
350 | vertical={true}
351 | >
352 |
358 | @@ -1893,7 +1845,7 @@ exports[`Issues Similar View renders with mocked data 1`] = `
359 | vertical={true}
360 | >
361 |
367 | @@ -1907,7 +1859,7 @@ exports[`Issues Similar View renders with mocked data 1`] = `
368 | vertical={true}
369 | >
370 |
376 |
--------------------------------------------------------------------------------
/tests/sentry/8/out:
--------------------------------------------------------------------------------
1 | [36mcommit b3e365e5db58046244d52cad596d0dd390d9c59c
2 | [0m[36mAuthor: k-fish <6111995+k-fish@users.noreply.github.com>
3 | [0m[36mDate: Mon Jun 22 11:53:59 2020 -0700
4 | [0m[36m
5 | [0m[36m ref(ts): Convert `scoreBar` to typescript (#19434)
6 | [0m[36m
7 | [0m[36m This converts the `scoreBar` component to typescript
8 | [0m[36m
9 | [0m[36m * ref(ts): Convert `scoreBar` to typescript
10 | [0m[36m * Switch ScoreBar to be a functional component
11 | [0m[36m
12 | [0m[36mdiff --git a/src/sentry/static/sentry/app/components/scoreBar.jsx b/src/sentry/static/sentry/app/components/scoreBar.jsx
13 | [0m[36mdeleted file mode 100644
14 | [0m[36mindex 049556bf76..0000000000
15 | [0m[33m--- a/src/sentry/static/sentry/app/components/scoreBar.jsx
16 | [0m[33m+++ /dev/null
17 | [0m[37m┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈[0m[37m 1[0m [31mimport PropTypes from 'prop-types';[0m [37m [0m
18 | [37m 2[0m [31mimport React from 'react';[0m [37m [0m
19 | [37m 3[0m [31mimport styled from '@emotion/styled';[0m [37m [0m
20 | [37m 4[0m [31m[0m [37m [0m
21 | [37m 5[0m [31mimport theme from 'app/utils/theme';[0m [37m [0m
22 | [37m 6[0m [31m[0m [37m [0m
23 | [37m 7[0m [31mclass ScoreBar extends React.Component {[0m [37m [0m
24 | [37m 8[0m [31m static propTypes = {[0m [37m [0m
25 | [37m 9[0m [31m vertical: PropTypes.bool,[0m [37m [0m
26 | [37m10[0m [31m score: PropTypes.number.isRequired,[0m [37m [0m
27 | [37m11[0m [31m /** Array of strings */[0m [37m [0m
28 | [37m12[0m [31m palette: PropTypes.arrayOf(PropTypes.string),[0m [37m [0m
29 | [37m13[0m [31m /** Array of classNames whose index maps to score */[0m [37m [0m
30 | [37m14[0m [31m paletteClassNames: PropTypes.arrayOf(PropTypes.string),[0m [37m [0m
31 | [37m15[0m [31m size: PropTypes.number,[0m [37m [0m
32 | [37m16[0m [31m thickness: PropTypes.number,[0m [37m [0m
33 | [37m17[0m [31m radius: PropTypes.number,[0m [37m [0m
34 | [37m18[0m [31m };[0m [37m [0m
35 | [37m19[0m [31m[0m [37m [0m
36 | [37m20[0m [31m static defaultProps = {[0m [37m [0m
37 | [37m21[0m [31m size: 40,[0m [37m [0m
38 | [37m22[0m [31m thickness: 4,[0m [37m [0m
39 | [37m23[0m [31m radius: 3,[0m [37m [0m
40 | [37m24[0m [31m palette: theme.similarity.colors,[0m [37m [0m
41 | [37m25[0m [31m };[0m [37m [0m
42 | [37m26[0m [31m[0m [37m [0m
43 | [37m27[0m [31m render() {[0m [37m [0m
44 | [37m28[0m [31m const {className, vertical, palette, score, size, thickne[0m [37m [0m
45 | [37m [0m [31mss, radius} = this.props;[0m [37m [0m
46 | [37m29[0m [31m const maxScore = palette.length;[0m [37m [0m
47 | [37m30[0m [31m[0m [37m [0m
48 | [37m31[0m [31m // Make sure score is between 0 and maxScore[0m [37m [0m
49 | [37m32[0m [31m const scoreInBounds = score >= maxScore ? maxScore : scor[0m [37m [0m
50 | [37m [0m [31me <= 0 ? 0 : score;[0m [37m [0m
51 | [37m33[0m [31m // Make sure paletteIndex is 0 based[0m [37m [0m
52 | [37m34[0m [31m const paletteIndex = scoreInBounds - 1;[0m [37m [0m
53 | [37m35[0m [31m[0m [37m [0m
54 | [37m36[0m [31m // Size of bar, depends on orientation, although we could[0m [37m [0m
55 | [37m [0m [31m just apply a transformation via css[0m [37m [0m
56 | [37m37[0m [31m const barProps = {[0m [37m [0m
57 | [37m38[0m [31m vertical,[0m [37m [0m
58 | [37m39[0m [31m thickness,[0m [37m [0m
59 | [37m40[0m [31m size,[0m [37m [0m
60 | [37m41[0m [31m radius,[0m [37m [0m
61 | [37m42[0m [31m };[0m [37m [0m
62 | [37m43[0m [31m[0m [37m [0m
63 | [37m44[0m [31m return ([0m [37m [0m
64 | [37m45[0m [31m [0m [37m [0m
65 | [37m46[0m [31m {[...Array(scoreInBounds)].map((_j, i) => ([0m [37m [0m
66 | [37m47[0m [31m [0m [37m [0m
68 | [37m48[0m [31m ))}[0m [37m [0m
69 | [37m49[0m [31m {[...Array(maxScore - scoreInBounds)].map((_j, i) => [0m [37m [0m
70 | [37m [0m [31m([0m [37m [0m
71 | [37m50[0m [31m [0m [37m [0m
72 | [37m51[0m [31m ))}[0m [37m [0m
73 | [37m52[0m [31m
[0m [37m [0m
74 | [37m53[0m [31m );[0m [37m [0m
75 | [37m54[0m [31m }[0m [37m [0m
76 | [37m55[0m [31m}[0m [37m [0m
77 | [37m56[0m [31m[0m [37m [0m
78 | [37m57[0m [31mconst StyledScoreBar = styled(ScoreBar)`[0m [37m [0m
79 | [37m58[0m [31m display: flex;[0m [37m [0m
80 | [37m59[0m [31m[0m [37m [0m
81 | [37m60[0m [31m ${p =>[0m [37m [0m
82 | [37m61[0m [31m p.vertical[0m [37m [0m
83 | [37m62[0m [31m ? `flex-direction: column-reverse;[0m [37m [0m
84 | [37m63[0m [31m justify-content: flex-end;`[0m [37m [0m
85 | [37m64[0m [31m : 'min-width: 80px;'};[0m [37m [0m
86 | [37m65[0m [31m`;[0m [37m [0m
87 | [37m66[0m [31m[0m [37m [0m
88 | [37m67[0m [31mconst Bar = styled('div')`[0m [37m [0m
89 | [37m68[0m [31m border-radius: ${p => p.radius}px;[0m [37m [0m
90 | [37m69[0m [31m margin: 2px;[0m [37m [0m
91 | [37m70[0m [31m ${p => p.empty && `background-color: ${p.theme.similarity.e[0m [37m [0m
92 | [37m [0m [31mmpty};`};[0m [37m [0m
93 | [37m71[0m [31m ${p => p.color && `background-color: ${p.color};`};[0m [37m [0m
94 | [37m72[0m [31m[0m [37m [0m
95 | [37m73[0m [31m width: ${p => (!p.vertical ? p.thickness : p.size)}px;[0m [37m [0m
96 | [37m74[0m [31m height: ${p => (!p.vertical ? p.size : p.thickness)}px;[0m [37m [0m
97 | [37m75[0m [31m`;[0m [37m [0m
98 | [37m76[0m [31m[0m [37m [0m
99 | [37m77[0m [31mexport default StyledScoreBar;[0m [37m [0m
100 | [36mdiff --git a/src/sentry/static/sentry/app/components/scoreBar.tsx b/src/sentry/static/sentry/app/components/scoreBar.tsx
101 | [0m[36mnew file mode 100644
102 | [0m[36mindex 0000000000..968ac64120
103 | [0m[33m--- /dev/null
104 | [0m[33m+++ b/src/sentry/static/sentry/app/components/scoreBar.tsx
105 | [0m[37m┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈[0m[37m [0m [37m 1[0m [32mimport PropTypes from 'prop-types';[0m
106 | [37m [0m [37m 2[0m [32mimport React from 'react';[0m
107 | [37m [0m [37m 3[0m [32mimport styled from '@emotion/styled';[0m
108 | [37m [0m [37m 4[0m [32m[0m
109 | [37m [0m [37m 5[0m [32mimport theme from 'app/utils/theme';[0m
110 | [37m [0m [37m 6[0m [32m[0m
111 | [37m [0m [37m 7[0m [32mtype Props = {[0m
112 | [37m [0m [37m 8[0m [32m score: number;[0m
113 | [37m [0m [37m 9[0m [32m size?: number;[0m
114 | [37m [0m [37m10[0m [32m thickness?: number;[0m
115 | [37m [0m [37m11[0m [32m radius?: number;[0m
116 | [37m [0m [37m12[0m [32m palette?: Readonly;[0m
117 | [37m [0m [37m13[0m [32m className?: string;[0m
118 | [37m [0m [37m14[0m [32m paletteClassNames?: string[];[0m
119 | [37m [0m [37m15[0m [32m vertical?: boolean;[0m
120 | [37m [0m [37m16[0m [32m};[0m
121 | [37m [0m [37m17[0m [32m[0m
122 | [37m [0m [37m18[0m [32mconst ScoreBar = ({[0m
123 | [37m [0m [37m19[0m [32m score,[0m
124 | [37m [0m [37m20[0m [32m className,[0m
125 | [37m [0m [37m21[0m [32m vertical,[0m
126 | [37m [0m [37m22[0m [32m size = 40,[0m
127 | [37m [0m [37m23[0m [32m thickness = 4,[0m
128 | [37m [0m [37m24[0m [32m radius = 3,[0m
129 | [37m [0m [37m25[0m [32m palette = theme.similarity.colors,[0m
130 | [37m [0m [37m26[0m [32m}: Props) => {[0m
131 | [37m [0m [37m27[0m [32m const maxScore = palette.length;[0m
132 | [37m [0m [37m28[0m [32m[0m
133 | [37m [0m [37m29[0m [32m // Make sure score is between 0 and maxScore[0m
134 | [37m [0m [37m30[0m [32m const scoreInBounds = score >= maxScore ? maxScore : score [0m
135 | [37m [0m [37m [0m [32m<= 0 ? 0 : score;[0m
136 | [37m [0m [37m31[0m [32m // Make sure paletteIndex is 0 based[0m
137 | [37m [0m [37m32[0m [32m const paletteIndex = scoreInBounds - 1;[0m
138 | [37m [0m [37m33[0m [32m[0m
139 | [37m [0m [37m34[0m [32m // Size of bar, depends on orientation, although we could j[0m
140 | [37m [0m [37m [0m [32must apply a transformation via css[0m
141 | [37m [0m [37m35[0m [32m const barProps = {[0m
142 | [37m [0m [37m36[0m [32m vertical,[0m
143 | [37m [0m [37m37[0m [32m thickness,[0m
144 | [37m [0m [37m38[0m [32m size,[0m
145 | [37m [0m [37m39[0m [32m radius,[0m
146 | [37m [0m [37m40[0m [32m };[0m
147 | [37m [0m [37m41[0m [32m[0m
148 | [37m [0m [37m42[0m [32m return ([0m
149 | [37m [0m [37m43[0m [32m [0m
150 | [37m [0m [37m44[0m [32m {[...Array(scoreInBounds)].map((_j, i) => ([0m
151 | [37m [0m [37m45[0m [32m [0m
153 | [37m [0m [37m46[0m [32m ))}[0m
154 | [37m [0m [37m47[0m [32m {[...Array(maxScore - scoreInBounds)].map((_j, i) => ([0m
155 | [37m [0m [37m48[0m [32m [0m
156 | [37m [0m [37m49[0m [32m ))}[0m
157 | [37m [0m [37m50[0m [32m
[0m
158 | [37m [0m [37m51[0m [32m );[0m
159 | [37m [0m [37m52[0m [32m};[0m
160 | [37m [0m [37m53[0m [32m[0m
161 | [37m [0m [37m54[0m [32mScoreBar.propTypes = {[0m
162 | [37m [0m [37m55[0m [32m vertical: PropTypes.bool,[0m
163 | [37m [0m [37m56[0m [32m score: PropTypes.number.isRequired,[0m
164 | [37m [0m [37m57[0m [32m /** Array of strings */[0m
165 | [37m [0m [37m58[0m [32m palette: PropTypes.arrayOf(PropTypes.string),[0m
166 | [37m [0m [37m59[0m [32m /** Array of classNames whose index maps to score */[0m
167 | [37m [0m [37m60[0m [32m paletteClassNames: PropTypes.arrayOf(PropTypes.string),[0m
168 | [37m [0m [37m61[0m [32m size: PropTypes.number,[0m
169 | [37m [0m [37m62[0m [32m thickness: PropTypes.number,[0m
170 | [37m [0m [37m63[0m [32m radius: PropTypes.number,[0m
171 | [37m [0m [37m64[0m [32m};[0m
172 | [37m [0m [37m65[0m [32m[0m
173 | [37m [0m [37m66[0m [32mconst StyledScoreBar = styled(ScoreBar)`[0m
174 | [37m [0m [37m67[0m [32m display: flex;[0m
175 | [37m [0m [37m68[0m [32m[0m
176 | [37m [0m [37m69[0m [32m ${p =>[0m
177 | [37m [0m [37m70[0m [32m p.vertical[0m
178 | [37m [0m [37m71[0m [32m ? `flex-direction: column-reverse;[0m
179 | [37m [0m [37m72[0m [32m justify-content: flex-end;`[0m
180 | [37m [0m [37m73[0m [32m : 'min-width: 80px;'};[0m
181 | [37m [0m [37m74[0m [32m`;[0m
182 | [37m [0m [37m75[0m [32m[0m
183 | [37m [0m [37m76[0m [32mtype BarProps = {[0m
184 | [37m [0m [37m77[0m [32m radius: number;[0m
185 | [37m [0m [37m78[0m [32m size: number;[0m
186 | [37m [0m [37m79[0m [32m thickness: number;[0m
187 | [37m [0m [37m80[0m [32m color?: string;[0m
188 | [37m [0m [37m81[0m [32m empty?: boolean;[0m
189 | [37m [0m [37m82[0m [32m vertical?: boolean;[0m
190 | [37m [0m [37m83[0m [32m};[0m
191 | [37m [0m [37m84[0m [32m[0m
192 | [37m [0m [37m85[0m [32mconst Bar = styled('div')`[0m
193 | [37m [0m [37m86[0m [32m border-radius: ${p => p.radius}px;[0m
194 | [37m [0m [37m87[0m [32m margin: 2px;[0m
195 | [37m [0m [37m88[0m [32m ${p => p.empty && `background-color: ${p.theme.similarity.e[0m
196 | [37m [0m [37m [0m [32mmpty};`};[0m
197 | [37m [0m [37m89[0m [32m ${p => p.color && `background-color: ${p.color};`};[0m
198 | [37m [0m [37m90[0m [32m[0m
199 | [37m [0m [37m91[0m [32m width: ${p => (!p.vertical ? p.thickness : p.size)}px;[0m
200 | [37m [0m [37m92[0m [32m height: ${p => (!p.vertical ? p.size : p.thickness)}px;[0m
201 | [37m [0m [37m93[0m [32m`;[0m
202 | [37m [0m [37m94[0m [32m[0m
203 | [37m [0m [37m95[0m [32mexport default StyledScoreBar;[0m
204 | [36mdiff --git a/tests/js/spec/views/organizationGroupDetails/__snapshots__/groupSimilar.spec.jsx.snap b/tests/js/spec/views/organizationGroupDetails/__snapshots__/groupSimilar.spec.jsx.snap
205 | [0m[36mindex be35bc42ae..7b7e51ca00 100644
206 | [0m[33m--- a/tests/js/spec/views/organizationGroupDetails/__snapshots__/groupSimilar.spec.jsx.snap
207 | [0m[33m+++ b/tests/js/spec/views/organizationGroupDetails/__snapshots__/groupSimilar.spec.jsx.snap
208 | [0m[37m┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈[0m[37m1669[0m onMouseLeave={[Function]} [37m1669[0m [0m onMouseLeave={[Function]}
209 | [37m1670[0m > [37m1670[0m [0m >
210 | [37m1671[0m [37m1674[0m [0m >
226 | [37m1687[0m [37m1679[0m [0m >
244 | [37m1704[0m [37m1682[0m [0m >
248 | [37m1707[0m
[37m1690[0m [0m >
252 | [37m1715[0m [37m1705[0m [0m >
260 | [37m1730[0m
[37m1720[0m [0m >
268 | [37m1745[0m
[37m1735[0m [0m >
276 | [37m1760[0m
[37m1750[0m [0m >
284 | [37m1775[0m
[37m1755[0m [0m />
290 | [37m┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈[0m[37m1807[0m onMouseLeave={[Function]} [37m1783[0m [0m onMouseLeave={[Function]}
291 | [37m1808[0m > [37m1784[0m [0m >
292 | [37m1809[0m
[37m1788[0m [0m >
308 | [37m1825[0m [37m1793[0m [0m >
326 | [37m1842[0m [37m1796[0m [0m >
330 | [37m1845[0m
[37m1804[0m [0m >
334 | [37m1853[0m [37m1809[0m [0m />
340 | [37m┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈[0m[37m1865[0m vertical={true} [37m1817[0m [0m vertical={true}
341 | [37m1866[0m > [37m1818[0m [0m >
342 | [37m1867[0m [37m1823[0m [0m />
348 | [37m┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈[0m[37m1879[0m vertical={true} [37m1831[0m [0m vertical={true}
349 | [37m1880[0m > [37m1832[0m [0m >
350 | [37m1881[0m [37m1837[0m [0m />
356 | [37m┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈[0m[37m1893[0m vertical={true} [37m1845[0m [0m vertical={true}
357 | [37m1894[0m > [37m1846[0m [0m >
358 | [37m1895[0m [37m1851[0m [0m />
364 | [37m┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈[0m[37m1907[0m vertical={true} [37m1859[0m [0m vertical={true}
365 | [37m1908[0m > [37m1860[0m [0m >
366 | [37m1909[0m [37m1865[0m [0m />
372 |
--------------------------------------------------------------------------------
/tests/sentry/9/in.diff:
--------------------------------------------------------------------------------
1 | commit 24ff1aa9e589590243f886e1ff41c9b53b4a58e1
2 | Author: Dan Fuller
3 | Date: Mon Jun 22 11:33:32 2020 -0700
4 |
5 | chore(subscriptions): Add logging about partitions/offsets when assigning/revoking partitions, and (#19474)
6 |
7 | when committing offsets
8 |
9 | I want to be able to see what the offsets are set to on the consumer so that we can come up with a
10 | more permanent solution for the issues where our consumer lag monitor misfires due to low
11 | transaction alert usage.
12 |
13 | diff --git a/src/sentry/snuba/query_subscription_consumer.py b/src/sentry/snuba/query_subscription_consumer.py
14 | index 1b824f8765..a7f57ae359 100644
15 | --- a/src/sentry/snuba/query_subscription_consumer.py
16 | +++ b/src/sentry/snuba/query_subscription_consumer.py
17 | @@ -87,12 +87,14 @@ class QuerySubscriptionConsumer(object):
18 | else:
19 | updated_offset = partition.offset
20 | self.offsets[partition.partition] = updated_offset
21 | + logger.info("query-subscription-consumer.on_assign", extra={"offsets": self.offsets})
22 |
23 | def on_revoke(consumer, partitions):
24 | partition_numbers = [partition.partition for partition in partitions]
25 | self.commit_offsets(partition_numbers)
26 | for partition_number in partition_numbers:
27 | self.offsets.pop(partition_number, None)
28 | + logger.info("query-subscription-consumer.on_revoke", extra={"offsets": self.offsets})
29 |
30 | self.consumer = Consumer(conf)
31 | self.consumer.subscribe([self.topic], on_assign=on_assign, on_revoke=on_revoke)
32 | @@ -131,6 +133,11 @@ class QuerySubscriptionConsumer(object):
33 | self.shutdown()
34 |
35 | def commit_offsets(self, partitions=None):
36 | + logger.info(
37 | + "query-subscription-consumer.commit_offsets",
38 | + extra={"offsets": self.offsets, "partitions": partitions},
39 | + )
40 | +
41 | if self.offsets and self.consumer:
42 | if partitions is None:
43 | partitions = self.offsets.keys()
44 |
--------------------------------------------------------------------------------
/tests/sentry/9/out:
--------------------------------------------------------------------------------
1 | [36mcommit 24ff1aa9e589590243f886e1ff41c9b53b4a58e1
2 | [0m[36mAuthor: Dan Fuller
3 | [0m[36mDate: Mon Jun 22 11:33:32 2020 -0700
4 | [0m[36m
5 | [0m[36m chore(subscriptions): Add logging about partitions/offsets when assigning/revoking partitions, and (#19474)
6 | [0m[36m
7 | [0m[36m when committing offsets
8 | [0m[36m
9 | [0m[36m I want to be able to see what the offsets are set to on the consumer so that we can come up with a
10 | [0m[36m more permanent solution for the issues where our consumer lag monitor misfires due to low
11 | [0m[36m transaction alert usage.
12 | [0m[36m
13 | [0m[36mdiff --git a/src/sentry/snuba/query_subscription_consumer.py b/src/sentry/snuba/query_subscription_consumer.py
14 | [0m[36mindex 1b824f8765..a7f57ae359 100644
15 | [0m[33m--- a/src/sentry/snuba/query_subscription_consumer.py
16 | [0m[33m+++ b/src/sentry/snuba/query_subscription_consumer.py
17 | [0m[37m┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈[0m[37m 87[0m else: [37m 87[0m [0m else:
18 | [37m 88[0m updated_offset = partition.offset [37m 88[0m [0m updated_offset = partition.offset
19 | [37m 89[0m self.offsets[partition.partition] = updated_ [37m 89[0m [0m self.offsets[partition.partition] = updated_
20 | [37m [0m offset [37m [0m offset
21 | [37m [0m [37m 90[0m [32m logger.info("query-subscription-consumer.on_assi[0m
22 | [37m [0m [37m [0m [32mgn", extra={"offsets": self.offsets})[0m
23 | [37m 90[0m [37m 91[0m [0m
24 | [37m 91[0m def on_revoke(consumer, partitions): [37m 92[0m [0m def on_revoke(consumer, partitions):
25 | [37m 92[0m partition_numbers = [partition.partition for par [37m 93[0m [0m partition_numbers = [partition.partition for par
26 | [37m [0m tition in partitions] [37m [0m tition in partitions]
27 | [37m 93[0m self.commit_offsets(partition_numbers) [37m 94[0m [0m self.commit_offsets(partition_numbers)
28 | [37m 94[0m for partition_number in partition_numbers: [37m 95[0m [0m for partition_number in partition_numbers:
29 | [37m 95[0m self.offsets.pop(partition_number, None) [37m 96[0m [0m self.offsets.pop(partition_number, None)
30 | [37m [0m [37m 97[0m [32m logger.info("query-subscription-consumer.on_revo[0m
31 | [37m [0m [37m [0m [32mke", extra={"offsets": self.offsets})[0m
32 | [37m 96[0m [37m 98[0m [0m
33 | [37m 97[0m self.consumer = Consumer(conf) [37m 99[0m [0m self.consumer = Consumer(conf)
34 | [37m 98[0m self.consumer.subscribe([self.topic], on_assign=on_a [37m100[0m [0m self.consumer.subscribe([self.topic], on_assign=on_a
35 | [37m [0m ssign, on_revoke=on_revoke) [37m [0m ssign, on_revoke=on_revoke)
36 | [37m┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈[0m[37m131[0m self.shutdown() [37m133[0m [0m self.shutdown()
37 | [37m132[0m [37m134[0m [0m
38 | [37m133[0m def commit_offsets(self, partitions=None): [37m135[0m [0m def commit_offsets(self, partitions=None):
39 | [37m [0m [37m136[0m [32m logger.info([0m
40 | [37m [0m [37m137[0m [32m "query-subscription-consumer.commit_offsets",[0m
41 | [37m [0m [37m138[0m [32m extra={"offsets": self.offsets, "partitions": pa[0m
42 | [37m [0m [37m [0m [32mrtitions},[0m
43 | [37m [0m [37m139[0m [32m )[0m
44 | [37m [0m [37m140[0m [32m[0m
45 | [37m134[0m if self.offsets and self.consumer: [37m141[0m [0m if self.offsets and self.consumer:
46 | [37m135[0m if partitions is None: [37m142[0m [0m if partitions is None:
47 | [37m136[0m partitions = self.offsets.keys() [37m143[0m [0m partitions = self.offsets.keys()
48 |
--------------------------------------------------------------------------------
/ydiff:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import signal
4 | signal.signal(signal.SIGPIPE, signal.SIG_DFL)
5 | signal.signal(signal.SIGINT, signal.SIG_DFL)
6 |
7 | import sys
8 | import difflib
9 | from os import get_terminal_size, environ
10 |
11 | # XXX: Since I'm using ydiff like `git diff | ydiff | less`,
12 | # both stdin and stdout are redirected, so we'll get inappropriate ioctl for those devices.
13 | # But we can use stderr (fd 2)!
14 | terminal_width = int(environ.get("YDIFF_WIDTH") or get_terminal_size(2)[0])
15 |
16 | COLOR_RESET = "\x1b[0m"
17 | COLOR_REVERSE = "\x1b[7m"
18 | COLOR_PLAIN = "\x1b[22m"
19 | COLOR_RED = "\x1b[31m"
20 | COLOR_GREEN = "\x1b[32m"
21 | COLOR_YELLOW = "\x1b[33m"
22 | COLOR_CYAN = "\x1b[36m"
23 | COLOR_GRAY = "\x1b[37m"
24 |
25 | hunk_meta_display = f"{COLOR_GRAY}{'┈' * terminal_width}{COLOR_RESET}"
26 |
27 |
28 | def strsplit(text, width):
29 | """strsplit() splits a given string into two substrings, \x1b-aware.
30 |
31 | It returns 3-tuple: (first string, second string, number of visible chars
32 | in the first string).
33 |
34 | If some color was active at the splitting point, then the first string is
35 | appended with the resetting sequence, and the second string is prefixed
36 | with all active colors.
37 | """
38 | first = ""
39 | found_colors = ""
40 | chars_cnt = 0
41 | append_len = 0
42 |
43 | while len(text):
44 | # First of all, check if current string begins with any escape sequence.
45 | if text[0] == "\x1b":
46 | color_end = text.find("m")
47 | if color_end != -1:
48 | color = text[:color_end+1]
49 | if color == COLOR_RESET:
50 | found_colors = ""
51 | else:
52 | found_colors += color
53 | append_len = len(color)
54 |
55 | if not append_len:
56 | # Current string does not start with any escape sequence, so,
57 | # either add one more visible char to the "first" string, or
58 | # break if that string is already large enough.
59 | if chars_cnt >= width:
60 | break
61 | chars_cnt += 1
62 | # would popfront be more efficient here?
63 | first += text[0]
64 | text = text[1:]
65 | continue
66 |
67 | first += text[:append_len]
68 | text = text[append_len:]
69 | append_len = 0
70 |
71 | second = text
72 |
73 | # If the first string has some active colors at the splitting point,
74 | # reset it and append the same colors to the second string.
75 | if found_colors:
76 | return first + COLOR_RESET, found_colors + second, chars_cnt
77 |
78 | return first, second, chars_cnt
79 |
80 |
81 | class Hunk(object):
82 | def __init__(self, hunk_headers, old_addr, new_addr):
83 | self._hunk_headers = hunk_headers
84 | self._old_addr = old_addr # tuple (start, offset)
85 | self._new_addr = new_addr # tuple (start, offset)
86 | self._hunk_list = [] # list of tuple (attr, line)
87 |
88 | def append(self, hunk_line):
89 | """hunk_line is a 2-element tuple: (attr, text), where attr is:
90 | '-': old, '+': new, ' ': common
91 | """
92 | self._hunk_list.append(hunk_line)
93 |
94 | def mdiff(self):
95 | """The difflib._mdiff() function returns an interator which returns a
96 | tuple: (from line tuple, to line tuple, boolean flag)
97 |
98 | from/to line tuple -- (line num, line text)
99 | line num -- integer or None (to indicate a context separation)
100 | line text -- original line text with following markers inserted:
101 | '\0+' -- marks start of added text
102 | '\0-' -- marks start of deleted text
103 | '\0^' -- marks start of changed text
104 | '\1' -- marks end of added/deleted/changed text
105 |
106 | boolean flag -- None indicates context separation, True indicates
107 | either "from" or "to" line contains a change, otherwise False.
108 | """
109 | return difflib._mdiff(self._get_old_text(), self._get_new_text())
110 |
111 | def _get_old_text(self):
112 | return [line for attr, line in self._hunk_list if attr != "+"]
113 |
114 | def _get_new_text(self):
115 | return [line for attr, line in self._hunk_list if attr != "-"]
116 |
117 | def is_completed(self):
118 | old_completed = self._old_addr[1] == len(self._get_old_text())
119 | if not old_completed:
120 | return False
121 | # new_completed
122 | return self._new_addr[1] == len(self._get_new_text())
123 |
124 |
125 | class UnifiedDiff(object):
126 | def __init__(self, headers=None, old_path=None, new_path=None, hunks=None):
127 | self._headers = headers or []
128 | self._old_path = old_path or None
129 | self._new_path = new_path or None
130 | self._hunks = hunks or []
131 |
132 | def is_old_path(self, line):
133 | return line.startswith("--- ")
134 |
135 | def is_new_path(self, line):
136 | return line.startswith("+++ ")
137 |
138 | def is_hunk_meta(self, line):
139 | return (
140 | line.startswith("@@ -")
141 | and line.find(" @@") >= 8
142 | )
143 |
144 | def parse_hunk_meta(self, hunk_meta):
145 | # @@ -3,7 +3,6 @@
146 | a = hunk_meta.split()[1].split(",") # -3 7
147 | if len(a) > 1:
148 | old_addr = (int(a[0][1:]), int(a[1]))
149 | else:
150 | # @@ -1 +1,2 @@
151 | old_addr = (int(a[0][1:]), 1)
152 |
153 | b = hunk_meta.split()[2].split(",") # +3 6
154 | if len(b) > 1:
155 | new_addr = (int(b[0][1:]), int(b[1]))
156 | else:
157 | # @@ -0,0 +1 @@
158 | new_addr = (int(b[0][1:]), 1)
159 |
160 | return old_addr, new_addr
161 |
162 | def parse_hunk_line(self, line):
163 | return line[0], line[1:]
164 |
165 | def is_old(self, line):
166 | return (
167 | line.startswith("-")
168 | and not self.is_old_path(line)
169 | )
170 |
171 | def is_new(self, line):
172 | return line.startswith("+") and not self.is_new_path(line)
173 |
174 | def is_common(self, line):
175 | return line.startswith(" ")
176 |
177 | def is_eof(self, line):
178 | # \ No newline at end of file
179 | # \ No newline at end of property
180 | return line.startswith(r"\ No newline at end of")
181 |
182 | def is_only_in_dir(self, line):
183 | return line.startswith("Only in ")
184 |
185 | def is_binary_differ(self, line):
186 | return line.startswith("Binary files") and line.endswith("differ")
187 |
188 |
189 | class DiffParser(object):
190 | def __init__(self, stream):
191 | self._stream = stream
192 |
193 | def get_diff_generator(self):
194 | """parse all diff lines, construct a list of UnifiedDiff objects"""
195 | diff = UnifiedDiff()
196 | headers = []
197 |
198 | for line in self._stream:
199 | if diff.is_old_path(line):
200 | # This is a new diff when current hunk is not yet genreated or
201 | # is completed. We yield previous diff if exists and construct
202 | # a new one for this case. Otherwise it's acutally an 'old'
203 | # line starts with '--- '.
204 | if not diff._hunks or diff._hunks[-1].is_completed():
205 | if diff._old_path and diff._new_path and diff._hunks:
206 | yield diff
207 | diff = UnifiedDiff(headers, line, None, None)
208 | headers = []
209 | else:
210 | diff._hunks[-1].append(diff.parse_hunk_line(line))
211 |
212 | elif diff.is_new_path(line) and diff._old_path:
213 | if not diff._new_path:
214 | diff._new_path = line
215 | else:
216 | diff._hunks[-1].append(diff.parse_hunk_line(line))
217 |
218 | elif diff.is_hunk_meta(line):
219 | hunk_meta = line
220 | old_addr, new_addr = diff.parse_hunk_meta(hunk_meta)
221 | hunk = Hunk(headers, old_addr, new_addr)
222 | headers = []
223 | diff._hunks.append(hunk)
224 |
225 | elif (
226 | diff._hunks
227 | and not headers
228 | and (diff.is_old(line) or diff.is_new(line) or diff.is_common(line))
229 | ):
230 | diff._hunks[-1].append(diff.parse_hunk_line(line))
231 |
232 | elif diff.is_eof(line):
233 | pass
234 |
235 | elif diff.is_only_in_dir(line) or diff.is_binary_differ(line):
236 | # 'Only in foo:' and 'Binary files ... differ' are considered
237 | # as separate diffs, so yield current diff, then this line
238 | #
239 | if diff._old_path and diff._new_path and diff._hunks:
240 | # Current diff is comppletely constructed
241 | yield diff
242 | headers.append(line)
243 | yield UnifiedDiff(headers, None, None, None)
244 | headers = []
245 | diff = UnifiedDiff()
246 |
247 | else:
248 | # All other non-recognized lines are considered as headers or
249 | # hunk headers respectively
250 | headers.append(line)
251 |
252 | # Validate and yield the last patch set if it is not yielded yet
253 | if diff._old_path:
254 | assert diff._new_path is not None
255 | if diff._hunks:
256 | assert len(diff._hunks[-1]._hunk_list) > 0
257 | yield diff
258 |
259 | if headers:
260 | # Tolerate dangling headers, just yield a UnifiedDiff object with
261 | # only header lines
262 | yield UnifiedDiff(headers, None, None, None)
263 |
264 |
265 | class DiffMarker(object):
266 | def markup_side_by_side(self, diff):
267 | def _fit_with_marker_mix(text):
268 | """Wrap input text which contains mdiff tags, markup at the
269 | meantime
270 | """
271 | out = COLOR_PLAIN
272 | while text:
273 | if text.startswith("\x00-"):
274 | out += f'{COLOR_REVERSE}{COLOR_RED}'
275 | text = text[2:]
276 | elif text.startswith("\x00+"):
277 | out += f'{COLOR_REVERSE}{COLOR_GREEN}'
278 | text = text[2:]
279 | elif text.startswith("\x00^"):
280 | out += f'{COLOR_REVERSE}{COLOR_YELLOW}'
281 | text = text[2:]
282 | elif text.startswith("\x01"):
283 | if len(text) > 1:
284 | out += f'{COLOR_RESET}{COLOR_PLAIN}'
285 | text = text[1:]
286 | else:
287 | # FIXME: utf-8 wchar might break the rule here, e.g.
288 | # u'\u554a' takes double width of a single letter, also
289 | # this depends on your terminal font. I guess audience of
290 | # this tool never put that kind of symbol in their code :-)
291 | out += text[0]
292 | text = text[1:]
293 |
294 | return out + COLOR_RESET
295 |
296 | # Set up number width, note last hunk might be empty
297 | try:
298 | start, offset = diff._hunks[-1]._old_addr
299 | max1 = start + offset - 1
300 | start, offset = diff._hunks[-1]._new_addr
301 | max2 = start + offset - 1
302 | except IndexError:
303 | max1 = max2 = 0
304 |
305 | num_width = max(len(str(max1)), len(str(max2)))
306 |
307 | # Each line is like 'nnn TEXT nnn TEXT\n', so width is half of
308 | # [terminal size minus the line number columns and 3 separating spaces.
309 | width = (terminal_width - num_width * 2 - 3) // 2
310 |
311 | for line in diff._headers:
312 | yield f"{COLOR_CYAN}{line}{COLOR_RESET}"
313 |
314 | if diff._old_path is not None and diff._new_path is not None:
315 | yield f"{COLOR_YELLOW}{diff._old_path}{COLOR_RESET}"
316 | yield f"{COLOR_YELLOW}{diff._new_path}{COLOR_RESET}"
317 |
318 | for hunk in diff._hunks:
319 | for hunk_header in hunk._hunk_headers:
320 | yield f"{COLOR_CYAN}{hunk_header}{COLOR_RESET}"
321 |
322 | yield hunk_meta_display
323 |
324 | for old, new, changed in hunk.mdiff():
325 | if old[0]:
326 | left_num = str(hunk._old_addr[0] + int(old[0]) - 1)
327 | else:
328 | left_num = " "
329 |
330 | if new[0]:
331 | right_num = str(hunk._new_addr[0] + int(new[0]) - 1)
332 | else:
333 | right_num = " "
334 |
335 | left = old[1].replace("\t", " " * 8).replace("\n", "").replace("\r", "")
336 | right = new[1].replace("\t", " " * 8).replace("\n", "").replace("\r", "")
337 |
338 | if changed:
339 | if not old[0]:
340 | left = ""
341 | right = right.rstrip("\x01")
342 | if right.startswith("\x00+"):
343 | right = right[2:]
344 | right = f"{COLOR_GREEN}{right}{COLOR_RESET}"
345 | elif not new[0]:
346 | left = left.rstrip("\x01")
347 | if left.startswith("\x00-"):
348 | left = left[2:]
349 | left = f"{COLOR_RED}{left}{COLOR_RESET}"
350 | right = ""
351 | else:
352 | left = _fit_with_marker_mix(left)
353 | right = _fit_with_marker_mix(right)
354 | else:
355 | right = f"{COLOR_RESET}{right}"
356 |
357 | # Need to wrap long lines, so here we'll iterate,
358 | # shaving off `width` chars from both left and right
359 | # strings, until both are empty. Also, line number needs to
360 | # be printed only for the first part.
361 | lncur = left_num
362 | rncur = right_num
363 | while left or right:
364 | # Split both left and right lines, preserving escaping
365 | # sequences correctly.
366 | lcur, left, llen = strsplit(left, width)
367 | rcur, right, rlen = strsplit(right, width)
368 |
369 | # Pad left line with spaces if needed
370 | if llen < width:
371 | lcur += " " * (width - llen)
372 | # XXX: this doesn't work lol
373 | # lcur = f"{lcur: <{width}}"
374 |
375 | yield f"{COLOR_GRAY}{lncur:>{num_width}}{COLOR_RESET} {lcur} {COLOR_GRAY}{rncur:>{num_width}}{COLOR_RESET} {rcur}\n"
376 |
377 | # Clean line numbers for further iterations
378 | lncur = ""
379 | rncur = ""
380 |
381 |
382 | for diff in DiffParser(sys.stdin).get_diff_generator():
383 | for line in DiffMarker().markup_side_by_side(diff):
384 | sys.stdout.buffer.write(line.encode())
385 |
--------------------------------------------------------------------------------