Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-09-05 09:12:22 +00:00
parent 6d706d5dc0
commit a7ad649614
75 changed files with 996 additions and 674 deletions

View File

@ -277,6 +277,7 @@ export default {
v-model="variable.key"
:token-list="$options.tokenList"
:label-text="__('Key')"
data-testid="pipeline-form-ci-variable-key"
data-qa-selector="ci_variable_key_field"
/>
@ -293,6 +294,7 @@ export default {
:state="variableValidationState"
rows="3"
max-rows="6"
data-testid="pipeline-form-ci-variable-value"
data-qa-selector="ci_variable_value_field"
class="gl-font-monospace!"
/>

View File

@ -255,6 +255,7 @@ export default {
v-model="key"
:token-list="$options.tokenList"
:label-text="__('Key')"
data-testid="pipeline-form-ci-variable-key"
data-qa-selector="ci_variable_key_field"
/>
@ -271,6 +272,7 @@ export default {
:state="variableValidationState"
rows="3"
max-rows="6"
data-testid="pipeline-form-ci-variable-value"
data-qa-selector="ci_variable_value_field"
class="gl-font-monospace!"
/>

View File

@ -163,7 +163,6 @@ export default {
v-gl-modal="'configure-feature-flags'"
variant="confirm"
category="secondary"
data-qa-selector="configure_feature_flags_button"
data-testid="ff-configure-button"
class="gl-mb-3"
>

View File

@ -142,9 +142,16 @@ export const dayInQuarter = (date, quarter) => {
export const millisecondsPerDay = 1000 * 60 * 60 * 24;
export const getDayDifference = (a, b) => {
const date1 = Date.UTC(a.getFullYear(), a.getMonth(), a.getDate());
const date2 = Date.UTC(b.getFullYear(), b.getMonth(), b.getDate());
/**
* Calculates the number of days between 2 specified dates, excluding the current date
*
* @param {Date} startDate the earlier date that we will substract from the end date
* @param {Date} endDate the last date in the range
* @return {Number} number of days in between
*/
export const getDayDifference = (startDate, endDate) => {
const date1 = Date.UTC(startDate.getFullYear(), startDate.getMonth(), startDate.getDate());
const date2 = Date.UTC(endDate.getFullYear(), endDate.getMonth(), endDate.getDate());
return Math.floor((date2 - date1) / millisecondsPerDay);
};

View File

@ -88,7 +88,8 @@ export default {
:action-primary="actionPrimary"
:title="actionText"
:visible="removeMemberModalVisible"
data-qa-selector="remove_member_modal_content"
data-qa-selector="remove_member_modal"
data-testid="remove-member-modal-content"
@primary="submitForm"
@hide="hideRemoveMemberModal"
>

View File

@ -391,7 +391,11 @@ export default {
};
</script>
<template>
<div class="prometheus-graphs" data-qa-selector="prometheus_graphs">
<div
class="prometheus-graphs"
data-qa-selector="prometheus_graphs_content"
data-testid="prometheus-graphs"
>
<div>
<gl-alert
v-if="!isDeprecationNoticeDismissed"

View File

@ -189,6 +189,7 @@ export default {
ref="monitorEnvironmentsDropdown"
class="flex-grow-1"
data-qa-selector="environments_dropdown"
data-testid="environments-dropdown"
toggle-class="dropdown-menu-toggle"
menu-class="monitor-environment-dropdown-menu"
:text="environmentDropdownText"

View File

@ -59,6 +59,7 @@ export default {
<resolve-discussion-button
v-if="discussion.resolvable"
data-qa-selector="resolve_discussion_button"
data-testid="resolve-discussion-button"
:is-resolving="isResolving"
:button-title="resolveButtonTitle"
@onClick="$emit('resolve')"

View File

@ -98,7 +98,7 @@ export default {
</div>
<template v-else>
<div data-qa-selector="packages-table">
<div data-testid="packages-table">
<packages-list-row
v-for="packageEntity in list"
:key="packageEntity.id"

View File

@ -78,7 +78,7 @@ export default {
</script>
<template>
<list-item data-qa-selector="package_row" :disabled="disabledRow">
<list-item data-testid="package-row" :disabled="disabledRow">
<template #left-primary>
<div class="gl-display-flex gl-align-items-center gl-mr-3 gl-min-w-0">
<gl-link

View File

@ -90,7 +90,7 @@ export default {
</script>
<template>
<list-item data-qa-selector="package_row">
<list-item data-testid="package-row">
<template #left-primary>
<div class="gl-display-flex gl-align-items-center gl-mr-3 gl-min-w-0">
<router-link

View File

@ -151,7 +151,7 @@ export default {
@primaryAction="showConfirmationModal"
>{{ $options.i18n.errorMessageBodyAlert }}</gl-alert
>
<div data-qa-selector="packages-table">
<div data-testid="packages-table">
<packages-list-row
v-for="packageEntity in list"
:key="packageEntity.id"

View File

@ -87,7 +87,7 @@ export default {
v-else-if="!loadingContentFailed && !isLoadingContent"
ref="content"
data-qa-selector="wiki_page_content"
data-testid="wiki_page_content"
data-testid="wiki-page-content"
class="js-wiki-page-content md"
v-html="content /* eslint-disable-line vue/no-v-html */"
></div>

View File

@ -244,7 +244,7 @@ export default {
/><span class="position-relative">{{ fullPath }}</span>
</component>
<!-- eslint-disable @gitlab/vue-require-i18n-strings -->
<gl-badge v-if="lfsOid" variant="muted" size="sm" class="ml-1" data-qa-selector="label-lfs"
<gl-badge v-if="lfsOid" variant="muted" size="sm" class="ml-1" data-testid="label-lfs"
>LFS</gl-badge
>
<!-- eslint-enable @gitlab/vue-require-i18n-strings -->

View File

@ -32,17 +32,14 @@ export default {
<div>
<runner-status-badge
:runner="runner"
size="sm"
class="gl-display-inline-block gl-max-w-full gl-text-truncate"
/>
<runner-upgrade-status-badge
:runner="runner"
size="sm"
class="gl-display-inline-block gl-max-w-full gl-text-truncate"
/>
<runner-paused-badge
v-if="paused"
size="sm"
class="gl-display-inline-block gl-max-w-full gl-text-truncate"
/>
</div>

View File

@ -57,7 +57,7 @@ export default {
:title="$options.i18n.I18N_LOCKED_RUNNER_DESCRIPTION"
name="lock"
/>
<runner-type-badge class="gl-ml-2" :type="runnerType" size="sm" />
<runner-type-badge class="gl-ml-2 gl-vertical-align-middle" :type="runnerType" size="sm" />
<tooltip-on-truncate class="gl-display-block gl-text-truncate" :title="description">
{{ description }}

View File

@ -38,31 +38,33 @@ export default {
</script>
<template>
<div
class="gl-display-flex gl-align-items-center gl-py-5 gl-border-b-1 gl-border-b-solid gl-border-b-gray-100"
class="gl-display-flex gl-justify-content-space-between gl-align-items-center gl-gap-3 gl-flex-wrap gl-py-5 gl-border-b-1 gl-border-b-solid gl-border-b-gray-100"
>
<div>
<div class="gl-display-flex gl-align-items-flex-start gl-gap-3 gl-flex-wrap">
<runner-status-badge :runner="runner" />
<runner-type-badge v-if="runner" :type="runner.runnerType" />
<template v-if="runner.createdAt">
<gl-sprintf :message="__('%{runner} created %{timeago}')">
<template #runner>
<strong>{{ heading }}</strong>
<gl-icon
v-if="runner.locked"
v-gl-tooltip="$options.I18N_LOCKED_RUNNER_DESCRIPTION"
name="lock"
:aria-label="$options.I18N_LOCKED_RUNNER_DESCRIPTION"
/>
</template>
<template #timeago>
<time-ago :time="runner.createdAt" />
</template>
</gl-sprintf>
</template>
<template v-else>
<strong>{{ heading }}</strong>
</template>
<span>
<template v-if="runner.createdAt">
<gl-sprintf :message="__('%{runner} created %{timeago}')">
<template #runner>
<strong>{{ heading }}</strong>
<gl-icon
v-if="runner.locked"
v-gl-tooltip="$options.I18N_LOCKED_RUNNER_DESCRIPTION"
name="lock"
:aria-label="$options.I18N_LOCKED_RUNNER_DESCRIPTION"
/>
</template>
<template #timeago>
<time-ago :time="runner.createdAt" />
</template>
</gl-sprintf>
</template>
<template v-else>
<strong>{{ heading }}</strong>
</template>
</span>
</div>
<div class="gl-ml-auto gl-flex-shrink-0"><slot name="actions"></slot></div>
<div class="gl-display-flex gl-gap-3 gl-flex-wrap"><slot name="actions"></slot></div>
</div>
</template>

View File

@ -14,7 +14,7 @@ export default {
};
</script>
<template>
<span class="gl-font-weight-bold"
<span class="gl-font-weight-bold gl-vertical-align-middle"
>#{{ getIdFromGraphQLId(runner.id) }} ({{ runner.shortSha }})</span
>
</template>

View File

@ -1,6 +1,6 @@
<script>
import { GlBadge, GlTooltipDirective } from '@gitlab/ui';
import { I18N_PAUSED_DESCRIPTION } from '../constants';
import { I18N_PAUSED, I18N_PAUSED_DESCRIPTION } from '../constants';
export default {
components: {
@ -9,11 +9,17 @@ export default {
directives: {
GlTooltip: GlTooltipDirective,
},
I18N_PAUSED,
I18N_PAUSED_DESCRIPTION,
};
</script>
<template>
<gl-badge v-gl-tooltip="$options.I18N_PAUSED_DESCRIPTION" variant="danger" v-bind="$attrs">
{{ s__('Runners|paused') }}
<gl-badge
v-gl-tooltip="$options.I18N_PAUSED_DESCRIPTION"
variant="warning"
icon="status-paused"
v-bind="$attrs"
>
{{ $options.I18N_PAUSED }}
</gl-badge>
</template>

View File

@ -1,8 +1,12 @@
<script>
import { GlBadge, GlTooltipDirective } from '@gitlab/ui';
import { __, s__, sprintf } from '~/locale';
import { __, sprintf } from '~/locale';
import { getTimeago } from '~/lib/utils/datetime_utility';
import {
I18N_STATUS_ONLINE,
I18N_STATUS_NEVER_CONTACTED,
I18N_STATUS_OFFLINE,
I18N_STATUS_STALE,
I18N_ONLINE_TIMEAGO_TOOLTIP,
I18N_NEVER_CONTACTED_TOOLTIP,
I18N_OFFLINE_TIMEAGO_TOOLTIP,
@ -39,26 +43,30 @@ export default {
switch (this.runner?.status) {
case STATUS_ONLINE:
return {
icon: 'status-active',
variant: 'success',
label: s__('Runners|online'),
label: I18N_STATUS_ONLINE,
tooltip: this.timeAgoTooltip(I18N_ONLINE_TIMEAGO_TOOLTIP),
};
case STATUS_NEVER_CONTACTED:
return {
icon: 'time-out',
variant: 'muted',
label: s__('Runners|never contacted'),
label: I18N_STATUS_NEVER_CONTACTED,
tooltip: I18N_NEVER_CONTACTED_TOOLTIP,
};
case STATUS_OFFLINE:
return {
icon: 'time-out',
variant: 'muted',
label: s__('Runners|offline'),
label: I18N_STATUS_OFFLINE,
tooltip: this.timeAgoTooltip(I18N_OFFLINE_TIMEAGO_TOOLTIP),
};
case STATUS_STALE:
return {
icon: 'time-out',
variant: 'warning',
label: s__('Runners|stale'),
label: I18N_STATUS_STALE,
// runner may have contacted (or not) and be stale: consider both cases.
tooltip: this.runner.contactedAt
? this.timeAgoTooltip(I18N_STALE_TIMEAGO_TOOLTIP)
@ -77,7 +85,13 @@ export default {
};
</script>
<template>
<gl-badge v-if="badge" v-gl-tooltip="badge.tooltip" :variant="badge.variant" v-bind="$attrs">
<gl-badge
v-if="badge"
v-gl-tooltip="badge.tooltip"
:variant="badge.variant"
:icon="badge.icon"
v-bind="$attrs"
>
{{ badge.label }}
</gl-badge>
</template>

View File

@ -1,26 +1,31 @@
<script>
import { GlBadge, GlTooltipDirective } from '@gitlab/ui';
import { s__ } from '~/locale';
import {
INSTANCE_TYPE,
GROUP_TYPE,
PROJECT_TYPE,
I18N_INSTANCE_TYPE,
I18N_INSTANCE_RUNNER_DESCRIPTION,
I18N_GROUP_TYPE,
I18N_GROUP_RUNNER_DESCRIPTION,
I18N_PROJECT_TYPE,
I18N_PROJECT_RUNNER_DESCRIPTION,
} from '../constants';
const BADGE_DATA = {
[INSTANCE_TYPE]: {
text: s__('Runners|shared'),
icon: 'users',
text: I18N_INSTANCE_TYPE,
tooltip: I18N_INSTANCE_RUNNER_DESCRIPTION,
},
[GROUP_TYPE]: {
text: s__('Runners|group'),
icon: 'group',
text: I18N_GROUP_TYPE,
tooltip: I18N_GROUP_RUNNER_DESCRIPTION,
},
[PROJECT_TYPE]: {
text: s__('Runners|specific'),
icon: 'project',
text: I18N_PROJECT_TYPE,
tooltip: I18N_PROJECT_RUNNER_DESCRIPTION,
},
};
@ -50,7 +55,13 @@ export default {
};
</script>
<template>
<gl-badge v-if="badge" v-gl-tooltip="badge.tooltip" variant="info" v-bind="$attrs">
<gl-badge
v-if="badge"
v-gl-tooltip="badge.tooltip"
variant="muted"
:icon="badge.icon"
v-bind="$attrs"
>
{{ badge.text }}
</gl-badge>
</template>

View File

@ -1,7 +1,7 @@
import { __, s__ } from '~/locale';
import { __ } from '~/locale';
import { OPERATOR_IS_ONLY } from '~/vue_shared/components/filtered_search_bar/constants';
import BaseToken from '~/vue_shared/components/filtered_search_bar/tokens/base_token.vue';
import { PARAM_KEY_PAUSED } from '../../constants';
import { PARAM_KEY_PAUSED, I18N_PAUSED } from '../../constants';
const options = [
{ value: 'true', title: __('Yes') },
@ -10,7 +10,7 @@ const options = [
export const pausedTokenConfig = {
icon: 'pause',
title: s__('Runners|Paused'),
title: I18N_PAUSED,
type: PARAM_KEY_PAUSED,
token: BaseToken,
unique: true,

View File

@ -1,7 +1,11 @@
import { __, s__ } from '~/locale';
import { __ } from '~/locale';
import { OPERATOR_IS_ONLY } from '~/vue_shared/components/filtered_search_bar/constants';
import BaseToken from '~/vue_shared/components/filtered_search_bar/tokens/base_token.vue';
import {
I18N_STATUS_ONLINE,
I18N_STATUS_NEVER_CONTACTED,
I18N_STATUS_OFFLINE,
I18N_STATUS_STALE,
STATUS_ONLINE,
STATUS_OFFLINE,
STATUS_NEVER_CONTACTED,
@ -10,10 +14,10 @@ import {
} from '../../constants';
const options = [
{ value: STATUS_ONLINE, title: s__('Runners|Online') },
{ value: STATUS_OFFLINE, title: s__('Runners|Offline') },
{ value: STATUS_NEVER_CONTACTED, title: s__('Runners|Never contacted') },
{ value: STATUS_STALE, title: s__('Runners|Stale') },
{ value: STATUS_ONLINE, title: I18N_STATUS_ONLINE },
{ value: STATUS_OFFLINE, title: I18N_STATUS_OFFLINE },
{ value: STATUS_NEVER_CONTACTED, title: I18N_STATUS_NEVER_CONTACTED },
{ value: STATUS_STALE, title: I18N_STATUS_STALE },
];
export const statusTokenConfig = {

View File

@ -1,7 +1,13 @@
<script>
import { s__ } from '~/locale';
import RunnerSingleStat from '~/runner/components/stat/runner_single_stat.vue';
import { STATUS_ONLINE, STATUS_OFFLINE, STATUS_STALE } from '../../constants';
import {
I18N_STATUS_ONLINE,
I18N_STATUS_OFFLINE,
I18N_STATUS_STALE,
STATUS_ONLINE,
STATUS_OFFLINE,
STATUS_STALE,
} from '../../constants';
export default {
components: {
@ -29,7 +35,7 @@ export default {
skip: this.statusCountSkip(STATUS_ONLINE),
variables: { ...this.variables, status: STATUS_ONLINE },
variant: 'success',
title: s__('Runners|Online'),
title: I18N_STATUS_ONLINE,
metaIcon: 'status-active',
},
},
@ -39,7 +45,7 @@ export default {
skip: this.statusCountSkip(STATUS_OFFLINE),
variables: { ...this.variables, status: STATUS_OFFLINE },
variant: 'muted',
title: s__('Runners|Offline'),
title: I18N_STATUS_OFFLINE,
metaIcon: 'status-waiting',
},
},
@ -49,7 +55,7 @@ export default {
skip: this.statusCountSkip(STATUS_STALE),
variables: { ...this.variables, status: STATUS_STALE },
variant: 'warning',
title: s__('Runners|Stale'),
title: I18N_STATUS_STALE,
metaIcon: 'time-out',
},
},

View File

@ -23,6 +23,12 @@ export const I18N_GROUP_RUNNER_DESCRIPTION = s__(
);
export const I18N_PROJECT_RUNNER_DESCRIPTION = s__('Runners|Associated with one or more projects');
// Status
export const I18N_STATUS_ONLINE = s__('Runners|Online');
export const I18N_STATUS_NEVER_CONTACTED = s__('Runners|Never contacted');
export const I18N_STATUS_OFFLINE = s__('Runners|Offline');
export const I18N_STATUS_STALE = s__('Runners|Stale');
// Status help popover
export const I18N_STATUS_POPOVER_TITLE = s__('Runners|Runner statuses');
@ -62,6 +68,7 @@ export const I18N_STALE_NEVER_CONTACTED_TOOLTIP = s__(
export const I18N_EDIT = __('Edit');
export const I18N_PAUSE = __('Pause');
export const I18N_PAUSED = s__('Runners|Paused');
export const I18N_PAUSE_TOOLTIP = s__('Runners|Pause from accepting jobs');
export const I18N_PAUSED_DESCRIPTION = s__('Runners|Not accepting jobs');
@ -94,7 +101,7 @@ export const I18N_NO_JOBS_FOUND = s__('Runners|This runner has not run any jobs.
// Styles
export const RUNNER_TAG_BADGE_VARIANT = 'neutral';
export const RUNNER_TAG_BADGE_VARIANT = 'info';
export const RUNNER_TAG_BG_CLASS = 'gl-bg-blue-100';
// Filtered search parameter names

View File

@ -16,7 +16,7 @@ export default {
<template>
<copyable-field
data-qa-selector="copy-forward-email"
data-testid="copy-forward-email"
:name="s__('RightSidebar|Issue email')"
:clipboard-tooltip-text="s__('RightSidebar|Copy email address')"
:value="issueEmailAddress"

View File

@ -1,2 +1,2 @@
.js-wiki-page-content.md.gl-pt-2{ data: { qa_selector: 'wiki_page_content', testid: 'wiki_page_content', tracking_context: wiki_page_tracking_context(@page).to_json } }
.js-wiki-page-content.md.gl-pt-2{ data: { qa_selector: 'wiki_page_content', testid: 'wiki-page-content', tracking_context: wiki_page_tracking_context(@page).to_json } }
= render_wiki_content(@page)

View File

@ -9,6 +9,6 @@
.gl-mt-5.gl-mb-3
.gl-display-flex.gl-justify-content-space-between
%h2.gl-mt-0.gl-mb-5{ data: { qa_selector: 'wiki_page_title', testid: 'wiki_page_title' } }= @page ? @page.human_title : _('Failed to retrieve page')
.js-wiki-page-content.md.gl-pt-2{ data: { qa_selector: 'wiki_page_content', testid: 'wiki_page_content' } }
.js-wiki-page-content.md.gl-pt-2{ data: { qa_selector: 'wiki_page_content', testid: 'wiki-page-content' } }
= _('The page could not be displayed because it timed out.')
= html_escape(_('You can view the source or %{linkStart}%{cloneIcon} clone the repository%{linkEnd}')) % { linkStart: "<a href=\"#{git_access_url}\">".html_safe, linkEnd: '</a>'.html_safe, cloneIcon: sprite_icon('download', css_class: 'gl-mr-2').html_safe }

View File

@ -27,6 +27,6 @@
- if can?(current_user, :create_wiki, @wiki.container) && @page.latest? && @valid_encoding
= link_to sprite_icon('pencil', css_class: 'gl-icon'), wiki_page_path(@wiki, @page, action: :edit), title: 'Edit', role: "button", class: 'btn gl-button btn-icon btn-default js-wiki-edit', data: { qa_selector: 'edit_page_button', testid: 'wiki_edit_button' }
.js-async-wiki-page-content.md.gl-pt-2{ data: { qa_selector: 'wiki_page_content', testid: 'wiki_page_content', tracking_context: wiki_page_tracking_context(@page).to_json, get_wiki_content_url: wiki_page_render_api_endpoint(@page) } }
.js-async-wiki-page-content.md.gl-pt-2{ data: { qa_selector: 'wiki_page_content', testid: 'wiki-page-content', tracking_context: wiki_page_tracking_context(@page).to_json, get_wiki_content_url: wiki_page_render_api_endpoint(@page) } }
= render 'shared/wikis/sidebar'

View File

@ -2,7 +2,7 @@
name: cache_issue_sums
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/95048
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/365940
milestone: '15.3'
milestone: '15.4'
type: development
group: group::product planning
default_enabled: false

View File

@ -81,7 +81,6 @@ module Gitlab
# @param expires_in [ActiveSupport::Duration, Integer] an expiry time for the cache entry
# @return [Array<String>]
def cached_collection(collection, presenter:, presenter_args:, context:, expires_in:)
total_count = collection.size
misses = 0
json = fetch_multi(presenter, collection, context: context, expires_in: expires_in) do |obj|
@ -92,7 +91,7 @@ module Gitlab
end
end
increment_cache_metric(render_type: :collection, total_count: total_count, miss_count: misses)
increment_cache_metric(render_type: :collection, total_count: collection.length, miss_count: misses)
json.values
end

View File

@ -34251,15 +34251,6 @@ msgstr ""
msgid "Runners|group"
msgstr ""
msgid "Runners|never contacted"
msgstr ""
msgid "Runners|offline"
msgstr ""
msgid "Runners|online"
msgstr ""
msgid "Runners|paused"
msgstr ""
@ -34269,15 +34260,6 @@ msgstr ""
msgid "Runners|specific"
msgstr ""
msgid "Runners|stale"
msgstr ""
msgid "Runners|upgrade available"
msgstr ""
msgid "Runners|upgrade recommended"
msgstr ""
msgid "Runner|Owner"
msgstr ""

View File

@ -9,7 +9,7 @@ module QA
include Page::Component::MembersFilter
view 'app/assets/javascripts/members/components/modals/remove_member_modal.vue' do
element :remove_member_modal_content
element :remove_member_modal
end
view 'app/assets/javascripts/pages/groups/group_members/index.js' do
@ -45,7 +45,7 @@ module QA
click_element :delete_member_button
end
within_element(:remove_member_modal_content) do
within_element(:remove_member_modal) do
click_button("Remove member")
end
end

View File

@ -10,7 +10,7 @@ module QA
LOADING_MESSAGE = 'Waiting for performance data'
view 'app/assets/javascripts/monitoring/components/dashboard.vue' do
element :prometheus_graphs
element :prometheus_graphs_content
end
view 'app/assets/javascripts/monitoring/components/dashboard_header.vue' do
@ -54,7 +54,7 @@ module QA
end
def has_metrics?
within_element :prometheus_graphs do
within_element :prometheus_graphs_content do
has_text?(EXPECTED_TITLE)
end
end
@ -102,7 +102,7 @@ module QA
end
def has_custom_metric?(metric)
within_element :prometheus_graphs do
within_element :prometheus_graphs_content do
has_text?(metric)
end
end
@ -114,7 +114,7 @@ module QA
end
def has_template_metric?(metric)
within_element :prometheus_graphs do
within_element :prometheus_graphs_content do
has_text?(metric)
end
end

View File

@ -6,12 +6,10 @@ module QA
module Packages
class Index < QA::Page::Base
view 'app/assets/javascripts/packages_and_registries/package_registry/components/list/package_list_row.vue' do
element :package_row
element :package_link
end
view 'app/assets/javascripts/packages_and_registries/infrastructure_registry/shared/package_list_row.vue' do
element :package_row
element :package_link
end

View File

@ -263,22 +263,6 @@ module QA
ENV['GITLAB_QA_PASSWORD_6']
end
def gitlab_qa_1p_email
ENV['GITLAB_QA_1P_EMAIL']
end
def gitlab_qa_1p_password
ENV['GITLAB_QA_1P_PASSWORD']
end
def gitlab_qa_1p_secret
ENV['GITLAB_QA_1P_SECRET']
end
def gitlab_qa_1p_github_uuid
ENV['GITLAB_QA_1P_GITHUB_UUID']
end
def jira_admin_username
ENV['JIRA_ADMIN_USERNAME']
end

View File

@ -50,7 +50,7 @@ RSpec.describe "Admin Runners" do
it 'shows an instance badge' do
within_runner_row(instance_runner.id) do
expect(page).to have_selector '.badge', text: 'shared'
expect(page).to have_selector '.badge', text: 'Instance'
end
end
end
@ -66,9 +66,9 @@ RSpec.describe "Admin Runners" do
it 'has all necessary texts' do
expect(page).to have_text "Register an instance runner"
expect(page).to have_text "Online 1"
expect(page).to have_text "Offline 2"
expect(page).to have_text "Stale 1"
expect(page).to have_text "#{s_('Runners|Online')} 1"
expect(page).to have_text "#{s_('Runners|Offline')} 2"
expect(page).to have_text "#{s_('Runners|Stale')} 1"
end
end
@ -145,7 +145,7 @@ RSpec.describe "Admin Runners" do
end
it 'shows paused runners' do
input_filtered_search_filter_is_only('Paused', 'Yes')
input_filtered_search_filter_is_only(s_('Runners|Paused'), 'Yes')
expect(page).to have_link('All 1')
@ -154,7 +154,7 @@ RSpec.describe "Admin Runners" do
end
it 'shows active runners' do
input_filtered_search_filter_is_only('Paused', 'No')
input_filtered_search_filter_is_only(s_('Runners|Paused'), 'No')
expect(page).to have_link('All 1')
@ -186,7 +186,7 @@ RSpec.describe "Admin Runners" do
end
it 'shows correct runner when status matches' do
input_filtered_search_filter_is_only('Status', 'Online')
input_filtered_search_filter_is_only('Status', s_('Runners|Online'))
expect(page).to have_link('All 2')
@ -197,7 +197,7 @@ RSpec.describe "Admin Runners" do
end
it 'shows correct runner when status is selected and search term is entered' do
input_filtered_search_filter_is_only('Status', 'Online')
input_filtered_search_filter_is_only('Status', s_('Runners|Online'))
input_filtered_search_keys('runner-1')
expect(page).to have_link('All 1')
@ -220,7 +220,7 @@ RSpec.describe "Admin Runners" do
expect(page).to have_content 'runner-never-contacted'
within_runner_row(never_contacted.id) do
expect(page).to have_selector '.badge', text: 'never contacted'
expect(page).to have_selector '.badge', text: s_('Runners|Never contacted')
end
end
@ -308,7 +308,7 @@ RSpec.describe "Admin Runners" do
visit admin_runners_path
input_filtered_search_filter_is_only('Paused', 'No')
input_filtered_search_filter_is_only(s_('Runners|Paused'), 'No')
expect(page).to have_content 'runner-project'
expect(page).to have_content 'runner-group'
@ -535,6 +535,36 @@ RSpec.describe "Admin Runners" do
let(:runner_page_path) { admin_runner_path(project_runner) }
end
describe 'breadcrumbs' do
it 'contains the current runner id and token' do
page.within '[data-testid="breadcrumb-links"]' do
expect(page).to have_link("##{project_runner.id} (#{project_runner.short_sha})")
expect(page.find('[data-testid="breadcrumb-current-link"]')).to have_content("Edit")
end
end
end
describe 'runner header', :js do
it 'contains the runner status, type and id' do
expect(page).to have_content("#{s_('Runners|Never contacted')} Project Runner ##{project_runner.id} created")
end
end
context 'when a runner is updated', :js do
before do
click_on _('Save changes')
wait_for_requests
end
it 'show success alert' do
expect(page.find('[data-testid="alert-success"]')).to have_content('saved')
end
it 'redirects to runner page' do
expect(current_url).to match(admin_runner_path(project_runner))
end
end
describe 'projects' do
it 'contains project names' do
expect(page).to have_content(project1.full_name)

View File

@ -3,14 +3,10 @@
require 'spec_helper'
RSpec.describe 'Commit > User view commits' do
let_it_be(:project) { create(:project, :public, :repository) }
let_it_be(:user) { project.creator }
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group, :public) }
before do
visit project_commits_path(project)
end
describe 'Commits List' do
shared_examples 'can view commits' do
it 'displays the correct number of commits per day in the header' do
expect(first('.js-commit-header').find('.commits-count').text).to eq('1 commit')
end
@ -19,4 +15,51 @@ RSpec.describe 'Commit > User view commits' do
expect(page).to have_selector('#commits-list > li:nth-child(2) > ul', count: 1)
end
end
describe 'Commits List' do
context 'when project is public' do
let(:project) { create(:project, :public, :repository, group: group) }
before do
visit project_commits_path(project)
end
it_behaves_like 'can view commits'
end
context 'when project is public with private repository' do
let(:project) { create(:project, :public, :repository, :repository_private, group: group) }
context 'and user is an inherited member from the group' do
context 'and user is a guest' do
before do
group.add_guest(user)
sign_in(user)
visit project_commits_path(project)
end
it_behaves_like 'can view commits'
end
end
end
context 'when project is private' do
let(:project) { create(:project, :private, :repository, group: group) }
context 'and user is an inherited member from the group' do
context 'and user is a guest' do
before do
group.add_guest(user)
sign_in(user)
visit project_commits_path(project)
end
it 'renders not found' do
expect(page).to have_title('Not Found')
expect(page).to have_content('Page Not Found')
end
end
end
end
end
end

View File

@ -61,7 +61,7 @@ RSpec.describe "Group Runners" do
it 'shows a group badge' do
within_runner_row(group_runner.id) do
expect(page).to have_selector '.badge', text: 'group'
expect(page).to have_selector '.badge', text: s_('Runners|Group')
end
end
@ -101,9 +101,9 @@ RSpec.describe "Group Runners" do
let(:runner) { project_runner }
end
it 'shows a project (specific) badge' do
it 'shows a project badge' do
within_runner_row(project_runner.id) do
expect(page).to have_selector '.badge', text: 'specific'
expect(page).to have_selector '.badge', text: s_('Runners|Project')
end
end
@ -137,7 +137,7 @@ RSpec.describe "Group Runners" do
focus_filtered_search
page.within(search_bar_selector) do
expect(page).to have_link('Paused')
expect(page).to have_link(s_('Runners|Paused'))
expect(page).to have_content('Status')
end
end

View File

@ -39,7 +39,7 @@ RSpec.describe 'Resolving all open threads in a merge request from an issue', :j
context 'resolving the thread' do
before do
find('button[data-qa-selector="resolve_discussion_button"]').click # rubocop:disable QA/SelectorUsage
find('button[data-testid="resolve-discussion-button"]').click
end
it 'hides the link for creating a new issue' do

View File

@ -35,7 +35,7 @@ RSpec.describe 'Resolve an open thread in a merge request by creating an issue',
context 'resolving the thread' do
before do
find('button[data-qa-selector="resolve_discussion_button"]').click # rubocop:disable QA/SelectorUsage
find('button[data-testid="resolve-discussion-button"]').click
end
it 'hides the link for creating a new issue' do

View File

@ -302,7 +302,9 @@ RSpec.describe 'Issue Sidebar' do
context 'sidebar', :js do
it 'finds issue copy forwarding email' do
expect(find('[data-qa-selector="copy-forward-email"]').text).to eq "Issue email: #{issue.creatable_note_email_address(user)}" # rubocop:disable QA/SelectorUsage
expect(
find('[data-testid="copy-forward-email"]').text
).to eq "Issue email: #{issue.creatable_note_email_address(user)}"
end
end
@ -338,7 +340,7 @@ RSpec.describe 'Issue Sidebar' do
end
it 'does not find issue email' do
expect(page).not_to have_selector('[data-qa-selector="copy-forward-email"]') # rubocop:disable QA/SelectorUsage
expect(page).not_to have_selector('[data-testid="copy-forward-email"]')
end
end
end

View File

@ -66,7 +66,7 @@ RSpec.describe 'Merge request > User resolves diff notes and threads', :js do
it 'allows user to mark thread as resolved' do
page.within '.diff-content' do
find('button[data-qa-selector="resolve_discussion_button"]').click # rubocop:disable QA/SelectorUsage
find('button[data-testid="resolve-discussion-button"]').click
end
expect(page).to have_selector('.discussion-body', visible: false)
@ -82,7 +82,7 @@ RSpec.describe 'Merge request > User resolves diff notes and threads', :js do
it 'allows user to unresolve thread' do
page.within '.diff-content' do
find('button[data-qa-selector="resolve_discussion_button"]').click # rubocop:disable QA/SelectorUsage
find('button[data-testid="resolve-discussion-button"]').click
click_button 'Unresolve thread'
end
@ -94,7 +94,7 @@ RSpec.describe 'Merge request > User resolves diff notes and threads', :js do
describe 'resolved thread' do
before do
page.within '.diff-content' do
find('button[data-qa-selector="resolve_discussion_button"]').click # rubocop:disable QA/SelectorUsage
find('button[data-testid="resolve-discussion-button"]').click
end
visit_merge_request
@ -194,7 +194,7 @@ RSpec.describe 'Merge request > User resolves diff notes and threads', :js do
it 'allows user to resolve from reply form without a comment' do
page.within '.diff-content' do
find('button[data-qa-selector="resolve_discussion_button"]').click # rubocop:disable QA/SelectorUsage
find('button[data-testid="resolve-discussion-button"]').click
end
page.within '.discussions-counter' do
@ -229,7 +229,7 @@ RSpec.describe 'Merge request > User resolves diff notes and threads', :js do
it 'hides jump to next button when all resolved' do
page.within '.diff-content' do
find('button[data-qa-selector="resolve_discussion_button"]').click # rubocop:disable QA/SelectorUsage
find('button[data-testid="resolve-discussion-button"]').click
end
expect(page).to have_selector('.discussion-next-btn', visible: false)
@ -324,7 +324,7 @@ RSpec.describe 'Merge request > User resolves diff notes and threads', :js do
it 'allows user to mark all threads as resolved' do
page.all('.discussion-reply-holder', count: 2).each do |reply_holder|
page.within reply_holder do
find('button[data-qa-selector="resolve_discussion_button"]').click # rubocop:disable QA/SelectorUsage
find('button[data-testid="resolve-discussion-button"]').click
end
end
@ -335,7 +335,7 @@ RSpec.describe 'Merge request > User resolves diff notes and threads', :js do
it 'allows user to quickly scroll to next unresolved thread' do
page.within('.discussion-reply-holder', match: :first) do
find('button[data-qa-selector="resolve_discussion_button"]').click # rubocop:disable QA/SelectorUsage
find('button[data-testid="resolve-discussion-button"]').click
end
page.within '.discussions-counter' do
@ -406,7 +406,7 @@ RSpec.describe 'Merge request > User resolves diff notes and threads', :js do
it 'allows user to mark thread as resolved' do
page.within '.diff-content' do
find('button[data-qa-selector="resolve_discussion_button"]').click # rubocop:disable QA/SelectorUsage
find('button[data-testid="resolve-discussion-button"]').click
end
page.within '.diff-content .note' do
@ -420,7 +420,7 @@ RSpec.describe 'Merge request > User resolves diff notes and threads', :js do
it 'allows user to unresolve thread' do
page.within '.diff-content' do
find('button[data-qa-selector="resolve_discussion_button"]').click # rubocop:disable QA/SelectorUsage
find('button[data-testid="resolve-discussion-button"]').click
click_button 'Unresolve thread'
end
@ -447,7 +447,7 @@ RSpec.describe 'Merge request > User resolves diff notes and threads', :js do
it 'allows user to comment & unresolve thread' do
page.within '.diff-content' do
find('button[data-qa-selector="resolve_discussion_button"]').click # rubocop:disable QA/SelectorUsage
find('button[data-testid="resolve-discussion-button"]').click
find_field('Reply…').click

View File

@ -30,9 +30,9 @@ RSpec.describe 'Environment > Metrics' do
click_link 'Monitoring'
expect(page).to have_current_path(project_metrics_dashboard_path(project, environment: environment.id))
expect(page).to have_css('[data-qa-selector="environments_dropdown"]') # rubocop:disable QA/SelectorUsage
expect(page).to have_css('[data-testid="environments-dropdown"]')
within('[data-qa-selector="environments_dropdown"]') do # rubocop:disable QA/SelectorUsage
within('[data-testid="environments-dropdown"]') do
# Click on the dropdown
click_on(environment.name)
@ -59,7 +59,7 @@ RSpec.describe 'Environment > Metrics' do
visit_environment(environment)
click_link 'Monitoring'
expect(page).to have_css('[data-qa-selector="prometheus_graphs"]') # rubocop:disable QA/SelectorUsage
expect(page).to have_css('[data-testid="prometheus-graphs"]')
end
it_behaves_like 'has environment selector'

View File

@ -90,7 +90,7 @@ RSpec.describe 'User sees feature flag list', :js do
it 'shows the empty page' do
expect(page).to have_text 'Get started with feature flags'
expect(page).to have_selector('.btn-confirm', text: 'New feature flag')
expect(page).to have_selector('[data-qa-selector="configure_feature_flags_button"]', text: 'Configure') # rubocop:disable QA/SelectorUsage
expect(page).to have_selector('[data-testid="ff-configure-button"]', text: 'Configure')
end
end
end

View File

@ -26,7 +26,7 @@ RSpec.describe 'Projects tree', :js do
expect(page).to have_selector('.tree-item')
expect(page).to have_content('add tests for .gitattributes custom highlighting')
expect(page).not_to have_selector('[data-testid="alert-danger"]')
expect(page).not_to have_selector('[data-qa-selector="label-lfs"]', text: 'LFS') # rubocop:disable QA/SelectorUsage
expect(page).not_to have_selector('[data-testid="label-lfs"]', text: 'LFS')
end
it 'renders tree table for a subtree without errors' do
@ -35,7 +35,7 @@ RSpec.describe 'Projects tree', :js do
expect(page).to have_selector('.tree-item')
expect(page).to have_content('add spaces in whitespace file')
expect(page).not_to have_selector('[data-qa-selector="label-lfs"]', text: 'LFS') # rubocop:disable QA/SelectorUsage
expect(page).not_to have_selector('[data-testid="label-lfs"]', text: 'LFS')
expect(page).not_to have_selector('[data-testid="alert-danger"]')
end
@ -112,7 +112,7 @@ RSpec.describe 'Projects tree', :js do
it 'renders LFS badge on blob item' do
visit project_tree_path(project, File.join('master', 'files/lfs'))
expect(page).to have_selector('[data-qa-selector="label-lfs"]', text: 'LFS') # rubocop:disable QA/SelectorUsage
expect(page).to have_selector('[data-testid="label-lfs"]', text: 'LFS')
end
end

View File

@ -3,7 +3,8 @@
exports[`Dashboard template matches the default snapshot 1`] = `
<div
class="prometheus-graphs"
data-qa-selector="prometheus_graphs"
data-qa-selector="prometheus_graphs_content"
data-testid="prometheus-graphs"
environmentstate="available"
metricsdashboardbasepath="/monitoring/monitor-project/-/metrics?environment=1"
metricsendpoint="/monitoring/monitor-project/-/environments/1/additional_metrics.json"
@ -60,6 +61,7 @@ exports[`Dashboard template matches the default snapshot 1`] = `
clearalltext="Clear all"
clearalltextclass="gl-px-5"
data-qa-selector="environments_dropdown"
data-testid="environments-dropdown"
headertext=""
hideheaderborder="true"
highlighteditemstitle="Selected"

View File

@ -3,7 +3,7 @@
exports[`packages_list_row renders 1`] = `
<div
class="gl-display-flex gl-flex-direction-column gl-border-b-solid gl-border-t-solid gl-border-t-1 gl-border-b-1 gl-border-t-transparent gl-border-b-gray-100"
data-qa-selector="package_row"
data-testid="package-row"
>
<div
class="gl-display-flex gl-align-items-center gl-py-3"

View File

@ -3,7 +3,7 @@
exports[`packages_list_row renders 1`] = `
<div
class="gl-display-flex gl-flex-direction-column gl-border-b-solid gl-border-t-solid gl-border-t-1 gl-border-b-1 gl-border-t-transparent gl-border-b-gray-100"
data-qa-selector="package_row"
data-testid="package-row"
>
<div
class="gl-display-flex gl-align-items-center gl-py-3"

View File

@ -38,7 +38,7 @@ describe('pages/shared/wikis/components/wiki_content', () => {
const findGlAlert = () => wrapper.findComponent(GlAlert);
const findGlSkeletonLoader = () => wrapper.findComponent(GlSkeletonLoader);
const findContent = () => wrapper.find('[data-testid="wiki_page_content"]');
const findContent = () => wrapper.find('[data-testid="wiki-page-content"]');
describe('when loading content', () => {
beforeEach(() => {

View File

@ -33,6 +33,12 @@ import {
CREATED_ASC,
CREATED_DESC,
DEFAULT_SORT,
I18N_STATUS_ONLINE,
I18N_STATUS_OFFLINE,
I18N_STATUS_STALE,
I18N_INSTANCE_TYPE,
I18N_GROUP_TYPE,
I18N_PROJECT_TYPE,
INSTANCE_TYPE,
PARAM_KEY_PAUSED,
PARAM_KEY_STATUS,
@ -156,15 +162,16 @@ describe('AdminRunnersApp', () => {
});
it('shows the runner tabs', () => {
expect(findRunnerTypeTabs().text()).toMatchInterpolatedText(
`All ${mockRunnersCount} Instance ${mockRunnersCount} Group ${mockRunnersCount} Project ${mockRunnersCount}`,
const tabs = findRunnerTypeTabs().text();
expect(tabs).toMatchInterpolatedText(
`All ${mockRunnersCount} ${I18N_INSTANCE_TYPE} ${mockRunnersCount} ${I18N_GROUP_TYPE} ${mockRunnersCount} ${I18N_PROJECT_TYPE} ${mockRunnersCount}`,
);
});
it('shows the total', () => {
expect(findRunnerStats().text()).toContain(`${s__('Runners|Online')} ${mockRunnersCount}`);
expect(findRunnerStats().text()).toContain(`${s__('Runners|Offline')} ${mockRunnersCount}`);
expect(findRunnerStats().text()).toContain(`${s__('Runners|Stale')} ${mockRunnersCount}`);
expect(findRunnerStats().text()).toContain(`${I18N_STATUS_ONLINE} ${mockRunnersCount}`);
expect(findRunnerStats().text()).toContain(`${I18N_STATUS_OFFLINE} ${mockRunnersCount}`);
expect(findRunnerStats().text()).toContain(`${I18N_STATUS_STALE} ${mockRunnersCount}`);
});
});

View File

@ -6,7 +6,7 @@ import RunnerTags from '~/runner/components/runner_tags.vue';
import RunnerSummaryField from '~/runner/components/cells/runner_summary_field.vue';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { INSTANCE_TYPE, PROJECT_TYPE } from '~/runner/constants';
import { INSTANCE_TYPE, I18N_INSTANCE_TYPE, PROJECT_TYPE } from '~/runner/constants';
import { allRunnersData } from '../../mock_data';
@ -69,7 +69,7 @@ describe('RunnerTypeCell', () => {
locked: true,
});
expect(wrapper.text()).toContain('shared');
expect(wrapper.text()).toContain(I18N_INSTANCE_TYPE);
});
it('Displays the runner version', () => {

View File

@ -3,7 +3,14 @@ import RunnerStatusCell from '~/runner/components/cells/runner_status_cell.vue';
import RunnerStatusBadge from '~/runner/components/runner_status_badge.vue';
import RunnerPausedBadge from '~/runner/components/runner_paused_badge.vue';
import { INSTANCE_TYPE, STATUS_ONLINE, STATUS_OFFLINE } from '~/runner/constants';
import {
I18N_PAUSED,
I18N_STATUS_ONLINE,
I18N_STATUS_OFFLINE,
INSTANCE_TYPE,
STATUS_ONLINE,
STATUS_OFFLINE,
} from '~/runner/constants';
describe('RunnerStatusCell', () => {
let wrapper;
@ -31,8 +38,8 @@ describe('RunnerStatusCell', () => {
it('Displays online status', () => {
createComponent();
expect(wrapper.text()).toMatchInterpolatedText('online');
expect(findStatusBadge().text()).toBe('online');
expect(wrapper.text()).toContain(I18N_STATUS_ONLINE);
expect(findStatusBadge().text()).toBe(I18N_STATUS_ONLINE);
});
it('Displays offline status', () => {
@ -42,8 +49,8 @@ describe('RunnerStatusCell', () => {
},
});
expect(wrapper.text()).toMatchInterpolatedText('offline');
expect(findStatusBadge().text()).toBe('offline');
expect(wrapper.text()).toMatchInterpolatedText(I18N_STATUS_OFFLINE);
expect(findStatusBadge().text()).toBe(I18N_STATUS_OFFLINE);
});
it('Displays paused status', () => {
@ -54,8 +61,8 @@ describe('RunnerStatusCell', () => {
},
});
expect(wrapper.text()).toMatchInterpolatedText('online paused');
expect(findPausedBadge().text()).toBe('paused');
expect(wrapper.text()).toMatchInterpolatedText(`${I18N_STATUS_ONLINE} ${I18N_PAUSED}`);
expect(findPausedBadge().text()).toBe(I18N_PAUSED);
});
it('Is empty when data is missing', () => {

View File

@ -2,7 +2,7 @@ import { __ } from '~/locale';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import RunnerSummaryCell from '~/runner/components/cells/runner_summary_cell.vue';
import RunnerTags from '~/runner/components/runner_tags.vue';
import { INSTANCE_TYPE, PROJECT_TYPE } from '~/runner/constants';
import { INSTANCE_TYPE, I18N_INSTANCE_TYPE, PROJECT_TYPE } from '~/runner/constants';
const mockId = '1';
const mockShortSha = '2P6oDVDm';
@ -46,7 +46,7 @@ describe('RunnerTypeCell', () => {
});
it('Displays the runner type', () => {
expect(wrapper.text()).toContain('shared');
expect(wrapper.text()).toContain(I18N_INSTANCE_TYPE);
});
it('Does not display the locked icon', () => {

View File

@ -1,6 +1,6 @@
import { GlSprintf } from '@gitlab/ui';
import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { GROUP_TYPE, STATUS_ONLINE } from '~/runner/constants';
import { I18N_STATUS_ONLINE, I18N_GROUP_TYPE, GROUP_TYPE, STATUS_ONLINE } from '~/runner/constants';
import { TYPE_CI_RUNNER } from '~/graphql_shared/constants';
import { convertToGraphQLId } from '~/graphql_shared/utils';
import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
@ -49,7 +49,7 @@ describe('RunnerHeader', () => {
},
});
expect(findRunnerStatusBadge().text()).toContain('online');
expect(findRunnerStatusBadge().text()).toContain(I18N_STATUS_ONLINE);
});
it('displays the runner type', () => {
@ -60,7 +60,7 @@ describe('RunnerHeader', () => {
},
});
expect(findRunnerTypeBadge().text()).toContain('group');
expect(findRunnerTypeBadge().text()).toContain(I18N_GROUP_TYPE);
});
it('displays the runner id', () => {

View File

@ -7,6 +7,7 @@ import {
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import RunnerList from '~/runner/components/runner_list.vue';
import RunnerStatusPopover from '~/runner/components/runner_status_popover.vue';
import { I18N_PROJECT_TYPE, I18N_STATUS_NEVER_CONTACTED } from '~/runner/constants';
import { allRunnersData, onlineContactTimeoutSecs, staleTimeoutSecs } from '../mock_data';
const mockRunners = allRunnersData.data.runners.nodes;
@ -91,7 +92,7 @@ describe('RunnerList', () => {
createComponent({}, mountExtended);
// Badges
expect(findCell({ fieldKey: 'status' }).text()).toMatchInterpolatedText('never contacted');
expect(findCell({ fieldKey: 'status' }).text()).toBe(I18N_STATUS_NEVER_CONTACTED);
// Runner summary
expect(findCell({ fieldKey: 'summary' }).text()).toContain(
@ -262,13 +263,15 @@ describe('RunnerList', () => {
const numericId = getIdFromGraphQLId(id);
// Badges
expect(findCell({ fieldKey: 'status' }).text()).toMatchInterpolatedText('never contacted');
expect(findCell({ fieldKey: 'status' }).text()).toMatchInterpolatedText(
I18N_STATUS_NEVER_CONTACTED,
);
// Runner summary
const summary = findCell({ fieldKey: 'summary' }).text();
expect(summary).toContain(`#${numericId} (${shortSha})`);
expect(summary).toContain('specific');
expect(summary).toContain(I18N_PROJECT_TYPE);
expect(summary).toContain(version);
expect(summary).toContain(description);

View File

@ -2,6 +2,7 @@ import { GlBadge } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import RunnerStatePausedBadge from '~/runner/components/runner_paused_badge.vue';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import { I18N_PAUSED } from '~/runner/constants';
describe('RunnerTypeBadge', () => {
let wrapper;
@ -29,8 +30,8 @@ describe('RunnerTypeBadge', () => {
});
it('renders paused state', () => {
expect(wrapper.text()).toBe('paused');
expect(findBadge().props('variant')).toBe('danger');
expect(wrapper.text()).toBe(I18N_PAUSED);
expect(findBadge().props('variant')).toBe('warning');
});
it('renders tooltip', () => {

View File

@ -3,12 +3,16 @@ import { shallowMount } from '@vue/test-utils';
import RunnerStatusBadge from '~/runner/components/runner_status_badge.vue';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import {
I18N_STATUS_ONLINE,
I18N_STATUS_NEVER_CONTACTED,
I18N_STATUS_OFFLINE,
I18N_STATUS_STALE,
I18N_NEVER_CONTACTED_TOOLTIP,
I18N_STALE_NEVER_CONTACTED_TOOLTIP,
STATUS_ONLINE,
STATUS_OFFLINE,
STATUS_STALE,
STATUS_NEVER_CONTACTED,
I18N_NEVER_CONTACTED_TOOLTIP,
I18N_STALE_NEVER_CONTACTED_TOOLTIP,
} from '~/runner/constants';
describe('RunnerTypeBadge', () => {
@ -46,7 +50,7 @@ describe('RunnerTypeBadge', () => {
it('renders online state', () => {
createComponent();
expect(wrapper.text()).toBe('online');
expect(wrapper.text()).toBe(I18N_STATUS_ONLINE);
expect(findBadge().props('variant')).toBe('success');
expect(getTooltip().value).toBe('Runner is online; last contact was 1 minute ago');
});
@ -59,7 +63,7 @@ describe('RunnerTypeBadge', () => {
},
});
expect(wrapper.text()).toBe('never contacted');
expect(wrapper.text()).toBe(I18N_STATUS_NEVER_CONTACTED);
expect(findBadge().props('variant')).toBe('muted');
expect(getTooltip().value).toBe(I18N_NEVER_CONTACTED_TOOLTIP);
});
@ -72,7 +76,7 @@ describe('RunnerTypeBadge', () => {
},
});
expect(wrapper.text()).toBe('offline');
expect(wrapper.text()).toBe(I18N_STATUS_OFFLINE);
expect(findBadge().props('variant')).toBe('muted');
expect(getTooltip().value).toBe('Runner is offline; last contact was 1 day ago');
});
@ -85,7 +89,7 @@ describe('RunnerTypeBadge', () => {
},
});
expect(wrapper.text()).toBe('stale');
expect(wrapper.text()).toBe(I18N_STATUS_STALE);
expect(findBadge().props('variant')).toBe('warning');
expect(getTooltip().value).toBe('Runner is stale; last contact was 1 year ago');
});
@ -98,7 +102,7 @@ describe('RunnerTypeBadge', () => {
},
});
expect(wrapper.text()).toBe('stale');
expect(wrapper.text()).toBe(I18N_STATUS_STALE);
expect(findBadge().props('variant')).toBe('warning');
expect(getTooltip().value).toBe(I18N_STALE_NEVER_CONTACTED_TOOLTIP);
});
@ -112,7 +116,7 @@ describe('RunnerTypeBadge', () => {
},
});
expect(wrapper.text()).toBe('online');
expect(wrapper.text()).toBe(I18N_STATUS_ONLINE);
expect(getTooltip().value).toBe('Runner is online; last contact was never');
});

View File

@ -1,6 +1,8 @@
import { GlBadge } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
import { RUNNER_TAG_BADGE_VARIANT } from '~/runner/constants';
import RunnerTag from '~/runner/components/runner_tag.vue';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
@ -48,7 +50,7 @@ describe('RunnerTag', () => {
it('Displays tags with correct style', () => {
expect(findBadge().props()).toMatchObject({
size: 'sm',
variant: 'neutral',
variant: RUNNER_TAG_BADGE_VARIANT,
});
});

View File

@ -34,7 +34,6 @@ describe('RunnerTags', () => {
it('Displays tags with correct style', () => {
expect(findBadge().props('size')).toBe('sm');
expect(findBadge().props('variant')).toBe('neutral');
});
it('Displays tags with md size', () => {

View File

@ -2,7 +2,14 @@ import { GlBadge } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import RunnerTypeBadge from '~/runner/components/runner_type_badge.vue';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import { INSTANCE_TYPE, GROUP_TYPE, PROJECT_TYPE } from '~/runner/constants';
import {
INSTANCE_TYPE,
GROUP_TYPE,
PROJECT_TYPE,
I18N_INSTANCE_TYPE,
I18N_GROUP_TYPE,
I18N_PROJECT_TYPE,
} from '~/runner/constants';
describe('RunnerTypeBadge', () => {
let wrapper;
@ -27,9 +34,9 @@ describe('RunnerTypeBadge', () => {
describe.each`
type | text
${INSTANCE_TYPE} | ${'shared'}
${GROUP_TYPE} | ${'group'}
${PROJECT_TYPE} | ${'specific'}
${INSTANCE_TYPE} | ${I18N_INSTANCE_TYPE}
${GROUP_TYPE} | ${I18N_GROUP_TYPE}
${PROJECT_TYPE} | ${I18N_PROJECT_TYPE}
`('displays $type runner', ({ type, text }) => {
beforeEach(() => {
createComponent({ props: { type } });
@ -37,7 +44,7 @@ describe('RunnerTypeBadge', () => {
it(`as "${text}" with an "info" variant`, () => {
expect(findBadge().text()).toBe(text);
expect(findBadge().props('variant')).toBe('info');
expect(findBadge().props('variant')).toBe('muted');
});
it('with a tooltip', () => {

View File

@ -1,8 +1,15 @@
import { shallowMount, mount } from '@vue/test-utils';
import { s__ } from '~/locale';
import RunnerStats from '~/runner/components/stat/runner_stats.vue';
import RunnerSingleStat from '~/runner/components/stat/runner_single_stat.vue';
import { INSTANCE_TYPE, STATUS_ONLINE, STATUS_OFFLINE, STATUS_STALE } from '~/runner/constants';
import {
I18N_STATUS_ONLINE,
I18N_STATUS_OFFLINE,
I18N_STATUS_STALE,
INSTANCE_TYPE,
STATUS_ONLINE,
STATUS_OFFLINE,
STATUS_STALE,
} from '~/runner/constants';
describe('RunnerStats', () => {
let wrapper;
@ -46,9 +53,9 @@ describe('RunnerStats', () => {
});
const text = wrapper.text();
expect(text).toContain(`${s__('Runners|Online')} 3`);
expect(text).toContain(`${s__('Runners|Offline')} 2`);
expect(text).toContain(`${s__('Runners|Stale')} 1`);
expect(text).toContain(`${I18N_STATUS_ONLINE} 3`);
expect(text).toContain(`${I18N_STATUS_OFFLINE} 2`);
expect(text).toContain(`${I18N_STATUS_STALE} 1`);
});
it('Skips query for other stats', () => {

View File

@ -28,6 +28,9 @@ import {
CREATED_ASC,
CREATED_DESC,
DEFAULT_SORT,
I18N_STATUS_ONLINE,
I18N_STATUS_OFFLINE,
I18N_STATUS_STALE,
INSTANCE_TYPE,
GROUP_TYPE,
PARAM_KEY_PAUSED,
@ -154,9 +157,9 @@ describe('GroupRunnersApp', () => {
});
const text = findRunnerStats().text();
expect(text).toContain(`${s__('Runners|Online')} ${mockGroupRunnersCount}`);
expect(text).toContain(`${s__('Runners|Offline')} ${mockGroupRunnersCount}`);
expect(text).toContain(`${s__('Runners|Stale')} ${mockGroupRunnersCount}`);
expect(text).toContain(`${I18N_STATUS_ONLINE} ${mockGroupRunnersCount}`);
expect(text).toContain(`${I18N_STATUS_OFFLINE} ${mockGroupRunnersCount}`);
expect(text).toContain(`${I18N_STATUS_STALE} ${mockGroupRunnersCount}`);
});
it('shows the runners list', async () => {

View File

@ -11,6 +11,7 @@ import RunnerUpdateForm from '~/runner/components/runner_update_form.vue';
import runnerFormQuery from '~/runner/graphql/edit/runner_form.query.graphql';
import RunnerEditApp from '~//runner/runner_edit/runner_edit_app.vue';
import { captureException } from '~/runner/sentry_utils';
import { I18N_STATUS_NEVER_CONTACTED, I18N_INSTANCE_TYPE } from '~/runner/constants';
import { runnerFormData } from '../mock_data';
@ -69,8 +70,8 @@ describe('RunnerEditApp', () => {
it('displays the runner type and status', async () => {
await createComponentWithApollo({ mountFn: mount });
expect(findRunnerHeader().text()).toContain(`never contacted`);
expect(findRunnerHeader().text()).toContain(`shared`);
expect(findRunnerHeader().text()).toContain(I18N_STATUS_NEVER_CONTACTED);
expect(findRunnerHeader().text()).toContain(I18N_INSTANCE_TYPE);
});
it('displays a loading runner form', () => {

View File

@ -9,6 +9,7 @@ RSpec.describe Mutations::Commits::Create do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :public, :repository) }
let_it_be(:group) { create(:group, :public) }
let(:context) do
GraphQL::Query::Context.new(
@ -39,20 +40,201 @@ RSpec.describe Mutations::Commits::Create do
let(:mutated_commit) { subject[:commit] }
it 'raises an error if the resource is not accessible to the user' do
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
context 'when user does not have enough permissions' do
before do
project.add_guest(user)
end
context 'when user is not a project member' do
it 'raises an error' do
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
context 'when user is a direct project member' do
context 'and user is a guest' do
before do
project.add_guest(user)
end
it 'raises an error' do
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
context 'and user is a developer' do
let(:deltas) { mutated_commit.raw_deltas }
before_all do
project.add_developer(user)
end
context 'when service successfully creates a new commit' do
it "returns the ETag path for the commit's pipeline" do
commit_pipeline_path = subject[:commit_pipeline_path]
expect(commit_pipeline_path).to match(%r(pipelines/sha/\w+))
end
it 'returns the content of the commit' do
expect(subject[:content]).to eq(actions.pluck(:content))
end
it 'returns a new commit' do
expect(mutated_commit).to have_attributes(message: message, project: project)
expect(subject[:errors]).to be_empty
expect_to_contain_deltas([
a_hash_including(a_mode: '0', b_mode: '100644', new_file: true, new_path: file_path)
])
end
end
context 'when request has multiple actions' do
let(:actions) do
[
{
action: 'create',
file_path: 'foo/foobar',
content: 'some content'
},
{
action: 'delete',
file_path: 'README.md'
},
{
action: 'move',
file_path: "LICENSE.md",
previous_path: "LICENSE",
content: "some content"
},
{
action: 'update',
file_path: 'VERSION',
content: 'new content'
},
{
action: 'chmod',
file_path: 'CHANGELOG',
execute_filemode: true
}
]
end
it 'returns a new commit' do
expect(mutated_commit).to have_attributes(message: message, project: project)
expect(subject[:errors]).to be_empty
expect_to_contain_deltas([
a_hash_including(a_mode: '0', b_mode: '100644', new_path: 'foo/foobar'),
a_hash_including(deleted_file: true, new_path: 'README.md'),
a_hash_including(deleted_file: true, new_path: 'LICENSE'),
a_hash_including(new_file: true, new_path: 'LICENSE.md'),
a_hash_including(new_file: false, new_path: 'VERSION'),
a_hash_including(a_mode: '100644', b_mode: '100755', new_path: 'CHANGELOG')
])
end
end
context 'when actions are not defined' do
let(:actions) { [] }
it 'returns a new commit' do
expect(mutated_commit).to have_attributes(message: message, project: project)
expect(subject[:errors]).to be_empty
expect_to_contain_deltas([])
end
end
context 'when branch does not exist' do
let(:branch) { 'unknown' }
it 'returns errors' do
expect(mutated_commit).to be_nil
expect(subject[:errors]).to match_array(['You can only create or edit files when you are on a branch'])
end
end
context 'when branch does not exist and a start branch is provided' do
let(:branch) { 'my-branch' }
let(:start_branch) { 'master' }
let(:actions) do
[
{
action: 'create',
file_path: 'ANOTHER_FILE.md',
content: 'Bye'
}
]
end
it 'returns a new commit' do
expect(mutated_commit).to have_attributes(message: message, project: project)
expect(subject[:errors]).to be_empty
expect(subject[:content]).to eq(actions.pluck(:content))
expect_to_contain_deltas([
a_hash_including(a_mode: '0', b_mode: '100644', new_file: true, new_path: 'ANOTHER_FILE.md')
])
end
end
context 'when message is not set' do
let(:message) { nil }
it 'returns errors' do
expect(mutated_commit).to be_nil
expect(subject[:errors].to_s).to match(/3:UserCommitFiles: empty CommitMessage/)
end
end
context 'when actions are incorrect' do
let(:actions) { [{ action: 'unknown', file_path: 'test.md', content: '' }] }
it 'returns errors' do
expect(mutated_commit).to be_nil
expect(subject[:errors]).to match_array(['Unknown action \'unknown\''])
end
end
context 'when branch is protected' do
before do
create(:protected_branch, project: project, name: branch)
end
it 'returns errors' do
expect(mutated_commit).to be_nil
expect(subject[:errors]).to match_array(['You are not allowed to push into this branch'])
end
end
end
end
context 'when user is an inherited member from the group' do
context 'when project is public with private repository' do
let(:project) { create(:project, :public, :repository, :repository_private, group: group) }
context 'and user is a guest' do
before do
group.add_guest(user)
end
it 'raises an error' do
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
end
context 'when project is private' do
let(:project) { create(:project, :private, :repository, group: group) }
context 'and user is a guest' do
before do
group.add_guest(user)
end
it 'raises an error' do
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
end
end
context 'when user is a maintainer of a different project' do
before do
create(:project_empty_repo).add_maintainer(user)
@ -62,153 +244,6 @@ RSpec.describe Mutations::Commits::Create do
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
context 'when the user can create a commit' do
let(:deltas) { mutated_commit.raw_deltas }
before_all do
project.add_developer(user)
end
context 'when service successfully creates a new commit' do
it "returns the ETag path for the commit's pipeline" do
commit_pipeline_path = subject[:commit_pipeline_path]
expect(commit_pipeline_path).to match(%r(pipelines/sha/\w+))
end
it 'returns the content of the commit' do
expect(subject[:content]).to eq(actions.pluck(:content))
end
it 'returns a new commit' do
expect(mutated_commit).to have_attributes(message: message, project: project)
expect(subject[:errors]).to be_empty
expect_to_contain_deltas([
a_hash_including(a_mode: '0', b_mode: '100644', new_file: true, new_path: file_path)
])
end
end
context 'when request has multiple actions' do
let(:actions) do
[
{
action: 'create',
file_path: 'foo/foobar',
content: 'some content'
},
{
action: 'delete',
file_path: 'README.md'
},
{
action: 'move',
file_path: "LICENSE.md",
previous_path: "LICENSE",
content: "some content"
},
{
action: 'update',
file_path: 'VERSION',
content: 'new content'
},
{
action: 'chmod',
file_path: 'CHANGELOG',
execute_filemode: true
}
]
end
it 'returns a new commit' do
expect(mutated_commit).to have_attributes(message: message, project: project)
expect(subject[:errors]).to be_empty
expect_to_contain_deltas([
a_hash_including(a_mode: '0', b_mode: '100644', new_path: 'foo/foobar'),
a_hash_including(deleted_file: true, new_path: 'README.md'),
a_hash_including(deleted_file: true, new_path: 'LICENSE'),
a_hash_including(new_file: true, new_path: 'LICENSE.md'),
a_hash_including(new_file: false, new_path: 'VERSION'),
a_hash_including(a_mode: '100644', b_mode: '100755', new_path: 'CHANGELOG')
])
end
end
context 'when actions are not defined' do
let(:actions) { [] }
it 'returns a new commit' do
expect(mutated_commit).to have_attributes(message: message, project: project)
expect(subject[:errors]).to be_empty
expect_to_contain_deltas([])
end
end
context 'when branch does not exist' do
let(:branch) { 'unknown' }
it 'returns errors' do
expect(mutated_commit).to be_nil
expect(subject[:errors]).to eq(['You can only create or edit files when you are on a branch'])
end
end
context 'when branch does not exist and a start branch is provided' do
let(:branch) { 'my-branch' }
let(:start_branch) { 'master' }
let(:actions) do
[
{
action: 'create',
file_path: 'ANOTHER_FILE.md',
content: 'Bye'
}
]
end
it 'returns a new commit' do
expect(mutated_commit).to have_attributes(message: message, project: project)
expect(subject[:errors]).to be_empty
expect(subject[:content]).to eq(actions.pluck(:content))
expect_to_contain_deltas([
a_hash_including(a_mode: '0', b_mode: '100644', new_file: true, new_path: 'ANOTHER_FILE.md')
])
end
end
context 'when message is not set' do
let(:message) { nil }
it 'returns errors' do
expect(mutated_commit).to be_nil
expect(subject[:errors].to_s).to match(/3:UserCommitFiles: empty CommitMessage/)
end
end
context 'when actions are incorrect' do
let(:actions) { [{ action: 'unknown', file_path: 'test.md', content: '' }] }
it 'returns errors' do
expect(mutated_commit).to be_nil
expect(subject[:errors]).to eq(['Unknown action \'unknown\''])
end
end
context 'when branch is protected' do
before do
create(:protected_branch, project: project, name: branch)
end
it 'returns errors' do
expect(mutated_commit).to be_nil
expect(subject[:errors]).to eq(['You are not allowed to push into this branch'])
end
end
end
end
def expect_to_contain_deltas(expected_deltas)

View File

@ -10,11 +10,12 @@ RSpec.describe Gitlab::Metrics::Exporter::BaseExporter do
describe 'when exporter is enabled' do
before do
allow(::WEBrick::HTTPServer).to receive(:new).with(
Port: anything,
BindAddress: anything,
Logger: anything,
AccessLog: anything
).and_call_original
{
Port: anything,
BindAddress: anything,
Logger: anything,
AccessLog: anything
}).and_call_original
allow(settings).to receive(:enabled).and_return(true)
allow(settings).to receive(:port).and_return(0)
@ -45,11 +46,12 @@ RSpec.describe Gitlab::Metrics::Exporter::BaseExporter do
it 'starts server with port and address from settings' do
expect(::WEBrick::HTTPServer).to receive(:new).with(
Port: port,
BindAddress: address,
Logger: anything,
AccessLog: anything
).and_wrap_original do |m, *args|
{
Port: port,
BindAddress: address,
Logger: anything,
AccessLog: anything
}).and_wrap_original do |m, *args|
m.call(DoNotListen: true, Logger: args.first[:Logger])
end

View File

@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe CommitPolicy do
describe '#rules' do
let(:group) { create(:group, :public) }
let(:user) { create(:user) }
let(:commit) { project.repository.head_commit }
let(:policy) { described_class.new(user, commit) }
@ -19,59 +20,119 @@ RSpec.describe CommitPolicy do
end
shared_examples 'cannot read commit nor create a note' do
it 'can not read commit' do
it 'cannot read commit' do
expect(policy).to be_disallowed(:read_commit)
end
it 'can not create a note' do
it 'cannot create a note' do
expect(policy).to be_disallowed(:create_note)
end
end
context 'when project is public' do
let(:project) { create(:project, :public, :repository) }
let(:project) { create(:project, :public, :repository, group: group) }
it_behaves_like 'can read commit and create a note'
context 'when the user is not a project member' do
it_behaves_like 'can read commit and create a note'
end
context 'when repository access level is private' do
let(:project) { create(:project, :public, :repository, :repository_private) }
let(:project) { create(:project, :public, :repository, :repository_private, group: group) }
context 'when the user is not a project member' do
it_behaves_like 'cannot read commit nor create a note'
end
context 'when the user is a direct project member' do
context 'and the user is a developer' do
before do
project.add_developer(user)
end
it_behaves_like 'can read commit and create a note'
end
end
context 'when the user is an inherited member from the group' do
context 'and the user is a guest' do
before do
group.add_guest(user)
end
it_behaves_like 'can read commit and create a note'
end
context 'and the user is a reporter' do
before do
group.add_reporter(user)
end
it_behaves_like 'can read commit and create a note'
end
context 'and the user is a developer' do
before do
group.add_developer(user)
end
it_behaves_like 'can read commit and create a note'
end
end
end
end
context 'when project is private' do
let(:project) { create(:project, :private, :repository, group: group) }
context 'when the user is not a project member' do
it_behaves_like 'cannot read commit nor create a note'
end
context 'when the user is a project member' do
context 'when the user is a direct project member' do
context 'and the user is a developer' do
before do
project.add_developer(user)
end
it_behaves_like 'can read commit and create a note'
end
end
end
context 'when project is private' do
let(:project) { create(:project, :private, :repository) }
context 'and the user is a guest' do
before do
project.add_guest(user)
end
it_behaves_like 'cannot read commit nor create a note'
it_behaves_like 'cannot read commit nor create a note'
context 'when the user is a project member' do
before do
project.add_developer(user)
end
it 'can read commit and create a note' do
expect(policy).to be_allowed(:read_commit)
it 'cannot download code' do
expect(policy).to be_disallowed(:download_code)
end
end
end
context 'when the user is a guest' do
before do
project.add_guest(user)
context 'when the user is an inherited member from the group' do
context 'and the user is a guest' do
before do
group.add_guest(user)
end
it_behaves_like 'cannot read commit nor create a note'
end
it_behaves_like 'cannot read commit nor create a note'
context 'and the user is a reporter' do
before do
group.add_reporter(user)
end
it 'cannot download code' do
expect(policy).to be_disallowed(:download_code)
it_behaves_like 'can read commit and create a note'
end
context 'and the user is a developer' do
before do
group.add_developer(user)
end
it_behaves_like 'can read commit and create a note'
end
end
end

View File

@ -15,6 +15,8 @@ RSpec.describe API::Commits do
let(:branch_with_slash) { project.repository.find_branch('improve/awesome') }
let(:project_id) { project.id }
let(:current_user) { nil }
let(:group) { create(:group, :public) }
let(:inherited_guest) { create(:user).tap { |u| group.add_guest(u) } }
before do
project.add_maintainer(user)
@ -56,311 +58,340 @@ RSpec.describe API::Commits do
end
end
context 'when authenticated', 'as a maintainer' do
let(:current_user) { user }
context 'when authenticated' do
context 'when user is a direct project member' do
context 'and user is a maintainer' do
let(:current_user) { user }
it_behaves_like 'project commits'
it_behaves_like 'project commits'
context "since optional parameter" do
it "returns project commits since provided parameter" do
commits = project.repository.commits("master", limit: 2)
after = commits.second.created_at
context "since optional parameter" do
it "returns project commits since provided parameter" do
commits = project.repository.commits("master", limit: 2)
after = commits.second.created_at
get api("/projects/#{project_id}/repository/commits?since=#{after.utc.iso8601}", user)
get api("/projects/#{project_id}/repository/commits?since=#{after.utc.iso8601}", user)
expect(json_response.size).to eq 2
expect(json_response.first["id"]).to eq(commits.first.id)
expect(json_response.second["id"]).to eq(commits.second.id)
end
expect(json_response.size).to eq 2
expect(json_response.first["id"]).to eq(commits.first.id)
expect(json_response.second["id"]).to eq(commits.second.id)
end
it 'include correct pagination headers' do
commits = project.repository.commits("master", limit: 2)
after = commits.second.created_at
it 'include correct pagination headers' do
commits = project.repository.commits("master", limit: 2)
after = commits.second.created_at
get api("/projects/#{project_id}/repository/commits?since=#{after.utc.iso8601}", user)
get api("/projects/#{project_id}/repository/commits?since=#{after.utc.iso8601}", user)
expect(response).to include_limited_pagination_headers
expect(response.headers['X-Page']).to eql('1')
end
end
context "until optional parameter" do
it "returns project commits until provided parameter" do
commits = project.repository.commits("master", limit: 20)
before = commits.second.created_at
get api("/projects/#{project_id}/repository/commits?until=#{before.utc.iso8601}", user)
if commits.size == 20
expect(json_response.size).to eq(20)
else
expect(json_response.size).to eq(commits.size - 1)
expect(response).to include_limited_pagination_headers
expect(response.headers['X-Page']).to eql('1')
end
end
expect(json_response.first["id"]).to eq(commits.second.id)
expect(json_response.second["id"]).to eq(commits.third.id)
end
context "until optional parameter" do
it "returns project commits until provided parameter" do
commits = project.repository.commits("master", limit: 20)
before = commits.second.created_at
it 'include correct pagination headers' do
commits = project.repository.commits("master", limit: 2)
before = commits.second.created_at
get api("/projects/#{project_id}/repository/commits?until=#{before.utc.iso8601}", user)
get api("/projects/#{project_id}/repository/commits?until=#{before.utc.iso8601}", user)
if commits.size == 20
expect(json_response.size).to eq(20)
else
expect(json_response.size).to eq(commits.size - 1)
end
expect(response).to include_limited_pagination_headers
expect(response.headers['X-Page']).to eql('1')
end
end
expect(json_response.first["id"]).to eq(commits.second.id)
expect(json_response.second["id"]).to eq(commits.third.id)
end
context "invalid xmlschema date parameters" do
it "returns an invalid parameter error message" do
get api("/projects/#{project_id}/repository/commits?since=invalid-date", user)
it 'include correct pagination headers' do
commits = project.repository.commits("master", limit: 2)
before = commits.second.created_at
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['error']).to eq('since is invalid')
end
end
get api("/projects/#{project_id}/repository/commits?until=#{before.utc.iso8601}", user)
context "with empty ref_name parameter" do
let(:route) { "/projects/#{project_id}/repository/commits?ref_name=" }
it_behaves_like 'project commits'
end
context 'when repository does not exist' do
let(:project) { create(:project, creator: user, path: 'my.project') }
it_behaves_like '404 response' do
let(:request) { get api(route, current_user) }
let(:message) { '404 Repository Not Found' }
end
end
context "path optional parameter" do
it "returns project commits matching provided path parameter" do
path = 'files/ruby/popen.rb'
get api("/projects/#{project_id}/repository/commits?path=#{path}", user)
expect(json_response.size).to eq(3)
expect(json_response.first["id"]).to eq("570e7b2abdd848b95f2f578043fc23bd6f6fd24d")
expect(response).to include_limited_pagination_headers
end
it 'include correct pagination headers' do
path = 'files/ruby/popen.rb'
get api("/projects/#{project_id}/repository/commits?path=#{path}", user)
expect(response).to include_limited_pagination_headers
expect(response.headers['X-Page']).to eql('1')
end
end
context 'all optional parameter' do
it 'returns all project commits' do
expected_commit_ids = project.repository.commits(nil, all: true, limit: 50).map(&:id)
get api("/projects/#{project_id}/repository/commits?all=true&per_page=50", user)
commit_ids = json_response.map { |c| c['id'] }
expect(response).to include_limited_pagination_headers
expect(commit_ids).to eq(expected_commit_ids)
expect(response.headers['X-Page']).to eql('1')
end
end
context 'first_parent optional parameter' do
it 'returns all first_parent commits' do
expected_commit_ids = project.repository.commits(SeedRepo::Commit::ID, limit: 50, first_parent: true).map(&:id)
get api("/projects/#{project_id}/repository/commits?per_page=50", user), params: { ref_name: SeedRepo::Commit::ID, first_parent: 'true' }
commit_ids = json_response.map { |c| c['id'] }
expect(response).to include_limited_pagination_headers
expect(expected_commit_ids.size).to eq(12)
expect(commit_ids).to eq(expected_commit_ids)
end
end
context 'with_stats optional parameter' do
let(:project) { create(:project, :public, :repository) }
it_behaves_like 'project commits', schema: 'public_api/v4/commits_with_stats' do
let(:route) { "/projects/#{project_id}/repository/commits?with_stats=true" }
it 'include commits details' do
commit = project.repository.commit
get api(route, current_user)
expect(json_response.first['stats']['additions']).to eq(commit.stats.additions)
expect(json_response.first['stats']['deletions']).to eq(commit.stats.deletions)
expect(json_response.first['stats']['total']).to eq(commit.stats.total)
end
end
end
context 'with pagination params' do
let(:page) { 1 }
let(:per_page) { 5 }
let(:ref_name) { 'master' }
let(:request) do
get api("/projects/#{project_id}/repository/commits?page=#{page}&per_page=#{per_page}&ref_name=#{ref_name}", user)
end
it 'returns correct headers' do
request
expect(response).to include_limited_pagination_headers
expect(response.headers['Link']).to match(/page=1&per_page=5/)
expect(response.headers['Link']).to match(/page=2&per_page=5/)
end
context 'viewing the first page' do
it 'returns the first 5 commits' do
request
commit = project.repository.commit
expect(json_response.size).to eq(per_page)
expect(json_response.first['id']).to eq(commit.id)
expect(response.headers['X-Page']).to eq('1')
end
end
context 'viewing the third page' do
let(:page) { 3 }
it 'returns the third 5 commits' do
request
commit = project.repository.commits('HEAD', limit: per_page, offset: (page - 1) * per_page).first
expect(json_response.size).to eq(per_page)
expect(json_response.first['id']).to eq(commit.id)
expect(response.headers['X-Page']).to eq('3')
end
end
context 'when pagination params are invalid' do
let_it_be(:project) { create(:project, :repository) }
using RSpec::Parameterized::TableSyntax
where(:page, :per_page, :error_message) do
0 | nil | 'page does not have a valid value'
-1 | nil | 'page does not have a valid value'
'a' | nil | 'page is invalid'
nil | 0 | 'per_page does not have a valid value'
nil | -1 | 'per_page does not have a valid value'
nil | 'a' | 'per_page is invalid'
expect(response).to include_limited_pagination_headers
expect(response.headers['X-Page']).to eql('1')
end
end
with_them do
it 'returns 400 response' do
request
context "invalid xmlschema date parameters" do
it "returns an invalid parameter error message" do
get api("/projects/#{project_id}/repository/commits?since=invalid-date", user)
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['error']).to eq(error_message)
expect(json_response['error']).to eq('since is invalid')
end
end
context 'when FF is off' do
before do
stub_feature_flags(only_positive_pagination_values: false)
context "with empty ref_name parameter" do
let(:route) { "/projects/#{project_id}/repository/commits?ref_name=" }
it_behaves_like 'project commits'
end
context 'when repository does not exist' do
let(:project) { create(:project, creator: user, path: 'my.project') }
it_behaves_like '404 response' do
let(:request) { get api(route, current_user) }
let(:message) { '404 Repository Not Found' }
end
end
context "path optional parameter" do
it "returns project commits matching provided path parameter" do
path = 'files/ruby/popen.rb'
get api("/projects/#{project_id}/repository/commits?path=#{path}", user)
expect(json_response.size).to eq(3)
expect(json_response.first["id"]).to eq("570e7b2abdd848b95f2f578043fc23bd6f6fd24d")
expect(response).to include_limited_pagination_headers
end
where(:page, :per_page, :error_message, :status) do
0 | nil | nil | :success
-10 | nil | nil | :internal_server_error
'a' | nil | 'page is invalid' | :bad_request
nil | 0 | 'per_page has a value not allowed' | :bad_request
nil | -1 | nil | :success
nil | 'a' | 'per_page is invalid' | :bad_request
it 'include correct pagination headers' do
path = 'files/ruby/popen.rb'
get api("/projects/#{project_id}/repository/commits?path=#{path}", user)
expect(response).to include_limited_pagination_headers
expect(response.headers['X-Page']).to eql('1')
end
end
context 'all optional parameter' do
it 'returns all project commits' do
expected_commit_ids = project.repository.commits(nil, all: true, limit: 50).map(&:id)
get api("/projects/#{project_id}/repository/commits?all=true&per_page=50", user)
commit_ids = json_response.map { |c| c['id'] }
expect(response).to include_limited_pagination_headers
expect(commit_ids).to eq(expected_commit_ids)
expect(response.headers['X-Page']).to eql('1')
end
end
context 'first_parent optional parameter' do
it 'returns all first_parent commits' do
expected_commit_ids = project.repository.commits(SeedRepo::Commit::ID, limit: 50, first_parent: true).map(&:id)
get api("/projects/#{project_id}/repository/commits?per_page=50", user), params: { ref_name: SeedRepo::Commit::ID, first_parent: 'true' }
commit_ids = json_response.map { |c| c['id'] }
expect(response).to include_limited_pagination_headers
expect(expected_commit_ids.size).to eq(12)
expect(commit_ids).to eq(expected_commit_ids)
end
end
context 'with_stats optional parameter' do
let(:project) { create(:project, :public, :repository) }
it_behaves_like 'project commits', schema: 'public_api/v4/commits_with_stats' do
let(:route) { "/projects/#{project_id}/repository/commits?with_stats=true" }
it 'include commits details' do
commit = project.repository.commit
get api(route, current_user)
expect(json_response.first['stats']['additions']).to eq(commit.stats.additions)
expect(json_response.first['stats']['deletions']).to eq(commit.stats.deletions)
expect(json_response.first['stats']['total']).to eq(commit.stats.total)
end
end
end
context 'with pagination params' do
let(:page) { 1 }
let(:per_page) { 5 }
let(:ref_name) { 'master' }
let(:request) do
get api("/projects/#{project_id}/repository/commits?page=#{page}&per_page=#{per_page}&ref_name=#{ref_name}", user)
end
with_them do
it 'returns a response' do
it 'returns correct headers' do
request
expect(response).to include_limited_pagination_headers
expect(response.headers['Link']).to match(/page=1&per_page=5/)
expect(response.headers['Link']).to match(/page=2&per_page=5/)
end
context 'viewing the first page' do
it 'returns the first 5 commits' do
request
expect(response).to have_gitlab_http_status(status)
commit = project.repository.commit
if error_message
expect(json_response.size).to eq(per_page)
expect(json_response.first['id']).to eq(commit.id)
expect(response.headers['X-Page']).to eq('1')
end
end
context 'viewing the third page' do
let(:page) { 3 }
it 'returns the third 5 commits' do
request
commit = project.repository.commits('HEAD', limit: per_page, offset: (page - 1) * per_page).first
expect(json_response.size).to eq(per_page)
expect(json_response.first['id']).to eq(commit.id)
expect(response.headers['X-Page']).to eq('3')
end
end
context 'when pagination params are invalid' do
let_it_be(:project) { create(:project, :repository) }
using RSpec::Parameterized::TableSyntax
where(:page, :per_page, :error_message) do
0 | nil | 'page does not have a valid value'
-1 | nil | 'page does not have a valid value'
'a' | nil | 'page is invalid'
nil | 0 | 'per_page does not have a valid value'
nil | -1 | 'per_page does not have a valid value'
nil | 'a' | 'per_page is invalid'
end
with_them do
it 'returns 400 response' do
request
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['error']).to eq(error_message)
end
end
context 'when FF is off' do
before do
stub_feature_flags(only_positive_pagination_values: false)
end
where(:page, :per_page, :error_message, :status) do
0 | nil | nil | :success
-10 | nil | nil | :internal_server_error
'a' | nil | 'page is invalid' | :bad_request
nil | 0 | 'per_page has a value not allowed' | :bad_request
nil | -1 | nil | :success
nil | 'a' | 'per_page is invalid' | :bad_request
end
with_them do
it 'returns a response' do
request
expect(response).to have_gitlab_http_status(status)
if error_message
expect(json_response['error']).to eq(error_message)
end
end
end
end
end
end
end
end
context 'with order parameter' do
let(:route) { "/projects/#{project_id}/repository/commits?ref_name=0031876&per_page=6&order=#{order}" }
context 'with order parameter' do
let(:route) { "/projects/#{project_id}/repository/commits?ref_name=0031876&per_page=6&order=#{order}" }
context 'set to topo' do
let(:order) { 'topo' }
context 'set to topo' do
let(:order) { 'topo' }
# git log --graph -n 6 --pretty=format:"%h" --topo-order 0031876
# * 0031876
# |\
# | * 48ca272
# | * 335bc94
# * | bf6e164
# * | 9d526f8
# |/
# * 1039376
it 'returns project commits ordered by topo order' do
commits = project.repository.commits("0031876", limit: 6, order: 'topo')
# git log --graph -n 6 --pretty=format:"%h" --topo-order 0031876
# * 0031876
# |\
# | * 48ca272
# | * 335bc94
# * | bf6e164
# * | 9d526f8
# |/
# * 1039376
it 'returns project commits ordered by topo order' do
commits = project.repository.commits("0031876", limit: 6, order: 'topo')
get api(route, current_user)
get api(route, current_user)
expect(json_response.size).to eq(6)
expect(json_response.map { |entry| entry["id"] }).to eq(commits.map(&:id))
expect(json_response.size).to eq(6)
expect(json_response.map { |entry| entry["id"] }).to eq(commits.map(&:id))
end
end
context 'set to default' do
let(:order) { 'default' }
# git log --graph -n 6 --pretty=format:"%h" --date-order 0031876
# * 0031876
# |\
# * | bf6e164
# | * 48ca272
# * | 9d526f8
# | * 335bc94
# |/
# * 1039376
it 'returns project commits ordered by default order' do
commits = project.repository.commits("0031876", limit: 6, order: 'default')
get api(route, current_user)
expect(json_response.size).to eq(6)
expect(json_response.map { |entry| entry["id"] }).to eq(commits.map(&:id))
end
end
context 'set to an invalid parameter' do
let(:order) { 'invalid' }
it_behaves_like '400 response' do
let(:request) { get api(route, current_user) }
end
end
end
end
context 'set to default' do
let(:order) { 'default' }
context 'with the optional trailers parameter' do
it 'includes the Git trailers' do
get api("/projects/#{project_id}/repository/commits?ref_name=6d394385cf567f80a8fd85055db1ab4c5295806f&trailers=true", current_user)
# git log --graph -n 6 --pretty=format:"%h" --date-order 0031876
# * 0031876
# |\
# * | bf6e164
# | * 48ca272
# * | 9d526f8
# | * 335bc94
# |/
# * 1039376
it 'returns project commits ordered by default order' do
commits = project.repository.commits("0031876", limit: 6, order: 'default')
commit = json_response[0]
get api(route, current_user)
expect(json_response.size).to eq(6)
expect(json_response.map { |entry| entry["id"] }).to eq(commits.map(&:id))
end
end
context 'set to an invalid parameter' do
let(:order) { 'invalid' }
it_behaves_like '400 response' do
let(:request) { get api(route, current_user) }
expect(commit['trailers']).to eq(
'Signed-off-by' => 'Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>'
)
end
end
end
end
context 'with the optional trailers parameter' do
it 'includes the Git trailers' do
get api("/projects/#{project_id}/repository/commits?ref_name=6d394385cf567f80a8fd85055db1ab4c5295806f&trailers=true", current_user)
context 'when user is an inherited member from the group' do
context 'when project is public with private repository' do
let(:project) { create(:project, :public, :repository, :repository_private, group: group) }
commit = json_response[0]
context 'and user is a guest' do
let(:current_user) { inherited_guest }
expect(commit['trailers']).to eq(
'Signed-off-by' => 'Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>'
)
it_behaves_like 'project commits'
end
end
context 'when project is private' do
let(:project) { create(:project, :private, :repository, group: group) }
context 'and user is a guest' do
let(:current_user) { inherited_guest }
it_behaves_like '404 response' do
let(:request) { get api(route) }
let(:message) { '404 Project Not Found' }
end
end
end
end
end
@ -466,11 +497,37 @@ RSpec.describe API::Commits do
end
context 'a new file in project repo' do
before do
post api(url, user), params: valid_c_params
context 'when user is a direct project member' do
before do
post api(url, user), params: valid_c_params
end
it_behaves_like 'successfully creates the commit'
end
it_behaves_like "successfully creates the commit"
context 'when user is an inherited member from the group' do
context 'when project is public with private repository' do
let(:project) { create(:project, :public, :repository, :repository_private, group: group) }
context 'and user is a guest' do
it_behaves_like '403 response' do
let(:request) { post api(url, inherited_guest), params: valid_c_params }
let(:message) { '403 Forbidden' }
end
end
end
context 'when project is private' do
let(:project) { create(:project, :private, :repository, group: group) }
context 'and user is a guest' do
it_behaves_like '403 response' do
let(:request) { post api(url, inherited_guest), params: valid_c_params }
let(:message) { '403 Forbidden' }
end
end
end
end
end
context 'a new file with utf8 chars in project repo' do

View File

@ -304,7 +304,7 @@ RSpec.shared_examples 'thread comments for issue, epic and merge request' do |re
let(:reply_id) { find("#{comments_selector} .note:last-of-type", match: :first)['data-note-id'] }
it 'can be replied to after resolving' do
find('button[data-qa-selector="resolve_discussion_button"]').click # rubocop:disable QA/SelectorUsage
find('button[data-testid="resolve-discussion-button"]').click
wait_for_requests
refresh
@ -316,7 +316,7 @@ RSpec.shared_examples 'thread comments for issue, epic and merge request' do |re
it 'shows resolved thread when toggled' do
submit_reply('a')
find('button[data-qa-selector="resolve_discussion_button"]').click # rubocop:disable QA/SelectorUsage
find('button[data-testid="resolve-discussion-button"]').click
wait_for_requests
expect(page).to have_selector(".note-row-#{note_id}", visible: true)

View File

@ -14,7 +14,7 @@ RSpec.shared_examples 'packages list' do |check_project_name: false|
end
def package_table_row(index)
page.all("#{packages_table_selector} > [data-qa-selector=\"package_row\"]")[index].text # rubocop:disable QA/SelectorUsage
page.all("#{packages_table_selector} > [data-testid=\"package-row\"]")[index].text
end
end
@ -84,7 +84,7 @@ RSpec.shared_examples 'shared package sorting' do
end
def packages_table_selector
'[data-qa-selector="packages-table"]' # rubocop:disable QA/SelectorUsage
'[data-testid="packages-table"]'
end
def click_sort_option(option, ascending)

View File

@ -64,9 +64,9 @@ end
RSpec.shared_examples 'shows no runners registered' do
it 'shows counts with 0' do
expect(page).to have_text "Online 0"
expect(page).to have_text "Offline 0"
expect(page).to have_text "Stale 0"
expect(page).to have_text "#{s_('Runners|Online')} 0"
expect(page).to have_text "#{s_('Runners|Offline')} 0"
expect(page).to have_text "#{s_('Runners|Stale')} 0"
end
it 'shows "no runners" message' do
@ -101,7 +101,7 @@ RSpec.shared_examples 'pauses, resumes and deletes a runner' do
within_runner_row(runner.id) do
click_button "Pause"
expect(page).to have_text 'paused'
expect(page).to have_text s_('Runners|Paused')
expect(page).to have_button 'Resume'
expect(page).not_to have_button 'Pause'

View File

@ -91,7 +91,7 @@ RSpec.shared_examples 'variable list' do |is_admin|
end
page.within('#add-ci-variable') do
find('[data-qa-selector="ci_variable_key_field"] input').set('new_key') # rubocop:disable QA/SelectorUsage
find('[data-testid="pipeline-form-ci-variable-key"] input').set('new_key')
click_button('Update variable')
end
@ -173,7 +173,7 @@ RSpec.shared_examples 'variable list' do |is_admin|
click_button('Add variable')
page.within('#add-ci-variable') do
find('[data-qa-selector="ci_variable_key_field"] input').set('empty_mask_key') # rubocop:disable QA/SelectorUsage
find('[data-testid="pipeline-form-ci-variable-key"] input').set('empty_mask_key')
find('[data-testid="ci-variable-protected-checkbox"]').click
find('[data-testid="ci-variable-masked-checkbox"]').click
@ -290,8 +290,8 @@ RSpec.shared_examples 'variable list' do |is_admin|
wait_for_requests
page.within('#add-ci-variable') do
find('[data-qa-selector="ci_variable_key_field"] input').set(key) # rubocop:disable QA/SelectorUsage
find('[data-qa-selector="ci_variable_value_field"]').set(value) if value.present? # rubocop:disable QA/SelectorUsage
find('[data-testid="pipeline-form-ci-variable-key"] input').set(key)
find('[data-testid="pipeline-form-ci-variable-value"]').set(value) if value.present?
find('[data-testid="ci-variable-protected-checkbox"]').click if protected
find('[data-testid="ci-variable-masked-checkbox"]').click if masked

View File

@ -64,7 +64,7 @@ RSpec.shared_examples 'User previews wiki changes' do
end
it_behaves_like 'relative links' do
let(:element) { page.find('[data-testid="wiki_page_content"]') }
let(:element) { page.find('[data-testid="wiki-page-content"]') }
end
end

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true
RSpec.shared_examples 'User views AsciiDoc page with includes' do
let_it_be(:wiki_content_selector) { '[data-qa-selector=wiki_page_content]' } # rubocop:disable QA/SelectorUsage
let_it_be(:wiki_content_selector) { '[data-testid=wiki-page-content]' }
let!(:included_wiki_page) { create_wiki_page('included_page', content: 'Content from the included page') }
let!(:wiki_page) { create_wiki_page('home', content: "Content from the main page.\ninclude::included_page.asciidoc[]") }

View File

@ -185,6 +185,28 @@ RSpec.shared_examples_for 'collection cache helper' do
end
end
context 'when presentable has a group by clause' do
let(:presentable) { MergeRequest.group(:id) }
it "returns the presentables" do
expect(transaction)
.to receive(:increment)
.with(:cached_object_operations_total, 0, { caller_id: caller_id, render_type: :collection, cache_hit: true }).once
expect(transaction)
.to receive(:increment)
.with(:cached_object_operations_total, MergeRequest.count, { caller_id: caller_id, render_type: :collection, cache_hit: false }).once
parsed = Gitlab::Json.parse(subject.to_s)
expect(parsed).to be_an(Array)
presentable.each_with_index do |item, i|
expect(parsed[i]["id"]).to eq(item.id)
end
end
end
context 'when the presentables all miss' do
it 'increments the counters' do
expect(transaction)