Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
1
Merge Requests
1
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
gitlab-ce
Commits
8a64864b
Commit
8a64864b
authored
Oct 06, 2021
by
Payton Burdette
Committed by
Phil Hughes
Oct 06, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Refactor load performance widget to use extensions
parent
e56d9a8f
Changes
8
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
371 additions
and
1 deletion
+371
-1
app/assets/javascripts/vue_merge_request_widget/components/extensions/base.vue
...s/vue_merge_request_widget/components/extensions/base.vue
+6
-1
config/feature_flags/development/refactor_mr_widgets_extensions.yml
...ture_flags/development/refactor_mr_widgets_extensions.yml
+8
-0
config/feature_flags/development/refactor_mr_widgets_extensions_user.yml
...flags/development/refactor_mr_widgets_extensions_user.yml
+8
-0
ee/app/assets/javascripts/vue_merge_request_widget/extensions/load_performance/index.js
...merge_request_widget/extensions/load_performance/index.js
+188
-0
ee/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
...avascripts/vue_merge_request_widget/mr_widget_options.vue
+12
-0
ee/app/controllers/ee/projects/merge_requests_controller.rb
ee/app/controllers/ee/projects/merge_requests_controller.rb
+2
-0
ee/spec/frontend/vue_mr_widget/extensions/load_performance/index_spec.js
...d/vue_mr_widget/extensions/load_performance/index_spec.js
+142
-0
locale/gitlab.pot
locale/gitlab.pot
+5
-0
No files found.
app/assets/javascripts/vue_merge_request_widget/components/extensions/base.vue
View file @
8a64864b
...
...
@@ -160,7 +160,12 @@ export default {
wclass=
"report-block-list"
class=
"report-block-container"
>
<li
v-for=
"data in fullData"
:key=
"data.id"
class=
"gl-display-flex gl-align-items-center"
>
<li
v-for=
"data in fullData"
:key=
"data.id"
class=
"gl-display-flex gl-align-items-center"
data-testid=
"extension-list-item"
>
<status-icon
v-if=
"data.icon"
:icon-name=
"data.icon.name"
:size=
"12"
/>
<div
class=
"gl-mt-2 gl-mb-2 gl-flex-wrap gl-align-self-center gl-display-flex"
>
<div
v-safe-html=
"data.text"
class=
"gl-mr-4"
></div>
...
...
config/feature_flags/development/refactor_mr_widgets_extensions.yml
0 → 100644
View file @
8a64864b
---
name
:
refactor_mr_widgets_extensions
introduced_by_url
:
https://gitlab.com/gitlab-org/gitlab/-/merge_requests/70993
rollout_issue_url
:
https://gitlab.com/gitlab-org/gitlab/-/issues/341759
milestone
:
'
14.4'
type
:
development
group
:
group::code review
default_enabled
:
false
config/feature_flags/development/refactor_mr_widgets_extensions_user.yml
0 → 100644
View file @
8a64864b
---
name
:
refactor_mr_widgets_extensions_user
introduced_by_url
:
https://gitlab.com/gitlab-org/gitlab/-/merge_requests/70993
rollout_issue_url
:
https://gitlab.com/gitlab-org/gitlab/-/issues/341759
milestone
:
'
14.4'
type
:
development
group
:
group::code review
default_enabled
:
false
ee/app/assets/javascripts/vue_merge_request_widget/extensions/load_performance/index.js
0 → 100644
View file @
8a64864b
import
{
s__
,
sprintf
,
n__
}
from
'
~/locale
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
{
formattedChangeInPercent
}
from
'
~/lib/utils/number_utils
'
;
import
{
EXTENSION_ICONS
}
from
'
~/vue_merge_request_widget/constants
'
;
export
default
{
name
:
'
WidgetLoadPerformance
'
,
props
:
[
'
loadPerformance
'
],
computed
:
{
summary
()
{
const
{
improved
,
degraded
,
same
}
=
this
.
collapsedData
;
const
changesFound
=
improved
.
length
+
degraded
.
length
+
same
.
length
;
const
text
=
sprintf
(
n__
(
'
ciReport|Load performance test metrics detected %{strongStart}%{changesFound}%{strongEnd} change
'
,
'
ciReport|Load performance test metrics detected %{strongStart}%{changesFound}%{strongEnd} changes
'
,
changesFound
,
),
{
changesFound
,
strongStart
:
`<strong>`
,
strongEnd
:
`</strong>`
,
},
false
,
);
const
reportNumbers
=
[];
if
(
degraded
.
length
>
0
)
{
reportNumbers
.
push
(
`<strong class="gl-text-red-500">
${
sprintf
(
s__
(
'
ciReport|%{degradedNum} degraded
'
),
{
degradedNum
:
degraded
.
length
,
})}
</strong>`
,
);
}
if
(
same
.
length
>
0
)
{
reportNumbers
.
push
(
`<strong class="gl-text-gray-700">
${
sprintf
(
s__
(
'
ciReport|%{sameNum} same
'
),
{
sameNum
:
same
.
length
,
})}
</strong>`
,
);
}
if
(
improved
.
length
>
0
)
{
reportNumbers
.
push
(
`<strong class="gl-text-green-500">
${
sprintf
(
s__
(
'
ciReport|%{improvedNum} improved
'
),
{
improvedNum
:
improved
.
length
,
})}
</strong>`
,
);
}
return
`
${
text
}
<br>
${
reportNumbers
.
join
(
'
,
'
)}
`
;
},
statusIcon
()
{
if
(
this
.
collapsedData
.
degraded
.
length
||
this
.
collapsedData
.
same
.
length
)
{
return
EXTENSION_ICONS
.
warning
;
}
return
EXTENSION_ICONS
.
success
;
},
},
methods
:
{
fetchCollapsedData
()
{
return
Promise
.
all
([
this
.
fetchReport
(
this
.
loadPerformance
?.
head_path
),
this
.
fetchReport
(
this
.
loadPerformance
?.
base_path
),
]).
then
((
values
)
=>
{
return
this
.
compareLoadPerformanceMetrics
(
values
[
0
],
values
[
1
]);
});
},
fetchFullData
()
{
const
{
improved
,
degraded
,
same
}
=
this
.
collapsedData
;
return
Promise
.
resolve
([...
improved
,
...
degraded
,
...
same
]);
},
compareLoadPerformanceMetrics
(
headMetrics
,
baseMetrics
)
{
const
headMetricsIndexed
=
this
.
normalizeLoadPerformanceMetrics
(
headMetrics
);
const
baseMetricsIndexed
=
this
.
normalizeLoadPerformanceMetrics
(
baseMetrics
);
const
improved
=
[];
const
degraded
=
[];
const
same
=
[];
Object
.
keys
(
headMetricsIndexed
).
forEach
((
metric
)
=>
{
const
headMetricData
=
headMetricsIndexed
[
metric
];
if
(
metric
in
baseMetricsIndexed
)
{
const
baseMetricData
=
baseMetricsIndexed
[
metric
];
const
metricData
=
{
name
:
metric
,
score
:
headMetricData
,
delta
:
parseFloat
((
parseFloat
(
headMetricData
)
-
parseFloat
(
baseMetricData
)).
toFixed
(
2
)),
};
if
(
metricData
.
delta
!==
0.0
)
{
const
isImproved
=
[
s__
(
'
ciReport|RPS
'
),
s__
(
'
ciReport|Checks
'
)].
includes
(
metric
)
?
metricData
.
delta
>
0
:
metricData
.
delta
<
0
;
if
(
isImproved
)
{
improved
.
push
(
this
.
prepareMetricData
(
metricData
,
{
name
:
EXTENSION_ICONS
.
success
,
}),
);
}
else
{
degraded
.
push
(
this
.
prepareMetricData
(
metricData
,
{
name
:
EXTENSION_ICONS
.
failed
,
}),
);
}
}
else
{
same
.
push
(
this
.
prepareMetricData
(
metricData
,
{
name
:
EXTENSION_ICONS
.
neutral
,
}),
);
}
}
});
return
{
improved
,
degraded
,
same
};
},
// normalize load performance metrics for comsumption
normalizeLoadPerformanceMetrics
(
loadPerformanceData
)
{
if
(
!
(
'
metrics
'
in
loadPerformanceData
))
return
{};
const
{
metrics
}
=
loadPerformanceData
;
const
indexedMetrics
=
{};
Object
.
keys
(
loadPerformanceData
.
metrics
).
forEach
((
metric
)
=>
{
switch
(
metric
)
{
case
'
http_reqs
'
:
indexedMetrics
[
s__
(
'
ciReport|RPS
'
)]
=
metrics
.
http_reqs
.
rate
;
break
;
case
'
http_req_waiting
'
:
indexedMetrics
[
s__
(
'
ciReport|TTFB P90
'
)]
=
metrics
.
http_req_waiting
[
'
p(90)
'
];
indexedMetrics
[
s__
(
'
ciReport|TTFB P95
'
)]
=
metrics
.
http_req_waiting
[
'
p(95)
'
];
break
;
case
'
checks
'
:
indexedMetrics
[
s__
(
'
ciReport|Checks
'
)]
=
`
${(
(
metrics
.
checks
.
passes
/
(
metrics
.
checks
.
passes
+
metrics
.
checks
.
fails
))
*
100.0
).
toFixed
(
2
)}
%`
;
break
;
default
:
break
;
}
});
return
indexedMetrics
;
},
prepareMetricData
(
metricData
,
icon
)
{
const
preparedMetricData
=
metricData
;
const
prefix
=
metricData
.
score
?
`
${
metricData
.
name
}
:`
:
metricData
.
name
;
const
score
=
metricData
.
score
?
`<strong>
${
this
.
formatScore
(
metricData
.
score
)}
</strong>`
:
''
;
const
delta
=
metricData
.
delta
?
`(
${
this
.
formatScore
(
metricData
.
delta
)}
)`
:
''
;
let
deltaPercent
=
''
;
if
(
metricData
.
delta
&&
metricData
.
score
)
{
const
oldScore
=
parseFloat
(
metricData
.
score
)
-
metricData
.
delta
;
deltaPercent
=
`(
${
formattedChangeInPercent
(
oldScore
,
metricData
.
score
)}
)`
;
}
const
text
=
`
${
prefix
}
${
score
}
${
delta
}
${
deltaPercent
}
`
;
preparedMetricData
.
icon
=
icon
;
preparedMetricData
.
text
=
text
;
return
preparedMetricData
;
},
formatScore
(
value
)
{
if
(
Number
(
value
)
&&
!
Number
.
isInteger
(
value
))
{
return
(
Math
.
floor
(
parseFloat
(
value
)
*
100
)
/
100
).
toFixed
(
2
);
}
return
value
;
},
fetchReport
(
endpoint
)
{
return
axios
.
get
(
endpoint
).
then
((
res
)
=>
res
.
data
);
},
},
};
ee/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
View file @
8a64864b
...
...
@@ -2,12 +2,14 @@
import
{
GlSprintf
,
GlLink
,
GlSafeHtmlDirective
}
from
'
@gitlab/ui
'
;
import
MrWidgetLicenses
from
'
ee/vue_shared/license_compliance/mr_widget_license_report.vue
'
;
import
reportsMixin
from
'
ee/vue_shared/security_reports/mixins/reports_mixin
'
;
import
{
registerExtension
}
from
'
~/vue_merge_request_widget/components/extensions
'
;
import
{
s__
,
__
,
sprintf
}
from
'
~/locale
'
;
import
CEWidgetOptions
from
'
~/vue_merge_request_widget/mr_widget_options.vue
'
;
import
MrWidgetEnableFeaturePrompt
from
'
./components/states/mr_widget_enable_feature_prompt.vue
'
;
import
MrWidgetJiraAssociationMissing
from
'
./components/states/mr_widget_jira_association_missing.vue
'
;
import
MrWidgetPolicyViolation
from
'
./components/states/mr_widget_policy_violation.vue
'
;
import
MrWidgetGeoSecondaryNode
from
'
./components/states/mr_widget_secondary_geo_node.vue
'
;
import
loadPerformanceExtension
from
'
./extensions/load_performance
'
;
export
default
{
components
:
{
...
...
@@ -184,11 +186,21 @@ export default {
},
hasLoadPerformancePaths
(
newVal
)
{
if
(
newVal
)
{
this
.
registerLoadPerformance
();
this
.
fetchLoadPerformance
();
}
},
},
methods
:
{
registerLoadPerformance
()
{
const
shouldShowExtension
=
window
.
gon
.
features
.
refactorMrWidgetsExtensions
||
window
.
gon
.
features
.
refactorMrWidgetsExtensionsUser
;
if
(
shouldShowExtension
)
{
registerExtension
(
loadPerformanceExtension
);
}
},
getServiceEndpoints
(
store
)
{
const
base
=
CEWidgetOptions
.
methods
.
getServiceEndpoints
(
store
);
...
...
ee/app/controllers/ee/projects/merge_requests_controller.rb
View file @
8a64864b
...
...
@@ -12,6 +12,8 @@ module EE
experiment
(
:security_reports_mr_widget_prompt
,
namespace:
@project
.
namespace
).
publish
push_frontend_feature_flag
(
:anonymous_visual_review_feedback
)
push_frontend_feature_flag
(
:missing_mr_security_scan_types
,
@project
)
push_frontend_feature_flag
(
:refactor_mr_widgets_extensions
,
@project
,
default_enabled: :yaml
)
push_frontend_feature_flag
(
:refactor_mr_widgets_extensions_user
,
current_user
,
default_enabled: :yaml
)
end
before_action
:authorize_read_pipeline!
,
only:
[
:container_scanning_reports
,
:dependency_scanning_reports
,
...
...
ee/spec/frontend/vue_mr_widget/extensions/load_performance/index_spec.js
0 → 100644
View file @
8a64864b
import
{
mount
}
from
'
@vue/test-utils
'
;
import
MockAdapter
from
'
axios-mock-adapter
'
;
import
{
nextTick
}
from
'
vue
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
extensionsContainer
from
'
~/vue_merge_request_widget/components/extensions/container
'
;
import
{
registerExtension
}
from
'
~/vue_merge_request_widget/components/extensions
'
;
import
loadPerformanceExtension
from
'
ee/vue_merge_request_widget/extensions/load_performance
'
;
import
waitForPromises
from
'
helpers/wait_for_promises
'
;
import
{
baseLoadPerformance
,
headLoadPerformance
}
from
'
../../mock_data
'
;
describe
(
'
Load performance extension
'
,
()
=>
{
let
wrapper
;
let
mock
;
const
DEFAULT_LOAD_PERFORMANCE
=
{
head_path
:
'
head.json
'
,
base_path
:
'
base.json
'
,
};
const
createComponent
=
()
=>
{
wrapper
=
mount
(
extensionsContainer
,
{
propsData
:
{
mr
:
{
loadPerformance
:
{
...
DEFAULT_LOAD_PERFORMANCE
,
},
},
},
});
};
beforeEach
(()
=>
{
createComponent
();
mock
=
new
MockAdapter
(
axios
);
});
afterEach
(()
=>
{
wrapper
.
destroy
();
});
describe
(
'
summary
'
,
()
=>
{
it
(
'
should render info about all issues
'
,
async
()
=>
{
mock
.
onGet
(
DEFAULT_LOAD_PERFORMANCE
.
head_path
).
reply
(
200
,
headLoadPerformance
);
mock
.
onGet
(
DEFAULT_LOAD_PERFORMANCE
.
base_path
).
reply
(
200
,
baseLoadPerformance
);
registerExtension
(
loadPerformanceExtension
);
await
waitForPromises
();
expect
(
wrapper
.
text
()).
toContain
(
'
Load performance test metrics detected 4 changes
'
);
expect
(
wrapper
.
text
()).
toContain
(
'
1 degraded, 1 same, 2 improved
'
);
});
it
(
'
should render info about fixed issues
'
,
async
()
=>
{
const
head
=
{
metrics
:
{
checks
:
{
fails
:
0
,
passes
:
100
,
value
:
0
,
},
},
};
const
base
=
{
metrics
:
{
checks
:
{
fails
:
2
,
passes
:
55
,
value
:
0
,
},
},
};
mock
.
onGet
(
DEFAULT_LOAD_PERFORMANCE
.
head_path
).
reply
(
200
,
head
);
mock
.
onGet
(
DEFAULT_LOAD_PERFORMANCE
.
base_path
).
reply
(
200
,
base
);
registerExtension
(
loadPerformanceExtension
);
await
waitForPromises
();
expect
(
wrapper
.
text
()).
toContain
(
'
Load performance test metrics detected 1 change
'
);
expect
(
wrapper
.
text
()).
toContain
(
'
1 improved
'
);
});
it
(
'
should render info about added issues
'
,
async
()
=>
{
const
head
=
{
metrics
:
{
checks
:
{
fails
:
1
,
passes
:
100
,
value
:
0
,
},
},
};
const
base
=
{
metrics
:
{
checks
:
{
fails
:
0
,
passes
:
55
,
value
:
0
,
},
},
};
mock
.
onGet
(
DEFAULT_LOAD_PERFORMANCE
.
head_path
).
reply
(
200
,
head
);
mock
.
onGet
(
DEFAULT_LOAD_PERFORMANCE
.
base_path
).
reply
(
200
,
base
);
registerExtension
(
loadPerformanceExtension
);
await
waitForPromises
();
expect
(
wrapper
.
text
()).
toContain
(
'
Load performance test metrics detected 1 change
'
);
expect
(
wrapper
.
text
()).
toContain
(
'
1 degraded
'
);
});
});
describe
(
'
expanded data
'
,
()
=>
{
beforeEach
(
async
()
=>
{
mock
.
onGet
(
DEFAULT_LOAD_PERFORMANCE
.
head_path
).
reply
(
200
,
headLoadPerformance
);
mock
.
onGet
(
DEFAULT_LOAD_PERFORMANCE
.
base_path
).
reply
(
200
,
baseLoadPerformance
);
registerExtension
(
loadPerformanceExtension
);
await
waitForPromises
();
wrapper
.
find
(
'
[data-testid="widget-extension"] [data-testid="toggle-button"]
'
)
.
trigger
(
'
click
'
);
await
nextTick
();
});
it
(
'
expanded data list items text
'
,
()
=>
{
const
listItems
=
wrapper
.
findAll
(
'
[data-testid="extension-list-item"]
'
);
expect
(
listItems
.
at
(
0
).
text
()).
toBe
(
'
TTFB P90: 100.60 (-3.50) (-3%)
'
);
expect
(
listItems
.
at
(
1
).
text
()).
toBe
(
'
RPS: 8.99 (1.20) (+15%)
'
);
expect
(
listItems
.
at
(
2
).
text
()).
toBe
(
'
TTFB P95: 125.45 (24.23) (+24%)
'
);
expect
(
listItems
.
at
(
3
).
text
()).
toBe
(
'
Checks: 100.00%
'
);
});
});
});
locale/gitlab.pot
View file @
8a64864b
...
...
@@ -39775,6 +39775,11 @@ msgstr ""
msgid "ciReport|Investigate this vulnerability by creating an issue"
msgstr ""
msgid "ciReport|Load performance test metrics detected %{strongStart}%{changesFound}%{strongEnd} change"
msgid_plural "ciReport|Load performance test metrics detected %{strongStart}%{changesFound}%{strongEnd} changes"
msgstr[0] ""
msgstr[1] ""
msgid "ciReport|Load performance test metrics: "
msgstr ""
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment