Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-04-01 06:09:29 +00:00
parent 4223ed2e83
commit 2db9c1eee2
53 changed files with 666 additions and 358 deletions

View file

@ -68,6 +68,7 @@ export default {
v-gl-modal.deploy-freeze-modal
icon="pencil"
data-testid="edit-deploy-freeze"
:aria-label="__('Edit deploy freeze')"
@click="setFreezePeriod(item)"
/>
</template>

View file

@ -1,13 +1,16 @@
<script>
import { GlButton, GlIcon, GlTooltipDirective } from '@gitlab/ui';
import permissionsQuery from 'shared_queries/design_management/design_permissions.query.graphql';
import { __, sprintf } from '~/locale';
import { __, s__, sprintf } from '~/locale';
import timeagoMixin from '~/vue_shared/mixins/timeago';
import { DESIGNS_ROUTE_NAME } from '../../router/constants';
import DeleteButton from '../delete_button.vue';
import DesignNavigation from './design_navigation.vue';
export default {
i18n: {
downloadButtonLabel: s__('DesignManagement|Download design'),
},
components: {
GlButton,
GlIcon,
@ -119,7 +122,8 @@ export default {
v-gl-tooltip.bottom
:href="image"
icon="download"
:title="s__('DesignManagement|Download design')"
:title="$options.i18n.downloadButtonLabel"
:aria-label="$options.i18n.downloadButtonLabel"
/>
<delete-button
v-if="isLatestVersion && canDeleteDesign"

View file

@ -49,6 +49,7 @@ export default {
mixins: [glFeatureFlagsMixin()],
i18n: {
...DIFF_FILE_HEADER,
compareButtonLabel: s__('Compare submodule commit revisions'),
},
props: {
discussionPath: {
@ -192,6 +193,9 @@ export default {
isReviewable() {
return reviewable(this.diffFile);
},
externalUrlLabel() {
return sprintf(__('View on %{url}'), { url: this.diffFile.formatted_external_url });
},
},
methods: {
...mapActions('diffs', [
@ -352,7 +356,8 @@ export default {
ref="externalLink"
v-gl-tooltip.hover
:href="diffFile.external_url"
:title="`View on ${diffFile.formatted_external_url}`"
:title="externalUrlLabel"
:aria-label="externalUrlLabel"
target="_blank"
data-track-event="click_toggle_external_button"
data-track-label="diff_toggle_external_button"
@ -444,7 +449,8 @@ export default {
v-gl-tooltip.hover
v-safe-html="submoduleDiffCompareLinkText"
class="submodule-compare"
:title="s__('Compare submodule commit revisions')"
:title="$options.i18n.compareButtonLabel"
:aria-label="$options.i18n.compareButtonLabel"
:href="diffFile.submodule_compare.url"
/>
</div>

View file

@ -7,6 +7,8 @@ import { labelForStrategy } from '../utils';
export default {
i18n: {
deleteLabel: __('Delete'),
editLabel: __('Edit'),
toggleLabel: __('Feature flag status'),
},
components: {
@ -215,19 +217,21 @@ export default {
<div class="table-action-buttons btn-group">
<template v-if="featureFlag.edit_path">
<gl-button
v-gl-tooltip.hover.bottom="__('Edit')"
v-gl-tooltip.hover.bottom="$options.i18n.editLabel"
class="js-feature-flag-edit-button"
icon="pencil"
:aria-label="$options.i18n.editLabel"
:href="featureFlag.edit_path"
/>
</template>
<template v-if="featureFlag.destroy_path">
<gl-button
v-gl-tooltip.hover.bottom="__('Delete')"
v-gl-tooltip.hover.bottom="$options.i18n.deleteLabel"
class="js-feature-flag-delete-button"
variant="danger"
icon="remove"
:disabled="!canDeleteFlag(featureFlag)"
:aria-label="$options.i18n.deleteLabel"
@click="setDeleteModalData(featureFlag)"
/>
</template>

View file

@ -30,6 +30,7 @@ import Strategy from './strategy.vue';
export default {
i18n: {
removeLabel: s__('FeatureFlags|Remove'),
statusLabel: s__('FeatureFlags|Status'),
},
components: {
@ -507,7 +508,8 @@ export default {
<gl-button
v-if="!isAllEnvironment(scope.environmentScope) && canUpdateScope(scope)"
v-gl-tooltip
:title="s__('FeatureFlags|Remove')"
:title="$options.i18n.removeLabel"
:aria-label="$options.i18n.removeLabel"
class="js-delete-scope btn-transparent pr-3 pl-3"
icon="clear"
data-testid="feature-flag-delete"

View file

@ -165,6 +165,7 @@ export default {
data-testid="delete-strategy-button"
variant="danger"
icon="remove"
:aria-label="__('Delete')"
@click="$emit('delete')"
/>
</div>

View file

@ -100,6 +100,7 @@ export default {
category="secondary"
variant="danger"
icon="remove"
:aria-label="$options.modal.actionPrimary.text"
data-testid="delete-user-list"
@click="confirmDeleteList(list)"
/>

View file

@ -13,6 +13,10 @@ import CsvExportModal from './csv_export_modal.vue';
import CsvImportModal from './csv_import_modal.vue';
export default {
i18n: {
exportAsCsvButtonText: __('Export as CSV'),
importIssuesText: __('Import issues'),
},
name: 'CsvImportExportButtons',
components: {
GlButtonGroup,
@ -57,16 +61,15 @@ export default {
return `${this.issuableType}-import-modal`;
},
importButtonText() {
return this.showLabel ? this.$options.importIssuesText : null;
return this.showLabel ? this.$options.i18n.importIssuesText : null;
},
importButtonTooltipText() {
return this.showLabel ? null : this.$options.importIssuesText;
return this.showLabel ? null : this.$options.i18n.importIssuesText;
},
importButtonIcon() {
return this.showLabel ? null : 'import';
},
},
importIssuesText: __('Import issues'),
};
</script>
@ -75,9 +78,10 @@ export default {
<gl-button-group>
<gl-button
v-if="showExportButton"
v-gl-tooltip.hover="__('Export as CSV')"
v-gl-tooltip.hover="$options.i18n.exportAsCsvButtonText"
v-gl-modal="exportModalId"
icon="export"
:aria-label="$options.i18n.exportAsCsvButtonText"
data-qa-selector="export_as_csv_button"
data-testid="export-csv-button"
/>

View file

@ -1,9 +1,15 @@
<script>
import { GlTooltipDirective, GlLink, GlButton } from '@gitlab/ui';
import { numberToHumanSize } from '~/lib/utils/number_utils';
import { __, sprintf } from '~/locale';
import { __, s__, sprintf } from '~/locale';
export default {
i18n: {
eraseLogButtonLabel: s__('Job|Erase job log'),
scrollToBottomButtonLabel: s__('Job|Scroll to bottom'),
scrollToTopButtonLabel: s__('Job|Scroll to top'),
showRawButtonLabel: s__('Job|Show complete raw'),
},
components: {
GlLink,
GlButton,
@ -82,7 +88,8 @@ export default {
<gl-button
v-if="rawPath"
v-gl-tooltip.body
:title="s__('Job|Show complete raw')"
:title="$options.i18n.showRawButtonLabel"
:aria-label="$options.i18n.showRawButtonLabel"
:href="rawPath"
data-testid="job-raw-link-controller"
icon="doc-text"
@ -91,7 +98,8 @@ export default {
<gl-button
v-if="erasePath"
v-gl-tooltip.body
:title="s__('Job|Erase job log')"
:title="$options.i18n.eraseLogButtonLabel"
:aria-label="$options.i18n.eraseLogButtonLabel"
:href="erasePath"
:data-confirm="__('Are you sure you want to erase this build?')"
class="gl-ml-3"
@ -102,23 +110,25 @@ export default {
<!-- eo links -->
<!-- scroll buttons -->
<div v-gl-tooltip :title="s__('Job|Scroll to top')" class="gl-ml-3">
<div v-gl-tooltip :title="$options.i18n.scrollToTopButtonLabel" class="gl-ml-3">
<gl-button
:disabled="isScrollTopDisabled"
class="btn-scroll"
data-testid="job-controller-scroll-top"
icon="scroll_up"
:aria-label="$options.i18n.scrollToTopButtonLabel"
@click="handleScrollToTop"
/>
</div>
<div v-gl-tooltip :title="s__('Job|Scroll to bottom')" class="gl-ml-3">
<div v-gl-tooltip :title="$options.i18n.scrollToBottomButtonLabel" class="gl-ml-3">
<gl-button
:disabled="isScrollBottomDisabled"
class="js-scroll-bottom btn-scroll"
data-testid="job-controller-scroll-bottom"
icon="scroll_down"
:class="{ animate: isScrollingDown }"
:aria-label="$options.i18n.scrollToBottomButtonLabel"
@click="handleScrollToBottom"
/>
</div>

View file

@ -10,6 +10,7 @@ import {
GlTooltipDirective,
} from '@gitlab/ui';
import { mapActions, mapState } from 'vuex';
import { s__ } from '~/locale';
import DateTimePicker from '~/vue_shared/components/date_time_picker/date_time_picker.vue';
import { timeRanges } from '~/vue_shared/constants';
import DashboardPanel from './dashboard_panel.vue';
@ -24,6 +25,9 @@ metrics:
`;
export default {
i18n: {
refreshButtonLabel: s__('Metrics|Refresh Prometheus data'),
},
components: {
GlCard,
GlForm,
@ -191,7 +195,8 @@ export default {
v-gl-tooltip
data-testid="previewRefreshButton"
icon="retry"
:title="s__('Metrics|Refresh Prometheus data')"
:title="$options.i18n.refreshButtonLabel"
:aria-label="$options.i18n.refreshButtonLabel"
@click="onRefresh"
/>
<dashboard-panel :graph-data="panelPreviewGraphData" />

View file

@ -0,0 +1,42 @@
<script>
import { GlButton, GlTooltipDirective } from '@gitlab/ui';
import { n__ } from '~/locale';
export default {
directives: {
GlTooltip: GlTooltipDirective,
},
components: {
GlButton,
},
props: {
commentsCount: {
type: Number,
required: true,
},
},
computed: {
tooltipText() {
return n__('%d comment on this commit', '%d comments on this commit', this.commentsCount);
},
showCommentButton() {
return this.commentsCount > 0;
},
},
};
</script>
<template>
<span
v-if="showCommentButton"
v-gl-tooltip
class="gl-display-none gl-sm-display-inline-block"
tabindex="0"
:title="tooltipText"
data-testid="comment-button-wrapper"
>
<gl-button icon="comment" class="gl-mr-3" disabled>
{{ commentsCount }}
</gl-button>
</span>
</template>

View file

@ -1,9 +1,11 @@
import initCherryPickCommitModal from './init_cherry_pick_commit_modal';
import initCommitCommentsButton from './init_commit_comments_button';
import initCommitOptionsDropdown from './init_commit_options_dropdown';
import initRevertCommitModal from './init_revert_commit_modal';
export default () => {
initRevertCommitModal();
initCherryPickCommitModal();
initCommitCommentsButton();
initCommitOptionsDropdown();
};

View file

@ -0,0 +1,18 @@
import Vue from 'vue';
import CommitCommentsButton from './components/commit_comments_button.vue';
export default function initCommitCommentsButton() {
const el = document.querySelector('#js-commit-comments-button');
if (!el) {
return false;
}
const { commentsCount } = el.dataset;
return new Vue({
el,
render: (createElement) =>
createElement(CommitCommentsButton, { props: { commentsCount: Number(commentsCount) } }),
});
}

View file

@ -28,6 +28,7 @@ import {
import $ from 'jquery';
import { mapGetters, mapActions, mapState } from 'vuex';
import descriptionVersionHistoryMixin from 'ee_else_ce/notes/mixins/description_version_history';
import { __ } from '~/locale';
import initMRPopovers from '~/mr_popover/';
import noteHeader from '~/notes/components/note_header.vue';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
@ -37,6 +38,9 @@ import TimelineEntryItem from './timeline_entry_item.vue';
const MAX_VISIBLE_COMMIT_LIST_COUNT = 3;
export default {
i18n: {
deleteButtonLabel: __('Remove description history'),
},
name: 'SystemNote',
components: {
GlIcon,
@ -139,7 +143,8 @@ export default {
<gl-button
v-if="displayDeleteButton"
v-gl-tooltip
:title="__('Remove description history')"
:title="$options.i18n.deleteButtonLabel"
:aria-label="$options.i18n.deleteButtonLabel"
variant="default"
category="tertiary"
icon="remove"

View file

@ -164,6 +164,7 @@ export default {
variant="link"
icon="close"
class="gl-mr-2 gl-w-auto! gl-p-2!"
:aria-label="__('Close')"
@click.prevent="handleDropdownCloseClick"
/>
</div>

View file

@ -5,32 +5,29 @@
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, safe_params.merge(rss_url_options).to_h, title: "#{@group.name} issues")
- if group_issues_count(state: 'all') == 0
= render 'shared/empty_states/issues', project_select_button: true
.top-area
= render 'shared/issuable/nav', type: :issues
.nav-controls
= render 'shared/issuable/feed_buttons'
- if @can_bulk_update
= render_if_exists 'shared/issuable/bulk_update_button', type: :issues
= render 'shared/new_project_item_select', path: 'issues/new', label: "New issue", type: :issues, with_feature_enabled: 'issues', with_shared: false, include_projects_in_subgroups: true
= render 'shared/issuable/search_bar', type: :issues
- if @can_bulk_update
= render_if_exists 'shared/issuable/group_bulk_update_sidebar', group: @group, type: :issues
- if Feature.enabled?(:vue_issuables_list, @group) && @issues.to_a.any?
- if use_startup_call?
- add_page_startup_api_call(api_v4_groups_issues_path(id: @group.id, params: startup_call_params))
.js-issuables-list{ data: { endpoint: expose_url(api_v4_groups_issues_path(id: @group.id)),
'can-bulk-edit': @can_bulk_update.to_json,
'empty-state-meta': { svg_path: image_path('illustrations/issues.svg') },
'sort-key': @sort,
type: 'issues',
'scoped-labels-available': scoped_labels_available?(@group).to_json } }
- else
.top-area
= render 'shared/issuable/nav', type: :issues
.nav-controls
= render 'shared/issuable/feed_buttons'
- if @can_bulk_update
= render_if_exists 'shared/issuable/bulk_update_button', type: :issues
= render 'shared/new_project_item_select', path: 'issues/new', label: "New issue", type: :issues, with_feature_enabled: 'issues', with_shared: false, include_projects_in_subgroups: true
= render 'shared/issuable/search_bar', type: :issues
- if @can_bulk_update
= render_if_exists 'shared/issuable/group_bulk_update_sidebar', group: @group, type: :issues
- if Feature.enabled?(:vue_issuables_list, @group)
- if use_startup_call?
- add_page_startup_api_call(api_v4_groups_issues_path(id: @group.id, params: startup_call_params))
.js-issuables-list{ data: { endpoint: expose_url(api_v4_groups_issues_path(id: @group.id)),
'can-bulk-edit': @can_bulk_update.to_json,
'empty-state-meta': { svg_path: image_path('illustrations/issues.svg') },
'sort-key': @sort,
type: 'issues',
'scoped-labels-available': scoped_labels_available?(@group).to_json } }
- else
= render 'shared/issues'
= render 'shared/issues', project_select_button: true

View file

@ -18,10 +18,7 @@
= commit_committer_link(@commit, avatar: true, size: 24)
#{time_ago_with_tooltip(@commit.committed_date)}
- if defined?(@notes_count) && @notes_count > 0
%span.btn.gl-button.btn-default.disabled.gl-button.btn-icon.d-none.d-sm-inline.gl-mr-3.has-tooltip{ title: n_("%d comment on this commit", "%d comments on this commit", @notes_count) % @notes_count }
= sprite_icon('comment')
= @notes_count
#js-commit-comments-button{ data: { comments_count: @notes_count.to_i } }
= link_to _('Browse files'), project_tree_path(@project, @commit), class: "btn gl-button btn-default gl-mr-3 gl-xs-w-full gl-xs-mb-3"
#js-commit-options-dropdown{ data: commit_options_dropdown_data(@project, @commit) }

View file

@ -3,4 +3,5 @@
= render partial: 'projects/issues/issue', collection: @issues
= paginate @issues, theme: "gitlab"
- else
= render 'shared/empty_states/issues'
- project_select_button = local_assigns.fetch(:project_select_button, false)
= render 'shared/empty_states/issues', project_select_button: project_select_button

View file

@ -50,16 +50,16 @@
.milestone-actions.d-flex.justify-content-sm-start.justify-content-md-end
- if @project # if in milestones list on project level
- if can_admin_group_milestones?
%button.js-promote-project-milestone-button.btn.gl-button.btn-default-tertiary.btn-sm.btn-grouped.has-tooltip{ title: s_('Milestones|Promote to Group Milestone'),
%button.js-promote-project-milestone-button.btn.gl-button.btn-icon.btn-default-tertiary.btn-sm.has-tooltip{ title: s_('Milestones|Promote to Group Milestone'),
disabled: true,
type: 'button',
data: { url: promote_project_milestone_path(milestone.project, milestone),
milestone_title: milestone.title,
group_name: @project.group.name } }
= sprite_icon('level-up', size: 14)
= sprite_icon('level-up', size: 14, css_class: 'gl-button-icon gl-icon')
- if can?(current_user, :admin_milestone, milestone)
- if milestone.closed?
= link_to s_('Milestones|Reopen Milestone'), milestone_path(milestone, milestone: { state_event: :activate }), method: :put, class: "btn gl-button btn-sm btn-grouped"
= link_to s_('Milestones|Reopen Milestone'), milestone_path(milestone, milestone: { state_event: :activate }), method: :put, class: "btn gl-button btn-sm gl-ml-3"
- else
= link_to s_('Milestones|Close Milestone'), milestone_path(milestone, milestone: { state_event: :close }), method: :put, class: "btn gl-button btn-warning-secondary btn-sm btn-grouped btn-close"
= link_to s_('Milestones|Close Milestone'), milestone_path(milestone, milestone: { state_event: :close }), method: :put, class: "btn gl-button btn-default btn-default-secondary btn-sm gl-ml-3"

View file

@ -0,0 +1,5 @@
---
title: Covert has-tooltip on commit page to pajamas
merge_request: 57858
author:
type: fixed

View file

@ -0,0 +1,5 @@
---
title: Add aria labels to icon-only buttons
merge_request: 57610
author:
type: fixed

View file

@ -0,0 +1,5 @@
---
title: Replace deprecated Close Milestone button on list view
merge_request: 57871
author:
type: changed

View file

@ -35,7 +35,7 @@ There are two places defined variables can be used. On the:
| `cache:key` | yes | Runner | The variable expansion is made by GitLab Runner's [internal variable expansion mechanism](#gitlab-runner-internal-variable-expansion-mechanism) |
| `artifacts:name` | yes | Runner | The variable expansion is made by GitLab Runner's shell environment |
| `script`, `before_script`, `after_script` | yes | Script execution shell | The variable expansion is made by the [execution shell environment](#execution-shell-environment) |
| `only:variables:[]`, `except:variables:[]` | no | n/a | The variable must be in the form of `$variable`. Not supported are the following:<br/><br/>- Variables that are based on the environment's name (`CI_ENVIRONMENT_NAME`, `CI_ENVIRONMENT_SLUG`).<br/>- Any other variables related to environment (currently only `CI_ENVIRONMENT_URL`).<br/>- [Persisted variables](#persisted-variables). |
| `only:variables:[]`, `except:variables:[]`, `rules:if` | no | n/a | The variable must be in the form of `$variable`. Not supported are the following:<br/><br/>- Variables that are based on the environment's name (`CI_ENVIRONMENT_NAME`, `CI_ENVIRONMENT_SLUG`).<br/>- Any other variables related to environment (currently only `CI_ENVIRONMENT_URL`).<br/>- [Persisted variables](#persisted-variables). |
### `config.toml` file

View file

@ -30,7 +30,7 @@ const createStore = () => new Vuex.Store({
// Notice that we are forcing all references to this module to use the same single instance of the store.
// We are also creating the store at import-time and there is nothing which can automatically dispose of it.
//
// As an alternative, we should simply export the `createStore` and let the client manage the
// As an alternative, we should export the `createStore` and let the client manage the
// lifecycle and instance of the store.
export default createStore();
```
@ -129,7 +129,7 @@ Here are some ills that Singletons often produce:
This is because of the limitations of languages like Java where everything has to be wrapped
in a class. In JavaScript we have things like object and function literals where we can solve
many problems with a module that simply exports utility functions.
many problems with a module that exports utility functions.
### When could the Singleton pattern be actually appropriate?**

View file

@ -254,7 +254,7 @@ end
**Creating a new table with a foreign key:**
We can simply wrap the `create_table` method with `with_lock_retries`:
We can wrap the `create_table` method with `with_lock_retries`:
```ruby
def up
@ -715,7 +715,7 @@ the `DROP TABLE` statement is likely to stall concurrent traffic until it fails
Table **has no records** (feature was never in use) and **no foreign
keys**:
- Simply use the `drop_table` method in your migration.
- Use the `drop_table` method in your migration.
```ruby
def change

View file

@ -74,7 +74,7 @@ different columns set) in the same table.
## The Solution
Fortunately there is a very simple solution to these problems: simply use a
Fortunately there is a very simple solution to these problems: use a
separate table for every type you would otherwise store in the same table. Using
a separate table allows you to use everything a database may provide to ensure
consistency and query data efficiently, without any additional application logic

View file

@ -223,4 +223,4 @@ When importing, GitLab would execute the following command, passing the `import_
git clone file://git:/tmp/lol
```
Git would simply ignore the `git:` part, interpret the path as `file:///tmp/lol`, and import the repository into the new project. This action could potentially give the attacker access to any repository in the system, whether private or not.
Git ignores the `git:` part, interpret the path as `file:///tmp/lol`, and imports the repository into the new project. This action could potentially give the attacker access to any repository in the system, whether private or not.

View file

@ -551,7 +551,7 @@ does not account for weights.
As we are [moving towards using `sidekiq-cluster` in
Free](https://gitlab.com/gitlab-org/gitlab/-/issues/34396), newly-added
workers do not need to have weights specified. They can simply use the
workers do not need to have weights specified. They can use the
default weight, which is 1.
## Worker context

View file

@ -313,9 +313,9 @@ Project.from("(#{union.to_sql}) projects")
## Ordering by Creation Date
When ordering records based on the time they were created you can simply order
When ordering records based on the time they were created, you can order
by the `id` column instead of ordering by `created_at`. Because IDs are always
unique and incremented in the order that rows are created this will produce the
unique and incremented in the order that rows are created, doing so will produce the
exact same results. This also means there's no need to add an index on
`created_at` to ensure consistent performance as `id` is already indexed by
default.

View file

@ -159,9 +159,9 @@ See [Review Apps](../review_apps.md) for more details about Review Apps.
## How do I run the tests?
If you are not [testing code in a merge request](#testing-code-in-merge-requests),
there are two main options for running the tests. If you simply want to run
the existing tests against a live GitLab instance or against a pre-built Docker image
you can use the [GitLab QA orchestrator](https://gitlab.com/gitlab-org/gitlab-qa/tree/master/README.md). See also [examples
there are two main options for running the tests. If you want to run
the existing tests against a live GitLab instance or against a pre-built Docker image,
use the [GitLab QA orchestrator](https://gitlab.com/gitlab-org/gitlab-qa/tree/master/README.md). See also [examples
of the test scenarios you can run via the orchestrator](https://gitlab.com/gitlab-org/gitlab-qa/blob/master/docs/what_tests_can_be_run.md#examples).
On the other hand, if you would like to run against a local development GitLab

View file

@ -122,7 +122,7 @@ avoid confusion or make the code more readable. For example, if a page object is
named `New`, it could be confusing to name the block argument `new` because that
is used to instantiate objects, so `new_page` would be acceptable.
We chose not to simply use `page` because that would shadow the
We chose not to use `page` because that would shadow the
Capybara DSL, potentially leading to confusion and bugs.
### Examples

View file

@ -755,7 +755,7 @@ If a manual mock is needed for a CE module, place it in `spec/frontend/mocks/ce`
any behavior, only provides a nice es6 compatible wrapper.
- [`__mocks__/monaco-editor/index.js`](https://gitlab.com/gitlab-org/gitlab/blob/b7f914cddec9fc5971238cdf12766e79fa1629d7/spec/frontend/__mocks__/monaco-editor/index.js) -
This mock is helpful because the Monaco package is completely incompatible in a Jest environment. In fact, webpack requires a special loader to make it work. This mock
simply makes this package consumable by Jest.
makes this package consumable by Jest.
### Keep mocks light

View file

@ -280,7 +280,7 @@ FROM users
WHERE twitter != '';
```
This query simply counts the number of users that have a Twitter profile set.
This query counts the number of users that have a Twitter profile set.
Let's run this using `EXPLAIN (ANALYZE, BUFFERS)`:
```sql
@ -388,7 +388,7 @@ we created the index:
CREATE INDEX CONCURRENTLY twitter_test ON users (twitter);
```
We simply told PostgreSQL to index all possible values of the `twitter` column,
We told PostgreSQL to index all possible values of the `twitter` column,
even empty strings. Our query in turn uses `WHERE twitter != ''`. This means
that the index does improve things, as we don't need to do a sequential scan,
but we may still encounter empty strings. This means PostgreSQL _has_ to apply a

View file

@ -679,7 +679,7 @@ Sidekiq processes](../administration/operations/extra_sidekiq_processes.md).
Whenever a change or deletion is made to an indexed GitLab object (a merge request description is changed, a file is deleted from the master branch in a repository, a project is deleted, etc), a document in the index is deleted. However, since these are "soft" deletes, the overall number of "deleted documents", and therefore wasted space, increases. Elasticsearch does intelligent merging of segments in order to remove these deleted documents. However, depending on the amount and type of activity in your GitLab installation, it's possible to see as much as 50% wasted space in the index.
In general, we recommend simply letting Elasticsearch merge and reclaim space automatically, with the default settings. From [Lucene's Handling of Deleted Documents](https://www.elastic.co/blog/lucenes-handling-of-deleted-documents "Lucene's Handling of Deleted Documents"), _"Overall, besides perhaps decreasing the maximum segment size, it is best to leave Lucene's defaults as-is and not fret too much about when deletes are reclaimed."_
In general, we recommend letting Elasticsearch merge and reclaim space automatically, with the default settings. From [Lucene's Handling of Deleted Documents](https://www.elastic.co/blog/lucenes-handling-of-deleted-documents "Lucene's Handling of Deleted Documents"), _"Overall, besides perhaps decreasing the maximum segment size, it is best to leave Lucene's defaults as-is and not fret too much about when deletes are reclaimed."_
However, some larger installations may wish to tune the merge policy settings:
@ -719,8 +719,7 @@ data).
The use of Elasticsearch in GitLab is only ever as a secondary data store.
This means that all of the data stored in Elasticsearch can always be derived
again from other data sources, specifically PostgreSQL and Gitaly. Therefore, if
the Elasticsearch data store is ever corrupted for whatever reason, you can
simply reindex everything from scratch.
the Elasticsearch data store is ever corrupted for whatever reason, you can reindex everything from scratch.
## Troubleshooting
@ -916,7 +915,7 @@ In GitLab 13.9, a change was made where [binary file names are being indexed](ht
### Last resort to recreate an index
There may be cases where somehow data never got indexed and it's not in the
queue, or the index is somehow in a state where migrations just simply cannot
queue, or the index is somehow in a state where migrations just cannot
proceed. It is always best to try to troubleshoot the root cause of the problem
using the above [troubleshooting](#troubleshooting) steps.

View file

@ -77,7 +77,7 @@ You can take action on Sentry Errors from within the GitLab UI.
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/39665) in GitLab 12.7.
From within the [Error Details](#error-details) page you can ignore a Sentry error by simply clicking the **Ignore** button near the top of the page.
From within the [Error Details](#error-details) page you can ignore a Sentry error by clicking the **Ignore** button near the top of the page.
Ignoring an error prevents it from appearing in the [Error Tracking List](#error-tracking-list), and silences notifications that were set up within Sentry.

View file

@ -379,7 +379,7 @@ it also provides a clear timeline and development structure.
![Use revert to keep branch flowing](img/revert.png)
If you want to revert changes introduced in certain `commit-id` you can simply
If you want to revert changes introduced in certain `commit-id`, you can
revert that `commit-id` (swap additions and deletions) in newly created commit:
You can do this with

View file

@ -66,4 +66,4 @@ Activating a user changes the user's state to active and consumes a
[seat](../../subscriptions/self_managed/index.md#billable-users).
NOTE:
A deactivated user can also activate their account themselves by simply logging back in via the UI.
A deactivated user can also activate their account themselves by logging back in via the UI.

View file

@ -994,7 +994,7 @@ False positives can be handled in two ways:
Checks perform testing of a specific type and can be turned on and off for specific configuration
profiles. The provided [configuration files](#configuration-files) define several profiles that you
can use. The profile definition in the configuration file lists all the checks that are active
during a scan. To turn off a specific check, simply remove it from the profile definition in the
during a scan. To turn off a specific check, remove it from the profile definition in the
configuration file. The profiles are defined in the `Profiles` section of the configuration file.
Example profile definition:

View file

@ -33715,6 +33715,9 @@ msgstr ""
msgid "View merge request"
msgstr ""
msgid "View on %{url}"
msgstr ""
msgid "View open merge request"
msgstr ""

View file

@ -578,7 +578,9 @@ module QA
autoload :LoopRunner, 'qa/specs/loop_runner'
module Helpers
autoload :ContextSelector, 'qa/specs/helpers/context_selector'
autoload :Quarantine, 'qa/specs/helpers/quarantine'
autoload :RSpec, 'qa/specs/helpers/rspec'
end
end

View file

@ -2,6 +2,7 @@
require 'gitlab/qa'
require 'uri'
require 'active_support/core_ext/object/blank'
module QA
module Runtime
@ -24,48 +25,6 @@ module QA
SUPPORTED_FEATURES
end
def context_matches?(*options)
return false unless Runtime::Scenario.attributes[:gitlab_address]
opts = {}
opts[:domain] = '.+'
opts[:tld] = '.com'
uri = URI(Runtime::Scenario.gitlab_address)
options.each do |option|
opts[:domain] = 'gitlab' if option == :production
if option.is_a?(Hash) && !option[:pipeline].nil? && !ci_project_name.nil?
return pipeline_matches?(option[:pipeline])
elsif option.is_a?(Hash) && !option[:subdomain].nil?
opts.merge!(option)
opts[:subdomain] = case option[:subdomain]
when Array
"(#{option[:subdomain].join("|")})."
when Regexp
option[:subdomain]
else
"(#{option[:subdomain]})."
end
end
end
uri.host.match?(/^#{opts[:subdomain]}#{opts[:domain]}#{opts[:tld]}$/)
end
alias_method :dot_com?, :context_matches?
def pipeline_matches?(pipeline_to_run_in)
Array(pipeline_to_run_in).any? { |pipeline| pipeline.to_s.casecmp?(pipeline_from_project_name) }
end
def pipeline_from_project_name
ci_project_name.to_s.start_with?('gitlab-qa') ? Runtime::Env.default_branch : ci_project_name
end
def additional_repository_storage
ENV['QA_ADDITIONAL_REPOSITORY_STORAGE']
end
@ -82,6 +41,10 @@ module QA
ENV['CI_JOB_URL']
end
def ci_job_name
ENV['CI_JOB_NAME']
end
def ci_project_name
ENV['CI_PROJECT_NAME']
end

View file

@ -181,7 +181,7 @@ module QA
end
def verify_storage_move(source_storage, destination_storage, repo_type: :project)
return if QA::Runtime::Env.dot_com?
return if Specs::Helpers::ContextSelector.dot_com?
repo_path = verify_storage_move_from_gitaly(source_storage[:name], repo_type: repo_type)

View file

@ -0,0 +1,86 @@
# frozen_string_literal: true
require 'rspec/core'
module QA
module Specs
module Helpers
module ContextSelector
extend self
def configure_rspec
::RSpec.configure do |config|
config.before do |example|
if example.metadata.key?(:only)
skip('Test is not compatible with this environment or pipeline') unless ContextSelector.context_matches?(example.metadata[:only])
elsif example.metadata.key?(:exclude)
skip('Test is excluded in this job') if ContextSelector.exclude?(example.metadata[:exclude])
end
end
end
end
def exclude?(*options)
return false unless Runtime::Env.ci_job_name.present?
context_matches?(*options)
end
def context_matches?(*options)
return false unless Runtime::Scenario.attributes[:gitlab_address]
opts = {}
opts[:domain] = '.+'
opts[:tld] = '.com'
uri = URI(Runtime::Scenario.gitlab_address)
options.each do |option|
opts[:domain] = 'gitlab' if option == :production
next unless option.is_a?(Hash)
if option[:pipeline].present? && Runtime::Env.ci_project_name.present?
return pipeline_matches?(option[:pipeline])
elsif option[:job].present?
return job_matches?(option[:job])
elsif option[:subdomain].present?
opts.merge!(option)
opts[:subdomain] = case option[:subdomain]
when Array
"(#{option[:subdomain].join("|")})."
when Regexp
option[:subdomain]
else
"(#{option[:subdomain]})."
end
end
end
uri.host.match?(/^#{opts[:subdomain]}#{opts[:domain]}#{opts[:tld]}$/)
end
alias_method :dot_com?, :context_matches?
def job_matches?(job_patterns)
Array(job_patterns).any? do |job|
pattern = job.is_a?(Regexp) ? job : Regexp.new(job)
pattern = Regexp.new(pattern.source, pattern.options | Regexp::IGNORECASE)
pattern =~ Runtime::Env.ci_job_name
end
end
def pipeline_matches?(pipeline_to_run_in)
Array(pipeline_to_run_in).any? { |pipeline| pipeline.to_s.casecmp?(pipeline_from_project_name(Runtime::Env.ci_project_name)) }
end
def pipeline_from_project_name(project_name)
project_name.to_s.start_with?('gitlab-qa') ? Runtime::Env.default_branch : project_name
end
end
end
end
end

View file

@ -6,22 +6,18 @@ module QA
module Specs
module Helpers
module Quarantine
include RSpec::Core::Pending
include ::RSpec::Core::Pending
extend self
def configure_rspec
RSpec.configure do |config|
::RSpec.configure do |config|
config.before(:context, :quarantine) do
Quarantine.skip_or_run_quarantined_contexts(config.inclusion_filter.rules, self.class)
end
config.before do |example|
Quarantine.skip_or_run_quarantined_tests_or_contexts(config.inclusion_filter.rules, example)
if example.metadata.key?(:only)
skip('Test is not compatible with this environment or pipeline') unless Runtime::Env.context_matches?(example.metadata[:only])
end
end
end
end
@ -55,7 +51,7 @@ module QA
if quarantine_tag.is_a?(Hash) && quarantine_tag&.key?(:only)
# If the :quarantine hash contains :only, we respect that.
# For instance `quarantine: { only: { subdomain: :staging } }` will only quarantine the test when it runs against staging.
return unless Runtime::Env.context_matches?(quarantine_tag[:only])
return unless ContextSelector.context_matches?(quarantine_tag[:only])
end
skip(quarantine_message(quarantine_tag))

View file

@ -0,0 +1,30 @@
# frozen_string_literal: true
require 'rspec/core'
module QA
module Specs
module Helpers
module RSpec
# We need a reporter for internal tests that's different from the reporter for
# external tests otherwise the results will be mixed up. We don't care about
# most reporting, but we do want to know if a test fails
class RaiseOnFailuresReporter < ::RSpec::Core::NullReporter
def self.example_failed(example)
raise example.exception
end
end
# We use an example group wrapper to prevent the state of internal tests
# expanding into the global state
# See: https://github.com/rspec/rspec-core/issues/2603
def describe_successfully(*args, &describe_body)
example_group = RSpec.describe(*args, &describe_body)
ran_successfully = example_group.run RaiseOnFailuresReporter
expect(ran_successfully).to eq true
example_group
end
end
end
end
end

View file

@ -44,7 +44,7 @@ module QA
tags_for_rspec.push(%w[--tag ~skip_signup_disabled]) if QA::Runtime::Env.signup_disabled?
tags_for_rspec.push(%w[--tag ~skip_live_env]) if QA::Runtime::Env.dot_com?
tags_for_rspec.push(%w[--tag ~skip_live_env]) if QA::Specs::Helpers::ContextSelector.dot_com?
QA::Runtime::Env.supported_features.each_key do |key|
tags_for_rspec.push(%W[--tag ~requires_#{key}]) unless QA::Runtime::Env.can_test? key

View file

@ -341,56 +341,4 @@ RSpec.describe QA::Runtime::Env do
end
end
end
describe '.context_matches?' do
it 'returns true when url has .com' do
QA::Runtime::Scenario.define(:gitlab_address, "https://staging.gitlab.com")
expect(described_class.dot_com?).to be_truthy
end
it 'returns false when url does not have .com' do
QA::Runtime::Scenario.define(:gitlab_address, "https://gitlab.test")
expect(described_class.dot_com?).to be_falsey
end
context 'with arguments' do
it 'returns true when :subdomain is set' do
QA::Runtime::Scenario.define(:gitlab_address, "https://staging.gitlab.com")
expect(described_class.dot_com?(subdomain: :staging)).to be_truthy
end
it 'matches multiple subdomains' do
QA::Runtime::Scenario.define(:gitlab_address, "https://staging.gitlab.com")
expect(described_class.context_matches?(subdomain: [:release, :staging])).to be_truthy
expect(described_class.context_matches?(:production, subdomain: [:release, :staging])).to be_truthy
end
it 'matches :production' do
QA::Runtime::Scenario.define(:gitlab_address, "https://gitlab.com/")
expect(described_class.context_matches?(:production)).to be_truthy
end
it 'doesnt match with mismatching switches' do
QA::Runtime::Scenario.define(:gitlab_address, 'https://gitlab.test')
aggregate_failures do
expect(described_class.context_matches?(tld: '.net')).to be_falsey
expect(described_class.context_matches?(:production)).to be_falsey
expect(described_class.context_matches?(subdomain: [:staging])).to be_falsey
expect(described_class.context_matches?(domain: 'example')).to be_falsey
end
end
end
it 'returns false for mismatching' do
QA::Runtime::Scenario.define(:gitlab_address, "https://staging.gitlab.com")
expect(described_class.context_matches?(:production)).to be_falsey
end
end
end

View file

@ -22,6 +22,7 @@ RSpec.configure do |config|
config.include ::Matchers
QA::Specs::Helpers::Quarantine.configure_rspec
QA::Specs::Helpers::ContextSelector.configure_rspec
config.before do |example|
QA::Runtime::Logger.debug("\nStarting test: #{example.full_description}\n")

View file

@ -0,0 +1,294 @@
# frozen_string_literal: true
require 'rspec/core/sandbox'
RSpec.configure do |c|
c.around do |ex|
RSpec::Core::Sandbox.sandboxed do |config|
# If there is an example-within-an-example, we want to make sure the inner example
# does not get a reference to the outer example (the real spec) if it calls
# something like `pending`
config.before(:context) { RSpec.current_example = nil }
config.color_mode = :off
ex.run
end
end
end
RSpec.describe QA::Specs::Helpers::ContextSelector do
include Helpers::StubENV
include QA::Specs::Helpers::RSpec
before do
QA::Runtime::Scenario.define(:gitlab_address, 'https://staging.gitlab.com')
described_class.configure_rspec
end
describe '.context_matches?' do
it 'returns true when url has .com' do
QA::Runtime::Scenario.define(:gitlab_address, "https://staging.gitlab.com")
expect(described_class.dot_com?).to be_truthy
end
it 'returns false when url does not have .com' do
QA::Runtime::Scenario.define(:gitlab_address, "https://gitlab.test")
expect(described_class.dot_com?).to be_falsey
end
context 'with arguments' do
it 'returns true when :subdomain is set' do
QA::Runtime::Scenario.define(:gitlab_address, "https://staging.gitlab.com")
expect(described_class.dot_com?(subdomain: :staging)).to be_truthy
end
it 'matches multiple subdomains' do
QA::Runtime::Scenario.define(:gitlab_address, "https://staging.gitlab.com")
expect(described_class.context_matches?(subdomain: [:release, :staging])).to be_truthy
expect(described_class.context_matches?(:production, subdomain: [:release, :staging])).to be_truthy
end
it 'matches :production' do
QA::Runtime::Scenario.define(:gitlab_address, "https://gitlab.com/")
expect(described_class.context_matches?(:production)).to be_truthy
end
it 'doesnt match with mismatching switches' do
QA::Runtime::Scenario.define(:gitlab_address, 'https://gitlab.test')
aggregate_failures do
expect(described_class.context_matches?(tld: '.net')).to be_falsey
expect(described_class.context_matches?(:production)).to be_falsey
expect(described_class.context_matches?(subdomain: [:staging])).to be_falsey
expect(described_class.context_matches?(domain: 'example')).to be_falsey
end
end
end
it 'returns false for mismatching' do
QA::Runtime::Scenario.define(:gitlab_address, "https://staging.gitlab.com")
expect(described_class.context_matches?(:production)).to be_falsey
end
end
describe 'description and context blocks' do
context 'with environment set' do
it 'can apply to contexts or descriptions' do
group = describe_successfully 'Runs in staging', only: { subdomain: :staging } do
it('runs in staging') {}
end
expect(group.examples[0].execution_result.status).to eq(:passed)
end
end
context 'with different environment set' do
before do
QA::Runtime::Scenario.define(:gitlab_address, 'https://gitlab.com')
described_class.configure_rspec
end
it 'does not run against production' do
group = describe_successfully 'Runs in staging', :something, only: { subdomain: :staging } do
it('runs in staging') {}
end
expect(group.examples[0].execution_result.status).to eq(:pending)
end
end
end
it 'runs only in staging' do
group = describe_successfully do
it('runs in staging', only: { subdomain: :staging }) {}
it('doesnt run in staging', only: :production) {}
it('runs in staging also', only: { subdomain: %i[release staging] }) {}
it('runs in any env') {}
end
expect(group.examples[0].execution_result.status).to eq(:passed)
expect(group.examples[1].execution_result.status).to eq(:pending)
expect(group.examples[2].execution_result.status).to eq(:passed)
expect(group.examples[3].execution_result.status).to eq(:passed)
end
context 'custom env' do
before do
QA::Runtime::Scenario.define(:gitlab_address, 'https://release.gitlab.net')
end
it 'runs on a custom environment' do
group = describe_successfully do
it('runs on release gitlab net', only: { tld: '.net', subdomain: :release, domain: 'gitlab' }) {}
it('does not run on release', only: :production) {}
end
expect(group.examples.first.execution_result.status).to eq(:passed)
expect(group.examples.last.execution_result.status).to eq(:pending)
end
end
context 'production' do
before do
QA::Runtime::Scenario.define(:gitlab_address, 'https://gitlab.com/')
end
it 'runs on production' do
group = describe_successfully do
it('runs on prod', only: :production) {}
it('does not run in prod', only: { subdomain: :staging }) {}
it('runs in prod and staging', only: { subdomain: /(staging.)?/, domain: 'gitlab' }) {}
end
expect(group.examples[0].execution_result.status).to eq(:passed)
expect(group.examples[1].execution_result.status).to eq(:pending)
expect(group.examples[2].execution_result.status).to eq(:passed)
end
end
it 'outputs a message for invalid environments' do
group = describe_successfully do
it('will skip', only: :production) {}
end
expect(group.examples.first.execution_result.pending_message).to match(/[Tt]est.*not compatible.*environment/)
end
context 'with pipeline constraints' do
context 'without CI_PROJECT_NAME set' do
before do
stub_env('CI_PROJECT_NAME', nil)
described_class.configure_rspec
end
it 'runs on any pipeline' do
group = describe_successfully do
it('runs given a single named pipeline', only: { pipeline: :nightly }) {}
it('runs given an array of pipelines', only: { pipeline: [:canary, :not_nightly] }) {}
end
aggregate_failures do
expect(group.examples[0].execution_result.status).to eq(:passed)
expect(group.examples[1].execution_result.status).to eq(:passed)
end
end
end
context 'when a pipeline triggered from the default branch runs in gitlab-qa' do
before do
stub_env('CI_PROJECT_NAME', 'gitlab-qa')
described_class.configure_rspec
end
it 'runs on default branch pipelines' do
group = describe_successfully do
it('runs on master pipeline given a single pipeline', only: { pipeline: :master }) {}
it('runs in master given an array of pipelines', only: { pipeline: [:canary, :master] }) {}
it('does not run in non-default pipelines', only: { pipeline: [:nightly, :not_nightly, :not_master] }) {}
end
aggregate_failures do
expect(group.examples[0].execution_result.status).to eq(:passed)
expect(group.examples[1].execution_result.status).to eq(:passed)
expect(group.examples[2].execution_result.status).to eq(:pending)
end
end
end
context 'with CI_PROJECT_NAME set' do
before do
stub_env('CI_PROJECT_NAME', 'NIGHTLY')
described_class.configure_rspec
end
it 'runs on designated pipeline' do
group = describe_successfully do
it('runs on nightly', only: { pipeline: :nightly }) {}
it('does not run in not_nightly', only: { pipeline: :not_nightly }) {}
it('runs on nightly given an array', only: { pipeline: [:canary, :nightly] }) {}
it('does not run in not_nightly given an array', only: { pipeline: [:not_nightly, :canary] }) {}
end
aggregate_failures do
expect(group.examples[0].execution_result.status).to eq(:passed)
expect(group.examples[1].execution_result.status).to eq(:pending)
expect(group.examples[2].execution_result.status).to eq(:passed)
expect(group.examples[3].execution_result.status).to eq(:pending)
end
end
end
end
context 'when excluding contexts' do
context 'with job constraints' do
context 'without CI_JOB_NAME set' do
before do
stub_env('CI_JOB_NAME', nil)
described_class.configure_rspec
end
it 'runs in any job' do
group = describe_successfully do
it('runs given a single named job', exclude: { job: 'ee:instance-image' }) {}
it('runs given a single regex pattern', exclude: { job: '.*:instance-image' }) {}
it('runs given an array of jobs', exclude: { job: %w[ee:instance-image qa-schedules-browser_ui-3_create] }) {}
it('runs given an array of regex patterns', exclude: { job: %w[ee:.* qa-schedules-browser_ui.*] }) {}
it('runs given a mix of strings and regex patterns', exclude: { job: %w[ee:instance-image qa-schedules-browser_ui.*] }) {}
end
aggregate_failures do
group.examples.each do |example|
expect(example.execution_result.status).to eq(:passed)
end
end
end
end
context 'with CI_JOB_NAME set' do
before do
stub_env('CI_JOB_NAME', 'ee:instance-image')
described_class.configure_rspec
end
it 'does not run in the specified job' do
group = describe_successfully do
it('skips given a single named job', exclude: { job: 'ee:instance-image' }) {}
it('skips given a single regex pattern', exclude: { job: '.*:instance-image' }) {}
it('skips given an array of jobs', exclude: { job: %w[ee:instance-image qa-schedules-browser_ui-3_create] }) {}
it('skips given an array of regex patterns', exclude: { job: %w[ee:.* qa-schedules-browser_ui.*] }) {}
it('skips given a mix of strings and regex patterns', exclude: { job: %w[ee:instance-image qa-schedules-browser_ui.*] }) {}
end
aggregate_failures do
group.examples.each do |example|
expect(example.execution_result.status).to eq(:pending)
end
end
end
it 'runs in jobs that do not match' do
group = describe_successfully do
it('runs given a single named job', exclude: { job: 'ce:instance-image' }) {}
it('runs given a single regex pattern', exclude: { job: '.*:instance-image-quarantine' }) {}
it('runs given an array of jobs', exclude: { job: %w[ce:instance-image qa-schedules-browser_ui-3_create] }) {}
it('runs given an array of regex patterns', exclude: { job: %w[ce:.* qa-schedules-browser_ui.*] }) {}
it('runs given a mix of strings and regex patterns', exclude: { job: %w[ce:instance-image qa-schedules-browser_ui.*] }) {}
end
aggregate_failures do
group.examples.each do |example|
expect(example.execution_result.status).to eq(:passed)
end
end
end
end
end
end
end

View file

@ -2,25 +2,6 @@
require 'rspec/core/sandbox'
# We need a reporter for internal tests that's different from the reporter for
# external tests otherwise the results will be mixed up. We don't care about
# most reporting, but we do want to know if a test fails
class RaiseOnFailuresReporter < RSpec::Core::NullReporter
def self.example_failed(example)
raise example.exception
end
end
# We use an example group wrapper to prevent the state of internal tests
# expanding into the global state
# See: https://github.com/rspec/rspec-core/issues/2603
def describe_successfully(*args, &describe_body)
example_group = RSpec.describe(*args, &describe_body)
ran_successfully = example_group.run RaiseOnFailuresReporter
expect(ran_successfully).to eq true
example_group
end
RSpec.configure do |c|
c.around do |ex|
RSpec::Core::Sandbox.sandboxed do |config|
@ -38,6 +19,7 @@ end
RSpec.describe QA::Specs::Helpers::Quarantine do
include Helpers::StubENV
include QA::Specs::Helpers::RSpec
describe '.skip_or_run_quarantined_contexts' do
context 'with no tag focused' do
@ -336,159 +318,4 @@ RSpec.describe QA::Specs::Helpers::Quarantine do
end
end
end
describe 'running against specific environments or pipelines' do
before do
QA::Runtime::Scenario.define(:gitlab_address, 'https://staging.gitlab.com')
described_class.configure_rspec
end
describe 'description and context blocks' do
context 'with environment set' do
it 'can apply to contexts or descriptions' do
group = describe_successfully 'Runs in staging', only: { subdomain: :staging } do
it('runs in staging') {}
end
expect(group.examples[0].execution_result.status).to eq(:passed)
end
end
context 'with different environment set' do
before do
QA::Runtime::Scenario.define(:gitlab_address, 'https://gitlab.com')
described_class.configure_rspec
end
it 'does not run against production' do
group = describe_successfully 'Runs in staging', :something, only: { subdomain: :staging } do
it('runs in staging') {}
end
expect(group.examples[0].execution_result.status).to eq(:pending)
end
end
end
it 'runs only in staging' do
group = describe_successfully do
it('runs in staging', only: { subdomain: :staging }) {}
it('doesnt run in staging', only: :production) {}
it('runs in staging also', only: { subdomain: %i[release staging] }) {}
it('runs in any env') {}
end
expect(group.examples[0].execution_result.status).to eq(:passed)
expect(group.examples[1].execution_result.status).to eq(:pending)
expect(group.examples[2].execution_result.status).to eq(:passed)
expect(group.examples[3].execution_result.status).to eq(:passed)
end
context 'custom env' do
before do
QA::Runtime::Scenario.define(:gitlab_address, 'https://release.gitlab.net')
end
it 'runs on a custom environment' do
group = describe_successfully do
it('runs on release gitlab net', only: { tld: '.net', subdomain: :release, domain: 'gitlab' }) {}
it('does not run on release', only: :production) {}
end
expect(group.examples.first.execution_result.status).to eq(:passed)
expect(group.examples.last.execution_result.status).to eq(:pending)
end
end
context 'production' do
before do
QA::Runtime::Scenario.define(:gitlab_address, 'https://gitlab.com/')
end
it 'runs on production' do
group = describe_successfully do
it('runs on prod', only: :production) {}
it('does not run in prod', only: { subdomain: :staging }) {}
it('runs in prod and staging', only: { subdomain: /(staging.)?/, domain: 'gitlab' }) {}
end
expect(group.examples[0].execution_result.status).to eq(:passed)
expect(group.examples[1].execution_result.status).to eq(:pending)
expect(group.examples[2].execution_result.status).to eq(:passed)
end
end
it 'outputs a message for invalid environments' do
group = describe_successfully do
it('will skip', only: :production) {}
end
expect(group.examples.first.execution_result.pending_message).to match(/[Tt]est.*not compatible.*environment/)
end
context 'with pipeline constraints' do
context 'without CI_PROJECT_NAME set' do
before do
stub_env('CI_PROJECT_NAME', nil)
described_class.configure_rspec
end
it 'runs on any pipeline' do
group = describe_successfully do
it('runs given a single named pipeline', only: { pipeline: :nightly }) {}
it('runs given an array of pipelines', only: { pipeline: [:canary, :not_nightly] }) {}
end
aggregate_failures do
expect(group.examples[0].execution_result.status).to eq(:passed)
expect(group.examples[1].execution_result.status).to eq(:passed)
end
end
end
context 'when a pipeline triggered from the default branch runs in gitlab-qa' do
before do
stub_env('CI_PROJECT_NAME', 'gitlab-qa')
described_class.configure_rspec
end
it 'runs on default branch pipelines' do
group = describe_successfully do
it('runs on master pipeline given a single pipeline', only: { pipeline: :master }) {}
it('runs in master given an array of pipelines', only: { pipeline: [:canary, :master] }) {}
it('does not run in non-default pipelines', only: { pipeline: [:nightly, :not_nightly, :not_master] }) {}
end
aggregate_failures do
expect(group.examples[0].execution_result.status).to eq(:passed)
expect(group.examples[1].execution_result.status).to eq(:passed)
expect(group.examples[2].execution_result.status).to eq(:pending)
end
end
end
context 'with CI_PROJECT_NAME set' do
before do
stub_env('CI_PROJECT_NAME', 'NIGHTLY')
described_class.configure_rspec
end
it 'runs on designated pipeline' do
group = describe_successfully do
it('runs on nightly', only: { pipeline: :nightly }) {}
it('does not run in not_nightly', only: { pipeline: :not_nightly }) {}
it('runs on nightly given an array', only: { pipeline: [:canary, :nightly] }) {}
it('does not run in not_nightly given an array', only: { pipeline: [:not_nightly, :canary] }) {}
end
aggregate_failures do
expect(group.examples[0].execution_result.status).to eq(:passed)
expect(group.examples[1].execution_result.status).to eq(:pending)
expect(group.examples[2].execution_result.status).to eq(:passed)
expect(group.examples[3].execution_result.status).to eq(:pending)
end
end
end
end
end
end

View file

@ -108,7 +108,7 @@ RSpec.describe 'Group issues page' do
it 'shows projects only with issues feature enabled', :js do
find('.empty-state .js-lazy-loaded')
find('.new-project-item-link').click
find('.empty-state .new-project-item-link').click
page.within('.select2-results') do
expect(page).to have_content(project.full_name)

View file

@ -41,6 +41,7 @@ exports[`Design management toolbar component renders design and updated data 1`]
/>
<gl-button-stub
aria-label="Download design"
buttontextclasses=""
category="primary"
href="/-/designs/306/7f747adcd4693afadbe968d7ba7d983349b9012d"

View file

@ -0,0 +1,42 @@
import { shallowMount } from '@vue/test-utils';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import CommitCommentsButton from '~/projects/commit/components/commit_comments_button.vue';
describe('CommitCommentsButton', () => {
let wrapper;
const createComponent = (props = {}) => {
wrapper = extendedWrapper(
shallowMount(CommitCommentsButton, {
propsData: {
commentsCount: 1,
...props,
},
}),
);
};
const tooltip = () => wrapper.findByTestId('comment-button-wrapper');
describe('Comment Button', () => {
it('has proper tooltip and button attributes for 1 comment', () => {
createComponent();
expect(tooltip().attributes('title')).toBe('1 comment on this commit');
expect(tooltip().text()).toBe('1');
});
it('has proper tooltip and button attributes for multiple comments', () => {
createComponent({ commentsCount: 2 });
expect(tooltip().attributes('title')).toBe('2 comments on this commit');
expect(tooltip().text()).toBe('2');
});
it('does not show when there are no comments', () => {
createComponent({ commentsCount: 0 });
expect(tooltip().exists()).toBe(false);
});
});
});