Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
d6e421b21e
commit
1cf95147ea
75 changed files with 865 additions and 782 deletions
|
@ -111,28 +111,10 @@ export default {
|
|||
type: String,
|
||||
required: true,
|
||||
},
|
||||
projectPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
logsPath: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: invalidUrl,
|
||||
},
|
||||
defaultBranch: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
metricsEndpoint: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
deploymentsEndpoint: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
emptyGettingStartedSvgPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
|
@ -153,10 +135,6 @@ export default {
|
|||
type: String,
|
||||
required: true,
|
||||
},
|
||||
currentEnvironmentName: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
customMetricsAvailable: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
|
@ -172,21 +150,6 @@ export default {
|
|||
required: false,
|
||||
default: invalidUrl,
|
||||
},
|
||||
dashboardEndpoint: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: invalidUrl,
|
||||
},
|
||||
dashboardsEndpoint: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: invalidUrl,
|
||||
},
|
||||
currentDashboard: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
smallEmptyState: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
|
@ -228,6 +191,8 @@ export default {
|
|||
'expandedPanel',
|
||||
'variables',
|
||||
'isUpdatingStarredValue',
|
||||
'currentDashboard',
|
||||
'currentEnvironmentName',
|
||||
]),
|
||||
...mapGetters('monitoringDashboard', [
|
||||
'selectedDashboard',
|
||||
|
@ -281,16 +246,6 @@ export default {
|
|||
},
|
||||
},
|
||||
created() {
|
||||
this.setInitialState({
|
||||
metricsEndpoint: this.metricsEndpoint,
|
||||
deploymentsEndpoint: this.deploymentsEndpoint,
|
||||
dashboardEndpoint: this.dashboardEndpoint,
|
||||
dashboardsEndpoint: this.dashboardsEndpoint,
|
||||
currentDashboard: this.currentDashboard,
|
||||
projectPath: this.projectPath,
|
||||
logsPath: this.logsPath,
|
||||
currentEnvironmentName: this.currentEnvironmentName,
|
||||
});
|
||||
window.addEventListener('keyup', this.onKeyup);
|
||||
},
|
||||
destroyed() {
|
||||
|
@ -310,7 +265,6 @@ export default {
|
|||
'fetchData',
|
||||
'fetchDashboardData',
|
||||
'setGettingStartedEmptyState',
|
||||
'setInitialState',
|
||||
'setPanelGroupMetrics',
|
||||
'filterEnvironments',
|
||||
'setExpandedPanel',
|
||||
|
|
|
@ -140,7 +140,6 @@ export const dateFormats = {
|
|||
* Currently used in `receiveMetricsDashboardSuccess` action.
|
||||
*/
|
||||
export const endpointKeys = [
|
||||
'metricsEndpoint',
|
||||
'deploymentsEndpoint',
|
||||
'dashboardEndpoint',
|
||||
'dashboardsEndpoint',
|
||||
|
|
|
@ -3,7 +3,7 @@ import { GlToast } from '@gitlab/ui';
|
|||
import Dashboard from '~/monitoring/components/dashboard.vue';
|
||||
import { parseBoolean } from '~/lib/utils/common_utils';
|
||||
import { getParameterValues } from '~/lib/utils/url_utility';
|
||||
import store from './stores';
|
||||
import { createStore } from './stores';
|
||||
|
||||
Vue.use(GlToast);
|
||||
|
||||
|
@ -13,6 +13,31 @@ export default (props = {}) => {
|
|||
if (el && el.dataset) {
|
||||
const [currentDashboard] = getParameterValues('dashboard');
|
||||
|
||||
const {
|
||||
deploymentsEndpoint,
|
||||
dashboardEndpoint,
|
||||
dashboardsEndpoint,
|
||||
projectPath,
|
||||
logsPath,
|
||||
currentEnvironmentName,
|
||||
...dataProps
|
||||
} = el.dataset;
|
||||
|
||||
const store = createStore({
|
||||
currentDashboard,
|
||||
deploymentsEndpoint,
|
||||
dashboardEndpoint,
|
||||
dashboardsEndpoint,
|
||||
projectPath,
|
||||
logsPath,
|
||||
currentEnvironmentName,
|
||||
});
|
||||
|
||||
// HTML attributes are always strings, parse other types.
|
||||
dataProps.hasMetrics = parseBoolean(dataProps.hasMetrics);
|
||||
dataProps.customMetricsAvailable = parseBoolean(dataProps.customMetricsAvailable);
|
||||
dataProps.prometheusAlertsAvailable = parseBoolean(dataProps.prometheusAlertsAvailable);
|
||||
|
||||
// eslint-disable-next-line no-new
|
||||
new Vue({
|
||||
el,
|
||||
|
@ -20,11 +45,7 @@ export default (props = {}) => {
|
|||
render(createElement) {
|
||||
return createElement(Dashboard, {
|
||||
props: {
|
||||
...el.dataset,
|
||||
currentDashboard,
|
||||
customMetricsAvailable: parseBoolean(el.dataset.customMetricsAvailable),
|
||||
prometheusAlertsAvailable: parseBoolean(el.dataset.prometheusAlertsAvailable),
|
||||
hasMetrics: parseBoolean(el.dataset.hasMetrics),
|
||||
...dataProps,
|
||||
...props,
|
||||
},
|
||||
});
|
||||
|
|
|
@ -314,8 +314,7 @@ export const receiveEnvironmentsDataFailure = ({ commit }) => {
|
|||
|
||||
export const fetchAnnotations = ({ state, dispatch }) => {
|
||||
const { start } = convertToFixedRange(state.timeRange);
|
||||
const dashboardPath =
|
||||
state.currentDashboard === '' ? DEFAULT_DASHBOARD_PATH : state.currentDashboard;
|
||||
const dashboardPath = state.currentDashboard || DEFAULT_DASHBOARD_PATH;
|
||||
return gqClient
|
||||
.mutate({
|
||||
mutation: getAnnotations,
|
||||
|
|
|
@ -15,11 +15,15 @@ export const monitoringDashboard = {
|
|||
state,
|
||||
};
|
||||
|
||||
export const createStore = () =>
|
||||
export const createStore = (initState = {}) =>
|
||||
new Vuex.Store({
|
||||
modules: {
|
||||
monitoringDashboard,
|
||||
monitoringDashboard: {
|
||||
...monitoringDashboard,
|
||||
state: {
|
||||
...state(),
|
||||
...initState,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export default createStore();
|
||||
|
|
|
@ -2,9 +2,9 @@ import invalidUrl from '~/lib/utils/invalid_url';
|
|||
|
||||
export default () => ({
|
||||
// API endpoints
|
||||
metricsEndpoint: null,
|
||||
deploymentsEndpoint: null,
|
||||
dashboardEndpoint: invalidUrl,
|
||||
dashboardsEndpoint: invalidUrl,
|
||||
|
||||
// Dashboard request parameters
|
||||
timeRange: null,
|
||||
|
@ -46,6 +46,7 @@ export default () => ({
|
|||
environments: [],
|
||||
environmentsSearchTerm: '',
|
||||
environmentsLoading: false,
|
||||
currentEnvironmentName: null,
|
||||
|
||||
// GitLab paths to other pages
|
||||
projectPath: null,
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// eslint-disable-next-line import/prefer-default-export
|
||||
export const DropdownVariant = {
|
||||
Sidebar: 'sidebar',
|
||||
Standalone: 'standalone',
|
||||
};
|
||||
|
||||
export const LIST_BUFFER_SIZE = 5;
|
||||
|
|
|
@ -3,15 +3,20 @@ import { mapState, mapGetters, mapActions } from 'vuex';
|
|||
import { GlLoadingIcon, GlButton, GlSearchBoxByType, GlLink } from '@gitlab/ui';
|
||||
|
||||
import { UP_KEY_CODE, DOWN_KEY_CODE, ENTER_KEY_CODE, ESC_KEY_CODE } from '~/lib/utils/keycodes';
|
||||
import SmartVirtualList from '~/vue_shared/components/smart_virtual_list.vue';
|
||||
|
||||
import LabelItem from './label_item.vue';
|
||||
|
||||
import { LIST_BUFFER_SIZE } from './constants';
|
||||
|
||||
export default {
|
||||
LIST_BUFFER_SIZE,
|
||||
components: {
|
||||
GlLoadingIcon,
|
||||
GlButton,
|
||||
GlSearchBoxByType,
|
||||
GlLink,
|
||||
SmartVirtualList,
|
||||
LabelItem,
|
||||
},
|
||||
data() {
|
||||
|
@ -139,10 +144,18 @@ export default {
|
|||
<gl-search-box-by-type v-model="searchKey" :autofocus="true" />
|
||||
</div>
|
||||
<div v-show="!labelsFetchInProgress" ref="labelsListContainer" class="dropdown-content">
|
||||
<ul class="list-unstyled mb-0">
|
||||
<smart-virtual-list
|
||||
:length="visibleLabels.length"
|
||||
:remain="$options.LIST_BUFFER_SIZE"
|
||||
:size="$options.LIST_BUFFER_SIZE"
|
||||
wclass="list-unstyled mb-0"
|
||||
wtag="ul"
|
||||
class="h-100"
|
||||
>
|
||||
<li v-for="(label, index) in visibleLabels" :key="label.id" class="d-block text-left">
|
||||
<label-item
|
||||
:label="label"
|
||||
:is-label-set="label.set"
|
||||
:highlight="index === currentHighlightItem"
|
||||
@clickLabel="handleLabelClick(label)"
|
||||
/>
|
||||
|
@ -150,7 +163,7 @@ export default {
|
|||
<li v-show="!visibleLabels.length" class="p-2 text-center">
|
||||
{{ __('No matching results') }}
|
||||
</li>
|
||||
</ul>
|
||||
</smart-virtual-list>
|
||||
</div>
|
||||
<div v-if="isDropdownVariantSidebar" class="dropdown-footer">
|
||||
<ul class="list-unstyled">
|
||||
|
@ -162,9 +175,9 @@ export default {
|
|||
>
|
||||
</li>
|
||||
<li>
|
||||
<gl-link :href="labelsManagePath" class="d-flex flex-row text-break-word label-item">{{
|
||||
footerManageLabelTitle
|
||||
}}</gl-link>
|
||||
<gl-link :href="labelsManagePath" class="d-flex flex-row text-break-word label-item">
|
||||
{{ footerManageLabelTitle }}
|
||||
</gl-link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
|
|
@ -11,6 +11,10 @@ export default {
|
|||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
isLabelSet: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
highlight: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
|
@ -19,7 +23,7 @@ export default {
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
isSet: this.label.set,
|
||||
isSet: this.isLabelSet,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
@ -29,6 +33,16 @@ export default {
|
|||
};
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
/**
|
||||
* This watcher assures that if user used
|
||||
* `Enter` key to set/unset label, changes
|
||||
* are reflected here too.
|
||||
*/
|
||||
isLabelSet(value) {
|
||||
this.isSet = value;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleClick() {
|
||||
this.isSet = !this.isSet;
|
||||
|
|
|
@ -28,6 +28,8 @@ module Types
|
|||
description: 'Timestamp of when the merge request was created'
|
||||
field :updated_at, Types::TimeType, null: false,
|
||||
description: 'Timestamp of when the merge request was last updated'
|
||||
field :merged_at, Types::TimeType, null: true, complexity: 5,
|
||||
description: 'Timestamp of when the merge request was merged, null if not merged'
|
||||
field :source_project, Types::ProjectType, null: true,
|
||||
description: 'Source project of the merge request'
|
||||
field :target_project, Types::ProjectType, null: false,
|
||||
|
@ -109,6 +111,8 @@ module Types
|
|||
resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(Milestone, obj.milestone_id).find }
|
||||
field :assignees, Types::UserType.connection_type, null: true, complexity: 5,
|
||||
description: 'Assignees of the merge request'
|
||||
field :author, Types::UserType, null: true,
|
||||
description: 'User who created this merge request'
|
||||
field :participants, Types::UserType.connection_type, null: true, complexity: 5,
|
||||
description: 'Participants in the merge request'
|
||||
field :subscribed, GraphQL::BOOLEAN_TYPE, method: :subscribed?, null: false, complexity: 5,
|
||||
|
|
1
bin/web
1
bin/web
|
@ -3,7 +3,6 @@
|
|||
set -e
|
||||
|
||||
cd $(dirname $0)/..
|
||||
app_root=$(pwd)
|
||||
|
||||
case "$USE_WEB_SERVER" in
|
||||
puma|"") # and the "" defines default
|
||||
|
|
5
changelogs/unreleased/ajk-gql-add-mr-author.yml
Normal file
5
changelogs/unreleased/ajk-gql-add-mr-author.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add missing Merge Request fields
|
||||
merge_request: 30935
|
||||
author:
|
||||
type: added
|
|
@ -32,3 +32,6 @@ providers:
|
|||
- [Shibboleth](../../integration/shibboleth.md)
|
||||
- [Smartcard](smartcard.md) **(PREMIUM ONLY)**
|
||||
- [Twitter](../../integration/twitter.md)
|
||||
|
||||
NOTE: **Note:**
|
||||
UltraAuth has removed their software which supports OmniAuth integration. We have therefore removed all references to UltraAuth integration.
|
||||
|
|
|
@ -116,7 +116,7 @@ expose the Registry on a port so that you can reuse the existing GitLab TLS
|
|||
certificate.
|
||||
|
||||
Assuming that the GitLab domain is `https://gitlab.example.com` and the port the
|
||||
Registry is exposed to the outside world is `4567`, here is what you need to set
|
||||
Registry is exposed to the outside world is `5050`, here is what you need to set
|
||||
in `gitlab.rb` or `gitlab.yml` if you are using Omnibus GitLab or installed
|
||||
GitLab from source respectively.
|
||||
|
||||
|
@ -130,7 +130,7 @@ otherwise you will run into conflicts.
|
|||
path to the existing TLS certificate and key used by GitLab:
|
||||
|
||||
```ruby
|
||||
registry_external_url 'https://gitlab.example.com:4567'
|
||||
registry_external_url 'https://gitlab.example.com:5050'
|
||||
```
|
||||
|
||||
Note how the `registry_external_url` is listening on HTTPS under the
|
||||
|
@ -151,7 +151,7 @@ otherwise you will run into conflicts.
|
|||
1. Validate using:
|
||||
|
||||
```shell
|
||||
openssl s_client -showcerts -servername gitlab.example.com -connect gitlab.example.com:443 > cacert.pem
|
||||
openssl s_client -showcerts -servername gitlab.example.com -connect gitlab.example.com:5050 > cacert.pem
|
||||
```
|
||||
|
||||
NOTE: **Note:**
|
||||
|
@ -166,7 +166,7 @@ If your certificate provider provides the CA Bundle certificates, append them to
|
|||
registry:
|
||||
enabled: true
|
||||
host: gitlab.example.com
|
||||
port: 4567
|
||||
port: 5050
|
||||
```
|
||||
|
||||
1. Save the file and [restart GitLab](../restart_gitlab.md#installations-from-source) for the changes to take effect.
|
||||
|
@ -176,7 +176,7 @@ Users should now be able to login to the Container Registry with their GitLab
|
|||
credentials using:
|
||||
|
||||
```shell
|
||||
docker login gitlab.example.com:4567
|
||||
docker login gitlab.example.com:5050
|
||||
```
|
||||
|
||||
### Configure Container Registry under its own domain
|
||||
|
@ -1062,7 +1062,7 @@ A user attempted to enable an S3-backed Registry. The `docker login` step went
|
|||
fine. However, when pushing an image, the output showed:
|
||||
|
||||
```plaintext
|
||||
The push refers to a repository [s3-testing.myregistry.com:4567/root/docker-test/docker-image]
|
||||
The push refers to a repository [s3-testing.myregistry.com:5050/root/docker-test/docker-image]
|
||||
dc5e59c14160: Pushing [==================================================>] 14.85 kB
|
||||
03c20c1a019a: Pushing [==================================================>] 2.048 kB
|
||||
a08f14ef632e: Pushing [==================================================>] 2.048 kB
|
||||
|
@ -1154,8 +1154,8 @@ Now that we have mitmproxy and Docker running, we can attempt to login and push
|
|||
a container image. You may need to run as root to do this. For example:
|
||||
|
||||
```shell
|
||||
docker login s3-testing.myregistry.com:4567
|
||||
docker push s3-testing.myregistry.com:4567/root/docker-test/docker-image
|
||||
docker login s3-testing.myregistry.com:5050
|
||||
docker push s3-testing.myregistry.com:5050/root/docker-test/docker-image
|
||||
```
|
||||
|
||||
In the example above, we see the following trace on the mitmproxy window:
|
||||
|
|
|
@ -5767,6 +5767,11 @@ type MergeRequest implements Noteable {
|
|||
last: Int
|
||||
): UserConnection
|
||||
|
||||
"""
|
||||
User who created this merge request
|
||||
"""
|
||||
author: User
|
||||
|
||||
"""
|
||||
Timestamp of when the merge request was created
|
||||
"""
|
||||
|
@ -5917,6 +5922,11 @@ type MergeRequest implements Noteable {
|
|||
"""
|
||||
mergeableDiscussionsState: Boolean
|
||||
|
||||
"""
|
||||
Timestamp of when the merge request was merged, null if not merged
|
||||
"""
|
||||
mergedAt: Time
|
||||
|
||||
"""
|
||||
The milestone of the merge request
|
||||
"""
|
||||
|
|
|
@ -16085,6 +16085,20 @@
|
|||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "author",
|
||||
"description": "User who created this merge request",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "OBJECT",
|
||||
"name": "User",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "createdAt",
|
||||
"description": "Timestamp of when the merge request was created",
|
||||
|
@ -16499,6 +16513,20 @@
|
|||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "mergedAt",
|
||||
"description": "Timestamp of when the merge request was merged, null if not merged",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Time",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "milestone",
|
||||
"description": "The milestone of the merge request",
|
||||
|
|
|
@ -860,6 +860,7 @@ Autogenerated return type of MarkAsSpamSnippet
|
|||
| Name | Type | Description |
|
||||
| --- | ---- | ---------- |
|
||||
| `allowCollaboration` | Boolean | Indicates if members of the target project can push to the fork |
|
||||
| `author` | User | User who created this merge request |
|
||||
| `createdAt` | Time! | Timestamp of when the merge request was created |
|
||||
| `defaultMergeCommitMessage` | String | Default merge commit message of the merge request |
|
||||
| `description` | String | Description of the merge request (Markdown rendered as HTML for caching) |
|
||||
|
@ -880,6 +881,7 @@ Autogenerated return type of MarkAsSpamSnippet
|
|||
| `mergeStatus` | String | Status of the merge request |
|
||||
| `mergeWhenPipelineSucceeds` | Boolean | Indicates if the merge has been set to be merged when its pipeline succeeds (MWPS) |
|
||||
| `mergeableDiscussionsState` | Boolean | Indicates if all discussions in the merge request have been resolved, allowing the merge request to be merged |
|
||||
| `mergedAt` | Time | Timestamp of when the merge request was merged, null if not merged |
|
||||
| `milestone` | Milestone | The milestone of the merge request |
|
||||
| `project` | Project! | Alias for target_project |
|
||||
| `projectId` | Int! | ID of the merge request project |
|
||||
|
|
|
@ -55,199 +55,7 @@ You can view the exact JSON payload sent to GitLab Inc. in the administration pa
|
|||
1. Expand the **Usage statistics** section.
|
||||
1. Click the **Preview payload** button.
|
||||
|
||||
<details>
|
||||
<summary>Click to view an example of the payload structure.</summary>
|
||||
|
||||
```json
|
||||
{
|
||||
"uuid": "0000000-0000-0000-0000-000000000000",
|
||||
"hostname": "example.com",
|
||||
"version": "12.10.0-pre",
|
||||
"installation_type": "omnibus-gitlab",
|
||||
"active_user_count": 999,
|
||||
"recorded_at": "2020-04-17T07:43:54.162+00:00",
|
||||
"edition": "EEU",
|
||||
"license_md5": "00000000000000000000000000000000",
|
||||
"license_id": null,
|
||||
"historical_max_users": 999,
|
||||
"licensee": {
|
||||
"Name": "ABC, Inc.",
|
||||
"Email": "email@example.com",
|
||||
"Company": "ABC, Inc."
|
||||
},
|
||||
"license_user_count": 999,
|
||||
"license_starts_at": "2020-01-01",
|
||||
"license_expires_at": "2021-01-01",
|
||||
"license_plan": "ultimate",
|
||||
"license_add_ons": {
|
||||
},
|
||||
"license_trial": false,
|
||||
"counts": {
|
||||
"assignee_lists": 999,
|
||||
"boards": 999,
|
||||
"ci_builds": 999,
|
||||
...
|
||||
},
|
||||
"container_registry_enabled": true,
|
||||
"dependency_proxy_enabled": false,
|
||||
"gitlab_shared_runners_enabled": true,
|
||||
"gravatar_enabled": true,
|
||||
"influxdb_metrics_enabled": true,
|
||||
"ldap_enabled": false,
|
||||
"mattermost_enabled": false,
|
||||
"omniauth_enabled": true,
|
||||
"prometheus_metrics_enabled": false,
|
||||
"reply_by_email_enabled": "incoming+%{key}@incoming.gitlab.com",
|
||||
"signup_enabled": true,
|
||||
"web_ide_clientside_preview_enabled": true,
|
||||
"ingress_modsecurity_enabled": true,
|
||||
"projects_with_expiration_policy_disabled": 999,
|
||||
"projects_with_expiration_policy_enabled": 999,
|
||||
...
|
||||
"elasticsearch_enabled": true,
|
||||
"license_trial_ends_on": null,
|
||||
"geo_enabled": false,
|
||||
"git": {
|
||||
"version": {
|
||||
"major": 2,
|
||||
"minor": 26,
|
||||
"patch": 1
|
||||
}
|
||||
},
|
||||
"gitaly": {
|
||||
"version": "12.10.0-rc1-93-g40980d40",
|
||||
"servers": 56,
|
||||
"filesystems": [
|
||||
"EXT_2_3_4"
|
||||
]
|
||||
},
|
||||
"gitlab_pages": {
|
||||
"enabled": true,
|
||||
"version": "1.17.0"
|
||||
},
|
||||
"database": {
|
||||
"adapter": "postgresql",
|
||||
"version": "9.6.15"
|
||||
},
|
||||
"app_server": {
|
||||
"type": "console"
|
||||
},
|
||||
"avg_cycle_analytics": {
|
||||
"issue": {
|
||||
"average": 999,
|
||||
"sd": 999,
|
||||
"missing": 999
|
||||
},
|
||||
"plan": {
|
||||
"average": null,
|
||||
"sd": 999,
|
||||
"missing": 999
|
||||
},
|
||||
"code": {
|
||||
"average": null,
|
||||
"sd": 999,
|
||||
"missing": 999
|
||||
},
|
||||
"test": {
|
||||
"average": null,
|
||||
"sd": 999,
|
||||
"missing": 999
|
||||
},
|
||||
"review": {
|
||||
"average": null,
|
||||
"sd": 999,
|
||||
"missing": 999
|
||||
},
|
||||
"staging": {
|
||||
"average": null,
|
||||
"sd": 999,
|
||||
"missing": 999
|
||||
},
|
||||
"production": {
|
||||
"average": null,
|
||||
"sd": 999,
|
||||
"missing": 999
|
||||
},
|
||||
"total": 999
|
||||
},
|
||||
"usage_activity_by_stage": {
|
||||
"configure": {
|
||||
"project_clusters_enabled": 999,
|
||||
...
|
||||
},
|
||||
"create": {
|
||||
"merge_requests": 999,
|
||||
...
|
||||
},
|
||||
"manage": {
|
||||
"events": 999,
|
||||
...
|
||||
},
|
||||
"monitor": {
|
||||
"clusters": 999,
|
||||
...
|
||||
},
|
||||
"package": {
|
||||
"projects_with_packages": 999
|
||||
},
|
||||
"plan": {
|
||||
"issues": 999,
|
||||
...
|
||||
},
|
||||
"release": {
|
||||
"deployments": 999,
|
||||
...
|
||||
},
|
||||
"secure": {
|
||||
"user_container_scanning_jobs": 999,
|
||||
...
|
||||
},
|
||||
"verify": {
|
||||
"ci_builds": 999,
|
||||
...
|
||||
}
|
||||
},
|
||||
"usage_activity_by_stage_monthly": {
|
||||
"configure": {
|
||||
"project_clusters_enabled": 999,
|
||||
...
|
||||
},
|
||||
"create": {
|
||||
"merge_requests": 999,
|
||||
...
|
||||
},
|
||||
"manage": {
|
||||
"events": 999,
|
||||
...
|
||||
},
|
||||
"monitor": {
|
||||
"clusters": 999,
|
||||
...
|
||||
},
|
||||
"package": {
|
||||
"projects_with_packages": 999
|
||||
},
|
||||
"plan": {
|
||||
"issues": 999,
|
||||
...
|
||||
},
|
||||
"release": {
|
||||
"deployments": 999,
|
||||
...
|
||||
},
|
||||
"secure": {
|
||||
"user_container_scanning_jobs": 999,
|
||||
...
|
||||
},
|
||||
"verify": {
|
||||
"ci_builds": 999,
|
||||
...
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
For an example payload, see [Example Usage Ping payload](#example-usage-ping-payload).
|
||||
|
||||
## Disable Usage Ping
|
||||
|
||||
|
@ -484,3 +292,196 @@ Examples of query optimization work:
|
|||
### 4. Ask for a Telemetry Review
|
||||
|
||||
On GitLab.com, we have DangerBot setup to monitor Telemetry related files and DangerBot will recommend a Telemetry review. Mention `@gitlab-org/growth/telemetry/engineers` in your MR for a review.
|
||||
|
||||
## Example Usage Ping payload
|
||||
|
||||
The following is example content of the Usage Ping payload.
|
||||
|
||||
```json
|
||||
{
|
||||
"uuid": "0000000-0000-0000-0000-000000000000",
|
||||
"hostname": "example.com",
|
||||
"version": "12.10.0-pre",
|
||||
"installation_type": "omnibus-gitlab",
|
||||
"active_user_count": 999,
|
||||
"recorded_at": "2020-04-17T07:43:54.162+00:00",
|
||||
"edition": "EEU",
|
||||
"license_md5": "00000000000000000000000000000000",
|
||||
"license_id": null,
|
||||
"historical_max_users": 999,
|
||||
"licensee": {
|
||||
"Name": "ABC, Inc.",
|
||||
"Email": "email@example.com",
|
||||
"Company": "ABC, Inc."
|
||||
},
|
||||
"license_user_count": 999,
|
||||
"license_starts_at": "2020-01-01",
|
||||
"license_expires_at": "2021-01-01",
|
||||
"license_plan": "ultimate",
|
||||
"license_add_ons": {
|
||||
},
|
||||
"license_trial": false,
|
||||
"counts": {
|
||||
"assignee_lists": 999,
|
||||
"boards": 999,
|
||||
"ci_builds": 999,
|
||||
...
|
||||
},
|
||||
"container_registry_enabled": true,
|
||||
"dependency_proxy_enabled": false,
|
||||
"gitlab_shared_runners_enabled": true,
|
||||
"gravatar_enabled": true,
|
||||
"influxdb_metrics_enabled": true,
|
||||
"ldap_enabled": false,
|
||||
"mattermost_enabled": false,
|
||||
"omniauth_enabled": true,
|
||||
"prometheus_metrics_enabled": false,
|
||||
"reply_by_email_enabled": "incoming+%{key}@incoming.gitlab.com",
|
||||
"signup_enabled": true,
|
||||
"web_ide_clientside_preview_enabled": true,
|
||||
"ingress_modsecurity_enabled": true,
|
||||
"projects_with_expiration_policy_disabled": 999,
|
||||
"projects_with_expiration_policy_enabled": 999,
|
||||
...
|
||||
"elasticsearch_enabled": true,
|
||||
"license_trial_ends_on": null,
|
||||
"geo_enabled": false,
|
||||
"git": {
|
||||
"version": {
|
||||
"major": 2,
|
||||
"minor": 26,
|
||||
"patch": 1
|
||||
}
|
||||
},
|
||||
"gitaly": {
|
||||
"version": "12.10.0-rc1-93-g40980d40",
|
||||
"servers": 56,
|
||||
"filesystems": [
|
||||
"EXT_2_3_4"
|
||||
]
|
||||
},
|
||||
"gitlab_pages": {
|
||||
"enabled": true,
|
||||
"version": "1.17.0"
|
||||
},
|
||||
"database": {
|
||||
"adapter": "postgresql",
|
||||
"version": "9.6.15"
|
||||
},
|
||||
"app_server": {
|
||||
"type": "console"
|
||||
},
|
||||
"avg_cycle_analytics": {
|
||||
"issue": {
|
||||
"average": 999,
|
||||
"sd": 999,
|
||||
"missing": 999
|
||||
},
|
||||
"plan": {
|
||||
"average": null,
|
||||
"sd": 999,
|
||||
"missing": 999
|
||||
},
|
||||
"code": {
|
||||
"average": null,
|
||||
"sd": 999,
|
||||
"missing": 999
|
||||
},
|
||||
"test": {
|
||||
"average": null,
|
||||
"sd": 999,
|
||||
"missing": 999
|
||||
},
|
||||
"review": {
|
||||
"average": null,
|
||||
"sd": 999,
|
||||
"missing": 999
|
||||
},
|
||||
"staging": {
|
||||
"average": null,
|
||||
"sd": 999,
|
||||
"missing": 999
|
||||
},
|
||||
"production": {
|
||||
"average": null,
|
||||
"sd": 999,
|
||||
"missing": 999
|
||||
},
|
||||
"total": 999
|
||||
},
|
||||
"usage_activity_by_stage": {
|
||||
"configure": {
|
||||
"project_clusters_enabled": 999,
|
||||
...
|
||||
},
|
||||
"create": {
|
||||
"merge_requests": 999,
|
||||
...
|
||||
},
|
||||
"manage": {
|
||||
"events": 999,
|
||||
...
|
||||
},
|
||||
"monitor": {
|
||||
"clusters": 999,
|
||||
...
|
||||
},
|
||||
"package": {
|
||||
"projects_with_packages": 999
|
||||
},
|
||||
"plan": {
|
||||
"issues": 999,
|
||||
...
|
||||
},
|
||||
"release": {
|
||||
"deployments": 999,
|
||||
...
|
||||
},
|
||||
"secure": {
|
||||
"user_container_scanning_jobs": 999,
|
||||
...
|
||||
},
|
||||
"verify": {
|
||||
"ci_builds": 999,
|
||||
...
|
||||
}
|
||||
},
|
||||
"usage_activity_by_stage_monthly": {
|
||||
"configure": {
|
||||
"project_clusters_enabled": 999,
|
||||
...
|
||||
},
|
||||
"create": {
|
||||
"merge_requests": 999,
|
||||
...
|
||||
},
|
||||
"manage": {
|
||||
"events": 999,
|
||||
...
|
||||
},
|
||||
"monitor": {
|
||||
"clusters": 999,
|
||||
...
|
||||
},
|
||||
"package": {
|
||||
"projects_with_packages": 999
|
||||
},
|
||||
"plan": {
|
||||
"issues": 999,
|
||||
...
|
||||
},
|
||||
"release": {
|
||||
"deployments": 999,
|
||||
...
|
||||
},
|
||||
"secure": {
|
||||
"user_container_scanning_jobs": 999,
|
||||
...
|
||||
},
|
||||
"verify": {
|
||||
"ci_builds": 999,
|
||||
...
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
|
@ -8,62 +8,50 @@ type: reference, howto
|
|||
|
||||
## Overview
|
||||
|
||||
If you are using [GitLab CI/CD](../../../ci/README.md), you can check your Docker
|
||||
images (or more precisely the containers) for known vulnerabilities by using
|
||||
[Clair](https://github.com/quay/clair) and [klar](https://github.com/optiopay/klar),
|
||||
two open source tools for Vulnerability Static Analysis for containers.
|
||||
Your application's Docker image may itself be based on Docker images that contain known
|
||||
vulnerabilities. By including an extra job in your pipeline that scans for those vulnerabilities and
|
||||
displays them in a merge request, you can use GitLab to audit your Docker-based apps.
|
||||
By default, container scanning in GitLab is based on [Clair](https://github.com/quay/clair) and
|
||||
[Klar](https://github.com/optiopay/klar), which are open-source tools for vulnerability static analysis in
|
||||
containers. [GitLab's Klar analyzer](https://gitlab.com/gitlab-org/security-products/analyzers/klar/)
|
||||
scans the containers and serves as a wrapper for Clair.
|
||||
|
||||
You can take advantage of Container Scanning by either [including the CI job](#configuration) in
|
||||
your existing `.gitlab-ci.yml` file or by implicitly using
|
||||
[Auto Container Scanning](../../../topics/autodevops/stages.md#auto-container-scanning-ultimate)
|
||||
that is provided by [Auto DevOps](../../../topics/autodevops/index.md).
|
||||
NOTE: **Note:**
|
||||
To integrate security scanners other than Clair and Klar into GitLab, see
|
||||
[Security scanner integration](../../../development/integrations/secure.md).
|
||||
|
||||
GitLab checks the Container Scanning report, compares the found vulnerabilities
|
||||
between the source and target branches, and shows the information right on the
|
||||
merge request.
|
||||
You can enable container scanning by doing one of the following:
|
||||
|
||||
- [Include the CI job](#configuration) in your existing `.gitlab-ci.yml` file.
|
||||
- Implicitly use [Auto Container Scanning](../../../topics/autodevops/stages.md#auto-container-scanning-ultimate)
|
||||
provided by [Auto DevOps](../../../topics/autodevops/index.md).
|
||||
|
||||
GitLab compares the found vulnerabilities between the source and target branches, and shows the
|
||||
information directly in the merge request.
|
||||
|
||||
![Container Scanning Widget](img/container_scanning_v13_0.png)
|
||||
|
||||
## Contribute your scanner
|
||||
|
||||
The [Security Scanner Integration](../../../development/integrations/secure.md) documentation explains how to integrate other security scanners into GitLab.
|
||||
|
||||
## Use cases
|
||||
|
||||
If you distribute your application with Docker, then there's a great chance
|
||||
that your image is based on other Docker images that may in turn contain some
|
||||
known vulnerabilities that could be exploited.
|
||||
|
||||
Having an extra job in your pipeline that checks for those vulnerabilities,
|
||||
and the fact that they are displayed inside a merge request, makes it very easy
|
||||
to perform audits for your Docker-based apps.
|
||||
|
||||
<!-- NOTE: The container scanning tool references the following heading in the code, so if you
|
||||
make a change to this heading, make sure to update the documentation URLs used in the
|
||||
container scanning tool (https://gitlab.com/gitlab-org/security-products/analyzers/klar) -->
|
||||
|
||||
## Requirements
|
||||
|
||||
To enable Container Scanning in your pipeline, you need:
|
||||
To enable Container Scanning in your pipeline, you need the following:
|
||||
|
||||
- A GitLab Runner with the
|
||||
[`docker`](https://docs.gitlab.com/runner/executors/docker.html) or
|
||||
[`kubernetes`](https://docs.gitlab.com/runner/install/kubernetes.html)
|
||||
executor.
|
||||
- Docker `18.09.03` or higher installed on the machine where the Runners are
|
||||
running. If you're using the shared Runners on GitLab.com, this is already
|
||||
the case.
|
||||
- To [build and push](../../packages/container_registry/index.md#container-registry-examples-with-gitlab-cicd)
|
||||
your Docker image to your project's Container Registry.
|
||||
The name of the Docker image should use the following
|
||||
[predefined environment variables](../../../ci/variables/predefined_variables.md)
|
||||
as defined below:
|
||||
- [GitLab Runner](https://docs.gitlab.com/runner/) with the [Docker](https://docs.gitlab.com/runner/executors/docker.html)
|
||||
or [Kubernetes](https://docs.gitlab.com/runner/install/kubernetes.html) executor.
|
||||
- Docker `18.09.03` or higher installed on the same computer as the Runner. If you're using the
|
||||
shared Runners on GitLab.com, then this is already the case.
|
||||
- [Build and push](../../packages/container_registry/index.md#container-registry-examples-with-gitlab-cicd)
|
||||
your Docker image to your project's container registry. The name of the Docker image should use
|
||||
the following [predefined environment variables](../../../ci/variables/predefined_variables.md):
|
||||
|
||||
```plaintext
|
||||
$CI_REGISTRY_IMAGE/$CI_COMMIT_REF_SLUG:$CI_COMMIT_SHA
|
||||
```
|
||||
|
||||
These can be used directly in your `.gitlab-ci.yml` file:
|
||||
You can use these directly in your `.gitlab-ci.yml` file:
|
||||
|
||||
```yaml
|
||||
build:
|
||||
|
@ -81,37 +69,35 @@ To enable Container Scanning in your pipeline, you need:
|
|||
|
||||
## Configuration
|
||||
|
||||
For GitLab 11.9 and later, to enable Container Scanning, you must
|
||||
[include](../../../ci/yaml/README.md#includetemplate) the
|
||||
[`Container-Scanning.gitlab-ci.yml` template](https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml)
|
||||
that's provided as a part of your GitLab installation.
|
||||
For GitLab versions earlier than 11.9, you can copy and use the job as defined
|
||||
in that template.
|
||||
How you enable Container Scanning depends on your GitLab version:
|
||||
|
||||
Add the following to your `.gitlab-ci.yml` file:
|
||||
- GitLab 11.9 and later: [Include](../../../ci/yaml/README.md#includetemplate) the
|
||||
[`Container-Scanning.gitlab-ci.yml` template](https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml)
|
||||
that comes with your GitLab installation.
|
||||
- GitLab versions earlier than 11.9: Copy and use the job from the
|
||||
[`Container-Scanning.gitlab-ci.yml` template](https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml).
|
||||
|
||||
To include the `Container-Scanning.gitlab-ci.yml` template (GitLab 11.9 and later), add the
|
||||
following to your `.gitlab-ci.yml` file:
|
||||
|
||||
```yaml
|
||||
include:
|
||||
- template: Container-Scanning.gitlab-ci.yml
|
||||
```
|
||||
|
||||
The included template will:
|
||||
The included template:
|
||||
|
||||
1. Create a `container_scanning` job in your CI/CD pipeline.
|
||||
1. Pull the already built Docker image from your project's
|
||||
[Container Registry](../../packages/container_registry/index.md) (see [requirements](#requirements))
|
||||
and scan it for possible vulnerabilities.
|
||||
- Creates a `container_scanning` job in your CI/CD pipeline.
|
||||
- Pulls the built Docker image from your project's [Container Registry](../../packages/container_registry/index.md)
|
||||
(see [requirements](#requirements)) and scans it for possible vulnerabilities.
|
||||
|
||||
The results will be saved as a
|
||||
GitLab saves the results as a
|
||||
[Container Scanning report artifact](../../../ci/pipelines/job_artifacts.md#artifactsreportscontainer_scanning-ultimate)
|
||||
that you can later download and analyze.
|
||||
Due to implementation limitations, we always take the latest Container Scanning
|
||||
artifact available. Behind the scenes, the
|
||||
[GitLab Klar analyzer](https://gitlab.com/gitlab-org/security-products/analyzers/klar/)
|
||||
is used and runs the scans.
|
||||
that you can download and analyze later. When downloading, you always receive the most-recent
|
||||
artifact.
|
||||
|
||||
The following is a sample `.gitlab-ci.yml` that will build your Docker image,
|
||||
push it to the Container Registry, and run Container Scanning:
|
||||
The following is a sample `.gitlab-ci.yml` that builds your Docker image, pushes it to the Container
|
||||
Registry, and scans the containers:
|
||||
|
||||
```yaml
|
||||
variables:
|
||||
|
@ -141,11 +127,15 @@ include:
|
|||
|
||||
### Customizing the Container Scanning settings
|
||||
|
||||
You can change container scanning settings by using the [`variables`](../../../ci/yaml/README.md#variables)
|
||||
parameter in your `.gitlab-ci.yml` to change [environment variables](#available-variables).
|
||||
There may be cases where you want to customize how GitLab scans your containers. For example, you
|
||||
may want to enable more verbose output from Clair or Klar, access a Docker registry that requires
|
||||
authentication, and more. To change such settings, use the [`variables`](../../../ci/yaml/README.md#variables)
|
||||
parameter in your `.gitlab-ci.yml` to set [environment variables](#available-variables).
|
||||
The environment variables you set in your `.gitlab-ci.yml` overwrite those in
|
||||
`Container-Scanning.gitlab-ci.yml`.
|
||||
|
||||
In the following example, we [include](../../../ci/yaml/README.md#include) the template and also
|
||||
set the `CLAIR_OUTPUT` variable to `High`:
|
||||
This example [includes](../../../ci/yaml/README.md#include) the Container Scanning template and
|
||||
enables verbose output from Clair by setting the `CLAIR_OUTPUT` environment variable to `High`:
|
||||
|
||||
```yaml
|
||||
include:
|
||||
|
@ -155,9 +145,6 @@ variables:
|
|||
CLAIR_OUTPUT: High
|
||||
```
|
||||
|
||||
The `CLAIR_OUTPUT` variable defined in the main `gitlab-ci.yml` will overwrite what's
|
||||
defined in `Container-Scanning.gitlab-ci.yml`, changing the Container Scanning behavior.
|
||||
|
||||
<!-- NOTE: The container scanning tool references the following heading in the code, so if you"
|
||||
make a change to this heading, make sure to update the documentation URLs used in the"
|
||||
container scanning tool (https://gitlab.com/gitlab-org/security-products/analyzers/klar)" -->
|
||||
|
@ -188,13 +175,9 @@ using environment variables.
|
|||
|
||||
### Overriding the Container Scanning template
|
||||
|
||||
CAUTION: **Deprecation:**
|
||||
Beginning in GitLab 13.0, the use of [`only` and `except`](../../../ci/yaml/README.md#onlyexcept-basic)
|
||||
is no longer supported. When overriding the template, you must use [`rules`](../../../ci/yaml/README.md#rules) instead.
|
||||
|
||||
If you want to override the job definition (for example, change properties like
|
||||
`variables`), you need to declare a `container_scanning` job after the
|
||||
template inclusion and specify any additional keys under it. For example:
|
||||
If you want to override the job definition (for example, to change properties like `variables`), you
|
||||
must declare a `container_scanning` job after the template inclusion, and then
|
||||
specify any additional keys. For example:
|
||||
|
||||
```yaml
|
||||
include:
|
||||
|
@ -205,15 +188,20 @@ container_scanning:
|
|||
GIT_STRATEGY: fetch
|
||||
```
|
||||
|
||||
CAUTION: **Deprecated:**
|
||||
GitLab 13.0 and later doesn't support [`only` and `except`](../../../ci/yaml/README.md#onlyexcept-basic).
|
||||
When overriding the template, you must use [`rules`](../../../ci/yaml/README.md#rules)
|
||||
instead.
|
||||
|
||||
### Vulnerability whitelisting
|
||||
|
||||
If you want to whitelist specific vulnerabilities, you'll need to:
|
||||
To whitelist specific vulnerabilities, follow these steps:
|
||||
|
||||
1. Set `GIT_STRATEGY: fetch` in your `.gitlab-ci.yml` file by following the instructions described in the
|
||||
[overriding the Container Scanning template](#overriding-the-container-scanning-template) section of this document.
|
||||
1. Define the whitelisted vulnerabilities in a YAML file named `clair-whitelist.yml` which must use the format described
|
||||
in the [whitelist example file](https://github.com/arminc/clair-scanner/blob/v12/example-whitelist.yaml).
|
||||
1. Add the `clair-whitelist.yml` file to the Git repository of your project.
|
||||
1. Set `GIT_STRATEGY: fetch` in your `.gitlab-ci.yml` file by following the instructions in
|
||||
[overriding the Container Scanning template](#overriding-the-container-scanning-template).
|
||||
1. Define the whitelisted vulnerabilities in a YAML file named `clair-whitelist.yml`. This must use
|
||||
the format described in the [whitelist example file](https://github.com/arminc/clair-scanner/blob/v12/example-whitelist.yaml).
|
||||
1. Add the `clair-whitelist.yml` file to your project's Git repository.
|
||||
|
||||
### Running Container Scanning in an offline environment
|
||||
|
||||
|
|
|
@ -498,42 +498,6 @@ BUNDLER_AUDIT_ADVISORY_DB_REF_NAME: "master"
|
|||
BUNDLER_AUDIT_ADVISORY_DB_URL: "gitlab.example.com/ruby-advisory-db.git"
|
||||
```
|
||||
|
||||
#### Java (Maven) projects
|
||||
|
||||
When using self-signed certificates, add the following job section to the `.gitlab-ci.yml`:
|
||||
|
||||
```yaml
|
||||
gemnasium-maven-dependency_scanning:
|
||||
variables:
|
||||
MAVEN_CLI_OPTS: "-s settings.xml -Dmaven.wagon.http.ssl.insecure=true -Dmaven.wagon.http.ssl.allowall=true -Dmaven.wagon.http.ssl.ignore.validity.dates=true"
|
||||
```
|
||||
|
||||
#### Java (Gradle) projects
|
||||
|
||||
When using self-signed certificates, add the following job section to the `.gitlab-ci.yml`:
|
||||
|
||||
```yaml
|
||||
gemnasium-maven-dependency_scanning:
|
||||
before_script:
|
||||
- echo -n | openssl s_client -connect maven-repo.example.com:443 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > /tmp/internal.crt
|
||||
- keytool -importcert -file /tmp/internal.crt -cacerts -storepass changeit -noprompt
|
||||
```
|
||||
|
||||
This adds the self-signed certificates of your Maven repository to the Java KeyStore of the analyzer's Docker image.
|
||||
|
||||
#### Scala (sbt) projects
|
||||
|
||||
When using self-signed certificates, add the following job section to the `.gitlab-ci.yml`:
|
||||
|
||||
```yaml
|
||||
gemnasium-maven-dependency_scanning:
|
||||
before_script:
|
||||
- echo -n | openssl s_client -connect maven-repo.example.com:443 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > /tmp/internal.crt
|
||||
- keytool -importcert -file /tmp/internal.crt -cacerts -storepass changeit -noprompt
|
||||
```
|
||||
|
||||
This adds the self-signed certificates of your Maven repository to the Java KeyStore of the analyzer's Docker image.
|
||||
|
||||
#### Python (setuptools)
|
||||
|
||||
When using self-signed certificates for your private PyPi repository, no extra job configuration (aside
|
||||
|
|
|
@ -348,6 +348,32 @@ npmRegistryServer: "https://npm.example.com"
|
|||
You can supply a custom root certificate to complete TLS verification by using the
|
||||
`ADDITIONAL_CA_CERT_BUNDLE` [environment variable](#available-variables).
|
||||
|
||||
### Configuring Bower projects
|
||||
|
||||
You can configure Bower projects by using a [`.bowerrc`](https://bower.io/docs/config/#bowerrc-specification)
|
||||
file.
|
||||
|
||||
#### Using private Bower registries
|
||||
|
||||
If you have a private Bower registry you can use the
|
||||
[`registry`](https://bower.io/docs/config/#bowerrc-specification)
|
||||
setting to specify its location.
|
||||
|
||||
For example:
|
||||
|
||||
```plaintext
|
||||
{
|
||||
"registry": "https://registry.bower.io"
|
||||
}
|
||||
```
|
||||
|
||||
#### Custom root certificates for Bower
|
||||
|
||||
You can supply a custom root certificate to complete TLS verification by using the
|
||||
`ADDITIONAL_CA_CERT_BUNDLE` [environment variable](#available-variables), or by
|
||||
specifying a `ca` setting in a [`.bowerrc`](https://bower.io/docs/config/#bowerrc-specification)
|
||||
file.
|
||||
|
||||
### Migration from `license_management` to `license_scanning`
|
||||
|
||||
In GitLab 12.8 a new name for `license_management` job was introduced. This change was made to improve clarity around the purpose of the scan, which is to scan and collect the types of licenses present in a projects dependencies.
|
||||
|
@ -451,6 +477,7 @@ The License Compliance job should now use local copies of the License Compliance
|
|||
your code and generate security reports, without requiring internet access.
|
||||
|
||||
Additional configuration may be needed for connecting to [private Maven repositories](#using-private-maven-repos),
|
||||
[private Bower registries](#using-private-bower-registries),
|
||||
[private NPM registries](#using-private-npm-registries), [private Yarn registries](#using-private-yarn-registries), and [private Python repositories](#using-private-python-repos).
|
||||
|
||||
Exact name matches are required for [project policies](#project-policies-for-license-compliance)
|
||||
|
|
|
@ -84,13 +84,25 @@ module Gitlab
|
|||
elsif resolved_type.is_a? Array
|
||||
# A simple list of rendered types each object being an object to authorize
|
||||
resolved_type.select do |single_object_type|
|
||||
allowed_access?(current_user, unpromise(single_object_type).object)
|
||||
allowed_access?(current_user, realized(single_object_type).object)
|
||||
end
|
||||
else
|
||||
raise "Can't authorize #{@field}"
|
||||
end
|
||||
end
|
||||
|
||||
# Ensure that we are dealing with realized objects, not delayed promises
|
||||
def realized(thing)
|
||||
case thing
|
||||
when BatchLoader::GraphQL
|
||||
thing.sync
|
||||
when GraphQL::Execution::Lazy
|
||||
thing.value # part of the private api, but we need to unwrap it here.
|
||||
else
|
||||
thing
|
||||
end
|
||||
end
|
||||
|
||||
def allowed_access?(current_user, object)
|
||||
object = object.sync if object.respond_to?(:sync)
|
||||
|
||||
|
@ -113,17 +125,6 @@ module Gitlab
|
|||
def scalar_type?
|
||||
node_type_for_basic_connection(@field.type).kind.scalar?
|
||||
end
|
||||
|
||||
# Sometimes we get promises, and have to resolve them. The dedicated way
|
||||
# of doing this (GitlabSchema.after_lazy) is a private framework method,
|
||||
# and so we use duck-typing interface inference here instead.
|
||||
def unpromise(maybe_promise)
|
||||
if maybe_promise.respond_to?(:value) && !maybe_promise.respond_to?(:object)
|
||||
maybe_promise.value
|
||||
else
|
||||
maybe_promise
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -67,7 +67,7 @@ gitaly_log="$app_root/log/gitaly.log"
|
|||
test -f /etc/default/gitlab && . /etc/default/gitlab
|
||||
|
||||
# Switch to the app_user if it is not they who are running the script.
|
||||
if [ `whoami` != "$app_user" ]; then
|
||||
if [ $(whoami) != "$app_user" ]; then
|
||||
eval su - "$app_user" -c $(echo \")$shell_path -l -c \'$0 "$@"\'$(echo \"); exit;
|
||||
fi
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#!/bin/sh
|
||||
|
||||
output=`git grep -En '^<<<<<<< '`
|
||||
output=$(git grep -En '^<<<<<<< ')
|
||||
echo $output
|
||||
test -z "$output"
|
||||
|
|
94
spec/frontend/droplab/hook_spec.js
Normal file
94
spec/frontend/droplab/hook_spec.js
Normal file
|
@ -0,0 +1,94 @@
|
|||
import Hook from '~/droplab/hook';
|
||||
import DropDown from '~/droplab/drop_down';
|
||||
|
||||
jest.mock('~/droplab/drop_down', () => jest.fn());
|
||||
|
||||
describe('Hook', () => {
|
||||
let testContext;
|
||||
|
||||
beforeEach(() => {
|
||||
testContext = {};
|
||||
});
|
||||
|
||||
describe('class constructor', () => {
|
||||
beforeEach(() => {
|
||||
testContext.trigger = { id: 'id' };
|
||||
testContext.list = {};
|
||||
testContext.plugins = {};
|
||||
testContext.config = {};
|
||||
|
||||
testContext.hook = new Hook(
|
||||
testContext.trigger,
|
||||
testContext.list,
|
||||
testContext.plugins,
|
||||
testContext.config,
|
||||
);
|
||||
});
|
||||
|
||||
it('should set .trigger', () => {
|
||||
expect(testContext.hook.trigger).toBe(testContext.trigger);
|
||||
});
|
||||
|
||||
it('should set .list', () => {
|
||||
expect(testContext.hook.list).toEqual({});
|
||||
});
|
||||
|
||||
it('should call DropDown constructor', () => {
|
||||
expect(DropDown).toHaveBeenCalledWith(testContext.list, testContext.config);
|
||||
});
|
||||
|
||||
it('should set .type', () => {
|
||||
expect(testContext.hook.type).toBe('Hook');
|
||||
});
|
||||
|
||||
it('should set .event', () => {
|
||||
expect(testContext.hook.event).toBe('click');
|
||||
});
|
||||
|
||||
it('should set .plugins', () => {
|
||||
expect(testContext.hook.plugins).toBe(testContext.plugins);
|
||||
});
|
||||
|
||||
it('should set .config', () => {
|
||||
expect(testContext.hook.config).toBe(testContext.config);
|
||||
});
|
||||
|
||||
it('should set .id', () => {
|
||||
expect(testContext.hook.id).toBe(testContext.trigger.id);
|
||||
});
|
||||
|
||||
describe('if config argument is undefined', () => {
|
||||
beforeEach(() => {
|
||||
testContext.config = undefined;
|
||||
|
||||
testContext.hook = new Hook(
|
||||
testContext.trigger,
|
||||
testContext.list,
|
||||
testContext.plugins,
|
||||
testContext.config,
|
||||
);
|
||||
});
|
||||
|
||||
it('should set .config to an empty object', () => {
|
||||
expect(testContext.hook.config).toEqual({});
|
||||
});
|
||||
});
|
||||
|
||||
describe('if plugins argument is undefined', () => {
|
||||
beforeEach(() => {
|
||||
testContext.plugins = undefined;
|
||||
|
||||
testContext.hook = new Hook(
|
||||
testContext.trigger,
|
||||
testContext.list,
|
||||
testContext.plugins,
|
||||
testContext.config,
|
||||
);
|
||||
});
|
||||
|
||||
it('should set .plugins to an empty array', () => {
|
||||
expect(testContext.hook.plugins).toEqual([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -6,7 +6,6 @@ import { objectToQuery } from '~/lib/utils/url_utility';
|
|||
import VueDraggable from 'vuedraggable';
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import statusCodes from '~/lib/utils/http_status';
|
||||
import { metricStates } from '~/monitoring/constants';
|
||||
import Dashboard from '~/monitoring/components/dashboard.vue';
|
||||
|
||||
|
@ -80,19 +79,6 @@ describe('Dashboard', () => {
|
|||
it('shows the environment selector', () => {
|
||||
expect(findEnvironmentsDropdown().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('sets initial state', () => {
|
||||
expect(store.dispatch).toHaveBeenCalledWith('monitoringDashboard/setInitialState', {
|
||||
currentDashboard: '',
|
||||
currentEnvironmentName: 'production',
|
||||
dashboardEndpoint: 'https://invalid',
|
||||
dashboardsEndpoint: 'https://invalid',
|
||||
deploymentsEndpoint: null,
|
||||
logsPath: '/path/to/logs',
|
||||
metricsEndpoint: 'http://test.host/monitoring/mock',
|
||||
projectPath: '/path/to/project',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('no data found', () => {
|
||||
|
@ -288,7 +274,10 @@ describe('Dashboard', () => {
|
|||
it('URL is updated with panel parameters and custom dashboard', () => {
|
||||
const dashboard = 'dashboard.yml';
|
||||
|
||||
createMountedWrapper({ hasMetrics: true, currentDashboard: dashboard });
|
||||
store.commit(`monitoringDashboard/${types.SET_INITIAL_STATE}`, {
|
||||
currentDashboard: dashboard,
|
||||
});
|
||||
createMountedWrapper({ hasMetrics: true });
|
||||
expandPanel(group, panel);
|
||||
|
||||
const expectedSearch = objectToQuery({
|
||||
|
@ -326,8 +315,10 @@ describe('Dashboard', () => {
|
|||
|
||||
describe('when all requests have been commited by the store', () => {
|
||||
beforeEach(() => {
|
||||
store.commit(`monitoringDashboard/${types.SET_INITIAL_STATE}`, {
|
||||
currentEnvironmentName: 'production',
|
||||
});
|
||||
createMountedWrapper({ hasMetrics: true });
|
||||
|
||||
setupStoreWithData(store);
|
||||
|
||||
return wrapper.vm.$nextTick();
|
||||
|
@ -785,7 +776,6 @@ describe('Dashboard', () => {
|
|||
|
||||
describe('cluster health', () => {
|
||||
beforeEach(() => {
|
||||
mock.onGet(propsData.metricsEndpoint).reply(statusCodes.OK, JSON.stringify({}));
|
||||
createShallowWrapper({ hasMetrics: true, showHeader: false });
|
||||
|
||||
// all_dashboards is not defined in health dashboards
|
||||
|
@ -877,7 +867,10 @@ describe('Dashboard', () => {
|
|||
|
||||
beforeEach(() => {
|
||||
setupStoreWithData(store);
|
||||
createShallowWrapper({ hasMetrics: true, currentDashboard });
|
||||
store.commit(`monitoringDashboard/${types.SET_INITIAL_STATE}`, {
|
||||
currentDashboard,
|
||||
});
|
||||
createShallowWrapper({ hasMetrics: true });
|
||||
|
||||
return wrapper.vm.$nextTick();
|
||||
});
|
||||
|
|
|
@ -14,7 +14,9 @@ describe('Dashboard template', () => {
|
|||
let mock;
|
||||
|
||||
beforeEach(() => {
|
||||
store = createStore();
|
||||
store = createStore({
|
||||
currentEnvironmentName: 'production',
|
||||
});
|
||||
mock = new MockAdapter(axios);
|
||||
|
||||
setupAllDashboards(store);
|
||||
|
|
|
@ -11,17 +11,12 @@ export const propsData = {
|
|||
settingsPath: '/path/to/settings',
|
||||
clustersPath: '/path/to/clusters',
|
||||
tagsPath: '/path/to/tags',
|
||||
projectPath: '/path/to/project',
|
||||
logsPath: '/path/to/logs',
|
||||
defaultBranch: 'master',
|
||||
metricsEndpoint: mockApiEndpoint,
|
||||
deploymentsEndpoint: null,
|
||||
emptyGettingStartedSvgPath: '/path/to/getting-started.svg',
|
||||
emptyLoadingSvgPath: '/path/to/loading.svg',
|
||||
emptyNoDataSvgPath: '/path/to/no-data.svg',
|
||||
emptyNoDataSmallSvgPath: '/path/to/no-data-small.svg',
|
||||
emptyUnableToConnectSvgPath: '/path/to/unable-to-connect.svg',
|
||||
currentEnvironmentName: 'production',
|
||||
customMetricsAvailable: false,
|
||||
customMetricsPath: '',
|
||||
validateQueryPath: '',
|
||||
|
|
|
@ -8,7 +8,7 @@ import createFlash from '~/flash';
|
|||
import { defaultTimeRange } from '~/vue_shared/constants';
|
||||
import { ENVIRONMENT_AVAILABLE_STATE } from '~/monitoring/constants';
|
||||
|
||||
import store from '~/monitoring/stores';
|
||||
import { createStore } from '~/monitoring/stores';
|
||||
import * as types from '~/monitoring/stores/mutation_types';
|
||||
import {
|
||||
fetchData,
|
||||
|
@ -52,20 +52,16 @@ import {
|
|||
|
||||
jest.mock('~/flash');
|
||||
|
||||
const resetStore = str => {
|
||||
str.replaceState({
|
||||
showEmptyState: true,
|
||||
emptyState: 'loading',
|
||||
groups: [],
|
||||
});
|
||||
};
|
||||
|
||||
describe('Monitoring store actions', () => {
|
||||
const { convertObjectPropsToCamelCase } = commonUtils;
|
||||
|
||||
let mock;
|
||||
let store;
|
||||
let state;
|
||||
|
||||
beforeEach(() => {
|
||||
store = createStore();
|
||||
state = store.state.monitoringDashboard;
|
||||
mock = new MockAdapter(axios);
|
||||
|
||||
jest.spyOn(commonUtils, 'backOff').mockImplementation(callback => {
|
||||
|
@ -83,7 +79,6 @@ describe('Monitoring store actions', () => {
|
|||
});
|
||||
});
|
||||
afterEach(() => {
|
||||
resetStore(store);
|
||||
mock.reset();
|
||||
|
||||
commonUtils.backOff.mockReset();
|
||||
|
@ -92,8 +87,6 @@ describe('Monitoring store actions', () => {
|
|||
|
||||
describe('fetchData', () => {
|
||||
it('dispatches fetchEnvironmentsData and fetchEnvironmentsData', () => {
|
||||
const { state } = store;
|
||||
|
||||
return testAction(
|
||||
fetchData,
|
||||
null,
|
||||
|
@ -111,8 +104,6 @@ describe('Monitoring store actions', () => {
|
|||
const origGon = window.gon;
|
||||
window.gon = { features: { metricsDashboardAnnotations: true } };
|
||||
|
||||
const { state } = store;
|
||||
|
||||
return testAction(
|
||||
fetchData,
|
||||
null,
|
||||
|
@ -131,7 +122,6 @@ describe('Monitoring store actions', () => {
|
|||
|
||||
describe('fetchDeploymentsData', () => {
|
||||
it('dispatches receiveDeploymentsDataSuccess on success', () => {
|
||||
const { state } = store;
|
||||
state.deploymentsEndpoint = '/success';
|
||||
mock.onGet(state.deploymentsEndpoint).reply(200, {
|
||||
deployments: deploymentData,
|
||||
|
@ -146,7 +136,6 @@ describe('Monitoring store actions', () => {
|
|||
);
|
||||
});
|
||||
it('dispatches receiveDeploymentsDataFailure on error', () => {
|
||||
const { state } = store;
|
||||
state.deploymentsEndpoint = '/error';
|
||||
mock.onGet(state.deploymentsEndpoint).reply(500);
|
||||
|
||||
|
@ -164,11 +153,8 @@ describe('Monitoring store actions', () => {
|
|||
});
|
||||
|
||||
describe('fetchEnvironmentsData', () => {
|
||||
const { state } = store;
|
||||
state.projectPath = 'gitlab-org/gitlab-test';
|
||||
|
||||
afterEach(() => {
|
||||
resetStore(store);
|
||||
beforeEach(() => {
|
||||
state.projectPath = 'gitlab-org/gitlab-test';
|
||||
});
|
||||
|
||||
it('setting SET_ENVIRONMENTS_FILTER should dispatch fetchEnvironmentsData', () => {
|
||||
|
@ -269,17 +255,14 @@ describe('Monitoring store actions', () => {
|
|||
});
|
||||
|
||||
describe('fetchAnnotations', () => {
|
||||
const { state } = store;
|
||||
state.timeRange = {
|
||||
start: '2020-04-15T12:54:32.137Z',
|
||||
end: '2020-08-15T12:54:32.137Z',
|
||||
};
|
||||
state.projectPath = 'gitlab-org/gitlab-test';
|
||||
state.currentEnvironmentName = 'production';
|
||||
state.currentDashboard = '.gitlab/dashboards/custom_dashboard.yml';
|
||||
|
||||
afterEach(() => {
|
||||
resetStore(store);
|
||||
beforeEach(() => {
|
||||
state.timeRange = {
|
||||
start: '2020-04-15T12:54:32.137Z',
|
||||
end: '2020-08-15T12:54:32.137Z',
|
||||
};
|
||||
state.projectPath = 'gitlab-org/gitlab-test';
|
||||
state.currentEnvironmentName = 'production';
|
||||
state.currentDashboard = '.gitlab/dashboards/custom_dashboard.yml';
|
||||
});
|
||||
|
||||
it('fetches annotations data and dispatches receiveAnnotationsSuccess', () => {
|
||||
|
@ -353,7 +336,6 @@ describe('Monitoring store actions', () => {
|
|||
});
|
||||
|
||||
describe('Toggles starred value of current dashboard', () => {
|
||||
const { state } = store;
|
||||
let unstarredDashboard;
|
||||
let starredDashboard;
|
||||
|
||||
|
@ -396,23 +378,19 @@ describe('Monitoring store actions', () => {
|
|||
});
|
||||
|
||||
describe('Set initial state', () => {
|
||||
let mockedState;
|
||||
beforeEach(() => {
|
||||
mockedState = storeState();
|
||||
});
|
||||
it('should commit SET_INITIAL_STATE mutation', done => {
|
||||
testAction(
|
||||
setInitialState,
|
||||
{
|
||||
metricsEndpoint: 'additional_metrics.json',
|
||||
currentDashboard: '.gitlab/dashboards/dashboard.yml',
|
||||
deploymentsEndpoint: 'deployments.json',
|
||||
},
|
||||
mockedState,
|
||||
state,
|
||||
[
|
||||
{
|
||||
type: types.SET_INITIAL_STATE,
|
||||
payload: {
|
||||
metricsEndpoint: 'additional_metrics.json',
|
||||
currentDashboard: '.gitlab/dashboards/dashboard.yml',
|
||||
deploymentsEndpoint: 'deployments.json',
|
||||
},
|
||||
},
|
||||
|
@ -423,15 +401,11 @@ describe('Monitoring store actions', () => {
|
|||
});
|
||||
});
|
||||
describe('Set empty states', () => {
|
||||
let mockedState;
|
||||
beforeEach(() => {
|
||||
mockedState = storeState();
|
||||
});
|
||||
it('should commit SET_METRICS_ENDPOINT mutation', done => {
|
||||
testAction(
|
||||
setGettingStartedEmptyState,
|
||||
null,
|
||||
mockedState,
|
||||
state,
|
||||
[
|
||||
{
|
||||
type: types.SET_GETTING_STARTED_EMPTY_STATE,
|
||||
|
@ -444,15 +418,11 @@ describe('Monitoring store actions', () => {
|
|||
});
|
||||
|
||||
describe('updateVariableValues', () => {
|
||||
let mockedState;
|
||||
beforeEach(() => {
|
||||
mockedState = storeState();
|
||||
});
|
||||
it('should commit UPDATE_VARIABLE_VALUES mutation', done => {
|
||||
testAction(
|
||||
updateVariableValues,
|
||||
{ pod: 'POD' },
|
||||
mockedState,
|
||||
state,
|
||||
[
|
||||
{
|
||||
type: types.UPDATE_VARIABLE_VALUES,
|
||||
|
@ -467,13 +437,11 @@ describe('Monitoring store actions', () => {
|
|||
|
||||
describe('fetchDashboard', () => {
|
||||
let dispatch;
|
||||
let state;
|
||||
let commit;
|
||||
const response = metricsDashboardResponse;
|
||||
beforeEach(() => {
|
||||
dispatch = jest.fn();
|
||||
commit = jest.fn();
|
||||
state = storeState();
|
||||
state.dashboardEndpoint = '/dashboard';
|
||||
});
|
||||
|
||||
|
@ -557,12 +525,10 @@ describe('Monitoring store actions', () => {
|
|||
describe('receiveMetricsDashboardSuccess', () => {
|
||||
let commit;
|
||||
let dispatch;
|
||||
let state;
|
||||
|
||||
beforeEach(() => {
|
||||
commit = jest.fn();
|
||||
dispatch = jest.fn();
|
||||
state = storeState();
|
||||
});
|
||||
|
||||
it('stores groups', () => {
|
||||
|
@ -623,13 +589,11 @@ describe('Monitoring store actions', () => {
|
|||
describe('fetchDashboardData', () => {
|
||||
let commit;
|
||||
let dispatch;
|
||||
let state;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.spyOn(Tracking, 'event');
|
||||
commit = jest.fn();
|
||||
dispatch = jest.fn();
|
||||
state = storeState();
|
||||
|
||||
state.timeRange = defaultTimeRange;
|
||||
});
|
||||
|
@ -731,7 +695,6 @@ describe('Monitoring store actions', () => {
|
|||
step: 60,
|
||||
};
|
||||
let metric;
|
||||
let state;
|
||||
let data;
|
||||
let prometheusEndpointPath;
|
||||
|
||||
|
@ -929,10 +892,7 @@ describe('Monitoring store actions', () => {
|
|||
});
|
||||
|
||||
describe('duplicateSystemDashboard', () => {
|
||||
let state;
|
||||
|
||||
beforeEach(() => {
|
||||
state = storeState();
|
||||
state.dashboardsEndpoint = '/dashboards.json';
|
||||
});
|
||||
|
||||
|
@ -1010,12 +970,6 @@ describe('Monitoring store actions', () => {
|
|||
});
|
||||
|
||||
describe('setExpandedPanel', () => {
|
||||
let state;
|
||||
|
||||
beforeEach(() => {
|
||||
state = storeState();
|
||||
});
|
||||
|
||||
it('Sets a panel as expanded', () => {
|
||||
const group = 'group_1';
|
||||
const panel = { title: 'A Panel' };
|
||||
|
@ -1031,12 +985,6 @@ describe('Monitoring store actions', () => {
|
|||
});
|
||||
|
||||
describe('clearExpandedPanel', () => {
|
||||
let state;
|
||||
|
||||
beforeEach(() => {
|
||||
state = storeState();
|
||||
});
|
||||
|
||||
it('Clears a panel as expanded', () => {
|
||||
return testAction(
|
||||
clearExpandedPanel,
|
||||
|
|
23
spec/frontend/monitoring/store/index_spec.js
Normal file
23
spec/frontend/monitoring/store/index_spec.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
import { createStore } from '~/monitoring/stores';
|
||||
|
||||
describe('Monitoring Store Index', () => {
|
||||
it('creates store with a `monitoringDashboard` namespace', () => {
|
||||
expect(createStore().state).toEqual({
|
||||
monitoringDashboard: expect.any(Object),
|
||||
});
|
||||
});
|
||||
|
||||
it('creates store with initial values', () => {
|
||||
const defaults = {
|
||||
deploymentsEndpoint: '/mock/deployments',
|
||||
dashboardEndpoint: '/mock/dashboard',
|
||||
dashboardsEndpoint: '/mock/dashboards',
|
||||
};
|
||||
|
||||
const { state } = createStore(defaults);
|
||||
|
||||
expect(state).toEqual({
|
||||
monitoringDashboard: expect.objectContaining(defaults),
|
||||
});
|
||||
});
|
||||
});
|
|
@ -128,13 +128,11 @@ describe('Monitoring mutations', () => {
|
|||
describe('SET_INITIAL_STATE', () => {
|
||||
it('should set all the endpoints', () => {
|
||||
mutations[types.SET_INITIAL_STATE](stateCopy, {
|
||||
metricsEndpoint: 'additional_metrics.json',
|
||||
deploymentsEndpoint: 'deployments.json',
|
||||
dashboardEndpoint: 'dashboard.json',
|
||||
projectPath: '/gitlab-org/gitlab-foss',
|
||||
currentEnvironmentName: 'production',
|
||||
});
|
||||
expect(stateCopy.metricsEndpoint).toEqual('additional_metrics.json');
|
||||
expect(stateCopy.deploymentsEndpoint).toEqual('deployments.json');
|
||||
expect(stateCopy.dashboardEndpoint).toEqual('dashboard.json');
|
||||
expect(stateCopy.projectPath).toEqual('/gitlab-org/gitlab-foss');
|
||||
|
@ -179,12 +177,10 @@ describe('Monitoring mutations', () => {
|
|||
describe('SET_ENDPOINTS', () => {
|
||||
it('should set all the endpoints', () => {
|
||||
mutations[types.SET_ENDPOINTS](stateCopy, {
|
||||
metricsEndpoint: 'additional_metrics.json',
|
||||
deploymentsEndpoint: 'deployments.json',
|
||||
dashboardEndpoint: 'dashboard.json',
|
||||
projectPath: '/gitlab-org/gitlab-foss',
|
||||
});
|
||||
expect(stateCopy.metricsEndpoint).toEqual('additional_metrics.json');
|
||||
expect(stateCopy.deploymentsEndpoint).toEqual('deployments.json');
|
||||
expect(stateCopy.dashboardEndpoint).toEqual('dashboard.json');
|
||||
expect(stateCopy.projectPath).toEqual('/gitlab-org/gitlab-foss');
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import $ from 'jquery';
|
||||
import setupToggleButtons from '~/toggle_buttons';
|
||||
import getSetTimeoutPromise from './helpers/set_timeout_promise_helper';
|
||||
import waitForPromises from './helpers/wait_for_promises';
|
||||
|
||||
function generateMarkup(isChecked = true) {
|
||||
return `
|
||||
|
@ -31,19 +31,16 @@ describe('ToggleButtons', () => {
|
|||
expect(wrapper.querySelector('.js-project-feature-toggle-input').value).toEqual('true');
|
||||
});
|
||||
|
||||
it('should toggle to unchecked when clicked', done => {
|
||||
it('should toggle to unchecked when clicked', () => {
|
||||
const wrapper = setupFixture(true);
|
||||
const toggleButton = wrapper.querySelector('.js-project-feature-toggle');
|
||||
|
||||
toggleButton.click();
|
||||
|
||||
getSetTimeoutPromise()
|
||||
.then(() => {
|
||||
expect(toggleButton.classList.contains('is-checked')).toEqual(false);
|
||||
expect(wrapper.querySelector('.js-project-feature-toggle-input').value).toEqual('false');
|
||||
})
|
||||
.then(done)
|
||||
.catch(done.fail);
|
||||
return waitForPromises().then(() => {
|
||||
expect(toggleButton.classList.contains('is-checked')).toEqual(false);
|
||||
expect(wrapper.querySelector('.js-project-feature-toggle-input').value).toEqual('false');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -58,24 +55,21 @@ describe('ToggleButtons', () => {
|
|||
expect(wrapper.querySelector('.js-project-feature-toggle-input').value).toEqual('false');
|
||||
});
|
||||
|
||||
it('should toggle to checked when clicked', done => {
|
||||
it('should toggle to checked when clicked', () => {
|
||||
const wrapper = setupFixture(false);
|
||||
const toggleButton = wrapper.querySelector('.js-project-feature-toggle');
|
||||
|
||||
toggleButton.click();
|
||||
|
||||
getSetTimeoutPromise()
|
||||
.then(() => {
|
||||
expect(toggleButton.classList.contains('is-checked')).toEqual(true);
|
||||
expect(wrapper.querySelector('.js-project-feature-toggle-input').value).toEqual('true');
|
||||
})
|
||||
.then(done)
|
||||
.catch(done.fail);
|
||||
return waitForPromises().then(() => {
|
||||
expect(toggleButton.classList.contains('is-checked')).toEqual(true);
|
||||
expect(wrapper.querySelector('.js-project-feature-toggle-input').value).toEqual('true');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should emit `trigger-change` event', done => {
|
||||
const changeSpy = jasmine.createSpy('changeEventHandler');
|
||||
it('should emit `trigger-change` event', () => {
|
||||
const changeSpy = jest.fn();
|
||||
const wrapper = setupFixture(false);
|
||||
const toggleButton = wrapper.querySelector('.js-project-feature-toggle');
|
||||
const input = wrapper.querySelector('.js-project-feature-toggle-input');
|
||||
|
@ -84,16 +78,13 @@ describe('ToggleButtons', () => {
|
|||
|
||||
toggleButton.click();
|
||||
|
||||
getSetTimeoutPromise()
|
||||
.then(() => {
|
||||
expect(changeSpy).toHaveBeenCalled();
|
||||
})
|
||||
.then(done)
|
||||
.catch(done.fail);
|
||||
return waitForPromises().then(() => {
|
||||
expect(changeSpy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('clickCallback', () => {
|
||||
it('should show loading indicator while waiting', done => {
|
||||
it('should show loading indicator while waiting', () => {
|
||||
const isChecked = true;
|
||||
const clickCallback = (newValue, toggleButton) => {
|
||||
const input = toggleButton.querySelector('.js-project-feature-toggle-input');
|
||||
|
@ -107,15 +98,12 @@ describe('ToggleButtons', () => {
|
|||
expect(input.value).toEqual('true');
|
||||
|
||||
// After the callback finishes, check that the loading state is gone
|
||||
getSetTimeoutPromise()
|
||||
.then(() => {
|
||||
expect(toggleButton.classList.contains('is-checked')).toEqual(false);
|
||||
expect(toggleButton.classList.contains('is-loading')).toEqual(false);
|
||||
expect(toggleButton.disabled).toEqual(false);
|
||||
expect(input.value).toEqual('false');
|
||||
})
|
||||
.then(done)
|
||||
.catch(done.fail);
|
||||
return waitForPromises().then(() => {
|
||||
expect(toggleButton.classList.contains('is-checked')).toEqual(false);
|
||||
expect(toggleButton.classList.contains('is-loading')).toEqual(false);
|
||||
expect(toggleButton.disabled).toEqual(false);
|
||||
expect(input.value).toEqual('false');
|
||||
});
|
||||
};
|
||||
|
||||
const wrapper = setupFixture(isChecked, clickCallback);
|
|
@ -1,5 +1,5 @@
|
|||
import Vue from 'vue';
|
||||
import mountComponent from 'spec/helpers/vue_mount_component_helper';
|
||||
import mountComponent from 'helpers/vue_mount_component_helper';
|
||||
import MrWidgetAuthor from '~/vue_merge_request_widget/components/mr_widget_author.vue';
|
||||
|
||||
describe('MrWidgetAuthor', () => {
|
|
@ -1,5 +1,5 @@
|
|||
import Vue from 'vue';
|
||||
import mountComponent from 'spec/helpers/vue_mount_component_helper';
|
||||
import mountComponent from 'helpers/vue_mount_component_helper';
|
||||
import MrWidgetAuthorTime from '~/vue_merge_request_widget/components/mr_widget_author_time.vue';
|
||||
|
||||
describe('MrWidgetAuthorTime', () => {
|
|
@ -1,5 +1,5 @@
|
|||
import Vue from 'vue';
|
||||
import mountComponent from 'spec/helpers/vue_mount_component_helper';
|
||||
import mountComponent from 'helpers/vue_mount_component_helper';
|
||||
import headerComponent from '~/vue_merge_request_widget/components/mr_widget_header.vue';
|
||||
|
||||
describe('MRWidgetHeader', () => {
|
|
@ -1,4 +1,6 @@
|
|||
import Vue from 'vue';
|
||||
import axios from 'axios';
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
import MemoryUsage from '~/vue_merge_request_widget/components/deployment/memory_usage.vue';
|
||||
import MRWidgetService from '~/vue_merge_request_widget/services/mr_widget_service';
|
||||
|
||||
|
@ -59,12 +61,20 @@ const messages = {
|
|||
describe('MemoryUsage', () => {
|
||||
let vm;
|
||||
let el;
|
||||
let mock;
|
||||
|
||||
beforeEach(() => {
|
||||
mock = new MockAdapter(axios);
|
||||
mock.onGet(`${url}.json`).reply(200);
|
||||
|
||||
vm = createComponent();
|
||||
el = vm.$el;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
mock.restore();
|
||||
});
|
||||
|
||||
describe('data', () => {
|
||||
it('should have default data', () => {
|
||||
const data = MemoryUsage.data();
|
||||
|
@ -127,6 +137,9 @@ describe('MemoryUsage', () => {
|
|||
|
||||
describe('computeGraphData', () => {
|
||||
it('should populate sparkline graph', () => {
|
||||
// ignore BoostrapVue warnings
|
||||
jest.spyOn(console, 'warn').mockImplementation();
|
||||
|
||||
vm.computeGraphData(metrics, deployment_time);
|
||||
const { hasMetrics, memoryMetrics, deploymentTime, memoryFrom, memoryTo } = vm;
|
||||
|
||||
|
@ -147,15 +160,15 @@ describe('MemoryUsage', () => {
|
|||
});
|
||||
|
||||
it('should load metrics data using MRWidgetService', done => {
|
||||
spyOn(MRWidgetService, 'fetchMetrics').and.returnValue(returnServicePromise(true));
|
||||
spyOn(vm, 'computeGraphData');
|
||||
jest.spyOn(MRWidgetService, 'fetchMetrics').mockReturnValue(returnServicePromise(true));
|
||||
jest.spyOn(vm, 'computeGraphData').mockImplementation(() => {});
|
||||
|
||||
vm.loadMetrics();
|
||||
setTimeout(() => {
|
||||
setImmediate(() => {
|
||||
expect(MRWidgetService.fetchMetrics).toHaveBeenCalledWith(url);
|
||||
expect(vm.computeGraphData).toHaveBeenCalledWith(metrics, deployment_time);
|
||||
done();
|
||||
}, 333);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -182,6 +195,9 @@ describe('MemoryUsage', () => {
|
|||
});
|
||||
|
||||
it('should show deployment memory usage when metrics are loaded', done => {
|
||||
// ignore BoostrapVue warnings
|
||||
jest.spyOn(console, 'warn').mockImplementation();
|
||||
|
||||
vm.loadingMetrics = false;
|
||||
vm.hasMetrics = true;
|
||||
vm.loadFailed = false;
|
|
@ -1,5 +1,5 @@
|
|||
import Vue from 'vue';
|
||||
import mountComponent from 'spec/helpers/vue_mount_component_helper';
|
||||
import mountComponent from 'helpers/vue_mount_component_helper';
|
||||
import mergeHelpComponent from '~/vue_merge_request_widget/components/mr_widget_merge_help.vue';
|
||||
|
||||
describe('MRWidgetMergeHelp', () => {
|
|
@ -1,6 +1,6 @@
|
|||
import Vue from 'vue';
|
||||
import mountComponent from 'spec/helpers/vue_mount_component_helper';
|
||||
import { trimText } from 'spec/helpers/text_helper';
|
||||
import mountComponent from 'helpers/vue_mount_component_helper';
|
||||
import { trimText } from 'helpers/text_helper';
|
||||
import pipelineComponent from '~/vue_merge_request_widget/components/mr_widget_pipeline.vue';
|
||||
import mockData from '../mock_data';
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import Vue from 'vue';
|
||||
import mountComponent from 'spec/helpers/vue_mount_component_helper';
|
||||
import mountComponent from 'helpers/vue_mount_component_helper';
|
||||
import eventHub from '~/vue_merge_request_widget/event_hub';
|
||||
import component from '~/vue_merge_request_widget/components/states/mr_widget_rebase.vue';
|
||||
|
||||
|
@ -105,7 +105,7 @@ describe('Merge request widget rebase component', () => {
|
|||
|
||||
describe('methods', () => {
|
||||
it('checkRebaseStatus', done => {
|
||||
spyOn(eventHub, '$emit');
|
||||
jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
|
||||
vm = mountComponent(Component, {
|
||||
mr: {},
|
||||
service: {
|
|
@ -1,5 +1,5 @@
|
|||
import Vue from 'vue';
|
||||
import mountComponent from 'spec/helpers/vue_mount_component_helper';
|
||||
import mountComponent from 'helpers/vue_mount_component_helper';
|
||||
import relatedLinksComponent from '~/vue_merge_request_widget/components/mr_widget_related_links.vue';
|
||||
|
||||
describe('MRWidgetRelatedLinks', () => {
|
|
@ -1,5 +1,5 @@
|
|||
import Vue from 'vue';
|
||||
import mountComponent from 'spec/helpers/vue_mount_component_helper';
|
||||
import mountComponent from 'helpers/vue_mount_component_helper';
|
||||
import mrStatusIcon from '~/vue_merge_request_widget/components/mr_widget_status_icon.vue';
|
||||
|
||||
describe('MR widget status icon component', () => {
|
|
@ -1,5 +1,5 @@
|
|||
import Vue from 'vue';
|
||||
import { mockTracking, triggerEvent } from 'spec/helpers/tracking_helper';
|
||||
import { mockTracking, triggerEvent } from 'helpers/tracking_helper';
|
||||
import component from '~/vue_merge_request_widget/components/review_app_link.vue';
|
||||
import mountComponent from '../../helpers/vue_mount_component_helper';
|
||||
|
||||
|
@ -42,7 +42,7 @@ describe('review app link', () => {
|
|||
});
|
||||
|
||||
it('tracks an event when clicked', () => {
|
||||
const spy = mockTracking('_category_', el, spyOn);
|
||||
const spy = mockTracking('_category_', el, jest.spyOn);
|
||||
triggerEvent(el);
|
||||
|
||||
expect(spy).toHaveBeenCalledWith('_category_', 'open_review_app', {
|
|
@ -1,5 +1,5 @@
|
|||
import Vue from 'vue';
|
||||
import mountComponent from 'spec/helpers/vue_mount_component_helper';
|
||||
import mountComponent from 'helpers/vue_mount_component_helper';
|
||||
import archivedComponent from '~/vue_merge_request_widget/components/states/mr_widget_archived.vue';
|
||||
|
||||
describe('MRWidgetArchived', () => {
|
|
@ -1,6 +1,6 @@
|
|||
import Vue from 'vue';
|
||||
import mountComponent from 'spec/helpers/vue_mount_component_helper';
|
||||
import { trimText } from 'spec/helpers/text_helper';
|
||||
import mountComponent from 'helpers/vue_mount_component_helper';
|
||||
import { trimText } from 'helpers/text_helper';
|
||||
import autoMergeEnabledComponent from '~/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled.vue';
|
||||
import MRWidgetService from '~/vue_merge_request_widget/services/mr_widget_service';
|
||||
import eventHub from '~/vue_merge_request_widget/event_hub';
|
||||
|
@ -14,7 +14,7 @@ describe('MRWidgetAutoMergeEnabled', () => {
|
|||
|
||||
beforeEach(() => {
|
||||
const Component = Vue.extend(autoMergeEnabledComponent);
|
||||
spyOn(eventHub, '$emit');
|
||||
jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
|
||||
|
||||
vm = mountComponent(Component, {
|
||||
mr: {
|
||||
|
@ -103,7 +103,7 @@ describe('MRWidgetAutoMergeEnabled', () => {
|
|||
const mrObj = {
|
||||
is_new_mr_data: true,
|
||||
};
|
||||
spyOn(vm.service, 'cancelAutomaticMerge').and.returnValue(
|
||||
jest.spyOn(vm.service, 'cancelAutomaticMerge').mockReturnValue(
|
||||
new Promise(resolve => {
|
||||
resolve({
|
||||
data: mrObj,
|
||||
|
@ -112,17 +112,17 @@ describe('MRWidgetAutoMergeEnabled', () => {
|
|||
);
|
||||
|
||||
vm.cancelAutomaticMerge();
|
||||
setTimeout(() => {
|
||||
setImmediate(() => {
|
||||
expect(vm.isCancellingAutoMerge).toBeTruthy();
|
||||
expect(eventHub.$emit).toHaveBeenCalledWith('UpdateWidgetData', mrObj);
|
||||
done();
|
||||
}, 333);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('removeSourceBranch', () => {
|
||||
it('should set flag and call service then request main component to update the widget', done => {
|
||||
spyOn(vm.service, 'merge').and.returnValue(
|
||||
jest.spyOn(vm.service, 'merge').mockReturnValue(
|
||||
Promise.resolve({
|
||||
data: {
|
||||
status: MWPS_MERGE_STRATEGY,
|
||||
|
@ -131,7 +131,7 @@ describe('MRWidgetAutoMergeEnabled', () => {
|
|||
);
|
||||
|
||||
vm.removeSourceBranch();
|
||||
setTimeout(() => {
|
||||
setImmediate(() => {
|
||||
expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested');
|
||||
expect(vm.service.merge).toHaveBeenCalledWith({
|
||||
sha,
|
||||
|
@ -139,7 +139,7 @@ describe('MRWidgetAutoMergeEnabled', () => {
|
|||
should_remove_source_branch: true,
|
||||
});
|
||||
done();
|
||||
}, 333);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,5 +1,5 @@
|
|||
import Vue from 'vue';
|
||||
import mountComponent from 'spec/helpers/vue_mount_component_helper';
|
||||
import mountComponent from 'helpers/vue_mount_component_helper';
|
||||
import checkingComponent from '~/vue_merge_request_widget/components/states/mr_widget_checking.vue';
|
||||
|
||||
describe('MRWidgetChecking', () => {
|
|
@ -1,5 +1,5 @@
|
|||
import Vue from 'vue';
|
||||
import mountComponent from 'spec/helpers/vue_mount_component_helper';
|
||||
import mountComponent from 'helpers/vue_mount_component_helper';
|
||||
import closedComponent from '~/vue_merge_request_widget/components/states/mr_widget_closed.vue';
|
||||
|
||||
describe('MRWidgetClosed', () => {
|
|
@ -1,7 +1,8 @@
|
|||
import $ from 'jquery';
|
||||
import { createLocalVue, shallowMount } from '@vue/test-utils';
|
||||
import { removeBreakLine } from 'spec/helpers/text_helper';
|
||||
import { removeBreakLine } from 'helpers/text_helper';
|
||||
import ConflictsComponent from '~/vue_merge_request_widget/components/states/mr_widget_conflicts.vue';
|
||||
import { TEST_HOST } from 'helpers/test_constants';
|
||||
|
||||
describe('MRWidgetConflicts', () => {
|
||||
let vm;
|
||||
|
@ -16,7 +17,7 @@ describe('MRWidgetConflicts', () => {
|
|||
}
|
||||
|
||||
beforeEach(() => {
|
||||
spyOn($.fn, 'popover').and.callThrough();
|
||||
jest.spyOn($.fn, 'popover');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
@ -185,7 +186,7 @@ describe('MRWidgetConflicts', () => {
|
|||
mr: {
|
||||
canMerge: true,
|
||||
canPushToSourceBranch: true,
|
||||
conflictResolutionPath: gl.TEST_HOST,
|
||||
conflictResolutionPath: TEST_HOST,
|
||||
sourceBranchProtected: true,
|
||||
conflictsDocsPath: '',
|
||||
},
|
||||
|
@ -207,7 +208,7 @@ describe('MRWidgetConflicts', () => {
|
|||
mr: {
|
||||
canMerge: true,
|
||||
canPushToSourceBranch: true,
|
||||
conflictResolutionPath: gl.TEST_HOST,
|
||||
conflictResolutionPath: TEST_HOST,
|
||||
sourceBranchProtected: false,
|
||||
conflictsDocsPath: '',
|
||||
},
|
|
@ -1,5 +1,5 @@
|
|||
import Vue from 'vue';
|
||||
import mountComponent from 'spec/helpers/vue_mount_component_helper';
|
||||
import mountComponent from 'helpers/vue_mount_component_helper';
|
||||
import failedToMergeComponent from '~/vue_merge_request_widget/components/states/mr_widget_failed_to_merge.vue';
|
||||
import eventHub from '~/vue_merge_request_widget/event_hub';
|
||||
|
||||
|
@ -11,9 +11,9 @@ describe('MRWidgetFailedToMerge', () => {
|
|||
|
||||
beforeEach(() => {
|
||||
Component = Vue.extend(failedToMergeComponent);
|
||||
spyOn(eventHub, '$emit');
|
||||
spyOn(window, 'setInterval').and.returnValue(dummyIntervalId);
|
||||
spyOn(window, 'clearInterval').and.stub();
|
||||
jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
|
||||
jest.spyOn(window, 'setInterval').mockReturnValue(dummyIntervalId);
|
||||
jest.spyOn(window, 'clearInterval').mockImplementation();
|
||||
mr = {
|
||||
mergeError: 'Merge error happened',
|
||||
};
|
||||
|
@ -83,7 +83,7 @@ describe('MRWidgetFailedToMerge', () => {
|
|||
|
||||
describe('updateTimer', () => {
|
||||
it('should update timer and emit event when timer end', () => {
|
||||
spyOn(vm, 'refresh');
|
||||
jest.spyOn(vm, 'refresh').mockImplementation(() => {});
|
||||
|
||||
expect(vm.timer).toEqual(10);
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import Vue from 'vue';
|
||||
import mountComponent from 'spec/helpers/vue_mount_component_helper';
|
||||
import mountComponent from 'helpers/vue_mount_component_helper';
|
||||
import mergedComponent from '~/vue_merge_request_widget/components/states/mr_widget_merged.vue';
|
||||
import eventHub from '~/vue_merge_request_widget/event_hub';
|
||||
|
||||
|
@ -52,7 +52,7 @@ describe('MRWidgetMerged', () => {
|
|||
removeSourceBranch() {},
|
||||
};
|
||||
|
||||
spyOn(eventHub, '$emit');
|
||||
jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
|
||||
|
||||
vm = mountComponent(Component, { mr, service });
|
||||
});
|
||||
|
@ -124,7 +124,7 @@ describe('MRWidgetMerged', () => {
|
|||
describe('methods', () => {
|
||||
describe('removeSourceBranch', () => {
|
||||
it('should set flag and call service then request main component to update the widget', done => {
|
||||
spyOn(vm.service, 'removeSourceBranch').and.returnValue(
|
||||
jest.spyOn(vm.service, 'removeSourceBranch').mockReturnValue(
|
||||
new Promise(resolve => {
|
||||
resolve({
|
||||
data: {
|
||||
|
@ -135,14 +135,14 @@ describe('MRWidgetMerged', () => {
|
|||
);
|
||||
|
||||
vm.removeSourceBranch();
|
||||
setTimeout(() => {
|
||||
const args = eventHub.$emit.calls.argsFor(0);
|
||||
setImmediate(() => {
|
||||
const args = eventHub.$emit.mock.calls[0];
|
||||
|
||||
expect(vm.isMakingRequest).toEqual(true);
|
||||
expect(args[0]).toEqual('MRWidgetUpdateRequested');
|
||||
expect(args[1]).not.toThrow();
|
||||
done();
|
||||
}, 333);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,5 +1,5 @@
|
|||
import Vue from 'vue';
|
||||
import mountComponent from 'spec/helpers/vue_mount_component_helper';
|
||||
import mountComponent from 'helpers/vue_mount_component_helper';
|
||||
import mergingComponent from '~/vue_merge_request_widget/components/states/mr_widget_merging.vue';
|
||||
|
||||
describe('MRWidgetMerging', () => {
|
|
@ -1,5 +1,5 @@
|
|||
import Vue from 'vue';
|
||||
import mountComponent from 'spec/helpers/vue_mount_component_helper';
|
||||
import mountComponent from 'helpers/vue_mount_component_helper';
|
||||
import missingBranchComponent from '~/vue_merge_request_widget/components/states/mr_widget_missing_branch.vue';
|
||||
|
||||
describe('MRWidgetMissingBranch', () => {
|
|
@ -1,5 +1,5 @@
|
|||
import Vue from 'vue';
|
||||
import mountComponent from 'spec/helpers/vue_mount_component_helper';
|
||||
import mountComponent from 'helpers/vue_mount_component_helper';
|
||||
import notAllowedComponent from '~/vue_merge_request_widget/components/states/mr_widget_not_allowed.vue';
|
||||
|
||||
describe('MRWidgetNotAllowed', () => {
|
|
@ -1,6 +1,6 @@
|
|||
import Vue from 'vue';
|
||||
import mountComponent from 'spec/helpers/vue_mount_component_helper';
|
||||
import { removeBreakLine } from 'spec/helpers/text_helper';
|
||||
import mountComponent from 'helpers/vue_mount_component_helper';
|
||||
import { removeBreakLine } from 'helpers/text_helper';
|
||||
import pipelineBlockedComponent from '~/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked.vue';
|
||||
|
||||
describe('MRWidgetPipelineBlocked', () => {
|
|
@ -1,5 +1,5 @@
|
|||
import Vue from 'vue';
|
||||
import { removeBreakLine } from 'spec/helpers/text_helper';
|
||||
import { removeBreakLine } from 'helpers/text_helper';
|
||||
import PipelineFailed from '~/vue_merge_request_widget/components/states/pipeline_failed.vue';
|
||||
|
||||
describe('PipelineFailed', () => {
|
|
@ -7,6 +7,15 @@ import CommitEdit from '~/vue_merge_request_widget/components/states/commit_edit
|
|||
import CommitMessageDropdown from '~/vue_merge_request_widget/components/states/commit_message_dropdown.vue';
|
||||
import eventHub from '~/vue_merge_request_widget/event_hub';
|
||||
import { MWPS_MERGE_STRATEGY, MTWPS_MERGE_STRATEGY } from '~/vue_merge_request_widget/constants';
|
||||
import { refreshUserMergeRequestCounts } from '~/commons/nav/user_merge_requests';
|
||||
import simplePoll from '~/lib/utils/simple_poll';
|
||||
|
||||
jest.mock('~/lib/utils/simple_poll', () =>
|
||||
jest.fn().mockImplementation(jest.requireActual('~/lib/utils/simple_poll').default),
|
||||
);
|
||||
jest.mock('~/commons/nav/user_merge_requests', () => ({
|
||||
refreshUserMergeRequestCounts: jest.fn(),
|
||||
}));
|
||||
|
||||
const commitMessage = 'This is the commit message';
|
||||
const squashCommitMessage = 'This is the squash commit message';
|
||||
|
@ -33,6 +42,7 @@ const createTestMr = customConfig => {
|
|||
targetBranch: 'master',
|
||||
preferredAutoMergeStrategy: MWPS_MERGE_STRATEGY,
|
||||
availableAutoMergeStrategies: [MWPS_MERGE_STRATEGY],
|
||||
mergeImmediatelyDocsPath: 'path/to/merge/immediately/docs',
|
||||
};
|
||||
|
||||
Object.assign(mr, customConfig.mr);
|
||||
|
@ -41,8 +51,8 @@ const createTestMr = customConfig => {
|
|||
};
|
||||
|
||||
const createTestService = () => ({
|
||||
merge() {},
|
||||
poll() {},
|
||||
merge: jest.fn(),
|
||||
poll: jest.fn().mockResolvedValue(),
|
||||
});
|
||||
|
||||
const createComponent = (customConfig = {}) => {
|
||||
|
@ -59,11 +69,9 @@ const createComponent = (customConfig = {}) => {
|
|||
|
||||
describe('ReadyToMerge', () => {
|
||||
let vm;
|
||||
let updateMrCountSpy;
|
||||
|
||||
beforeEach(() => {
|
||||
vm = createComponent();
|
||||
updateMrCountSpy = spyOnDependency(ReadyToMerge, 'refreshUserMergeRequestCounts');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
@ -347,19 +355,21 @@ describe('ReadyToMerge', () => {
|
|||
});
|
||||
|
||||
it('should handle merge when pipeline succeeds', done => {
|
||||
spyOn(eventHub, '$emit');
|
||||
spyOn(vm.service, 'merge').and.returnValue(returnPromise('merge_when_pipeline_succeeds'));
|
||||
jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
|
||||
jest
|
||||
.spyOn(vm.service, 'merge')
|
||||
.mockReturnValue(returnPromise('merge_when_pipeline_succeeds'));
|
||||
vm.removeSourceBranch = false;
|
||||
vm.handleMergeButtonClick(true);
|
||||
|
||||
setTimeout(() => {
|
||||
setImmediate(() => {
|
||||
expect(vm.isMakingRequest).toBeTruthy();
|
||||
expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested');
|
||||
|
||||
const params = vm.service.merge.calls.argsFor(0)[0];
|
||||
const params = vm.service.merge.mock.calls[0][0];
|
||||
|
||||
expect(params).toEqual(
|
||||
jasmine.objectContaining({
|
||||
expect.objectContaining({
|
||||
sha: vm.mr.sha,
|
||||
commit_message: vm.mr.commitMessage,
|
||||
should_remove_source_branch: false,
|
||||
|
@ -367,67 +377,56 @@ describe('ReadyToMerge', () => {
|
|||
}),
|
||||
);
|
||||
done();
|
||||
}, 333);
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle merge failed', done => {
|
||||
spyOn(eventHub, '$emit');
|
||||
spyOn(vm.service, 'merge').and.returnValue(returnPromise('failed'));
|
||||
jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
|
||||
jest.spyOn(vm.service, 'merge').mockReturnValue(returnPromise('failed'));
|
||||
vm.handleMergeButtonClick(false, true);
|
||||
|
||||
setTimeout(() => {
|
||||
setImmediate(() => {
|
||||
expect(vm.isMakingRequest).toBeTruthy();
|
||||
expect(eventHub.$emit).toHaveBeenCalledWith('FailedToMerge', undefined);
|
||||
|
||||
const params = vm.service.merge.calls.argsFor(0)[0];
|
||||
const params = vm.service.merge.mock.calls[0][0];
|
||||
|
||||
expect(params.should_remove_source_branch).toBeTruthy();
|
||||
expect(params.auto_merge_strategy).toBeUndefined();
|
||||
done();
|
||||
}, 333);
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle merge action accepted case', done => {
|
||||
spyOn(vm.service, 'merge').and.returnValue(returnPromise('success'));
|
||||
spyOn(vm, 'initiateMergePolling');
|
||||
jest.spyOn(vm.service, 'merge').mockReturnValue(returnPromise('success'));
|
||||
jest.spyOn(vm, 'initiateMergePolling').mockImplementation(() => {});
|
||||
vm.handleMergeButtonClick();
|
||||
|
||||
setTimeout(() => {
|
||||
setImmediate(() => {
|
||||
expect(vm.isMakingRequest).toBeTruthy();
|
||||
expect(vm.initiateMergePolling).toHaveBeenCalled();
|
||||
|
||||
const params = vm.service.merge.calls.argsFor(0)[0];
|
||||
const params = vm.service.merge.mock.calls[0][0];
|
||||
|
||||
expect(params.should_remove_source_branch).toBeTruthy();
|
||||
expect(params.auto_merge_strategy).toBeUndefined();
|
||||
done();
|
||||
}, 333);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('initiateMergePolling', () => {
|
||||
beforeEach(() => {
|
||||
jasmine.clock().install();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jasmine.clock().uninstall();
|
||||
});
|
||||
|
||||
it('should call simplePoll', () => {
|
||||
const simplePoll = spyOnDependency(ReadyToMerge, 'simplePoll');
|
||||
vm.initiateMergePolling();
|
||||
|
||||
expect(simplePoll).toHaveBeenCalledWith(jasmine.any(Function), { timeout: 0 });
|
||||
expect(simplePoll).toHaveBeenCalledWith(expect.any(Function), { timeout: 0 });
|
||||
});
|
||||
|
||||
it('should call handleMergePolling', () => {
|
||||
spyOn(vm, 'handleMergePolling');
|
||||
jest.spyOn(vm, 'handleMergePolling').mockImplementation(() => {});
|
||||
|
||||
vm.initiateMergePolling();
|
||||
|
||||
jasmine.clock().tick(2000);
|
||||
|
||||
expect(vm.handleMergePolling).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
@ -448,9 +447,9 @@ describe('ReadyToMerge', () => {
|
|||
});
|
||||
|
||||
it('should call start and stop polling when MR merged', done => {
|
||||
spyOn(eventHub, '$emit');
|
||||
spyOn(vm.service, 'poll').and.returnValue(returnPromise('merged'));
|
||||
spyOn(vm, 'initiateRemoveSourceBranchPolling');
|
||||
jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
|
||||
jest.spyOn(vm.service, 'poll').mockReturnValue(returnPromise('merged'));
|
||||
jest.spyOn(vm, 'initiateRemoveSourceBranchPolling').mockImplementation(() => {});
|
||||
|
||||
let cpc = false; // continuePollingCalled
|
||||
let spc = false; // stopPollingCalled
|
||||
|
@ -463,26 +462,26 @@ describe('ReadyToMerge', () => {
|
|||
spc = true;
|
||||
},
|
||||
);
|
||||
setTimeout(() => {
|
||||
setImmediate(() => {
|
||||
expect(vm.service.poll).toHaveBeenCalled();
|
||||
expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested');
|
||||
expect(eventHub.$emit).toHaveBeenCalledWith('FetchActionsContent');
|
||||
expect(vm.initiateRemoveSourceBranchPolling).toHaveBeenCalled();
|
||||
expect(updateMrCountSpy).toHaveBeenCalled();
|
||||
expect(refreshUserMergeRequestCounts).toHaveBeenCalled();
|
||||
expect(cpc).toBeFalsy();
|
||||
expect(spc).toBeTruthy();
|
||||
|
||||
done();
|
||||
}, 333);
|
||||
});
|
||||
});
|
||||
|
||||
it('updates status box', done => {
|
||||
spyOn(vm.service, 'poll').and.returnValue(returnPromise('merged'));
|
||||
spyOn(vm, 'initiateRemoveSourceBranchPolling');
|
||||
jest.spyOn(vm.service, 'poll').mockReturnValue(returnPromise('merged'));
|
||||
jest.spyOn(vm, 'initiateRemoveSourceBranchPolling').mockImplementation(() => {});
|
||||
|
||||
vm.handleMergePolling(() => {}, () => {});
|
||||
|
||||
setTimeout(() => {
|
||||
setImmediate(() => {
|
||||
const statusBox = document.querySelector('.status-box');
|
||||
|
||||
expect(statusBox.classList.contains('status-box-mr-merged')).toBeTruthy();
|
||||
|
@ -493,12 +492,12 @@ describe('ReadyToMerge', () => {
|
|||
});
|
||||
|
||||
it('hides close button', done => {
|
||||
spyOn(vm.service, 'poll').and.returnValue(returnPromise('merged'));
|
||||
spyOn(vm, 'initiateRemoveSourceBranchPolling');
|
||||
jest.spyOn(vm.service, 'poll').mockReturnValue(returnPromise('merged'));
|
||||
jest.spyOn(vm, 'initiateRemoveSourceBranchPolling').mockImplementation(() => {});
|
||||
|
||||
vm.handleMergePolling(() => {}, () => {});
|
||||
|
||||
setTimeout(() => {
|
||||
setImmediate(() => {
|
||||
expect(document.querySelector('.btn-close').classList.contains('hidden')).toBeTruthy();
|
||||
|
||||
done();
|
||||
|
@ -506,12 +505,12 @@ describe('ReadyToMerge', () => {
|
|||
});
|
||||
|
||||
it('updates merge request count badge', done => {
|
||||
spyOn(vm.service, 'poll').and.returnValue(returnPromise('merged'));
|
||||
spyOn(vm, 'initiateRemoveSourceBranchPolling');
|
||||
jest.spyOn(vm.service, 'poll').mockReturnValue(returnPromise('merged'));
|
||||
jest.spyOn(vm, 'initiateRemoveSourceBranchPolling').mockImplementation(() => {});
|
||||
|
||||
vm.handleMergePolling(() => {}, () => {});
|
||||
|
||||
setTimeout(() => {
|
||||
setImmediate(() => {
|
||||
expect(document.querySelector('.js-merge-counter').textContent).toBe('0');
|
||||
|
||||
done();
|
||||
|
@ -519,8 +518,8 @@ describe('ReadyToMerge', () => {
|
|||
});
|
||||
|
||||
it('should continue polling until MR is merged', done => {
|
||||
spyOn(vm.service, 'poll').and.returnValue(returnPromise('some_other_state'));
|
||||
spyOn(vm, 'initiateRemoveSourceBranchPolling');
|
||||
jest.spyOn(vm.service, 'poll').mockReturnValue(returnPromise('some_other_state'));
|
||||
jest.spyOn(vm, 'initiateRemoveSourceBranchPolling').mockImplementation(() => {});
|
||||
|
||||
let cpc = false; // continuePollingCalled
|
||||
let spc = false; // stopPollingCalled
|
||||
|
@ -533,19 +532,18 @@ describe('ReadyToMerge', () => {
|
|||
spc = true;
|
||||
},
|
||||
);
|
||||
setTimeout(() => {
|
||||
setImmediate(() => {
|
||||
expect(cpc).toBeTruthy();
|
||||
expect(spc).toBeFalsy();
|
||||
|
||||
done();
|
||||
}, 333);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('initiateRemoveSourceBranchPolling', () => {
|
||||
it('should emit event and call simplePoll', () => {
|
||||
spyOn(eventHub, '$emit');
|
||||
const simplePoll = spyOnDependency(ReadyToMerge, 'simplePoll');
|
||||
jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
|
||||
|
||||
vm.initiateRemoveSourceBranchPolling();
|
||||
|
||||
|
@ -565,8 +563,8 @@ describe('ReadyToMerge', () => {
|
|||
});
|
||||
|
||||
it('should call start and stop polling when MR merged', done => {
|
||||
spyOn(eventHub, '$emit');
|
||||
spyOn(vm.service, 'poll').and.returnValue(returnPromise(false));
|
||||
jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
|
||||
jest.spyOn(vm.service, 'poll').mockReturnValue(returnPromise(false));
|
||||
|
||||
let cpc = false; // continuePollingCalled
|
||||
let spc = false; // stopPollingCalled
|
||||
|
@ -579,10 +577,10 @@ describe('ReadyToMerge', () => {
|
|||
spc = true;
|
||||
},
|
||||
);
|
||||
setTimeout(() => {
|
||||
setImmediate(() => {
|
||||
expect(vm.service.poll).toHaveBeenCalled();
|
||||
|
||||
const args = eventHub.$emit.calls.argsFor(0);
|
||||
const args = eventHub.$emit.mock.calls[0];
|
||||
|
||||
expect(args[0]).toEqual('MRWidgetUpdateRequested');
|
||||
expect(args[1]).toBeDefined();
|
||||
|
@ -594,11 +592,11 @@ describe('ReadyToMerge', () => {
|
|||
expect(spc).toBeTruthy();
|
||||
|
||||
done();
|
||||
}, 333);
|
||||
});
|
||||
});
|
||||
|
||||
it('should continue polling until MR is merged', done => {
|
||||
spyOn(vm.service, 'poll').and.returnValue(returnPromise(true));
|
||||
jest.spyOn(vm.service, 'poll').mockReturnValue(returnPromise(true));
|
||||
|
||||
let cpc = false; // continuePollingCalled
|
||||
let spc = false; // stopPollingCalled
|
||||
|
@ -611,12 +609,12 @@ describe('ReadyToMerge', () => {
|
|||
spc = true;
|
||||
},
|
||||
);
|
||||
setTimeout(() => {
|
||||
setImmediate(() => {
|
||||
expect(cpc).toBeTruthy();
|
||||
expect(spc).toBeFalsy();
|
||||
|
||||
done();
|
||||
}, 333);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,6 +1,6 @@
|
|||
import Vue from 'vue';
|
||||
import mountComponent from 'spec/helpers/vue_mount_component_helper';
|
||||
import { removeBreakLine } from 'spec/helpers/text_helper';
|
||||
import mountComponent from 'helpers/vue_mount_component_helper';
|
||||
import { removeBreakLine } from 'helpers/text_helper';
|
||||
import ShaMismatch from '~/vue_merge_request_widget/components/states/sha_mismatch.vue';
|
||||
|
||||
describe('ShaMismatch', () => {
|
|
@ -1,6 +1,7 @@
|
|||
import Vue from 'vue';
|
||||
import mountComponent from 'spec/helpers/vue_mount_component_helper';
|
||||
import mountComponent from 'helpers/vue_mount_component_helper';
|
||||
import UnresolvedDiscussions from '~/vue_merge_request_widget/components/states/unresolved_discussions.vue';
|
||||
import { TEST_HOST } from 'helpers/test_constants';
|
||||
|
||||
describe('UnresolvedDiscussions', () => {
|
||||
const Component = Vue.extend(UnresolvedDiscussions);
|
||||
|
@ -14,7 +15,7 @@ describe('UnresolvedDiscussions', () => {
|
|||
beforeEach(() => {
|
||||
vm = mountComponent(Component, {
|
||||
mr: {
|
||||
createIssueToResolveDiscussionsPath: gl.TEST_HOST,
|
||||
createIssueToResolveDiscussionsPath: TEST_HOST,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
@ -25,7 +26,7 @@ describe('UnresolvedDiscussions', () => {
|
|||
);
|
||||
|
||||
expect(vm.$el.innerText).toContain('Create an issue to resolve them later');
|
||||
expect(vm.$el.querySelector('.js-create-issue').getAttribute('href')).toEqual(gl.TEST_HOST);
|
||||
expect(vm.$el.querySelector('.js-create-issue').getAttribute('href')).toEqual(TEST_HOST);
|
||||
});
|
||||
});
|
||||
|
|
@ -1,6 +1,9 @@
|
|||
import Vue from 'vue';
|
||||
import WorkInProgress from '~/vue_merge_request_widget/components/states/work_in_progress.vue';
|
||||
import eventHub from '~/vue_merge_request_widget/event_hub';
|
||||
import createFlash from '~/flash';
|
||||
|
||||
jest.mock('~/flash');
|
||||
|
||||
const createComponent = () => {
|
||||
const Component = Vue.extend(WorkInProgress);
|
||||
|
@ -47,9 +50,8 @@ describe('Wip', () => {
|
|||
it('should make a request to service and handle response', done => {
|
||||
const vm = createComponent();
|
||||
|
||||
const flashSpy = spyOnDependency(WorkInProgress, 'createFlash').and.returnValue(true);
|
||||
spyOn(eventHub, '$emit');
|
||||
spyOn(vm.service, 'removeWIP').and.returnValue(
|
||||
jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
|
||||
jest.spyOn(vm.service, 'removeWIP').mockReturnValue(
|
||||
new Promise(resolve => {
|
||||
resolve({
|
||||
data: mrObj,
|
||||
|
@ -58,12 +60,15 @@ describe('Wip', () => {
|
|||
);
|
||||
|
||||
vm.handleRemoveWIP();
|
||||
setTimeout(() => {
|
||||
setImmediate(() => {
|
||||
expect(vm.isMakingRequest).toBeTruthy();
|
||||
expect(eventHub.$emit).toHaveBeenCalledWith('UpdateWidgetData', mrObj);
|
||||
expect(flashSpy).toHaveBeenCalledWith('The merge request can now be merged.', 'notice');
|
||||
expect(createFlash).toHaveBeenCalledWith(
|
||||
'The merge request can now be merged.',
|
||||
'notice',
|
||||
);
|
||||
done();
|
||||
}, 333);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,7 +1,7 @@
|
|||
import Vue from 'vue';
|
||||
import Mousetrap from 'mousetrap';
|
||||
import { file } from 'jest/ide/helpers';
|
||||
import timeoutPromise from 'spec/helpers/set_timeout_promise_helper';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import FindFileComponent from '~/vue_shared/components/file_finder/index.vue';
|
||||
import { UP_KEY_CODE, DOWN_KEY_CODE, ENTER_KEY_CODE, ESC_KEY_CODE } from '~/lib/utils/keycodes';
|
||||
|
||||
|
@ -48,7 +48,7 @@ describe('File finder item spec', () => {
|
|||
],
|
||||
});
|
||||
|
||||
setTimeout(done);
|
||||
setImmediate(done);
|
||||
});
|
||||
|
||||
it('renders list of blobs', () => {
|
||||
|
@ -60,7 +60,7 @@ describe('File finder item spec', () => {
|
|||
it('filters entries', done => {
|
||||
vm.searchText = 'index';
|
||||
|
||||
setTimeout(() => {
|
||||
setImmediate(() => {
|
||||
expect(vm.$el.textContent).toContain('index.js');
|
||||
expect(vm.$el.textContent).not.toContain('component.js');
|
||||
|
||||
|
@ -71,7 +71,7 @@ describe('File finder item spec', () => {
|
|||
it('shows clear button when searchText is not empty', done => {
|
||||
vm.searchText = 'index';
|
||||
|
||||
setTimeout(() => {
|
||||
setImmediate(() => {
|
||||
expect(vm.$el.querySelector('.dropdown-input').classList).toContain('has-value');
|
||||
expect(vm.$el.querySelector('.dropdown-input-search').classList).toContain('hidden');
|
||||
|
||||
|
@ -82,11 +82,11 @@ describe('File finder item spec', () => {
|
|||
it('clear button resets searchText', done => {
|
||||
vm.searchText = 'index';
|
||||
|
||||
timeoutPromise()
|
||||
waitForPromises()
|
||||
.then(() => {
|
||||
vm.$el.querySelector('.dropdown-input-clear').click();
|
||||
})
|
||||
.then(timeoutPromise)
|
||||
.then(waitForPromises)
|
||||
.then(() => {
|
||||
expect(vm.searchText).toBe('');
|
||||
})
|
||||
|
@ -95,14 +95,14 @@ describe('File finder item spec', () => {
|
|||
});
|
||||
|
||||
it('clear button focues search input', done => {
|
||||
spyOn(vm.$refs.searchInput, 'focus');
|
||||
jest.spyOn(vm.$refs.searchInput, 'focus').mockImplementation(() => {});
|
||||
vm.searchText = 'index';
|
||||
|
||||
timeoutPromise()
|
||||
waitForPromises()
|
||||
.then(() => {
|
||||
vm.$el.querySelector('.dropdown-input-clear').click();
|
||||
})
|
||||
.then(timeoutPromise)
|
||||
.then(waitForPromises)
|
||||
.then(() => {
|
||||
expect(vm.$refs.searchInput.focus).toHaveBeenCalled();
|
||||
})
|
||||
|
@ -114,7 +114,7 @@ describe('File finder item spec', () => {
|
|||
it('returns 1 when no filtered entries exist', done => {
|
||||
vm.searchText = 'testing 123';
|
||||
|
||||
setTimeout(() => {
|
||||
setImmediate(() => {
|
||||
expect(vm.listShowCount).toBe(1);
|
||||
|
||||
done();
|
||||
|
@ -134,7 +134,7 @@ describe('File finder item spec', () => {
|
|||
it('returns 33 when entries dont exist', done => {
|
||||
vm.searchText = 'testing 123';
|
||||
|
||||
setTimeout(() => {
|
||||
setImmediate(() => {
|
||||
expect(vm.listHeight).toBe(33);
|
||||
|
||||
done();
|
||||
|
@ -146,7 +146,7 @@ describe('File finder item spec', () => {
|
|||
it('returns length of filtered blobs', done => {
|
||||
vm.searchText = 'index';
|
||||
|
||||
setTimeout(() => {
|
||||
setImmediate(() => {
|
||||
expect(vm.filteredBlobsLength).toBe(1);
|
||||
|
||||
done();
|
||||
|
@ -160,7 +160,7 @@ describe('File finder item spec', () => {
|
|||
vm.focusedIndex = 1;
|
||||
vm.searchText = 'test';
|
||||
|
||||
setTimeout(() => {
|
||||
setImmediate(() => {
|
||||
expect(vm.focusedIndex).toBe(0);
|
||||
|
||||
done();
|
||||
|
@ -173,11 +173,11 @@ describe('File finder item spec', () => {
|
|||
vm.searchText = 'test';
|
||||
vm.visible = true;
|
||||
|
||||
timeoutPromise()
|
||||
waitForPromises()
|
||||
.then(() => {
|
||||
vm.visible = false;
|
||||
})
|
||||
.then(timeoutPromise)
|
||||
.then(waitForPromises)
|
||||
.then(() => {
|
||||
expect(vm.searchText).toBe('');
|
||||
})
|
||||
|
@ -189,7 +189,7 @@ describe('File finder item spec', () => {
|
|||
|
||||
describe('openFile', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(vm, '$emit');
|
||||
jest.spyOn(vm, '$emit').mockImplementation(() => {});
|
||||
});
|
||||
|
||||
it('closes file finder', () => {
|
||||
|
@ -210,11 +210,11 @@ describe('File finder item spec', () => {
|
|||
const event = new CustomEvent('keyup');
|
||||
event.keyCode = ENTER_KEY_CODE;
|
||||
|
||||
spyOn(vm, 'openFile');
|
||||
jest.spyOn(vm, 'openFile').mockImplementation(() => {});
|
||||
|
||||
vm.$refs.searchInput.dispatchEvent(event);
|
||||
|
||||
setTimeout(() => {
|
||||
setImmediate(() => {
|
||||
expect(vm.openFile).toHaveBeenCalledWith(vm.files[0]);
|
||||
|
||||
done();
|
||||
|
@ -225,11 +225,11 @@ describe('File finder item spec', () => {
|
|||
const event = new CustomEvent('keyup');
|
||||
event.keyCode = ESC_KEY_CODE;
|
||||
|
||||
spyOn(vm, '$emit');
|
||||
jest.spyOn(vm, '$emit').mockImplementation(() => {});
|
||||
|
||||
vm.$refs.searchInput.dispatchEvent(event);
|
||||
|
||||
setTimeout(() => {
|
||||
setImmediate(() => {
|
||||
expect(vm.$emit).toHaveBeenCalledWith('toggle', false);
|
||||
|
||||
done();
|
||||
|
@ -303,7 +303,7 @@ describe('File finder item spec', () => {
|
|||
beforeEach(done => {
|
||||
createComponent();
|
||||
|
||||
spyOn(vm, 'toggle');
|
||||
jest.spyOn(vm, 'toggle').mockImplementation(() => {});
|
||||
|
||||
vm.$nextTick(done);
|
||||
});
|
|
@ -1,13 +1,16 @@
|
|||
import Vue from 'vue';
|
||||
import { mount } from '@vue/test-utils';
|
||||
import mountComponent from 'spec/helpers/vue_mount_component_helper';
|
||||
import mountComponent from 'helpers/vue_mount_component_helper';
|
||||
import Icon from '~/vue_shared/components/icon.vue';
|
||||
import iconsPath from '@gitlab/svgs/dist/icons.svg';
|
||||
|
||||
describe('Sprite Icon Component', function() {
|
||||
describe('Initialization', function() {
|
||||
jest.mock('@gitlab/svgs/dist/icons.svg', () => 'testing');
|
||||
|
||||
describe('Sprite Icon Component', () => {
|
||||
describe('Initialization', () => {
|
||||
let icon;
|
||||
|
||||
beforeEach(function() {
|
||||
beforeEach(() => {
|
||||
const IconComponent = Vue.extend(Icon);
|
||||
|
||||
icon = mountComponent(IconComponent, {
|
||||
|
@ -20,20 +23,20 @@ describe('Sprite Icon Component', function() {
|
|||
icon.$destroy();
|
||||
});
|
||||
|
||||
it('should return a defined Vue component', function() {
|
||||
it('should return a defined Vue component', () => {
|
||||
expect(icon).toBeDefined();
|
||||
});
|
||||
|
||||
it('should have <svg> as a child element', function() {
|
||||
it('should have <svg> as a child element', () => {
|
||||
expect(icon.$el.tagName).toBe('svg');
|
||||
});
|
||||
|
||||
it('should have <use> as a child element with the correct href', function() {
|
||||
it('should have <use> as a child element with the correct href', () => {
|
||||
expect(icon.$el.firstChild.tagName).toBe('use');
|
||||
expect(icon.$el.firstChild.getAttribute('xlink:href')).toBe(`${gon.sprite_icons}#commit`);
|
||||
expect(icon.$el.firstChild.getAttribute('xlink:href')).toBe(`${iconsPath}#commit`);
|
||||
});
|
||||
|
||||
it('should properly compute iconSizeClass', function() {
|
||||
it('should properly compute iconSizeClass', () => {
|
||||
expect(icon.iconSizeClass).toBe('s32');
|
||||
});
|
||||
|
||||
|
@ -43,7 +46,7 @@ describe('Sprite Icon Component', function() {
|
|||
expect(icon.$options.props.size.validator(9001)).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should properly render img css', function() {
|
||||
it('should properly render img css', () => {
|
||||
const { classList } = icon.$el;
|
||||
const containsSizeClass = classList.contains('s32');
|
||||
|
||||
|
@ -51,16 +54,18 @@ describe('Sprite Icon Component', function() {
|
|||
});
|
||||
|
||||
it('`name` validator should return false for non existing icons', () => {
|
||||
jest.spyOn(console, 'warn').mockImplementation();
|
||||
|
||||
expect(Icon.props.name.validator('non_existing_icon_sprite')).toBe(false);
|
||||
});
|
||||
|
||||
it('`name` validator should return false for existing icons', () => {
|
||||
it('`name` validator should return true for existing icons', () => {
|
||||
expect(Icon.props.name.validator('commit')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should call registered listeners when they are triggered', () => {
|
||||
const clickHandler = jasmine.createSpy('clickHandler');
|
||||
const clickHandler = jest.fn();
|
||||
const wrapper = mount(Icon, {
|
||||
propsData: { name: 'commit' },
|
||||
listeners: { click: clickHandler },
|
|
@ -1,5 +1,5 @@
|
|||
import Vue from 'vue';
|
||||
import mountComponent from 'spec/helpers/vue_mount_component_helper';
|
||||
import mountComponent from 'helpers/vue_mount_component_helper';
|
||||
import panelResizer from '~/vue_shared/components/panel_resizer.vue';
|
||||
|
||||
describe('Panel Resizer component', () => {
|
||||
|
@ -69,12 +69,12 @@ describe('Panel Resizer component', () => {
|
|||
side: 'left',
|
||||
});
|
||||
|
||||
spyOn(vm, '$emit');
|
||||
jest.spyOn(vm, '$emit').mockImplementation(() => {});
|
||||
triggerEvent('mousedown', vm.$el);
|
||||
triggerEvent('mousemove', document);
|
||||
triggerEvent('mouseup', document);
|
||||
|
||||
expect(vm.$emit.calls.allArgs()).toEqual([
|
||||
expect(vm.$emit.mock.calls).toEqual([
|
||||
['resize-start', 100],
|
||||
['update:size', 100],
|
||||
['resize-end', 100],
|
|
@ -3,6 +3,7 @@ import { shallowMount, createLocalVue } from '@vue/test-utils';
|
|||
|
||||
import { GlButton, GlLoadingIcon, GlSearchBoxByType, GlLink } from '@gitlab/ui';
|
||||
import { UP_KEY_CODE, DOWN_KEY_CODE, ENTER_KEY_CODE, ESC_KEY_CODE } from '~/lib/utils/keycodes';
|
||||
import SmartVirtualList from '~/vue_shared/components/smart_virtual_list.vue';
|
||||
import DropdownContentsLabelsView from '~/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view.vue';
|
||||
import LabelItem from '~/vue_shared/components/sidebar/labels_select_vue/label_item.vue';
|
||||
|
||||
|
@ -224,6 +225,10 @@ describe('DropdownContentsLabelsView', () => {
|
|||
expect(searchInputEl.attributes('autofocus')).toBe('true');
|
||||
});
|
||||
|
||||
it('renders smart-virtual-list element', () => {
|
||||
expect(wrapper.find(SmartVirtualList).exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('renders label elements for all labels', () => {
|
||||
expect(wrapper.findAll(LabelItem)).toHaveLength(mockLabels.length);
|
||||
});
|
||||
|
|
|
@ -4,10 +4,13 @@ import { GlIcon, GlLink } from '@gitlab/ui';
|
|||
import LabelItem from '~/vue_shared/components/sidebar/labels_select_vue/label_item.vue';
|
||||
import { mockRegularLabel } from './mock_data';
|
||||
|
||||
const createComponent = ({ label = mockRegularLabel, highlight = true } = {}) =>
|
||||
const mockLabel = { ...mockRegularLabel, set: true };
|
||||
|
||||
const createComponent = ({ label = mockLabel, highlight = true } = {}) =>
|
||||
shallowMount(LabelItem, {
|
||||
propsData: {
|
||||
label,
|
||||
isLabelSet: label.set,
|
||||
highlight,
|
||||
},
|
||||
});
|
||||
|
@ -28,13 +31,29 @@ describe('LabelItem', () => {
|
|||
it('returns an object containing `backgroundColor` based on `label` prop', () => {
|
||||
expect(wrapper.vm.labelBoxStyle).toEqual(
|
||||
expect.objectContaining({
|
||||
backgroundColor: mockRegularLabel.color,
|
||||
backgroundColor: mockLabel.color,
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('watchers', () => {
|
||||
describe('isLabelSet', () => {
|
||||
it('sets value of `isLabelSet` to `isSet` data prop', () => {
|
||||
expect(wrapper.vm.isSet).toBe(true);
|
||||
|
||||
wrapper.setProps({
|
||||
isLabelSet: false,
|
||||
});
|
||||
|
||||
return wrapper.vm.$nextTick(() => {
|
||||
expect(wrapper.vm.isSet).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('methods', () => {
|
||||
describe('handleClick', () => {
|
||||
it('sets value of `isSet` data prop to opposite of its current value', () => {
|
||||
|
@ -52,7 +71,7 @@ describe('LabelItem', () => {
|
|||
wrapper.vm.handleClick();
|
||||
|
||||
expect(wrapper.emitted('clickLabel')).toBeTruthy();
|
||||
expect(wrapper.emitted('clickLabel')[0]).toEqual([mockRegularLabel]);
|
||||
expect(wrapper.emitted('clickLabel')[0]).toEqual([mockLabel]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -105,7 +124,7 @@ describe('LabelItem', () => {
|
|||
});
|
||||
|
||||
it('renders label title', () => {
|
||||
expect(wrapper.text()).toContain(mockRegularLabel.title);
|
||||
expect(wrapper.text()).toContain(mockLabel.title);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import Vue from 'vue';
|
||||
import mountComponent from 'spec/helpers/vue_mount_component_helper';
|
||||
import { mount } from '@vue/test-utils';
|
||||
import SmartVirtualScrollList from '~/vue_shared/components/smart_virtual_list.vue';
|
||||
|
||||
describe('Toggle Button', () => {
|
||||
|
@ -28,7 +28,7 @@ describe('Toggle Button', () => {
|
|||
</smart-virtual-scroll-list>`,
|
||||
});
|
||||
|
||||
return mountComponent(Component);
|
||||
return mount(Component).vm;
|
||||
};
|
||||
|
||||
afterEach(() => {
|
|
@ -9,13 +9,21 @@ describe('AutofocusOnShow directive', () => {
|
|||
describe('with input invisible on component render', () => {
|
||||
let el;
|
||||
|
||||
beforeAll(() => {
|
||||
beforeEach(() => {
|
||||
setFixtures('<div id="container" style="display: none;"><input id="inputel"/></div>');
|
||||
el = document.querySelector('#inputel');
|
||||
|
||||
window.IntersectionObserver = class {
|
||||
observe = jest.fn();
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
delete window.IntersectionObserver;
|
||||
});
|
||||
|
||||
it('should bind IntersectionObserver on input element', () => {
|
||||
spyOn(el, 'focus');
|
||||
jest.spyOn(el, 'focus').mockImplementation(() => {});
|
||||
|
||||
autofocusonshow.inserted(el);
|
||||
|
||||
|
@ -27,7 +35,7 @@ describe('AutofocusOnShow directive', () => {
|
|||
el.visibilityObserver = {
|
||||
disconnect: () => {},
|
||||
};
|
||||
spyOn(el.visibilityObserver, 'disconnect');
|
||||
jest.spyOn(el.visibilityObserver, 'disconnect').mockImplementation(() => {});
|
||||
|
||||
autofocusonshow.unbind(el);
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import testAction from 'spec/helpers/vuex_action_helper';
|
||||
import testAction from 'helpers/vuex_action_helper';
|
||||
import * as types from '~/vuex_shared/modules/modal/mutation_types';
|
||||
import * as actions from '~/vuex_shared/modules/modal/actions';
|
||||
|
|
@ -23,7 +23,7 @@ describe GitlabSchema.types['MergeRequest'] do
|
|||
source_branch_exists target_branch_exists
|
||||
upvotes downvotes head_pipeline pipelines task_completion_status
|
||||
milestone assignees participants subscribed labels discussion_locked time_estimate
|
||||
total_time_spent reference
|
||||
total_time_spent reference author merged_at
|
||||
]
|
||||
|
||||
expect(described_class).to have_graphql_fields(*expected_fields)
|
||||
|
|
|
@ -1,73 +0,0 @@
|
|||
import Hook from '~/droplab/hook';
|
||||
|
||||
describe('Hook', function() {
|
||||
describe('class constructor', function() {
|
||||
beforeEach(function() {
|
||||
this.trigger = { id: 'id' };
|
||||
this.list = {};
|
||||
this.plugins = {};
|
||||
this.config = {};
|
||||
this.dropdown = {};
|
||||
|
||||
this.dropdownConstructor = spyOnDependency(Hook, 'DropDown').and.returnValue(this.dropdown);
|
||||
|
||||
this.hook = new Hook(this.trigger, this.list, this.plugins, this.config);
|
||||
});
|
||||
|
||||
it('should set .trigger', function() {
|
||||
expect(this.hook.trigger).toBe(this.trigger);
|
||||
});
|
||||
|
||||
it('should set .list', function() {
|
||||
expect(this.hook.list).toBe(this.dropdown);
|
||||
});
|
||||
|
||||
it('should call DropDown constructor', function() {
|
||||
expect(this.dropdownConstructor).toHaveBeenCalledWith(this.list, this.config);
|
||||
});
|
||||
|
||||
it('should set .type', function() {
|
||||
expect(this.hook.type).toBe('Hook');
|
||||
});
|
||||
|
||||
it('should set .event', function() {
|
||||
expect(this.hook.event).toBe('click');
|
||||
});
|
||||
|
||||
it('should set .plugins', function() {
|
||||
expect(this.hook.plugins).toBe(this.plugins);
|
||||
});
|
||||
|
||||
it('should set .config', function() {
|
||||
expect(this.hook.config).toBe(this.config);
|
||||
});
|
||||
|
||||
it('should set .id', function() {
|
||||
expect(this.hook.id).toBe(this.trigger.id);
|
||||
});
|
||||
|
||||
describe('if config argument is undefined', function() {
|
||||
beforeEach(function() {
|
||||
this.config = undefined;
|
||||
|
||||
this.hook = new Hook(this.trigger, this.list, this.plugins, this.config);
|
||||
});
|
||||
|
||||
it('should set .config to an empty object', function() {
|
||||
expect(this.hook.config).toEqual({});
|
||||
});
|
||||
});
|
||||
|
||||
describe('if plugins argument is undefined', function() {
|
||||
beforeEach(function() {
|
||||
this.plugins = undefined;
|
||||
|
||||
this.hook = new Hook(this.trigger, this.list, this.plugins, this.config);
|
||||
});
|
||||
|
||||
it('should set .plugins to an empty array', function() {
|
||||
expect(this.hook.plugins).toEqual([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,2 +0,0 @@
|
|||
export { default } from '../../frontend/vue_mr_widget/mock_data';
|
||||
export * from '../../frontend/vue_mr_widget/mock_data';
|
|
@ -1 +0,0 @@
|
|||
export * from '../../../../frontend/vue_shared/components/issue/related_issuable_mock_data';
|
|
@ -37,6 +37,30 @@ describe 'getting merge request information nested in a project' do
|
|||
expect(merge_request_graphql_data['webUrl']).to be_present
|
||||
end
|
||||
|
||||
it 'includes author' do
|
||||
post_graphql(query, current_user: current_user)
|
||||
|
||||
expect(merge_request_graphql_data['author']['username']).to eq(merge_request.author.username)
|
||||
end
|
||||
|
||||
it 'includes correct mergedAt value when merged' do
|
||||
time = 1.week.ago
|
||||
merge_request.mark_as_merged
|
||||
merge_request.metrics.update_columns(merged_at: time)
|
||||
|
||||
post_graphql(query, current_user: current_user)
|
||||
retrieved = merge_request_graphql_data['mergedAt']
|
||||
|
||||
expect(Time.zone.parse(retrieved)).to be_within(1.second).of(time)
|
||||
end
|
||||
|
||||
it 'includes nil mergedAt value when not merged' do
|
||||
post_graphql(query, current_user: current_user)
|
||||
retrieved = merge_request_graphql_data['mergedAt']
|
||||
|
||||
expect(retrieved).to be_nil
|
||||
end
|
||||
|
||||
context 'permissions on the merge request' do
|
||||
it 'includes the permissions for the current user on a public project' do
|
||||
expected_permissions = {
|
||||
|
|
|
@ -25,7 +25,7 @@ RSpec.shared_examples 'thread comments' do |resource_name|
|
|||
end
|
||||
|
||||
if resource_name == 'issue'
|
||||
it "clicking 'Comment & close #{resource_name}' will post a comment and close the #{resource_name}" do
|
||||
it "clicking 'Comment & close #{resource_name}' will post a comment and close the #{resource_name}", quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/218757' do
|
||||
find("#{form_selector} .note-textarea").send_keys(comment)
|
||||
|
||||
click_button 'Comment & close issue'
|
||||
|
@ -206,7 +206,7 @@ RSpec.shared_examples 'thread comments' do |resource_name|
|
|||
end
|
||||
|
||||
if resource_name == 'issue'
|
||||
it "clicking 'Start thread & close #{resource_name}' will post a thread and close the #{resource_name}" do
|
||||
it "clicking 'Start thread & close #{resource_name}' will post a thread and close the #{resource_name}", quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/218757' do
|
||||
click_button 'Start thread & close issue'
|
||||
|
||||
expect(page).to have_content(comment)
|
||||
|
|
Loading…
Reference in a new issue