Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-05-05 12:09:31 +00:00
parent 7d175806de
commit f44809bf96
65 changed files with 783 additions and 415 deletions

View File

@ -6,8 +6,7 @@ build-qa-image:
stage: build-images
needs: []
script:
- '[[ -d "ee/" ]] || export GITLAB_EDITION="ce"'
- export QA_IMAGE="${CI_REGISTRY}/${CI_PROJECT_PATH}/gitlab-${GITLAB_EDITION:-ee}-qa:${CI_COMMIT_REF_SLUG}"
- export QA_IMAGE="${CI_REGISTRY}/${CI_PROJECT_PATH}/gitlab-ee-qa:${CI_COMMIT_REF_SLUG}"
- /kaniko/executor --context=${CI_PROJECT_DIR} --dockerfile=${CI_PROJECT_DIR}/qa/Dockerfile --destination=${QA_IMAGE} --cache=true
review-cleanup:
@ -71,7 +70,6 @@ review-deploy:
resource_group: "review/${CI_COMMIT_REF_NAME}"
allow_failure: true
before_script:
- '[[ -d "ee/" ]] || export GITLAB_EDITION="ce"'
- export GITLAB_SHELL_VERSION=$(<GITLAB_SHELL_VERSION)
- export GITALY_VERSION=$(<GITALY_SERVER_VERSION)
- export GITLAB_WORKHORSE_VERSION=$(<GITLAB_WORKHORSE_VERSION)
@ -148,8 +146,7 @@ review-stop:
GITHUB_ACCESS_TOKEN: "${REVIEW_APPS_QA_GITHUB_ACCESS_TOKEN}"
EE_LICENSE: "${REVIEW_APPS_EE_LICENSE}"
before_script:
- '[[ -d "ee/" ]] || export GITLAB_EDITION="ce"'
- export QA_IMAGE="${CI_REGISTRY}/${CI_PROJECT_PATH}/gitlab-${GITLAB_EDITION:-ee}-qa:${CI_COMMIT_REF_SLUG}"
- export QA_IMAGE="${CI_REGISTRY}/${CI_PROJECT_PATH}/gitlab-ee-qa:${CI_COMMIT_REF_SLUG}"
- export CI_ENVIRONMENT_URL="$(cat environment_url.txt)"
- echo "${CI_ENVIRONMENT_URL}"
- echo "${QA_IMAGE}"

View File

@ -472,6 +472,8 @@
################
.review:rules:mr-and-schedule-auto:
rules:
- <<: *if-not-ee
when: never
- <<: *if-dot-com-gitlab-org-merge-request
changes: *code-qa-patterns
when: on_success
@ -480,6 +482,8 @@
.review:rules:mr-and-schedule-auto-if-frontend-manual-otherwise:
rules:
- <<: *if-not-ee
when: never
- <<: *if-dot-com-gitlab-org-merge-request
changes: *frontend-patterns
when: on_success
@ -492,12 +496,16 @@
.review:rules:mr-only-auto:
rules:
- <<: *if-not-ee
when: never
- <<: *if-dot-com-gitlab-org-merge-request
changes: *code-qa-patterns
when: on_success
.review:rules:mr-only-auto-if-frontend-manual-otherwise:
rules:
- <<: *if-not-ee
when: never
- <<: *if-dot-com-gitlab-org-merge-request
changes: *frontend-patterns
when: on_success
@ -507,12 +515,16 @@
.review:rules:mr-only-manual:
rules:
- <<: *if-not-ee
when: never
- <<: *if-dot-com-gitlab-org-merge-request
changes: *code-qa-patterns
when: manual
.review:rules:review-cleanup:
rules:
- <<: *if-not-ee
when: never
- <<: *if-dot-com-gitlab-org-merge-request
changes: *code-qa-patterns
when: manual

View File

@ -335,10 +335,76 @@ RSpec/AnyInstanceOf:
RSpec/ImplicitSubject:
Enabled: false
# WIP See https://gitlab.com/gitlab-org/gitlab/-/issues/211580
RSpec/LeakyConstantDeclaration:
Enabled: true
Exclude:
- 'spec/**/*.rb'
- 'spec/db/schema_spec.rb'
- 'spec/graphql/gitlab_schema_spec.rb'
- 'spec/helpers/visibility_level_helper_spec.rb'
- 'spec/initializers/secret_token_spec.rb'
- 'spec/lib/declarative_policy_spec.rb'
- 'spec/lib/feature_spec.rb'
- 'spec/lib/gitlab/background_migration/migrate_issue_trackers_sensitive_data_spec.rb'
- 'spec/lib/gitlab/ci/build/credentials/factory_spec.rb'
- 'spec/lib/gitlab/ci/config/entry/retry_spec.rb'
- 'spec/lib/gitlab/cluster/mixins/puma_cluster_spec.rb'
- 'spec/lib/gitlab/cluster/mixins/unicorn_http_server_spec.rb'
- 'spec/lib/gitlab/config/entry/factory_spec.rb'
- 'spec/lib/gitlab/config/entry/simplifiable_spec.rb'
- 'spec/lib/gitlab/database/migration_helpers_spec.rb'
- 'spec/lib/gitlab/database/obsolete_ignored_columns_spec.rb'
- 'spec/lib/gitlab/database/with_lock_retries_spec.rb'
- 'spec/lib/gitlab/git/diff_collection_spec.rb'
- 'spec/lib/gitlab/graphql/pagination/keyset/connection_spec.rb'
- 'spec/lib/gitlab/health_checks/master_check_spec.rb'
- 'spec/lib/gitlab/import_export/attribute_configuration_spec.rb'
- 'spec/lib/gitlab/import_export/import_test_coverage_spec.rb'
- 'spec/lib/gitlab/import_export/project/relation_factory_spec.rb'
- 'spec/lib/gitlab/jira_import/issues_importer_spec.rb'
- 'spec/lib/gitlab/no_cache_headers_spec.rb'
- 'spec/lib/gitlab/path_regex_spec.rb'
- 'spec/lib/gitlab/quick_actions/dsl_spec.rb'
- 'spec/lib/gitlab/sidekiq_middleware/client_metrics_spec.rb'
- 'spec/lib/gitlab/sidekiq_middleware/server_metrics_spec.rb'
- 'spec/lib/gitlab/sidekiq_middleware_spec.rb'
- 'spec/lib/gitlab/view/presenter/factory_spec.rb'
- 'spec/lib/marginalia_spec.rb'
- 'spec/lib/omni_auth/strategies/jwt_spec.rb'
- 'spec/lib/system_check/simple_executor_spec.rb'
- 'spec/lib/system_check_spec.rb'
- 'spec/mailers/notify_spec.rb'
- 'spec/migrations/20191125114345_add_admin_mode_protected_path_spec.rb'
- 'spec/migrations/encrypt_plaintext_attributes_on_application_settings_spec.rb'
- 'spec/migrations/cleanup_optimistic_locking_nulls_pt2_fixed_spec.rb'
- 'spec/models/clusters/cluster_spec.rb'
- 'spec/models/concerns/batch_destroy_dependent_associations_spec.rb'
- 'spec/models/concerns/blocks_json_serialization_spec.rb'
- 'spec/models/concerns/bulk_insert_safe_spec.rb'
- 'spec/models/concerns/bulk_insertable_associations_spec.rb'
- 'spec/models/concerns/mentionable_spec.rb'
- 'spec/models/concerns/reactive_caching_spec.rb'
- 'spec/models/concerns/triggerable_hooks_spec.rb'
- 'spec/models/repository_spec.rb'
- 'spec/models/tree_spec.rb'
- 'spec/policies/merge_request_policy_spec.rb'
- 'spec/requests/api/graphql/tasks/task_completion_status_spec.rb'
- 'spec/requests/api/statistics_spec.rb'
- 'spec/rubocop/cop/rspec/env_assignment_spec.rb'
- 'spec/serializers/commit_entity_spec.rb'
- 'spec/services/ci/retry_build_service_spec.rb'
- 'spec/services/clusters/applications/check_installation_progress_service_spec.rb'
- 'spec/services/clusters/applications/check_uninstall_progress_service_spec.rb'
- 'spec/services/clusters/applications/check_upgrade_progress_service_spec.rb'
- 'spec/services/clusters/applications/ingress_modsecurity_usage_service_spec.rb'
- 'spec/services/issues/resolve_discussions_spec.rb'
- 'spec/services/metrics/dashboard/clone_dashboard_service_spec.rb'
- 'spec/support/shared_contexts/spam_constants.rb'
- 'spec/support/shared_examples/quick_actions/issuable/issuable_quick_actions_shared_examples.rb'
- 'spec/support_specs/helpers/active_record/query_recorder_spec.rb'
- 'spec/support_specs/matchers/exceed_query_limit_helpers_spec.rb'
- 'spec/uploaders/content_type_whitelist_spec.rb'
- 'spec/uploaders/records_uploads_spec.rb'
RSpec/EmptyLineAfterHook:
Enabled: false

View File

@ -1,5 +1,9 @@
Please view this file on the master branch, on stable branches it's out of date.
## 12.10.4 (2020-05-05)
- No changes.
## 12.10.2 (2020-04-30)
### Security (3 changes)
@ -59,6 +63,10 @@ Please view this file on the master branch, on stable branches it's out of date.
- Add health status counts to usage data. !28964
## 12.9.6 (2020-05-05)
- No changes.
## 12.9.5 (2020-04-30)
### Security (3 changes)

View File

@ -5,6 +5,7 @@ import {
GlLoadingIcon,
GlTable,
GlAlert,
GlIcon,
GlNewDropdown,
GlNewDropdownItem,
} from '@gitlab/ui';
@ -64,6 +65,7 @@ export default {
TimeAgo,
GlNewDropdown,
GlNewDropdownItem,
GlIcon,
},
props: {
projectPath: {
@ -144,6 +146,18 @@ export default {
fixed
stacked="md"
>
<template #cell(severity)="{ item }">
<div class="d-inline-flex align-items-center justify-content-between">
<gl-icon
class="mr-2"
:size="12"
:name="`severity-${item.severity.toLowerCase()}`"
:class="`icon-${item.severity.toLowerCase()}`"
/>
{{ item.severity }}
</div>
</template>
<template #cell(startedAt)="{ item }">
<time-ago :time="item.startedAt" />
</template>

View File

@ -3,6 +3,7 @@ import dark from './dark';
import monokai from './monokai';
import solarizedLight from './solarized_light';
import solarizedDark from './solarized_dark';
import none from './none';
export const themes = [
{
@ -25,6 +26,10 @@ export const themes = [
name: 'monokai',
data: monokai,
},
{
name: 'none',
data: none,
},
];
export const DEFAULT_THEME = 'white';

View File

@ -0,0 +1,17 @@
export default {
base: 'vs',
inherit: false,
rules: [],
colors: {
'editor.foreground': '#2e2e2e',
'editor.selectionBackground': '#aad6f8',
'editor.lineHighlightBackground': '#fffeeb',
'editorCursor.foreground': '#666666',
'editorWhitespace.foreground': '#bbbbbb',
'editorLineNumber.foreground': '#cccccc',
'diffEditor.insertedTextBackground': '#a0f5b420',
'diffEditor.removedTextBackground': '#f9d7dc20',
'editorIndentGuide.activeBackground': '#cccccc',
},
};

View File

@ -209,7 +209,7 @@ export default {
:series-config="metricSeriesConfig"
>
<slot></slot>
<template v-slot:tooltipContent="slotProps">
<template #tooltip-content="slotProps">
<div
v-for="(content, seriesIndex) in slotProps.tooltip.content"
:key="seriesIndex"

View File

@ -113,6 +113,9 @@ export default {
</script>
<template>
<div>
<div class="mb-3">
<h3>{{ s__('PipelineCharts|CI / CD Analytics') }}</h3>
</div>
<h4 class="my-4">{{ s__('PipelineCharts|Overall statistics') }}</h4>
<div class="row">
<div class="col-md-6">

View File

@ -162,7 +162,7 @@ export default {
:state="isNameValid(link)"
@change="onLinkTitleInput(link.id, $event)"
/>
<template v-slot:invalid-feedback>
<template #invalid-feedback>
<span v-if="hasEmptyName(link)" class="invalid-feedback d-inline">
{{ __('Link title is required') }}
</span>

View File

@ -66,7 +66,7 @@ export default {
<template>
<assignee-avatar-link
v-if="hasOneUser"
v-slot="{ user }"
#default="{ user }"
tooltip-placement="left"
:tooltip-has-name="false"
:user="firstUser"

View File

@ -83,7 +83,7 @@ export default {
:source-branch-link="branchLink"
:troubleshooting-docs-path="mr.troubleshootingDocsPath"
/>
<template v-slot:footer>
<template #footer>
<div v-if="mr.exposedArtifactsPath" class="js-exposed-artifacts">
<artifacts-app :endpoint="mr.exposedArtifactsPath" />
</div>

View File

@ -0,0 +1,5 @@
// eslint-disable-next-line import/prefer-default-export
export const DropdownVariant = {
Sidebar: 'sidebar',
Standalone: 'standalone',
};

View File

@ -1,21 +1,35 @@
<script>
import { mapGetters } from 'vuex';
import { GlDeprecatedButton, GlIcon } from '@gitlab/ui';
import { mapActions, mapGetters } from 'vuex';
import { GlButton, GlIcon } from '@gitlab/ui';
export default {
components: {
GlDeprecatedButton,
GlButton,
GlIcon,
},
computed: {
...mapGetters(['dropdownButtonText']),
...mapGetters(['dropdownButtonText', 'isDropdownVariantStandalone']),
},
methods: {
...mapActions(['toggleDropdownContents']),
handleButtonClick(e) {
if (this.isDropdownVariantStandalone) {
this.toggleDropdownContents();
e.stopPropagation();
}
},
},
};
</script>
<template>
<gl-deprecated-button class="labels-select-dropdown-button w-100 text-left">
<span class="dropdown-toggle-text">{{ dropdownButtonText }}</span>
<gl-button
class="labels-select-dropdown-button js-dropdown-button w-100 text-left"
@click="handleButtonClick"
>
<span class="dropdown-toggle-text" :class="{ 'flex-fill': isDropdownVariantStandalone }">{{
dropdownButtonText
}}</span>
<gl-icon name="chevron-down" class="pull-right" />
</gl-deprecated-button>
</gl-button>
</template>

View File

@ -1,18 +1,10 @@
<script>
import { mapState, mapActions } from 'vuex';
import {
GlTooltipDirective,
GlDeprecatedButton,
GlIcon,
GlFormInput,
GlLink,
GlLoadingIcon,
} from '@gitlab/ui';
import { GlTooltipDirective, GlButton, GlFormInput, GlLink, GlLoadingIcon } from '@gitlab/ui';
export default {
components: {
GlDeprecatedButton,
GlIcon,
GlButton,
GlFormInput,
GlLink,
GlLoadingIcon,
@ -60,25 +52,23 @@ export default {
<template>
<div class="labels-select-contents-create js-labels-create">
<div class="dropdown-title d-flex align-items-center pt-0 pb-2">
<gl-deprecated-button
<gl-button
:aria-label="__('Go back')"
variant="link"
size="sm"
size="small"
class="js-btn-back dropdown-header-button p-0"
icon="arrow-left"
@click="toggleDropdownContentsCreateView"
>
<gl-icon name="arrow-left" />
</gl-deprecated-button>
/>
<span class="flex-grow-1">{{ labelsCreateTitle }}</span>
<gl-deprecated-button
<gl-button
:aria-label="__('Close')"
variant="link"
size="sm"
size="small"
class="dropdown-header-button p-0"
icon="close"
@click="toggleDropdownContents"
>
<gl-icon name="close" />
</gl-deprecated-button>
/>
</div>
<div class="dropdown-input">
<gl-form-input
@ -107,21 +97,19 @@ export default {
</div>
</div>
<div class="dropdown-actions clearfix pt-2 px-2">
<gl-deprecated-button
<gl-button
:disabled="disableCreate"
variant="primary"
category="primary"
variant="success"
class="pull-left d-flex align-items-center"
@click="handleCreateClick"
>
<gl-loading-icon v-show="labelCreateInProgress" :inline="true" class="mr-1" />
{{ __('Create') }}
</gl-deprecated-button>
<gl-deprecated-button
class="pull-right js-btn-cancel-create"
@click="toggleDropdownContentsCreateView"
>
</gl-button>
<gl-button class="pull-right js-btn-cancel-create" @click="toggleDropdownContentsCreateView">
{{ __('Cancel') }}
</gl-deprecated-button>
</gl-button>
</div>
</div>
</template>

View File

@ -1,13 +1,13 @@
<script>
import { mapState, mapGetters, mapActions } from 'vuex';
import { GlLoadingIcon, GlDeprecatedButton, GlIcon, GlSearchBoxByType, GlLink } from '@gitlab/ui';
import { GlLoadingIcon, GlButton, GlIcon, GlSearchBoxByType, GlLink } from '@gitlab/ui';
import { UP_KEY_CODE, DOWN_KEY_CODE, ENTER_KEY_CODE, ESC_KEY_CODE } from '~/lib/utils/keycodes';
export default {
components: {
GlLoadingIcon,
GlDeprecatedButton,
GlButton,
GlIcon,
GlSearchBoxByType,
GlLink,
@ -20,6 +20,8 @@ export default {
},
computed: {
...mapState([
'allowLabelCreate',
'allowMultiselect',
'labelsManagePath',
'labels',
'labelsFetchInProgress',
@ -27,7 +29,7 @@ export default {
'footerCreateLabelTitle',
'footerManageLabelTitle',
]),
...mapGetters(['selectedLabelsList']),
...mapGetters(['selectedLabelsList', 'isDropdownVariantSidebar']),
visibleLabels() {
if (this.searchKey) {
return this.labels.filter(label =>
@ -56,6 +58,7 @@ export default {
'toggleDropdownContentsCreateView',
'fetchLabels',
'updateSelectedLabels',
'toggleDropdownContents',
]),
getDropdownLabelBoxStyle(label) {
return {
@ -111,6 +114,7 @@ export default {
},
handleLabelClick(label) {
this.updateSelectedLabels([label]);
if (!this.allowMultiselect) this.toggleDropdownContents();
},
},
};
@ -123,17 +127,16 @@ export default {
class="labels-fetch-loading position-absolute d-flex align-items-center w-100 h-100"
size="md"
/>
<div class="dropdown-title d-flex align-items-center pt-0 pb-2">
<div v-if="isDropdownVariantSidebar" class="dropdown-title d-flex align-items-center pt-0 pb-2">
<span class="flex-grow-1">{{ labelsListTitle }}</span>
<gl-deprecated-button
<gl-button
:aria-label="__('Close')"
variant="link"
size="sm"
size="small"
class="dropdown-header-button p-0"
icon="close"
@click="toggleDropdownContents"
>
<gl-icon name="close" />
</gl-deprecated-button>
/>
</div>
<div class="dropdown-input">
<gl-search-box-by-type v-model="searchKey" :autofocus="true" />
@ -157,14 +160,13 @@ export default {
</li>
</ul>
</div>
<div class="dropdown-footer">
<div v-if="isDropdownVariantSidebar" class="dropdown-footer">
<ul class="list-unstyled">
<li>
<gl-deprecated-button
variant="link"
<li v-if="allowLabelCreate">
<gl-link
class="d-flex w-100 flex-row text-break-word label-item"
@click="toggleDropdownContentsCreateView"
>{{ footerCreateLabelTitle }}</gl-deprecated-button
>{{ footerCreateLabelTitle }}</gl-link
>
</li>
<li>

View File

@ -1,7 +1,7 @@
<script>
import $ from 'jquery';
import Vue from 'vue';
import Vuex, { mapState, mapActions } from 'vuex';
import Vuex, { mapState, mapActions, mapGetters } from 'vuex';
import { __ } from '~/locale';
import DropdownValueCollapsed from '~/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed.vue';
@ -13,6 +13,8 @@ import DropdownValue from './dropdown_value.vue';
import DropdownButton from './dropdown_button.vue';
import DropdownContents from './dropdown_contents.vue';
import { DropdownVariant } from './constants';
Vue.use(Vuex);
export default {
@ -33,14 +35,19 @@ export default {
type: Boolean,
required: true,
},
allowMultiselect: {
type: Boolean,
required: false,
default: false,
},
allowScopedLabels: {
type: Boolean,
required: true,
},
dropdownOnly: {
type: Boolean,
variant: {
type: String,
required: false,
default: false,
default: DropdownVariant.Sidebar,
},
selectedLabels: {
type: Array,
@ -90,6 +97,10 @@ export default {
},
computed: {
...mapState(['showDropdownButton', 'showDropdownContents']),
...mapGetters(['isDropdownVariantSidebar', 'isDropdownVariantStandalone']),
dropdownButtonVisible() {
return this.isDropdownVariantSidebar ? this.showDropdownButton : true;
},
},
watch: {
selectedLabels(selectedLabels) {
@ -100,9 +111,10 @@ export default {
},
mounted() {
this.setInitialState({
dropdownOnly: this.dropdownOnly,
variant: this.variant,
allowLabelEdit: this.allowLabelEdit,
allowLabelCreate: this.allowLabelCreate,
allowMultiselect: this.allowMultiselect,
allowScopedLabels: this.allowScopedLabels,
selectedLabels: this.selectedLabels,
labelsFetchPath: this.labelsFetchPath,
@ -148,13 +160,20 @@ export default {
// as the dropdown wrapper is not using `GlDropdown` as
// it will also require us to use `BDropdownForm`
// which is yet to be implemented in GitLab UI.
const hasExceptionClass = [
'js-dropdown-button',
'js-btn-cancel-create',
'js-sidebar-dropdown-toggle',
].some(className => target?.classList.contains(className));
const hadExceptionParent = ['.js-btn-back', '.js-labels-list'].some(
className => $(target).parents(className).length,
);
if (
this.showDropdownButton &&
this.showDropdownContents &&
!$(target).parents('.js-btn-back').length &&
!$(target).parents('.js-labels-list').length &&
!target?.classList.contains('js-btn-cancel-create') &&
!target?.classList.contains('js-sidebar-dropdown-toggle') &&
!hadExceptionParent &&
!hasExceptionClass &&
!this.$refs.dropdownButtonCollapsed?.$el.contains(target) &&
!this.$refs.dropdownContents?.$el.contains(target)
) {
@ -175,10 +194,12 @@ export default {
</script>
<template>
<div class="labels-select-wrapper position-relative">
<div v-if="!dropdownOnly">
<div
class="labels-select-wrapper position-relative"
:class="{ 'is-standalone': isDropdownVariantStandalone }"
>
<template v-if="isDropdownVariantSidebar">
<dropdown-value-collapsed
v-if="allowLabelCreate"
ref="dropdownButtonCollapsed"
:labels="selectedLabels"
@onValueClick="handleCollapsedValueClick"
@ -190,8 +211,18 @@ export default {
<dropdown-value v-show="!showDropdownButton">
<slot></slot>
</dropdown-value>
<dropdown-button v-show="showDropdownButton" />
<dropdown-contents v-if="showDropdownButton && showDropdownContents" ref="dropdownContents" />
</div>
<dropdown-button v-show="dropdownButtonVisible" />
<dropdown-contents
v-if="dropdownButtonVisible && showDropdownContents"
ref="dropdownContents"
/>
</template>
<template v-if="isDropdownVariantStandalone">
<dropdown-button v-show="dropdownButtonVisible" />
<dropdown-contents
v-if="dropdownButtonVisible && showDropdownContents"
ref="dropdownContents"
/>
</template>
</div>
</template>

View File

@ -1,4 +1,5 @@
import { __, s__, sprintf } from '~/locale';
import { DropdownVariant } from '../constants';
/**
* Returns string representing current labels
@ -6,8 +7,11 @@ import { __, s__, sprintf } from '~/locale';
*
* @param {object} state
*/
export const dropdownButtonText = state => {
const selectedLabels = state.labels.filter(label => label.set);
export const dropdownButtonText = (state, getters) => {
const selectedLabels = getters.isDropdownVariantSidebar
? state.labels.filter(label => label.set)
: state.selectedLabels;
if (!selectedLabels.length) {
return __('Label');
} else if (selectedLabels.length > 1) {
@ -26,5 +30,19 @@ export const dropdownButtonText = state => {
*/
export const selectedLabelsList = state => state.selectedLabels.map(label => label.id);
/**
* Returns boolean representing whether dropdown variant
* is `sidebar`
* @param {object} state
*/
export const isDropdownVariantSidebar = state => state.variant === DropdownVariant.Sidebar;
/**
* Returns boolean representing whether dropdown variant
* is `standalone`
* @param {object} state
*/
export const isDropdownVariantStandalone = state => state.variant === DropdownVariant.Standalone;
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};

View File

@ -1,4 +1,5 @@
import * as types from './mutation_types';
import { DropdownVariant } from '../constants';
export default {
[types.SET_INITIAL_STATE](state, props) {
@ -10,7 +11,7 @@ export default {
},
[types.TOGGLE_DROPDOWN_CONTENTS](state) {
if (!state.dropdownOnly) {
if (state.variant === DropdownVariant.Sidebar) {
state.showDropdownButton = !state.showDropdownButton;
}
state.showDropdownContents = !state.showDropdownContents;
@ -68,7 +69,16 @@ export default {
set: !label.set,
});
} else {
allLabels.push(label);
// In case multiselect is not allowed
// we unselect any existing selected label
const unchangedLabel = state.allowMultiselect
? label
: {
...label,
touched: true,
set: false,
};
allLabels.push(unchangedLabel);
}
return allLabels;
}, []);

View File

@ -13,10 +13,11 @@ export default () => ({
labelsFilterBasePath: '',
// UI Flags
variant: '',
allowLabelCreate: false,
allowLabelEdit: false,
allowScopedLabels: false,
dropdownOnly: false,
allowMultiselect: false,
showDropdownButton: false,
showDropdownContents: false,
showDropdownContentsCreateView: false,

View File

@ -1032,6 +1032,16 @@ header.header-content .dropdown-menu.frequent-items-dropdown-menu {
}
.labels-select-wrapper {
&.is-standalone {
.labels-select-dropdown-contents {
max-height: 350px;
.dropdown-content {
height: 250px;
}
}
}
.labels-select-dropdown-contents {
min-height: $dropdown-min-height;
max-height: 330px;

View File

@ -76,8 +76,9 @@
min-height: 0; // firefox fix
}
// Apply theme related overrides only to the white theme
.theme-white .blob-editor-container {
// Apply theme related overrides only to the white theme and none theme
.theme-white .blob-editor-container,
.theme-none .blob-editor-container {
.monaco-diff-editor {
.editor.modified {
box-shadow: none;
@ -139,7 +140,8 @@
}
}
.theme-white .multi-file-editor-holder {
.theme-white .multi-file-editor-holder,
.theme-none .multi-file-editor-holder {
&.is-readonly,
.editor.original {
.monaco-editor,

View File

@ -1,4 +1,28 @@
.alert-management-list {
.icon-critical {
color: $red-800;
}
.icon-high {
color: $red-600;
}
.icon-medium {
color: $orange-400;
}
.icon-low {
color: $orange-300;
}
.icon-info {
color: $blue-400;
}
.icon-unknown {
color: $gray-400;
}
// these styles need to be deleted once GlTable component looks in GitLab same as in @gitlab/ui
table {
color: $gray-700;

View File

@ -187,7 +187,6 @@
.stage-events {
width: 60%;
overflow: scroll;
min-height: 467px;
}

View File

@ -16,8 +16,8 @@ class Timelog < ApplicationRecord
)
end
scope :between_dates, -> (start_date, end_date) do
where('spent_at BETWEEN ? AND ?', start_date, end_date)
scope :between_times, -> (start_time, end_time) do
where('spent_at BETWEEN ? AND ?', start_time, end_time)
end
def issuable

View File

@ -60,11 +60,11 @@ module Projects
end
def to_partial_path
'projects/deploy_keys/index'
'../../shared/deploy_keys/index'
end
def form_partial_path
'projects/deploy_keys/form'
'shared/deploy_keys/project_group_form'
end
private

View File

@ -57,7 +57,7 @@
%nav.stage-nav
%ul
%stage-nav-item{ "v-for" => "stage in state.stages", ":key" => '`ca-stage-title-${stage.title}`', '@select' => 'selectStage(stage)', ":title" => "stage.title", ":is-user-allowed" => "stage.isUserAllowed", ":value" => "stage.value", ":is-active" => "stage.active" }
.section.stage-events
.section.stage-events.overflow-auto
%gl-loading-icon{ "v-show" => "isLoadingStage", "size" => "lg" }
%template{ "v-if" => "currentStage && !currentStage.isUserAllowed" }
= render partial: "no_access"

View File

@ -1,15 +1,14 @@
- expanded = expanded_by_default?
%section.qa-deploy-keys-settings.settings.no-animate#js-deploy-keys-settings{ class: ('expanded' if expanded), data: { qa_selector: 'deploy_keys_settings' } }
.settings-header
%h4
Deploy Keys
%h4= _('Deploy Keys')
%button.btn.js-settings-toggle{ type: 'button' }
= expanded ? 'Collapse' : 'Expand'
%p
Deploy keys allow read-only or read-write (if enabled) access to your repository. Deploy keys can be used for CI, staging or production servers. You can create a deploy key or add an existing one.
= _('Deploy keys allow read-only or read-write (if enabled) access to your repository. Deploy keys can be used for CI, staging or production servers. You can create a deploy key or add an existing one.')
.settings-content
%h5.prepend-top-0
Create a new deploy key for this project
= _('Create a new deploy key for this project')
= render @deploy_keys.form_partial_path
%hr
#js-deploy-keys{ data: { endpoint: project_deploy_keys_path(@project), project_id: @project.id } }

View File

@ -8,17 +8,17 @@
= f.text_area :key, class: "form-control", rows: 5, required: true
.form-group.row
%p.light.append-bottom-0
Paste a machine public key here. Read more about how to generate it
= _('Paste a machine public key here. Read more about how to generate it')
= link_to "here", help_page_path("ssh/README")
= f.fields_for :deploy_keys_projects do |deploy_keys_project_form|
.form-group.row
= deploy_keys_project_form.label :can_push do
= deploy_keys_project_form.check_box :can_push
%strong Write access allowed
%strong= _('Write access allowed')
.form-group.row
%p.light.append-bottom-0
Allow this key to push to repository as well? (Default only allows pull access.)
= _('Allow this key to push to repository as well? (Default only allows pull access.)')
.form-group.row
= f.submit "Add key", class: "btn-success btn"

View File

@ -0,0 +1,5 @@
---
title: Add severity icons for alert management
merge_request: 30472
author:
type: changed

View File

@ -0,0 +1,5 @@
---
title: None syntax highlighting theme for Web IDE
merge_request: 31056
author:
type: added

View File

@ -0,0 +1,5 @@
---
title: 'CI / CD Analytics: Add title to page'
merge_request: 30891
author:
type: added

View File

@ -0,0 +1,5 @@
---
title: 'Issues Analytics: Add title to group-level page'
merge_request: 31057
author:
type: added

View File

@ -4097,9 +4097,14 @@ type Group {
before: String
"""
List time logs within a time range where the logged date is before end_date parameter.
List time logs within a date range where the logged date is equal to or before endDate
"""
endDate: Time!
endDate: Time
"""
List time-logs within a time range where the logged time is equal to or before endTime
"""
endTime: Time
"""
Returns the first _n_ elements from the list.
@ -4112,9 +4117,14 @@ type Group {
last: Int
"""
List time logs within a time range where the logged date is after start_date parameter.
List time logs within a date range where the logged date is equal to or after startDate
"""
startDate: Time!
startDate: Time
"""
List time-logs within a time range where the logged time is equal to or after startTime
"""
startTime: Time
): TimelogConnection!
"""

View File

@ -11530,29 +11530,41 @@
"args": [
{
"name": "startDate",
"description": "List time logs within a time range where the logged date is after start_date parameter.",
"description": "List time logs within a date range where the logged date is equal to or after startDate",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Time",
"ofType": null
}
"kind": "SCALAR",
"name": "Time",
"ofType": null
},
"defaultValue": null
},
{
"name": "endDate",
"description": "List time logs within a time range where the logged date is before end_date parameter.",
"description": "List time logs within a date range where the logged date is equal to or before endDate",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Time",
"ofType": null
}
"kind": "SCALAR",
"name": "Time",
"ofType": null
},
"defaultValue": null
},
{
"name": "startTime",
"description": "List time-logs within a time range where the logged time is equal to or after startTime",
"type": {
"kind": "SCALAR",
"name": "Time",
"ofType": null
},
"defaultValue": null
},
{
"name": "endTime",
"description": "List time-logs within a time range where the logged time is equal to or before endTime",
"type": {
"kind": "SCALAR",
"name": "Time",
"ofType": null
},
"defaultValue": null
},

View File

@ -6129,6 +6129,9 @@ msgstr ""
msgid "Create a new branch"
msgstr ""
msgid "Create a new deploy key for this project"
msgstr ""
msgid "Create a new file as there are no files yet. Afterwards, you'll be able to commit your changes."
msgstr ""
@ -6983,6 +6986,9 @@ msgstr ""
msgid "Deploy key was successfully updated."
msgstr ""
msgid "Deploy keys allow read-only or read-write (if enabled) access to your repository. Deploy keys can be used for CI, staging or production servers. You can create a deploy key or add an existing one."
msgstr ""
msgid "Deploy progress not found. To see pods, ensure your environment matches %{linkStart}deploy board criteria%{linkEnd}."
msgstr ""
@ -12487,9 +12493,6 @@ msgstr ""
msgid "Live preview"
msgstr ""
msgid "Load more vulnerabilities"
msgstr ""
msgid "Loading"
msgstr ""
@ -13975,15 +13978,6 @@ msgstr ""
msgid "No thanks, don't show this again"
msgstr ""
msgid "No vulnerabilities found for this group"
msgstr ""
msgid "No vulnerabilities found for this pipeline"
msgstr ""
msgid "No vulnerabilities found for this project"
msgstr ""
msgid "No vulnerabilities present"
msgstr ""
@ -14799,6 +14793,9 @@ msgstr ""
msgid "Past due"
msgstr ""
msgid "Paste a machine public key here. Read more about how to generate it"
msgstr ""
msgid "Paste a machine public key here. Read more about how to generate it %{link_start}here%{link_end}"
msgstr ""
@ -14934,6 +14931,9 @@ msgstr ""
msgid "Pipeline: %{status}"
msgstr ""
msgid "PipelineCharts|CI / CD Analytics"
msgstr ""
msgid "PipelineCharts|Failed:"
msgstr ""
@ -18248,99 +18248,6 @@ msgstr ""
msgid "Security Dashboard"
msgstr ""
msgid "Security Dashboard|Error fetching the vulnerability counts. Please check your network connection and try again."
msgstr ""
msgid "Security Dashboard|Error fetching the vulnerability list. Please check your network connection and try again."
msgstr ""
msgid "Security Dashboard|Issue Created"
msgstr ""
msgid "Security Reports|Comment added to '%{vulnerabilityName}'"
msgstr ""
msgid "Security Reports|Comment deleted on '%{vulnerabilityName}'"
msgstr ""
msgid "Security Reports|Comment edited on '%{vulnerabilityName}'"
msgstr ""
msgid "Security Reports|Create issue"
msgstr ""
msgid "Security Reports|Dismiss Selected"
msgstr ""
msgid "Security Reports|Dismiss vulnerability"
msgstr ""
msgid "Security Reports|Dismissed '%{vulnerabilityName}'"
msgstr ""
msgid "Security Reports|Dismissed '%{vulnerabilityName}'. Turn off the hide dismissed toggle to view."
msgstr ""
msgid "Security Reports|Either you don't have permission to view this dashboard or the dashboard has not been setup. Please check your permission settings with your administrator or check your dashboard configurations to proceed."
msgstr ""
msgid "Security Reports|False positive"
msgstr ""
msgid "Security Reports|Learn more about setting up your dashboard"
msgstr ""
msgid "Security Reports|More info"
msgstr ""
msgid "Security Reports|Oops, something doesn't seem right."
msgstr ""
msgid "Security Reports|Security reports can only be accessed by authorized users."
msgstr ""
msgid "Security Reports|Select a reason"
msgstr ""
msgid "Security Reports|There was an error adding the comment."
msgstr ""
msgid "Security Reports|There was an error creating the issue."
msgstr ""
msgid "Security Reports|There was an error creating the merge request."
msgstr ""
msgid "Security Reports|There was an error deleting the comment."
msgstr ""
msgid "Security Reports|There was an error dismissing the vulnerabilities."
msgstr ""
msgid "Security Reports|There was an error dismissing the vulnerability."
msgstr ""
msgid "Security Reports|There was an error reverting the dismissal."
msgstr ""
msgid "Security Reports|There was an error reverting this dismissal."
msgstr ""
msgid "Security Reports|Undo dismiss"
msgstr ""
msgid "Security Reports|Won't fix / Accept risk"
msgstr ""
msgid "Security Reports|You do not have sufficient permissions to access this report"
msgstr ""
msgid "Security Reports|You must sign in as an authorized user to see this report"
msgstr ""
msgid "Security Reports|[No reason]"
msgstr ""
msgid "Security configuration help link"
msgstr ""
@ -18371,85 +18278,202 @@ msgstr ""
msgid "SecurityConfiguration|Status"
msgstr ""
msgid "SecurityDashboard|%{firstProject} and %{secondProject}"
msgid "SecurityReports|%{firstProject} and %{secondProject}"
msgstr ""
msgid "SecurityDashboard|%{firstProject}, %{secondProject}, and %{rest}"
msgid "SecurityReports|%{firstProject}, %{secondProject}, and %{rest}"
msgstr ""
msgid "SecurityDashboard|Add a project to your dashboard"
msgid "SecurityReports|Add a project to your dashboard"
msgstr ""
msgid "SecurityDashboard|Add or remove projects from your dashboard"
msgid "SecurityReports|Add or remove projects from your dashboard"
msgstr ""
msgid "SecurityDashboard|Add projects"
msgid "SecurityReports|Add projects"
msgstr ""
msgid "SecurityDashboard|Each vulnerability now has a unique page that can be directly linked to, shared, referenced, and tracked as the single source of truth. Vulnerability occurrences also persist across scanner runs, which improves tracking and visibility and reduces duplicates between scans."
msgid "SecurityReports|Comment added to '%{vulnerabilityName}'"
msgstr ""
msgid "SecurityDashboard|Edit dashboard"
msgid "SecurityReports|Comment deleted on '%{vulnerabilityName}'"
msgstr ""
msgid "SecurityDashboard|Hide dismissed"
msgid "SecurityReports|Comment edited on '%{vulnerabilityName}'"
msgstr ""
msgid "SecurityDashboard|Introducing standalone vulnerabilities"
msgid "SecurityReports|Create issue"
msgstr ""
msgid "SecurityDashboard|Learn More"
msgid "SecurityReports|Dismiss Selected"
msgstr ""
msgid "SecurityDashboard|Monitor vulnerabilities in your code"
msgid "SecurityReports|Dismiss vulnerability"
msgstr ""
msgid "SecurityDashboard|More information"
msgid "SecurityReports|Dismissed '%{vulnerabilityName}'"
msgstr ""
msgid "SecurityDashboard|No vulnerabilities found for dashboard"
msgid "SecurityReports|Dismissed '%{vulnerabilityName}'. Turn off the hide dismissed toggle to view."
msgstr ""
msgid "SecurityDashboard|Pipeline %{pipelineLink} triggered %{timeago} by %{user}"
msgid "SecurityReports|Each vulnerability now has a unique page that can be directly linked to, shared, referenced, and tracked as the single source of truth. Vulnerability occurrences also persist across scanner runs, which improves tracking and visibility and reduces duplicates between scans."
msgstr ""
msgid "SecurityDashboard|Project"
msgid "SecurityReports|Edit dashboard"
msgstr ""
msgid "SecurityDashboard|Projects added"
msgid "SecurityReports|Either you don't have permission to view this dashboard or the dashboard has not been setup. Please check your permission settings with your administrator or check your dashboard configurations to proceed."
msgstr ""
msgid "SecurityDashboard|Remove project from dashboard"
msgid "SecurityReports|Error fetching the vulnerability counts. Please check your network connection and try again."
msgstr ""
msgid "SecurityDashboard|Report type"
msgid "SecurityReports|Error fetching the vulnerability list. Please check your network connection and try again."
msgstr ""
msgid "SecurityDashboard|Return to dashboard"
msgid "SecurityReports|False positive"
msgstr ""
msgid "SecurityDashboard|Security Dashboard"
msgid "SecurityReports|Hide dismissed"
msgstr ""
msgid "SecurityDashboard|Select a project to add by using the project search field above."
msgid "SecurityReports|Introducing standalone vulnerabilities"
msgstr ""
msgid "SecurityDashboard|Severity"
msgid "SecurityReports|Issue Created"
msgstr ""
msgid "SecurityDashboard|Status"
msgid "SecurityReports|Learn More"
msgstr ""
msgid "SecurityDashboard|The security dashboard displays the latest security findings for projects you wish to monitor. Select \"Edit dashboard\" to add and remove projects."
msgid "SecurityReports|Learn more about setting up your dashboard"
msgstr ""
msgid "SecurityDashboard|The security dashboard displays the latest security report. Use it to find and fix vulnerabilities."
msgid "SecurityReports|Load more vulnerabilities"
msgstr ""
msgid "SecurityDashboard|There was an error while generating the report."
msgid "SecurityReports|Monitor vulnerabilities in your code"
msgstr ""
msgid "SecurityDashboard|Unable to add %{invalidProjects}"
msgid "SecurityReports|More info"
msgstr ""
msgid "SecurityReports|More information"
msgstr ""
msgid "SecurityReports|No vulnerabilities found for dashboard"
msgstr ""
msgid "SecurityReports|No vulnerabilities found for this group"
msgstr ""
msgid "SecurityReports|No vulnerabilities found for this pipeline"
msgstr ""
msgid "SecurityReports|No vulnerabilities found for this project"
msgstr ""
msgid "SecurityReports|Oops, something doesn't seem right."
msgstr ""
msgid "SecurityReports|Pipeline %{pipelineLink} triggered %{timeago} by %{user}"
msgstr ""
msgid "SecurityReports|Project"
msgstr ""
msgid "SecurityReports|Projects added"
msgstr ""
msgid "SecurityReports|Remove project from dashboard"
msgstr ""
msgid "SecurityReports|Report type"
msgstr ""
msgid "SecurityReports|Return to dashboard"
msgstr ""
msgid "SecurityReports|Security Dashboard"
msgstr ""
msgid "SecurityReports|Security reports can only be accessed by authorized users."
msgstr ""
msgid "SecurityReports|Select a project to add by using the project search field above."
msgstr ""
msgid "SecurityReports|Select a reason"
msgstr ""
msgid "SecurityReports|Severity"
msgstr ""
msgid "SecurityReports|Status"
msgstr ""
msgid "SecurityReports|The security dashboard displays the latest security findings for projects you wish to monitor. Select \"Edit dashboard\" to add and remove projects."
msgstr ""
msgid "SecurityReports|The security dashboard displays the latest security report. Use it to find and fix vulnerabilities."
msgstr ""
msgid "SecurityReports|There was an error adding the comment."
msgstr ""
msgid "SecurityReports|There was an error creating the issue."
msgstr ""
msgid "SecurityReports|There was an error creating the merge request."
msgstr ""
msgid "SecurityReports|There was an error deleting the comment."
msgstr ""
msgid "SecurityReports|There was an error dismissing the vulnerabilities."
msgstr ""
msgid "SecurityReports|There was an error dismissing the vulnerability."
msgstr ""
msgid "SecurityReports|There was an error reverting the dismissal."
msgstr ""
msgid "SecurityReports|There was an error reverting this dismissal."
msgstr ""
msgid "SecurityReports|There was an error while generating the report."
msgstr ""
msgid "SecurityReports|Unable to add %{invalidProjects}"
msgstr ""
msgid "SecurityReports|Undo dismiss"
msgstr ""
msgid "SecurityReports|While it's rare to have no vulnerabilities for your group, it can happen. In any event, we ask that you double check your settings to make sure you've set up your dashboard correctly."
msgstr ""
msgid "SecurityReports|While it's rare to have no vulnerabilities for your pipeline, it can happen. In any event, we ask that you double check your settings to make sure all security scanning jobs have passed successfully."
msgstr ""
msgid "SecurityReports|While it's rare to have no vulnerabilities for your project, it can happen. In any event, we ask that you double check your settings to make sure you've set up your dashboard correctly."
msgstr ""
msgid "SecurityReports|While it's rare to have no vulnerabilities, it can happen. In any event, we ask that you please double check your settings to make sure you've set up your dashboard correctly."
msgstr ""
msgid "SecurityReports|Won't fix / Accept risk"
msgstr ""
msgid "SecurityReports|You do not have sufficient permissions to access this report"
msgstr ""
msgid "SecurityReports|You must sign in as an authorized user to see this report"
msgstr ""
msgid "SecurityReports|[No reason]"
msgstr ""
msgid "See GitLab's %{password_policy_guidelines}"
@ -23696,15 +23720,6 @@ msgstr ""
msgid "When:"
msgstr ""
msgid "While it's rare to have no vulnerabilities for your group, it can happen. In any event, we ask that you double check your settings to make sure you've set up your dashboard correctly."
msgstr ""
msgid "While it's rare to have no vulnerabilities for your pipeline, it can happen. In any event, we ask that you double check your settings to make sure all security scanning jobs have passed successfully."
msgstr ""
msgid "While it's rare to have no vulnerabilities for your project, it can happen. In any event, we ask that you double check your settings to make sure you've set up your dashboard correctly."
msgstr ""
msgid "While it's rare to have no vulnerabilities, it can happen. In any event, we ask that you please double check your settings to make sure you've set up your dashboard correctly."
msgstr ""

View File

@ -5,7 +5,7 @@ module QA
module Project
module Settings
class DeployKeys < Page::Base
view 'app/views/projects/deploy_keys/_form.html.haml' do
view 'app/views/shared/deploy_keys/_form.html.haml' do
element :deploy_key_title, 'text_field :title' # rubocop:disable QA/ElementWithPattern
element :deploy_key_key, 'text_area :key' # rubocop:disable QA/ElementWithPattern
end

View File

@ -19,7 +19,7 @@ module QA
element :deploy_tokens_settings
end
view 'app/views/projects/deploy_keys/_index.html.haml' do
view 'app/views/shared/deploy_keys/_index.html.haml' do
element :deploy_keys_settings
end

View File

@ -236,7 +236,6 @@ function base_config_changed() {
function deploy() {
local namespace="${KUBE_NAMESPACE}"
local release="${CI_ENVIRONMENT_SLUG}"
local edition="${GITLAB_EDITION:-ee}"
local base_config_file_ref="master"
if [[ "$(base_config_changed)" == "true" ]]; then base_config_file_ref="${CI_COMMIT_SHA}"; fi
local base_config_file="https://gitlab.com/gitlab-org/gitlab/raw/${base_config_file_ref}/scripts/review_apps/base-config.yaml"
@ -244,13 +243,13 @@ function deploy() {
echoinfo "Deploying ${release}..." true
IMAGE_REPOSITORY="registry.gitlab.com/gitlab-org/build/cng-mirror"
gitlab_migrations_image_repository="${IMAGE_REPOSITORY}/gitlab-rails-${edition}"
gitlab_sidekiq_image_repository="${IMAGE_REPOSITORY}/gitlab-sidekiq-${edition}"
gitlab_unicorn_image_repository="${IMAGE_REPOSITORY}/gitlab-webservice-${edition}"
gitlab_task_runner_image_repository="${IMAGE_REPOSITORY}/gitlab-task-runner-${edition}"
gitlab_migrations_image_repository="${IMAGE_REPOSITORY}/gitlab-rails-ee"
gitlab_sidekiq_image_repository="${IMAGE_REPOSITORY}/gitlab-sidekiq-ee"
gitlab_unicorn_image_repository="${IMAGE_REPOSITORY}/gitlab-webservice-ee"
gitlab_task_runner_image_repository="${IMAGE_REPOSITORY}/gitlab-task-runner-ee"
gitlab_gitaly_image_repository="${IMAGE_REPOSITORY}/gitaly"
gitlab_shell_image_repository="${IMAGE_REPOSITORY}/gitlab-shell"
gitlab_workhorse_image_repository="${IMAGE_REPOSITORY}/gitlab-workhorse-${edition}"
gitlab_workhorse_image_repository="${IMAGE_REPOSITORY}/gitlab-workhorse-ee"
create_application_secret

View File

@ -5,7 +5,7 @@ require 'spec_helper'
describe Gitlab::Application do # rubocop:disable RSpec/FilePath
using RSpec::Parameterized::TableSyntax
FILTERED_PARAM = ActiveSupport::ParameterFilter::FILTERED
filtered_param = ActiveSupport::ParameterFilter::FILTERED
context 'when parameters are logged' do
describe 'rails does not leak confidential parameters' do
@ -19,11 +19,11 @@ describe Gitlab::Application do # rubocop:disable RSpec/FilePath
where(:input_url, :output_query) do
'/' | {}
'/?safe=1' | { 'safe' => '1' }
'/?private_token=secret' | { 'private_token' => FILTERED_PARAM }
'/?mixed=1&private_token=secret' | { 'mixed' => '1', 'private_token' => FILTERED_PARAM }
'/?note=secret&noteable=1&prefix_note=2' | { 'note' => FILTERED_PARAM, 'noteable' => '1', 'prefix_note' => '2' }
'/?note[note]=secret&target_type=1' | { 'note' => FILTERED_PARAM, 'target_type' => '1' }
'/?safe[note]=secret&target_type=1' | { 'safe' => { 'note' => FILTERED_PARAM }, 'target_type' => '1' }
'/?private_token=secret' | { 'private_token' => filtered_param }
'/?mixed=1&private_token=secret' | { 'mixed' => '1', 'private_token' => filtered_param }
'/?note=secret&noteable=1&prefix_note=2' | { 'note' => filtered_param, 'noteable' => '1', 'prefix_note' => '2' }
'/?note[note]=secret&target_type=1' | { 'note' => filtered_param, 'target_type' => '1' }
'/?safe[note]=secret&target_type=1' | { 'safe' => { 'note' => filtered_param }, 'target_type' => '1' }
end
with_them do

View File

@ -183,14 +183,14 @@ describe 'Merge request > User resolves conflicts', :js do
end
end
UNRESOLVABLE_CONFLICTS = {
unresolvable_conflicts = {
'conflict-too-large' => 'when the conflicts contain a large file',
'conflict-binary-file' => 'when the conflicts contain a binary file',
'conflict-missing-side' => 'when the conflicts contain a file edited in one branch and deleted in another',
'conflict-non-utf8' => 'when the conflicts contain a non-UTF-8 file'
}.freeze
UNRESOLVABLE_CONFLICTS.each do |source_branch, description|
unresolvable_conflicts.each do |source_branch, description|
context description do
let(:merge_request) { create_merge_request(source_branch) }

View File

@ -14,13 +14,13 @@ describe "User creates milestone", :js do
end
it "creates milestone" do
TITLE = "v2.3".freeze
title = "v2.3".freeze
fill_in("Title", with: TITLE)
fill_in("Title", with: title)
fill_in("Description", with: "# Description header")
click_button("Create milestone")
expect(page).to have_content(TITLE)
expect(page).to have_content(title)
.and have_content("Issues")
.and have_header_with_correct_id_and_link(1, "Description header", "description-header")

View File

@ -14,13 +14,13 @@ describe "User views milestone" do
end
it "avoids N+1 database queries" do
ISSUE_PARAMS = { project: project, assignees: [user], author: user, milestone: milestone, labels: labels }.freeze
issue_params = { project: project, assignees: [user], author: user, milestone: milestone, labels: labels }.freeze
create(:labeled_issue, ISSUE_PARAMS)
create(:labeled_issue, issue_params)
control = ActiveRecord::QueryRecorder.new { visit_milestone }
create(:labeled_issue, ISSUE_PARAMS)
create(:labeled_issue, issue_params)
expect { visit_milestone }.not_to exceed_query_limit(control)
end

View File

@ -16,18 +16,18 @@ describe "User creates branch", :js do
end
it "creates new branch" do
BRANCH_NAME = "deploy_keys".freeze
branch_name = "deploy_keys".freeze
create_branch(BRANCH_NAME)
create_branch(branch_name)
expect(page).to have_content(BRANCH_NAME)
expect(page).to have_content(branch_name)
end
context "when branch name is invalid" do
it "does not create new branch" do
INVALID_BRANCH_NAME = "1.0 stable".freeze
invalid_branch_name = "1.0 stable".freeze
fill_in("branch_name", with: INVALID_BRANCH_NAME)
fill_in("branch_name", with: invalid_branch_name)
page.find("body").click # defocus the branch_name input
select_branch("master")

View File

@ -19,7 +19,7 @@ describe "User edits a comment on a commit", :js do
end
it "edits comment" do
NEW_COMMENT_TEXT = "+1 Awesome!".freeze
new_comment_text = "+1 Awesome!".freeze
page.within(".main-notes-list") do
note = find(".note")
@ -31,14 +31,14 @@ describe "User edits a comment on a commit", :js do
page.find(".current-note-edit-form textarea")
page.within(".current-note-edit-form") do
fill_in("note[note]", with: NEW_COMMENT_TEXT)
fill_in("note[note]", with: new_comment_text)
click_button("Save comment")
end
wait_for_requests
page.within(".note") do
expect(page).to have_content(NEW_COMMENT_TEXT)
expect(page).to have_content(new_comment_text)
end
end
end

View File

@ -88,18 +88,18 @@ describe "User interacts with deploy keys", :js do
end
it "adds new key" do
DEPLOY_KEY_TITLE = attributes_for(:key)[:title]
DEPLOY_KEY_BODY = attributes_for(:key)[:key]
deploy_key_title = attributes_for(:key)[:title]
deploy_key_body = attributes_for(:key)[:key]
fill_in("deploy_key_title", with: DEPLOY_KEY_TITLE)
fill_in("deploy_key_key", with: DEPLOY_KEY_BODY)
fill_in("deploy_key_title", with: deploy_key_title)
fill_in("deploy_key_key", with: deploy_key_body)
click_button("Add key")
expect(current_path).to eq(project_settings_repository_path(project))
page.within(".deploy-keys") do
expect(page).to have_content(DEPLOY_KEY_TITLE)
expect(page).to have_content(deploy_key_title)
end
end
end

View File

@ -1,5 +1,5 @@
import { mount } from '@vue/test-utils';
import { GlEmptyState, GlTable, GlAlert, GlLoadingIcon, GlNewDropdown } from '@gitlab/ui';
import { GlEmptyState, GlTable, GlAlert, GlLoadingIcon, GlNewDropdown, GlIcon } from '@gitlab/ui';
import AlertManagementList from '~/alert_management/components/alert_management_list.vue';
import mockAlerts from '../mocks/alerts.json';
@ -113,5 +113,22 @@ describe('AlertManagementList', () => {
});
expect(findStatusDropdown().exists()).toBe(true);
});
it('shows correct severity icons', () => {
mountComponent({
props: { alertManagementEnabled: true, userCanEnableAlertManagement: true },
data: { alerts: mockAlerts, errored: false },
loading: false,
});
return wrapper.vm.$nextTick().then(() => {
expect(wrapper.find(GlTable).exists()).toBe(true);
expect(
findAlertsTable()
.find(GlIcon)
.classes('icon-critical'),
).toBe(true);
});
});
});
});

View File

@ -45,6 +45,3 @@ describe('Clusters store actions', () => {
});
});
});
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};

View File

@ -55,6 +55,3 @@ describe('Contributors store actions', () => {
});
});
});
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};

View File

@ -74,6 +74,3 @@ describe('Contributors Store Getters', () => {
});
});
});
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};

View File

@ -75,7 +75,7 @@ describe('awsServicesFacade', () => {
});
it('return list of regions where each item has a name and value', () => {
expect(fetchRoles()).resolves.toEqual(rolesOutput);
return expect(fetchRoles()).resolves.toEqual(rolesOutput);
});
});
@ -91,7 +91,7 @@ describe('awsServicesFacade', () => {
});
it('return list of roles where each item has a name and value', () => {
expect(fetchRegions()).resolves.toEqual(regionsOutput);
return expect(fetchRegions()).resolves.toEqual(regionsOutput);
});
});
@ -112,7 +112,7 @@ describe('awsServicesFacade', () => {
});
it('return list of key pairs where each item has a name and value', () => {
expect(fetchKeyPairs({ region })).resolves.toEqual(keyPairsOutput);
return expect(fetchKeyPairs({ region })).resolves.toEqual(keyPairsOutput);
});
});
@ -133,7 +133,7 @@ describe('awsServicesFacade', () => {
});
it('return list of vpcs where each item has a name and value', () => {
expect(fetchVpcs({ region })).resolves.toEqual(vpcsOutput);
return expect(fetchVpcs({ region })).resolves.toEqual(vpcsOutput);
});
});
@ -151,7 +151,7 @@ describe('awsServicesFacade', () => {
});
it('uses name tag value as the vpc name', () => {
expect(fetchVpcs({ region })).resolves.toEqual(vpcsOutput);
return expect(fetchVpcs({ region })).resolves.toEqual(vpcsOutput);
});
});
@ -167,7 +167,7 @@ describe('awsServicesFacade', () => {
});
it('return list of subnets where each item has a name and value', () => {
expect(fetchSubnets({ region, vpc })).resolves.toEqual(subnetsOutput);
return expect(fetchSubnets({ region, vpc })).resolves.toEqual(subnetsOutput);
});
});
@ -189,7 +189,7 @@ describe('awsServicesFacade', () => {
});
it('return list of security groups where each item has a name and value', () => {
expect(fetchSecurityGroups({ region, vpc })).resolves.toEqual(securityGroupsOutput);
return expect(fetchSecurityGroups({ region, vpc })).resolves.toEqual(securityGroupsOutput);
});
});
});

View File

@ -8,12 +8,13 @@ describe('Mock auto-injection', () => {
failMock = jest.spyOn(global, 'fail').mockImplementation();
});
it('~/lib/utils/axios_utils', done => {
expect(axios.get('http://gitlab.com')).rejects.toThrow('Unexpected unmocked request');
setImmediate(() => {
expect(failMock).toHaveBeenCalledTimes(1);
done();
});
it('~/lib/utils/axios_utils', () => {
return Promise.all([
expect(axios.get('http://gitlab.com')).rejects.toThrow('Unexpected unmocked request'),
setImmediate(() => {
expect(failMock).toHaveBeenCalledTimes(1);
}),
]);
});
it('jQuery.ajax()', () => {

View File

@ -81,7 +81,8 @@ describe('DuplicateDashboardForm', () => {
it('with the inital form values', () => {
expect(wrapper.emitted().change).toHaveLength(1);
expect(lastChange()).resolves.toEqual({
return expect(lastChange()).resolves.toEqual({
branch: '',
commitMessage: expect.any(String),
dashboard: dashboardGitResponse[0].path,
@ -92,7 +93,7 @@ describe('DuplicateDashboardForm', () => {
it('containing an inputted file name', () => {
setValue('fileName', 'my_dashboard.yml');
expect(lastChange()).resolves.toMatchObject({
return expect(lastChange()).resolves.toMatchObject({
fileName: 'my_dashboard.yml',
});
});
@ -100,7 +101,7 @@ describe('DuplicateDashboardForm', () => {
it('containing a default commit message when no message is set', () => {
setValue('commitMessage', '');
expect(lastChange()).resolves.toMatchObject({
return expect(lastChange()).resolves.toMatchObject({
commitMessage: expect.stringContaining('Create custom dashboard'),
});
});
@ -108,7 +109,7 @@ describe('DuplicateDashboardForm', () => {
it('containing an inputted commit message', () => {
setValue('commitMessage', 'My commit message');
expect(lastChange()).resolves.toMatchObject({
return expect(lastChange()).resolves.toMatchObject({
commitMessage: expect.stringContaining('My commit message'),
});
});
@ -116,7 +117,7 @@ describe('DuplicateDashboardForm', () => {
it('containing an inputted branch name', () => {
setValue('branchName', 'a-new-branch');
expect(lastChange()).resolves.toMatchObject({
return expect(lastChange()).resolves.toMatchObject({
branch: 'a-new-branch',
});
});
@ -125,13 +126,14 @@ describe('DuplicateDashboardForm', () => {
setChecked(wrapper.vm.$options.radioVals.DEFAULT);
setValue('branchName', 'a-new-branch');
expect(lastChange()).resolves.toMatchObject({
branch: defaultBranch,
});
return wrapper.vm.$nextTick(() => {
expect(findByRef('branchName').isVisible()).toBe(false);
});
return Promise.all([
expect(lastChange()).resolves.toMatchObject({
branch: defaultBranch,
}),
wrapper.vm.$nextTick(() => {
expect(findByRef('branchName').isVisible()).toBe(false);
}),
]);
});
it('when `new` branch option is chosen, focuses on the branch name input', () => {

View File

@ -212,13 +212,6 @@ describe.skip('Old Notes (~/notes.js)', () => {
jest.spyOn($note, 'toggleClass');
});
afterEach(() => {
expect(typeof urlUtility.getLocationHash.mock).toBe('object');
urlUtility.getLocationHash.mockRestore();
expect(urlUtility.getLocationHash.mock).toBeUndefined();
expect(urlUtility.getLocationHash()).toBeNull();
});
// urlUtility is a dependency of the notes module. Its getLocatinHash() method should be called internally.
it('sets target when hash matches', () => {
@ -629,48 +622,6 @@ describe.skip('Old Notes (~/notes.js)', () => {
done();
});
});
// This is a bad test carried over from the Karma -> Jest migration.
// The corresponding test in the Karma suite tests for
// elements and methods that don't actually exist, and gives a false
// positive pass.
/*
it('should show flash error message when comment failed to be updated', done => {
mockNotesPost();
jest.spyOn(notes, 'addFlash').mockName('addFlash');
$('.js-comment-button').click();
deferredPromise()
.then(() => {
const $noteEl = $notesContainer.find(`#note_${note.id}`);
$noteEl.find('.js-note-edit').click();
$noteEl.find('textarea.js-note-text').val(updatedComment);
mockNotesPostError();
$noteEl.find('.js-comment-save-button').click();
notes.updateComment({preventDefault: () => {}});
})
.then(() => deferredPromise())
.then(() => {
const $updatedNoteEl = $notesContainer.find(`#note_${note.id}`);
expect($updatedNoteEl.hasClass('.being-posted')).toEqual(false); // Remove being-posted visuals
expect(
$updatedNoteEl
.find('.note-text')
.text()
.trim(),
).toEqual(sampleComment); // See if comment reverted back to original
expect(notes.addFlash).toHaveBeenCalled();
expect(notes.flashContainer.style.display).not.toBe('none');
done();
})
.catch(done.fail);
}, 5000);
*/
});
describe('postComment with Slash commands', () => {

View File

@ -143,7 +143,7 @@ describe('Release detail actions', () => {
{ type: types.RECEIVE_UPDATE_RELEASE_SUCCESS },
]));
describe('when the releaseShowPage feature flag is enabled', () => {
it('redirects to the releases page if releaseShowPage feature flag is enabled', () => {
const rootState = { featureFlags: { releaseShowPage: true } };
const updatedState = merge({}, state, {
releasesPagePath: 'path/to/releases/page',

View File

@ -1,10 +1,3 @@
/* eslint-disable jest/valid-describe */
/*
* ESLint disable directive can be removed once
* https://github.com/jest-community/eslint-plugin-jest/issues/203
* is resolved
*/
import createState from '~/releases/stores/modules/detail/state';
import mutations from '~/releases/stores/modules/detail/mutations';
import * as types from '~/releases/stores/modules/detail/mutation_types';
@ -27,6 +20,7 @@ describe('Release detail mutations', () => {
release = convertObjectPropsToCamelCase(originalRelease);
});
// eslint-disable-next-line jest/valid-describe
describe(types.REQUEST_RELEASE, () => {
it('set state.isFetchingRelease to true', () => {
mutations[types.REQUEST_RELEASE](state);
@ -35,6 +29,7 @@ describe('Release detail mutations', () => {
});
});
// eslint-disable-next-line jest/valid-describe
describe(types.RECEIVE_RELEASE_SUCCESS, () => {
it('handles a successful response from the server', () => {
mutations[types.RECEIVE_RELEASE_SUCCESS](state, release);
@ -49,6 +44,7 @@ describe('Release detail mutations', () => {
});
});
// eslint-disable-next-line jest/valid-describe
describe(types.RECEIVE_RELEASE_ERROR, () => {
it('handles an unsuccessful response from the server', () => {
const error = { message: 'An error occurred!' };
@ -62,6 +58,7 @@ describe('Release detail mutations', () => {
});
});
// eslint-disable-next-line jest/valid-describe
describe(types.UPDATE_RELEASE_TITLE, () => {
it("updates the release's title", () => {
state.release = release;
@ -72,6 +69,7 @@ describe('Release detail mutations', () => {
});
});
// eslint-disable-next-line jest/valid-describe
describe(types.UPDATE_RELEASE_NOTES, () => {
it("updates the release's notes", () => {
state.release = release;
@ -82,6 +80,7 @@ describe('Release detail mutations', () => {
});
});
// eslint-disable-next-line jest/valid-describe
describe(types.REQUEST_UPDATE_RELEASE, () => {
it('set state.isUpdatingRelease to true', () => {
mutations[types.REQUEST_UPDATE_RELEASE](state);
@ -90,6 +89,7 @@ describe('Release detail mutations', () => {
});
});
// eslint-disable-next-line jest/valid-describe
describe(types.RECEIVE_UPDATE_RELEASE_SUCCESS, () => {
it('handles a successful response from the server', () => {
mutations[types.RECEIVE_UPDATE_RELEASE_SUCCESS](state, release);
@ -100,6 +100,7 @@ describe('Release detail mutations', () => {
});
});
// eslint-disable-next-line jest/valid-describe
describe(types.RECEIVE_UPDATE_RELEASE_ERROR, () => {
it('handles an unsuccessful response from the server', () => {
const error = { message: 'An error occurred!' };
@ -111,6 +112,7 @@ describe('Release detail mutations', () => {
});
});
// eslint-disable-next-line jest/valid-describe
describe(types.ADD_EMPTY_ASSET_LINK, () => {
it('adds a new, empty link object to the release', () => {
state.release = release;
@ -130,6 +132,7 @@ describe('Release detail mutations', () => {
});
});
// eslint-disable-next-line jest/valid-describe
describe(types.UPDATE_ASSET_LINK_URL, () => {
it('updates an asset link with a new URL', () => {
state.release = release;
@ -145,6 +148,7 @@ describe('Release detail mutations', () => {
});
});
// eslint-disable-next-line jest/valid-describe
describe(types.UPDATE_ASSET_LINK_NAME, () => {
it('updates an asset link with a new name', () => {
state.release = release;
@ -160,6 +164,7 @@ describe('Release detail mutations', () => {
});
});
// eslint-disable-next-line jest/valid-describe
describe(types.REMOVE_ASSET_LINK, () => {
it('removes an asset link from the release', () => {
state.release = release;

View File

@ -48,7 +48,7 @@ describe('submitContentChanges', () => {
it('notifies error when branch could not be created', () => {
Api.createBranch.mockRejectedValueOnce();
expect(submitContentChanges({ username, projectId })).rejects.toThrow(
return expect(submitContentChanges({ username, projectId })).rejects.toThrow(
SUBMIT_CHANGES_BRANCH_ERROR,
);
});
@ -72,7 +72,7 @@ describe('submitContentChanges', () => {
it('notifies error when content could not be committed', () => {
Api.commitMultiple.mockRejectedValueOnce();
expect(submitContentChanges({ username, projectId })).rejects.toThrow(
return expect(submitContentChanges({ username, projectId })).rejects.toThrow(
SUBMIT_CHANGES_COMMIT_ERROR,
);
});
@ -93,7 +93,7 @@ describe('submitContentChanges', () => {
it('notifies error when merge request could not be created', () => {
Api.createProjectMergeRequest.mockRejectedValueOnce();
expect(submitContentChanges({ username, projectId })).rejects.toThrow(
return expect(submitContentChanges({ username, projectId })).rejects.toThrow(
SUBMIT_CHANGES_MERGE_REQUEST_ERROR,
);
});

View File

@ -33,9 +33,32 @@ describe('DropdownButton', () => {
wrapper.destroy();
});
describe('methods', () => {
describe('handleButtonClick', () => {
it('calls action `toggleDropdownContents` and stops event propagation when `state.variant` is "standalone"', () => {
const event = {
stopPropagation: jest.fn(),
};
wrapper = createComponent({
...mockConfig,
variant: 'standalone',
});
jest.spyOn(wrapper.vm, 'toggleDropdownContents');
wrapper.vm.handleButtonClick(event);
expect(wrapper.vm.toggleDropdownContents).toHaveBeenCalled();
expect(event.stopPropagation).toHaveBeenCalled();
wrapper.destroy();
});
});
});
describe('template', () => {
it('renders component container element', () => {
expect(wrapper.is('gl-deprecated-button-stub')).toBe(true);
expect(wrapper.is('gl-button-stub')).toBe(true);
});
it('renders button text element', () => {

View File

@ -1,7 +1,7 @@
import Vuex from 'vuex';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import { GlDeprecatedButton, GlIcon, GlFormInput, GlLink, GlLoadingIcon } from '@gitlab/ui';
import { GlButton, GlFormInput, GlLink, GlLoadingIcon } from '@gitlab/ui';
import DropdownContentsCreateView from '~/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_create_view.vue';
import labelSelectModule from '~/vue_shared/components/sidebar/labels_select_vue/store';
@ -127,12 +127,12 @@ describe('DropdownContentsCreateView', () => {
it('renders dropdown back button element', () => {
const backBtnEl = wrapper
.find('.dropdown-title')
.findAll(GlDeprecatedButton)
.findAll(GlButton)
.at(0);
expect(backBtnEl.exists()).toBe(true);
expect(backBtnEl.attributes('aria-label')).toBe('Go back');
expect(backBtnEl.find(GlIcon).props('name')).toBe('arrow-left');
expect(backBtnEl.props('icon')).toBe('arrow-left');
});
it('renders dropdown title element', () => {
@ -145,12 +145,12 @@ describe('DropdownContentsCreateView', () => {
it('renders dropdown close button element', () => {
const closeBtnEl = wrapper
.find('.dropdown-title')
.findAll(GlDeprecatedButton)
.findAll(GlButton)
.at(1);
expect(closeBtnEl.exists()).toBe(true);
expect(closeBtnEl.attributes('aria-label')).toBe('Close');
expect(closeBtnEl.find(GlIcon).props('name')).toBe('close');
expect(closeBtnEl.props('icon')).toBe('close');
});
it('renders label title input element', () => {
@ -192,7 +192,7 @@ describe('DropdownContentsCreateView', () => {
it('renders create button element', () => {
const createBtnEl = wrapper
.find('.dropdown-actions')
.findAll(GlDeprecatedButton)
.findAll(GlButton)
.at(0);
expect(createBtnEl.exists()).toBe(true);
@ -213,7 +213,7 @@ describe('DropdownContentsCreateView', () => {
it('renders cancel button element', () => {
const cancelBtnEl = wrapper
.find('.dropdown-actions')
.findAll(GlDeprecatedButton)
.findAll(GlButton)
.at(1);
expect(cancelBtnEl.exists()).toBe(true);

View File

@ -1,7 +1,7 @@
import Vuex from 'vuex';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import { GlDeprecatedButton, GlLoadingIcon, GlIcon, GlSearchBoxByType, GlLink } from '@gitlab/ui';
import { GlButton, GlLoadingIcon, GlIcon, GlSearchBoxByType, GlLink } from '@gitlab/ui';
import { UP_KEY_CODE, DOWN_KEY_CODE, ENTER_KEY_CODE, ESC_KEY_CODE } from '~/lib/utils/keycodes';
import DropdownContentsLabelsView from '~/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view.vue';
@ -41,13 +41,19 @@ const createComponent = (initialState = mockConfig) => {
describe('DropdownContentsLabelsView', () => {
let wrapper;
let wrapperStandalone;
beforeEach(() => {
wrapper = createComponent();
wrapperStandalone = createComponent({
...mockConfig,
variant: 'standalone',
});
});
afterEach(() => {
wrapper.destroy();
wrapperStandalone.destroy();
});
describe('computed', () => {
@ -165,13 +171,24 @@ describe('DropdownContentsLabelsView', () => {
});
describe('handleLabelClick', () => {
it('calls action `updateSelectedLabels` with provided `label` param', () => {
beforeEach(() => {
jest.spyOn(wrapper.vm, 'updateSelectedLabels').mockImplementation();
});
it('calls action `updateSelectedLabels` with provided `label` param', () => {
wrapper.vm.handleLabelClick(mockRegularLabel);
expect(wrapper.vm.updateSelectedLabels).toHaveBeenCalledWith([mockRegularLabel]);
});
it('calls action `toggleDropdownContents` when `state.allowMultiselect` is false', () => {
jest.spyOn(wrapper.vm, 'toggleDropdownContents');
wrapper.vm.$store.state.allowMultiselect = false;
wrapper.vm.handleLabelClick(mockRegularLabel);
expect(wrapper.vm.toggleDropdownContents).toHaveBeenCalled();
});
});
});
@ -198,12 +215,15 @@ describe('DropdownContentsLabelsView', () => {
expect(titleEl.text()).toBe('Assign labels');
});
it('does not render dropdown title element when `state.variant` is "standalone"', () => {
expect(wrapperStandalone.find('.dropdown-title').exists()).toBe(false);
});
it('renders dropdown close button element', () => {
const closeButtonEl = wrapper.find('.dropdown-title').find(GlDeprecatedButton);
const closeButtonEl = wrapper.find('.dropdown-title').find(GlButton);
expect(closeButtonEl.exists()).toBe(true);
expect(closeButtonEl.find(GlIcon).exists()).toBe(true);
expect(closeButtonEl.find(GlIcon).props('name')).toBe('close');
expect(closeButtonEl.props('icon')).toBe('close');
});
it('renders label search input element', () => {
@ -253,13 +273,36 @@ describe('DropdownContentsLabelsView', () => {
});
it('renders footer list items', () => {
const createLabelBtn = wrapper.find('.dropdown-footer').find(GlDeprecatedButton);
const manageLabelsLink = wrapper.find('.dropdown-footer').find(GlLink);
const createLabelLink = wrapper
.find('.dropdown-footer')
.findAll(GlLink)
.at(0);
const manageLabelsLink = wrapper
.find('.dropdown-footer')
.findAll(GlLink)
.at(1);
expect(createLabelBtn.exists()).toBe(true);
expect(createLabelBtn.text()).toBe('Create label');
expect(createLabelLink.exists()).toBe(true);
expect(createLabelLink.text()).toBe('Create label');
expect(manageLabelsLink.exists()).toBe(true);
expect(manageLabelsLink.text()).toBe('Manage labels');
});
it('does not render "Create label" footer link when `state.allowLabelCreate` is `false`', () => {
wrapper.vm.$store.state.allowLabelCreate = false;
return wrapper.vm.$nextTick(() => {
const createLabelLink = wrapper
.find('.dropdown-footer')
.findAll(GlLink)
.at(0);
expect(createLabelLink.text()).not.toBe('Create label');
});
});
it('does not render footer list items when `state.variant` is "standalone"', () => {
expect(wrapperStandalone.find('.dropdown-footer').exists()).toBe(false);
});
});
});

View File

@ -89,6 +89,19 @@ describe('LabelsSelectRoot', () => {
expect(wrapper.attributes('class')).toContain('labels-select-wrapper position-relative');
});
it('renders component root element with CSS class `is-standalone` when `state.variant` is "standalone"', () => {
const wrapperStandalone = createComponent({
...mockConfig,
variant: 'standalone',
});
return wrapperStandalone.vm.$nextTick(() => {
expect(wrapperStandalone.classes()).toContain('is-standalone');
wrapperStandalone.destroy();
});
});
it('renders `dropdown-value-collapsed` component when `allowLabelCreate` prop is `true`', () => {
expect(wrapper.find(DropdownValueCollapsed).exists()).toBe(true);
});
@ -101,13 +114,16 @@ describe('LabelsSelectRoot', () => {
const wrapperDropdownValue = createComponent(mockConfig, {
default: 'None',
});
wrapperDropdownValue.vm.$store.state.showDropdownButton = false;
const valueComp = wrapperDropdownValue.find(DropdownValue);
return wrapperDropdownValue.vm.$nextTick(() => {
const valueComp = wrapperDropdownValue.find(DropdownValue);
expect(valueComp.exists()).toBe(true);
expect(valueComp.text()).toBe('None');
expect(valueComp.exists()).toBe(true);
expect(valueComp.text()).toBe('None');
wrapperDropdownValue.destroy();
wrapperDropdownValue.destroy();
});
});
it('renders `dropdown-button` component when `showDropdownButton` prop is `true`', () => {

View File

@ -30,8 +30,10 @@ export const mockConfig = {
allowLabelEdit: true,
allowLabelCreate: true,
allowScopedLabels: true,
allowMultiselect: true,
labelsListTitle: 'Assign labels',
labelsCreateTitle: 'Create label',
variant: 'sidebar',
dropdownOnly: false,
selectedLabels: [mockRegularLabel, mockScopedLabel],
labelsSelectInProgress: false,

View File

@ -5,19 +5,25 @@ describe('LabelsSelect Getters', () => {
it('returns string "Label" when state.labels has no selected labels', () => {
const labels = [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }];
expect(getters.dropdownButtonText({ labels })).toBe('Label');
expect(getters.dropdownButtonText({ labels }, { isDropdownVariantSidebar: true })).toBe(
'Label',
);
});
it('returns label title when state.labels has only 1 label', () => {
const labels = [{ id: 1, title: 'Foobar', set: true }];
expect(getters.dropdownButtonText({ labels })).toBe('Foobar');
expect(getters.dropdownButtonText({ labels }, { isDropdownVariantSidebar: true })).toBe(
'Foobar',
);
});
it('returns first label title and remaining labels count when state.labels has more than 1 label', () => {
const labels = [{ id: 1, title: 'Foo', set: true }, { id: 2, title: 'Bar', set: true }];
expect(getters.dropdownButtonText({ labels })).toBe('Foo +1 more');
expect(getters.dropdownButtonText({ labels }, { isDropdownVariantSidebar: true })).toBe(
'Foo +1 more',
);
});
});
@ -28,4 +34,16 @@ describe('LabelsSelect Getters', () => {
expect(getters.selectedLabelsList({ selectedLabels })).toEqual([1, 2, 3, 4]);
});
});
describe('isDropdownVariantSidebar', () => {
it('returns `true` when `state.variant` is "sidebar"', () => {
expect(getters.isDropdownVariantSidebar({ variant: 'sidebar' })).toBe(true);
});
});
describe('isDropdownVariantStandalone', () => {
it('returns `true` when `state.variant` is "standalone"', () => {
expect(getters.isDropdownVariantStandalone({ variant: 'standalone' })).toBe(true);
});
});
});

View File

@ -29,6 +29,7 @@ describe('LabelsSelect Mutations', () => {
const state = {
dropdownOnly: false,
showDropdownButton: false,
variant: 'sidebar',
};
mutations[types.TOGGLE_DROPDOWN_CONTENTS](state);
@ -154,10 +155,27 @@ describe('LabelsSelect Mutations', () => {
describe(`${types.UPDATE_SELECTED_LABELS}`, () => {
const labels = [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }];
it('updates `state.labels` to include `touched` and `set` props based on provided `labels` param', () => {
it('updates `state.labels` to include `touched` and `set` props based on provided `labels` param when `state.allowMultiselect` is `true`', () => {
const updatedLabelIds = [2, 4];
const state = {
labels,
allowMultiselect: true,
};
mutations[types.UPDATE_SELECTED_LABELS](state, { labels });
state.labels.forEach(label => {
if (updatedLabelIds.includes(label.id)) {
expect(label.touched).toBe(true);
expect(label.set).toBe(true);
}
});
});
it('updates `state.labels` to include `touched` and `set` props based on provided `labels` param when `state.allowMultiselect` is `false`', () => {
const updatedLabelIds = [2];
const state = {
labels,
allowMultiselect: false,
};
mutations[types.UPDATE_SELECTED_LABELS](state, { labels });

View File

@ -56,12 +56,12 @@ RSpec.describe Timelog do
end
end
describe 'between_dates' do
it 'returns collection of timelogs within given dates' do
describe 'between_times' do
it 'returns collection of timelogs within given times' do
create(:timelog, spent_at: 65.days.ago)
timelog1 = create(:timelog, spent_at: 15.days.ago)
timelog2 = create(:timelog, spent_at: 5.days.ago)
timelogs = described_class.between_dates(20.days.ago, 1.day.ago)
timelogs = described_class.between_times(20.days.ago, 1.day.ago)
expect(timelogs).to contain_exactly(timelog1, timelog2)
end