Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
c5b1e86b43
commit
6986c1adc2
|
@ -6,6 +6,6 @@
|
|||
|
||||
<!-- Consider adding related issues and epics to this issue. You can also reference the Feature Proposal Template (https://gitlab.com/gitlab-org/gitlab/-/blob/master/.gitlab/issue_templates/Feature%20proposal.md) for additional details to consider adding to this issue. Additionally, as a data oriented organization, when your feature exits planning breakdown, consider adding the `What does success look like, and how can we measure that?` section.
|
||||
|
||||
/label ~"group::" ~"section::" ~"Category::" ~"GitLab Core"/~"GitLab Starter"/~"GitLab Premium"/~"GitLab Ultimate"
|
||||
/label ~"group::" ~"section::" ~"Category::" ~"GitLab Core"/~"GitLab Premium"/~"GitLab Ultimate"
|
||||
|
||||
-->
|
||||
|
|
|
@ -97,7 +97,7 @@ Create tracking issue using the the Snowplow event tracking template. See https:
|
|||
### What is the type of buyer?
|
||||
|
||||
<!-- What is the buyer persona for this feature? See https://about.gitlab.com/handbook/marketing/product-marketing/roles-personas/buyer-persona/
|
||||
In which enterprise tier should this feature go? See https://about.gitlab.com/handbook/product/pricing/#four-tiers -->
|
||||
In which enterprise tier should this feature go? See https://about.gitlab.com/handbook/product/pricing/#three-tiers -->
|
||||
|
||||
### Is this a cross-stage feature?
|
||||
|
||||
|
|
|
@ -96,7 +96,7 @@ Define both the success metrics and acceptance criteria. Note that success metri
|
|||
### What is the type of buyer?
|
||||
|
||||
What is the buyer persona for this feature? See https://about.gitlab.com/handbook/marketing/product-marketing/roles-personas/buyer-persona/
|
||||
In which enterprise tier should this feature go? See https://about.gitlab.com/handbook/product/pricing/#four-tiers
|
||||
In which enterprise tier should this feature go? See https://about.gitlab.com/handbook/product/pricing/#three-tiers
|
||||
|
||||
### Is this a cross-stage feature?
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
f319db221de49c979f3606e3934bd76c218f813d
|
||||
2d3b680ab7d7652377c17c962c2628d98a6ffc9a
|
||||
|
|
|
@ -2,21 +2,24 @@
|
|||
import { GlButton, GlLoadingIcon, GlModal, GlLink } from '@gitlab/ui';
|
||||
import { getParameterByName } from '~/lib/utils/common_utils';
|
||||
import eventHub from '~/pipelines/event_hub';
|
||||
import pipelinesMixin from '~/pipelines/mixins/pipelines';
|
||||
import PipelinesPaginationApiMixin from '~/pipelines/mixins/pipelines_pagination_api_mixin';
|
||||
import PipelinesMixin from '~/pipelines/mixins/pipelines_mixin';
|
||||
import PipelinesService from '~/pipelines/services/pipelines_service';
|
||||
import PipelineStore from '~/pipelines/stores/pipelines_store';
|
||||
import PipelinesTableComponent from '~/pipelines/components/pipelines_list/pipelines_table.vue';
|
||||
import TablePagination from '~/vue_shared/components/pagination/table_pagination.vue';
|
||||
import SvgBlankState from '~/pipelines/components/pipelines_list/blank_state.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
TablePagination,
|
||||
GlButton,
|
||||
GlLink,
|
||||
GlLoadingIcon,
|
||||
GlModal,
|
||||
GlLink,
|
||||
PipelinesTableComponent,
|
||||
TablePagination,
|
||||
SvgBlankState,
|
||||
},
|
||||
mixins: [pipelinesMixin, PipelinesPaginationApiMixin],
|
||||
mixins: [PipelinesMixin],
|
||||
props: {
|
||||
endpoint: {
|
||||
type: String,
|
||||
|
|
|
@ -4,6 +4,7 @@ import { GlLink, GlIcon, GlLabel, GlFormCheckbox, GlTooltipDirective } from '@gi
|
|||
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
|
||||
import { isScopedLabel } from '~/lib/utils/common_utils';
|
||||
import { getTimeago } from '~/lib/utils/datetime_utility';
|
||||
import { isExternal, setUrlFragment } from '~/lib/utils/url_utility';
|
||||
import { __, sprintf } from '~/locale';
|
||||
import IssuableAssignees from '~/vue_shared/components/issue/issue_assignees.vue';
|
||||
import timeagoMixin from '~/vue_shared/mixins/timeago';
|
||||
|
@ -47,17 +48,14 @@ export default {
|
|||
author() {
|
||||
return this.issuable.author;
|
||||
},
|
||||
webUrl() {
|
||||
return this.issuable.gitlabWebUrl || this.issuable.webUrl;
|
||||
},
|
||||
authorId() {
|
||||
return getIdFromGraphQLId(`${this.author.id}`);
|
||||
},
|
||||
isIssuableUrlExternal() {
|
||||
// Check if URL is relative, which means it is internal.
|
||||
if (!/^https?:\/\//g.test(this.issuable.webUrl)) {
|
||||
return false;
|
||||
}
|
||||
// In case URL is absolute, it may or may not be internal,
|
||||
// hence use `gon.gitlab_url` which is current instance domain.
|
||||
return !this.issuable.webUrl.includes(gon.gitlab_url);
|
||||
return isExternal(this.webUrl);
|
||||
},
|
||||
labels() {
|
||||
return this.issuable.labels?.nodes || this.issuable.labels || [];
|
||||
|
@ -91,6 +89,9 @@ export default {
|
|||
this.hasSlotContents('status') || this.showDiscussions || this.issuable.assignees,
|
||||
);
|
||||
},
|
||||
issuableNotesLink() {
|
||||
return setUrlFragment(this.webUrl, 'notes');
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
hasSlotContents(slotName) {
|
||||
|
@ -144,7 +145,7 @@ export default {
|
|||
name="eye-slash"
|
||||
:title="__('Confidential')"
|
||||
/>
|
||||
<gl-link :href="issuable.webUrl" v-bind="issuableTitleProps"
|
||||
<gl-link :href="webUrl" v-bind="issuableTitleProps"
|
||||
>{{ issuable.title
|
||||
}}<gl-icon v-if="isIssuableUrlExternal" name="external-link" class="gl-ml-2"
|
||||
/></gl-link>
|
||||
|
@ -206,7 +207,7 @@ export default {
|
|||
<gl-link
|
||||
v-gl-tooltip:tooltipcontainer.top
|
||||
:title="__('Comments')"
|
||||
:href="`${issuable.webUrl}#notes`"
|
||||
:href="issuableNotesLink"
|
||||
:class="{ 'no-comments': !issuable.userDiscussionsCount }"
|
||||
class="gl-reset-color!"
|
||||
>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { GlIcon } from '@gitlab/ui';
|
||||
import { GlIcon, GlLoadingIcon } from '@gitlab/ui';
|
||||
import { isEqual } from 'lodash';
|
||||
import { deprecatedCreateFlash as createFlash } from '~/flash';
|
||||
import { getParameterByName } from '~/lib/utils/common_utils';
|
||||
|
@ -7,22 +7,28 @@ import { __, s__ } from '~/locale';
|
|||
import NavigationTabs from '~/vue_shared/components/navigation_tabs.vue';
|
||||
import TablePagination from '~/vue_shared/components/pagination/table_pagination.vue';
|
||||
import { ANY_TRIGGER_AUTHOR, RAW_TEXT_WARNING, FILTER_TAG_IDENTIFIER } from '../../constants';
|
||||
import pipelinesMixin from '../../mixins/pipelines';
|
||||
import PipelinesPaginationApiMixin from '../../mixins/pipelines_pagination_api_mixin';
|
||||
import PipelinesMixin from '../../mixins/pipelines_mixin';
|
||||
import PipelinesService from '../../services/pipelines_service';
|
||||
import { validateParams } from '../../utils';
|
||||
import EmptyState from './empty_state.vue';
|
||||
import NavigationControls from './nav_controls.vue';
|
||||
import PipelinesFilteredSearch from './pipelines_filtered_search.vue';
|
||||
import PipelinesTableComponent from './pipelines_table.vue';
|
||||
import SvgBlankState from './blank_state.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
TablePagination,
|
||||
EmptyState,
|
||||
GlIcon,
|
||||
GlLoadingIcon,
|
||||
NavigationTabs,
|
||||
NavigationControls,
|
||||
PipelinesFilteredSearch,
|
||||
GlIcon,
|
||||
PipelinesTableComponent,
|
||||
SvgBlankState,
|
||||
TablePagination,
|
||||
},
|
||||
mixins: [pipelinesMixin, PipelinesPaginationApiMixin],
|
||||
mixins: [PipelinesMixin],
|
||||
props: {
|
||||
store: {
|
||||
type: Object,
|
||||
|
@ -217,6 +223,20 @@ export default {
|
|||
this.requestData = { page: this.page, scope: this.scope, ...this.validatedParams };
|
||||
},
|
||||
methods: {
|
||||
onChangeTab(scope) {
|
||||
if (this.scope === scope) {
|
||||
return;
|
||||
}
|
||||
|
||||
let params = {
|
||||
scope,
|
||||
page: '1',
|
||||
};
|
||||
|
||||
params = this.onChangeWithFilter(params);
|
||||
|
||||
this.updateContent(params);
|
||||
},
|
||||
successCallback(resp) {
|
||||
// Because we are polling & the user is interacting verify if the response received
|
||||
// matches the last request made
|
||||
|
|
|
@ -1,21 +1,13 @@
|
|||
import { GlLoadingIcon } from '@gitlab/ui';
|
||||
import Visibility from 'visibilityjs';
|
||||
import { deprecatedCreateFlash as createFlash } from '~/flash';
|
||||
import Poll from '~/lib/utils/poll';
|
||||
import { historyPushState, buildUrlWithCurrentLocation } from '~/lib/utils/common_utils';
|
||||
import { validateParams } from '~/pipelines/utils';
|
||||
import { __ } from '~/locale';
|
||||
import SvgBlankState from '../components/pipelines_list/blank_state.vue';
|
||||
import EmptyState from '../components/pipelines_list/empty_state.vue';
|
||||
import PipelinesTableComponent from '../components/pipelines_list/pipelines_table.vue';
|
||||
import { CANCEL_REQUEST } from '../constants';
|
||||
import eventHub from '../event_hub';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
PipelinesTableComponent,
|
||||
SvgBlankState,
|
||||
EmptyState,
|
||||
GlLoadingIcon,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isLoading: false,
|
||||
|
@ -76,6 +68,25 @@ export default {
|
|||
this.poll.stop();
|
||||
},
|
||||
methods: {
|
||||
updateInternalState(parameters) {
|
||||
this.poll.stop();
|
||||
|
||||
const queryString = Object.keys(parameters)
|
||||
.map((parameter) => {
|
||||
const value = parameters[parameter];
|
||||
// update internal state for UI
|
||||
this[parameter] = value;
|
||||
return `${parameter}=${encodeURIComponent(value)}`;
|
||||
})
|
||||
.join('&');
|
||||
|
||||
// update polling parameters
|
||||
this.requestData = parameters;
|
||||
|
||||
historyPushState(buildUrlWithCurrentLocation(`?${queryString}`));
|
||||
|
||||
this.isLoading = true;
|
||||
},
|
||||
/**
|
||||
* Handles URL and query parameter changes.
|
||||
* When the user uses the pagination or the tabs,
|
||||
|
@ -184,5 +195,23 @@ export default {
|
|||
})
|
||||
.finally(() => this.store.toggleIsRunningPipeline(false));
|
||||
},
|
||||
onChangePage(page) {
|
||||
/* URLS parameters are strings, we need to parse to match types */
|
||||
let params = {
|
||||
page: Number(page).toString(),
|
||||
};
|
||||
|
||||
if (this.scope) {
|
||||
params.scope = this.scope;
|
||||
}
|
||||
|
||||
params = this.onChangeWithFilter(params);
|
||||
|
||||
this.updateContent(params);
|
||||
},
|
||||
|
||||
onChangeWithFilter(params) {
|
||||
return { ...params, ...validateParams(this.requestData) };
|
||||
},
|
||||
},
|
||||
};
|
|
@ -1,66 +0,0 @@
|
|||
/**
|
||||
* API callbacks for pagination and tabs
|
||||
*
|
||||
* Components need to have `scope`, `page` and `requestData`
|
||||
*/
|
||||
import { validateParams } from '~/pipelines/utils';
|
||||
import { historyPushState, buildUrlWithCurrentLocation } from '../../lib/utils/common_utils';
|
||||
|
||||
export default {
|
||||
methods: {
|
||||
onChangeTab(scope) {
|
||||
if (this.scope === scope) {
|
||||
return;
|
||||
}
|
||||
|
||||
let params = {
|
||||
scope,
|
||||
page: '1',
|
||||
};
|
||||
|
||||
params = this.onChangeWithFilter(params);
|
||||
|
||||
this.updateContent(params);
|
||||
},
|
||||
|
||||
onChangePage(page) {
|
||||
/* URLS parameters are strings, we need to parse to match types */
|
||||
let params = {
|
||||
page: Number(page).toString(),
|
||||
};
|
||||
|
||||
if (this.scope) {
|
||||
params.scope = this.scope;
|
||||
}
|
||||
|
||||
params = this.onChangeWithFilter(params);
|
||||
|
||||
this.updateContent(params);
|
||||
},
|
||||
|
||||
onChangeWithFilter(params) {
|
||||
return { ...params, ...validateParams(this.requestData) };
|
||||
},
|
||||
|
||||
updateInternalState(parameters) {
|
||||
// stop polling
|
||||
this.poll.stop();
|
||||
|
||||
const queryString = Object.keys(parameters)
|
||||
.map((parameter) => {
|
||||
const value = parameters[parameter];
|
||||
// update internal state for UI
|
||||
this[parameter] = value;
|
||||
return `${parameter}=${encodeURIComponent(value)}`;
|
||||
})
|
||||
.join('&');
|
||||
|
||||
// update polling parameters
|
||||
this.requestData = parameters;
|
||||
|
||||
historyPushState(buildUrlWithCurrentLocation(`?${queryString}`));
|
||||
|
||||
this.isLoading = true;
|
||||
},
|
||||
},
|
||||
};
|
|
@ -80,6 +80,7 @@ export default {
|
|||
lockingState: s__('Terraform|Locking state'),
|
||||
name: s__('Terraform|Name'),
|
||||
pipeline: s__('Terraform|Pipeline'),
|
||||
removing: s__('Terraform|Removing'),
|
||||
unknownUser: s__('Terraform|Unknown User'),
|
||||
unlockingState: s__('Terraform|Unlocking state'),
|
||||
updatedUser: s__('Terraform|%{user} updated %{timeAgo}'),
|
||||
|
@ -141,6 +142,15 @@ export default {
|
|||
</p>
|
||||
</div>
|
||||
|
||||
<div v-else-if="item.loadingRemove" class="gl-mx-3">
|
||||
<p
|
||||
class="gl-display-flex gl-justify-content-start gl-align-items-baseline gl-m-0 gl-text-red-500"
|
||||
>
|
||||
<gl-loading-icon class="gl-pr-1" />
|
||||
{{ $options.i18n.removing }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-else-if="item.lockedAt"
|
||||
:id="`terraformLockedBadgeContainer${item.name}`"
|
||||
|
|
|
@ -9,7 +9,7 @@ import {
|
|||
GlModal,
|
||||
GlSprintf,
|
||||
} from '@gitlab/ui';
|
||||
import { s__ } from '~/locale';
|
||||
import { s__, sprintf } from '~/locale';
|
||||
import addDataToState from '../graphql/mutations/add_data_to_state.mutation.graphql';
|
||||
import lockState from '../graphql/mutations/lock_state.mutation.graphql';
|
||||
import removeState from '../graphql/mutations/remove_state.mutation.graphql';
|
||||
|
@ -52,6 +52,7 @@ export default {
|
|||
),
|
||||
modalRemove: s__('Terraform|Remove'),
|
||||
remove: s__('Terraform|Remove state file and versions'),
|
||||
removeSuccessful: s__('Terraform|%{name} successfully removed'),
|
||||
unlock: s__('Terraform|Unlock'),
|
||||
},
|
||||
computed: {
|
||||
|
@ -121,10 +122,13 @@ export default {
|
|||
loadingRemove: true,
|
||||
});
|
||||
|
||||
this.stateActionMutation(removeState);
|
||||
this.stateActionMutation(
|
||||
removeState,
|
||||
sprintf(this.$options.i18n.removeSuccessful, { name: this.state.name }),
|
||||
);
|
||||
}
|
||||
},
|
||||
stateActionMutation(mutation) {
|
||||
stateActionMutation(mutation, successMessage = null) {
|
||||
let errorMessages = [];
|
||||
|
||||
this.$apollo
|
||||
|
@ -143,6 +147,10 @@ export default {
|
|||
data?.terraformStateLock?.errors ||
|
||||
data?.terraformStateUnlock?.errors ||
|
||||
[];
|
||||
|
||||
if (errorMessages.length === 0 && successMessage) {
|
||||
this.$toast.show(successMessage);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
errorMessages = [this.$options.i18n.errorUpdate];
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import { defaultDataIdFromObject } from 'apollo-cache-inmemory';
|
||||
import { GlToast } from '@gitlab/ui';
|
||||
import Vue from 'vue';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import createDefaultClient from '~/lib/graphql';
|
||||
import TerraformList from './components/terraform_list.vue';
|
||||
import resolvers from './graphql/resolvers';
|
||||
|
||||
Vue.use(GlToast);
|
||||
Vue.use(VueApollo);
|
||||
|
||||
export default () => {
|
||||
|
|
|
@ -159,7 +159,7 @@ export default {
|
|||
<div class="rebase-state-find-class-convention media media-body space-children">
|
||||
<span
|
||||
v-if="rebaseInProgress || isMakingRequest"
|
||||
class="gl-font-weight-bold gl-ml-0!"
|
||||
class="gl-font-weight-bold"
|
||||
data-testid="rebase-message"
|
||||
>{{ __('Rebase in progress') }}</span
|
||||
>
|
||||
|
|
|
@ -1,88 +0,0 @@
|
|||
<script>
|
||||
import { GlLoadingIcon, GlIcon } from '@gitlab/ui';
|
||||
import { s__ } from '../../locale';
|
||||
|
||||
const ICON_ON = 'status_success_borderless';
|
||||
const ICON_OFF = 'status_failed_borderless';
|
||||
const LABEL_ON = s__('ToggleButton|Toggle Status: ON');
|
||||
const LABEL_OFF = s__('ToggleButton|Toggle Status: OFF');
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlIcon,
|
||||
GlLoadingIcon,
|
||||
},
|
||||
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change',
|
||||
},
|
||||
|
||||
props: {
|
||||
name: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
value: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
disabledInput: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
isLoading: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
toggleIcon() {
|
||||
return this.value ? ICON_ON : ICON_OFF;
|
||||
},
|
||||
ariaLabel() {
|
||||
return this.value ? LABEL_ON : LABEL_OFF;
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
toggleFeature() {
|
||||
if (!this.disabledInput) this.$emit('change', !this.value);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<label class="gl-mt-2">
|
||||
<input v-if="name" :name="name" :value="value" type="hidden" />
|
||||
<button
|
||||
type="button"
|
||||
role="switch"
|
||||
class="project-feature-toggle"
|
||||
:aria-label="ariaLabel"
|
||||
:aria-checked="value"
|
||||
:class="{
|
||||
'is-checked': value,
|
||||
'gl-blue-500': value,
|
||||
'is-disabled': disabledInput,
|
||||
'is-loading': isLoading,
|
||||
}"
|
||||
@click.prevent="toggleFeature"
|
||||
>
|
||||
<gl-loading-icon class="loading-icon" />
|
||||
<span class="toggle-icon">
|
||||
<gl-icon
|
||||
:size="18"
|
||||
:name="toggleIcon"
|
||||
:class="value ? 'gl-text-blue-500' : 'gl-text-gray-400'"
|
||||
/>
|
||||
</span>
|
||||
</button>
|
||||
</label>
|
||||
</template>
|
|
@ -177,7 +177,7 @@ class LabelsFinder < UnionFinder
|
|||
end
|
||||
|
||||
if group?
|
||||
@projects = if params[:include_subgroups]
|
||||
@projects = if params[:include_descendant_groups]
|
||||
@projects.in_namespace(group.self_and_descendants.select(:id))
|
||||
else
|
||||
@projects.in_namespace(group.id)
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Resolvers
|
||||
class GroupLabelsResolver < LabelsResolver
|
||||
type Types::LabelType.connection_type, null: true
|
||||
|
||||
argument :include_descendant_groups, GraphQL::BOOLEAN_TYPE,
|
||||
required: false,
|
||||
description: 'Include labels from descendant groups.',
|
||||
default_value: false
|
||||
|
||||
argument :only_group_labels, GraphQL::BOOLEAN_TYPE,
|
||||
required: false,
|
||||
description: 'Include only group level labels.',
|
||||
default_value: false
|
||||
end
|
||||
end
|
|
@ -0,0 +1,46 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Resolvers
|
||||
class LabelsResolver < BaseResolver
|
||||
include Gitlab::Graphql::Authorize::AuthorizeResource
|
||||
|
||||
authorize :read_label
|
||||
|
||||
type Types::LabelType.connection_type, null: true
|
||||
|
||||
argument :search_term, GraphQL::STRING_TYPE,
|
||||
required: false,
|
||||
description: 'A search term to find labels with.'
|
||||
|
||||
argument :include_ancestor_groups, GraphQL::BOOLEAN_TYPE,
|
||||
required: false,
|
||||
description: 'Include labels from ancestor groups.',
|
||||
default_value: false
|
||||
|
||||
def resolve(**args)
|
||||
return Label.none if parent.nil?
|
||||
|
||||
authorize!(parent)
|
||||
|
||||
# LabelsFinder uses `search` param, so we transform `search_term` into `search`
|
||||
args[:search] = args.delete(:search_term)
|
||||
LabelsFinder.new(current_user, parent_param.merge(args)).execute
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def parent
|
||||
object.respond_to?(:sync) ? object.sync : object
|
||||
end
|
||||
|
||||
def parent_param
|
||||
key = case parent
|
||||
when Group then :group
|
||||
when Project then :project
|
||||
else raise "Unexpected parent type: #{parent.class}"
|
||||
end
|
||||
|
||||
{ "#{key}": parent }
|
||||
end
|
||||
end
|
||||
end
|
|
@ -107,17 +107,8 @@ module Types
|
|||
field :labels,
|
||||
Types::LabelType.connection_type,
|
||||
null: true,
|
||||
description: 'Labels available on this group.' do
|
||||
argument :search_term, GraphQL::STRING_TYPE,
|
||||
required: false,
|
||||
description: 'A search term to find labels with.'
|
||||
end
|
||||
|
||||
def labels(search_term: nil)
|
||||
LabelsFinder
|
||||
.new(current_user, group: group, search: search_term)
|
||||
.execute
|
||||
end
|
||||
description: 'Labels available on this group.',
|
||||
resolver: Resolvers::GroupLabelsResolver
|
||||
|
||||
def avatar_url
|
||||
object.avatar_url(only_path: false)
|
||||
|
|
|
@ -5,7 +5,7 @@ module Types
|
|||
class PackageTypeEnum < BaseEnum
|
||||
PACKAGE_TYPE_NAMES = {
|
||||
pypi: 'PyPI',
|
||||
npm: 'NPM'
|
||||
npm: 'npm'
|
||||
}.freeze
|
||||
|
||||
::Packages::Package.package_types.keys.each do |package_type|
|
||||
|
|
|
@ -337,17 +337,8 @@ module Types
|
|||
field :labels,
|
||||
Types::LabelType.connection_type,
|
||||
null: true,
|
||||
description: 'Labels available on this project.' do
|
||||
argument :search_term, GraphQL::STRING_TYPE,
|
||||
required: false,
|
||||
description: 'A search term to find labels with.'
|
||||
end
|
||||
|
||||
def labels(search_term: nil)
|
||||
LabelsFinder
|
||||
.new(current_user, project: project, search: search_term)
|
||||
.execute
|
||||
end
|
||||
description: 'Labels available on this project.',
|
||||
resolver: Resolvers::LabelsResolver
|
||||
|
||||
def avatar_url
|
||||
object.avatar_url(only_path: false)
|
||||
|
|
|
@ -74,7 +74,8 @@ module Enums
|
|||
remote_source: 4,
|
||||
external_project_source: 5,
|
||||
bridge_source: 6,
|
||||
parameter_source: 7
|
||||
parameter_source: 7,
|
||||
compliance_source: 8
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Display loading when removing Terraform state
|
||||
merge_request: 53897
|
||||
author:
|
||||
type: changed
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
title: Adds only_group_labels and include_ancestor_labels and include_descendant_groups
|
||||
arguments to the project and group labels resolvers respectively
|
||||
merge_request: 53639
|
||||
author:
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Track YAML-less Auto DevOps inclusions separately
|
||||
merge_request: 53383
|
||||
author:
|
||||
type: changed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix alignment of 'Rebase in progress' label
|
||||
merge_request: 54189
|
||||
author:
|
||||
type: fixed
|
|
@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/300400
|
|||
milestone: '13.9'
|
||||
type: development
|
||||
group: group::continuous integration
|
||||
default_enabled: false
|
||||
default_enabled: true
|
||||
|
|
|
@ -3973,7 +3973,7 @@ type ComplianceFramework {
|
|||
|
||||
"""
|
||||
Full path of the compliance pipeline configuration stored in a project
|
||||
repository, such as `.gitlab/compliance/soc2/.gitlab-ci.yml`.
|
||||
repository, such as `.gitlab/.compliance-gitlab-ci.yml@compliance/hippa`.
|
||||
"""
|
||||
pipelineConfigurationFullPath: String
|
||||
}
|
||||
|
@ -4031,7 +4031,7 @@ input ComplianceFrameworkInput {
|
|||
|
||||
"""
|
||||
Full path of the compliance pipeline configuration stored in a project
|
||||
repository, such as `.gitlab/compliance/soc2/.gitlab-ci.yml`.
|
||||
repository, such as `.gitlab/.compliance-gitlab-ci.yml@compliance/hippa`.
|
||||
"""
|
||||
pipelineConfigurationFullPath: String
|
||||
}
|
||||
|
@ -11619,11 +11619,26 @@ type Group {
|
|||
"""
|
||||
first: Int
|
||||
|
||||
"""
|
||||
Include labels from ancestor groups.
|
||||
"""
|
||||
includeAncestorGroups: Boolean = false
|
||||
|
||||
"""
|
||||
Include labels from descendant groups.
|
||||
"""
|
||||
includeDescendantGroups: Boolean = false
|
||||
|
||||
"""
|
||||
Returns the last _n_ elements from the list.
|
||||
"""
|
||||
last: Int
|
||||
|
||||
"""
|
||||
Include only group level labels.
|
||||
"""
|
||||
onlyGroupLabels: Boolean = false
|
||||
|
||||
"""
|
||||
A search term to find labels with.
|
||||
"""
|
||||
|
@ -18179,7 +18194,7 @@ enum PackageTypeEnum {
|
|||
MAVEN
|
||||
|
||||
"""
|
||||
Packages from the NPM package manager
|
||||
Packages from the npm package manager
|
||||
"""
|
||||
NPM
|
||||
|
||||
|
@ -18383,7 +18398,7 @@ type Pipeline {
|
|||
"""
|
||||
Configuration source of the pipeline (UNKNOWN_SOURCE, REPOSITORY_SOURCE,
|
||||
AUTO_DEVOPS_SOURCE, WEBIDE_SOURCE, REMOTE_SOURCE, EXTERNAL_PROJECT_SOURCE,
|
||||
BRIDGE_SOURCE, PARAMETER_SOURCE)
|
||||
BRIDGE_SOURCE, PARAMETER_SOURCE, COMPLIANCE_SOURCE)
|
||||
"""
|
||||
configSource: PipelineConfigSourceEnum
|
||||
|
||||
|
@ -18654,6 +18669,7 @@ type PipelineCancelPayload {
|
|||
enum PipelineConfigSourceEnum {
|
||||
AUTO_DEVOPS_SOURCE
|
||||
BRIDGE_SOURCE
|
||||
COMPLIANCE_SOURCE
|
||||
EXTERNAL_PROJECT_SOURCE
|
||||
PARAMETER_SOURCE
|
||||
REMOTE_SOURCE
|
||||
|
@ -19833,6 +19849,11 @@ type Project {
|
|||
"""
|
||||
first: Int
|
||||
|
||||
"""
|
||||
Include labels from ancestor groups.
|
||||
"""
|
||||
includeAncestorGroups: Boolean = false
|
||||
|
||||
"""
|
||||
Returns the last _n_ elements from the list.
|
||||
"""
|
||||
|
|
|
@ -10821,7 +10821,7 @@
|
|||
},
|
||||
{
|
||||
"name": "pipelineConfigurationFullPath",
|
||||
"description": "Full path of the compliance pipeline configuration stored in a project repository, such as `.gitlab/compliance/soc2/.gitlab-ci.yml`.",
|
||||
"description": "Full path of the compliance pipeline configuration stored in a project repository, such as `.gitlab/.compliance-gitlab-ci.yml@compliance/hippa`.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -10991,7 +10991,7 @@
|
|||
},
|
||||
{
|
||||
"name": "pipelineConfigurationFullPath",
|
||||
"description": "Full path of the compliance pipeline configuration stored in a project repository, such as `.gitlab/compliance/soc2/.gitlab-ci.yml`.",
|
||||
"description": "Full path of the compliance pipeline configuration stored in a project repository, such as `.gitlab/.compliance-gitlab-ci.yml@compliance/hippa`.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
|
@ -31572,6 +31572,46 @@
|
|||
"name": "labels",
|
||||
"description": "Labels available on this group.",
|
||||
"args": [
|
||||
{
|
||||
"name": "searchTerm",
|
||||
"description": "A search term to find labels with.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "includeAncestorGroups",
|
||||
"description": "Include labels from ancestor groups.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Boolean",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"name": "includeDescendantGroups",
|
||||
"description": "Include labels from descendant groups.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Boolean",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"name": "onlyGroupLabels",
|
||||
"description": "Include only group level labels.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Boolean",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"name": "after",
|
||||
"description": "Returns the elements in the list that come after the specified cursor.",
|
||||
|
@ -31611,16 +31651,6 @@
|
|||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "searchTerm",
|
||||
"description": "A search term to find labels with.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
}
|
||||
],
|
||||
"type": {
|
||||
|
@ -53370,7 +53400,7 @@
|
|||
},
|
||||
{
|
||||
"name": "NPM",
|
||||
"description": "Packages from the NPM package manager",
|
||||
"description": "Packages from the npm package manager",
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
|
@ -53950,7 +53980,7 @@
|
|||
},
|
||||
{
|
||||
"name": "configSource",
|
||||
"description": "Configuration source of the pipeline (UNKNOWN_SOURCE, REPOSITORY_SOURCE, AUTO_DEVOPS_SOURCE, WEBIDE_SOURCE, REMOTE_SOURCE, EXTERNAL_PROJECT_SOURCE, BRIDGE_SOURCE, PARAMETER_SOURCE)",
|
||||
"description": "Configuration source of the pipeline (UNKNOWN_SOURCE, REPOSITORY_SOURCE, AUTO_DEVOPS_SOURCE, WEBIDE_SOURCE, REMOTE_SOURCE, EXTERNAL_PROJECT_SOURCE, BRIDGE_SOURCE, PARAMETER_SOURCE, COMPLIANCE_SOURCE)",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -54847,6 +54877,12 @@
|
|||
"description": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "COMPLIANCE_SOURCE",
|
||||
"description": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"possibleTypes": null
|
||||
|
@ -57711,6 +57747,26 @@
|
|||
"name": "labels",
|
||||
"description": "Labels available on this project.",
|
||||
"args": [
|
||||
{
|
||||
"name": "searchTerm",
|
||||
"description": "A search term to find labels with.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "includeAncestorGroups",
|
||||
"description": "Include labels from ancestor groups.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Boolean",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"name": "after",
|
||||
"description": "Returns the elements in the list that come after the specified cursor.",
|
||||
|
@ -57750,16 +57806,6 @@
|
|||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "searchTerm",
|
||||
"description": "A search term to find labels with.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
}
|
||||
],
|
||||
"type": {
|
||||
|
|
|
@ -630,7 +630,7 @@ Represents a ComplianceFramework associated with a Project.
|
|||
| `description` | String! | Description of the compliance framework. |
|
||||
| `id` | ID! | Compliance framework ID. |
|
||||
| `name` | String! | Name of the compliance framework. |
|
||||
| `pipelineConfigurationFullPath` | String | Full path of the compliance pipeline configuration stored in a project repository, such as `.gitlab/compliance/soc2/.gitlab-ci.yml`. |
|
||||
| `pipelineConfigurationFullPath` | String | Full path of the compliance pipeline configuration stored in a project repository, such as `.gitlab/.compliance-gitlab-ci.yml@compliance/hippa`. |
|
||||
|
||||
### ComposerMetadata
|
||||
|
||||
|
@ -2785,7 +2785,7 @@ Information about pagination in a connection..
|
|||
| `beforeSha` | String | Base SHA of the source branch. |
|
||||
| `cancelable` | Boolean! | Specifies if a pipeline can be canceled. |
|
||||
| `committedAt` | Time | Timestamp of the pipeline's commit. |
|
||||
| `configSource` | PipelineConfigSourceEnum | Configuration source of the pipeline (UNKNOWN_SOURCE, REPOSITORY_SOURCE, AUTO_DEVOPS_SOURCE, WEBIDE_SOURCE, REMOTE_SOURCE, EXTERNAL_PROJECT_SOURCE, BRIDGE_SOURCE, PARAMETER_SOURCE) |
|
||||
| `configSource` | PipelineConfigSourceEnum | Configuration source of the pipeline (UNKNOWN_SOURCE, REPOSITORY_SOURCE, AUTO_DEVOPS_SOURCE, WEBIDE_SOURCE, REMOTE_SOURCE, EXTERNAL_PROJECT_SOURCE, BRIDGE_SOURCE, PARAMETER_SOURCE, COMPLIANCE_SOURCE) |
|
||||
| `coverage` | Float | Coverage percentage. |
|
||||
| `createdAt` | Time! | Timestamp of the pipeline's creation. |
|
||||
| `detailedStatus` | DetailedStatus! | Detailed status of the pipeline. |
|
||||
|
@ -5203,7 +5203,7 @@ Rotation length unit of an on-call rotation.
|
|||
| `GENERIC` | Packages from the Generic package manager |
|
||||
| `GOLANG` | Packages from the Golang package manager |
|
||||
| `MAVEN` | Packages from the Maven package manager |
|
||||
| `NPM` | Packages from the NPM package manager |
|
||||
| `NPM` | Packages from the npm package manager |
|
||||
| `NUGET` | Packages from the Nuget package manager |
|
||||
| `PYPI` | Packages from the PyPI package manager |
|
||||
| `RUBYGEMS` | Packages from the Rubygems package manager |
|
||||
|
@ -5214,6 +5214,7 @@ Rotation length unit of an on-call rotation.
|
|||
| ----- | ----------- |
|
||||
| `AUTO_DEVOPS_SOURCE` | |
|
||||
| `BRIDGE_SOURCE` | |
|
||||
| `COMPLIANCE_SOURCE` | |
|
||||
| `EXTERNAL_PROJECT_SOURCE` | |
|
||||
| `PARAMETER_SOURCE` | |
|
||||
| `REMOTE_SOURCE` | |
|
||||
|
|
|
@ -155,23 +155,23 @@ The [Security Scanner Integration](../../../development/integrations/secure.md)
|
|||
## Analyzers Data
|
||||
|
||||
| Property / Tool | Apex | Bandit | Brakeman | ESLint security | SpotBugs | Flawfinder | Gosec | Kubesec Scanner | MobSF | NodeJsScan | PHP CS Security Audit | Security code Scan (.NET) | Semgrep | Sobelow |
|
||||
| --------------------------------------- | :------------------: | :------------------: | :------------------: | :------------------: | :------------------: | :------------------: | :------------------: | :------------------: | :------------------: | :------------------: | :---------------------: | :-------------------------: | :-------------------------: | :----------------: |
|
||||
| Severity | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✗ | ⚠ | ✗ |
|
||||
| Title | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| Description | ✓ | ✗ | ✗ | ✓ | ✓ | ✗ | ✗ | ✓ | ✓ | ✓ | ✗ | ✗ | ✓ | ✓ |
|
||||
| File | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| Start line | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| End line | ✓ | ✓ | ✗ | ✓ | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
|
||||
| Start column | ✓ | ✗ | ✗ | ✓ | ✓ | ✓ | ✓ | ✗ | ✗ | ✗ | ✓ | ✓ | ✓ | ✗ |
|
||||
| End column | ✓ | ✗ | ✗ | ✓ | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
|
||||
| External ID (for example, CVE) | ✗ | ✗ | ⚠ | ✗ | ⚠ | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ⚠ | ✗ |
|
||||
| URLs | ✓ | ✗ | ✓ | ✗ | ⚠ | ✗ | ⚠ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
|
||||
| Internal doc/explanation | ✓ | ⚠ | ✓ | ✗ | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ |
|
||||
| Solution | ✓ | ✗ | ✗ | ✗ | ⚠ | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ⚠ | ✗ |
|
||||
|--------------------------------|------|--------|----------|-----------------|----------|------------|-------|-----------------|-------|------------|-----------------------|---------------------------|---------|---------|
|
||||
| Affected item (for example, class or package) | ✓ | ✗ | ✓ | ✗ | ✓ | ✓ | ✗ | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
|
||||
| Confidence | ✗ | ✓ | ✓ | ✗ | ✓ | x | ✓ | ✓ | ✗ | ✗ | ✗ | ✗ | ⚠ | ✓ |
|
||||
| Source code extract | ✗ | ✓ | ✓ | ✓ | ✗ | ✓ | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
|
||||
| Description | ✓ | ✗ | ✗ | ✓ | ✓ | ✗ | ✗ | ✓ | ✓ | ✓ | ✗ | ✗ | ✓ | ✓ |
|
||||
| End column | ✓ | ✗ | ✗ | ✓ | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
|
||||
| End line | ✓ | ✓ | ✗ | ✓ | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
|
||||
| External ID (for example, CVE) | ✗ | ✗ | ⚠ | ✗ | ⚠ | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ⚠ | ✗ |
|
||||
| File | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| Internal doc/explanation | ✓ | ⚠ | ✓ | ✗ | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ |
|
||||
| Internal ID | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✗ | ✗ | ✗ | ✓ | ✓ | ✓ | ✓ |
|
||||
| Severity | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✗ | ⚠ | ✗ |
|
||||
| Solution | ✓ | ✗ | ✗ | ✗ | ⚠ | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ⚠ | ✗ |
|
||||
| Source code extract | ✗ | ✓ | ✓ | ✓ | ✗ | ✓ | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
|
||||
| Start column | ✓ | ✗ | ✗ | ✓ | ✓ | ✓ | ✓ | ✗ | ✗ | ✗ | ✓ | ✓ | ✓ | ✗ |
|
||||
| Start line | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| Title | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| URLs | ✓ | ✗ | ✓ | ✗ | ⚠ | ✗ | ⚠ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
|
||||
|
||||
- ✓ => we have that data
|
||||
- ⚠ => we have that data but it's partially reliable, or we need to extract it from unstructured content
|
||||
|
|
|
@ -657,15 +657,16 @@ license_scanning:
|
|||
The License Compliance job should now use local copies of the License Compliance analyzers to scan
|
||||
your code and generate security reports, without requiring internet access.
|
||||
|
||||
Additional configuration may be needed for connecting to
|
||||
[private Bower registries](#using-private-bower-registries),
|
||||
[private Bundler registries](#using-private-bundler-registries),
|
||||
[private Conan registries](#using-private-bower-registries),
|
||||
[private Go registries](#using-private-go-registries),
|
||||
[private Maven repositories](#using-private-maven-repositories),
|
||||
[private npm registries](#using-private-npm-registries),
|
||||
[private Python repositories](#using-private-python-repositories),
|
||||
and [private Yarn registries](#using-private-yarn-registries).
|
||||
Additional configuration may be needed for connecting to private registries for:
|
||||
|
||||
- [Bower](#using-private-bower-registries),
|
||||
- [Bundler](#using-private-bundler-registries),
|
||||
- [Conan](#using-private-bower-registries),
|
||||
- [Go](#using-private-go-registries),
|
||||
- [Maven repositories](#using-private-maven-repositories),
|
||||
- [npm](#using-private-npm-registries),
|
||||
- [Python repositories](#using-private-python-repositories),
|
||||
- [Yarn](#using-private-yarn-registries).
|
||||
|
||||
### SPDX license list name matching
|
||||
|
||||
|
|
|
@ -34,16 +34,22 @@ module Gitlab
|
|||
private
|
||||
|
||||
def find_config
|
||||
SOURCES.each do |source|
|
||||
sources.each do |source|
|
||||
config = source.new(@pipeline, @command)
|
||||
return config if config.exists?
|
||||
end
|
||||
|
||||
nil
|
||||
end
|
||||
|
||||
def sources
|
||||
SOURCES
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Gitlab::Ci::Pipeline::Chain::Config::Content.prepend_if_ee('EE::Gitlab::Ci::Pipeline::Chain::Config::Content')
|
||||
|
|
|
@ -19,7 +19,7 @@ module Gitlab
|
|||
|
||||
def track_event(template)
|
||||
Gitlab::UsageDataCounters::CiTemplateUniqueCounter
|
||||
.track_unique_project_event(project_id: pipeline.project_id, template: template)
|
||||
.track_unique_project_event(project_id: pipeline.project_id, template: template, config_source: pipeline.config_source)
|
||||
end
|
||||
|
||||
def included_templates
|
||||
|
|
|
@ -7,6 +7,9 @@ module Gitlab
|
|||
class UrlBlocker
|
||||
BlockedUrlError = Class.new(StandardError)
|
||||
|
||||
GETADDRINFO_TIMEOUT_SECONDS = 15
|
||||
private_constant :GETADDRINFO_TIMEOUT_SECONDS
|
||||
|
||||
class << self
|
||||
# Validates the given url according to the constraints specified by arguments.
|
||||
#
|
||||
|
@ -110,7 +113,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
def get_address_info(uri, dns_rebind_protection)
|
||||
Addrinfo.getaddrinfo(uri.hostname, get_port(uri), nil, :STREAM).map do |addr|
|
||||
Addrinfo.getaddrinfo(uri.hostname, get_port(uri), nil, :STREAM, timeout: GETADDRINFO_TIMEOUT_SECONDS).map do |addr|
|
||||
addr.ipv6_v4mapped? ? addr.ipv6_to_ipv4 : addr
|
||||
end
|
||||
rescue SocketError
|
||||
|
|
|
@ -4,6 +4,7 @@ module Gitlab::UsageDataCounters
|
|||
class CiTemplateUniqueCounter
|
||||
REDIS_SLOT = 'ci_templates'.freeze
|
||||
|
||||
# NOTE: Events originating from implicit Auto DevOps pipelines get prefixed with `implicit_`
|
||||
TEMPLATE_TO_EVENT = {
|
||||
'5-Minute-Production-App.gitlab-ci.yml' => '5_min_production_app',
|
||||
'Auto-DevOps.gitlab-ci.yml' => 'auto_devops',
|
||||
|
@ -18,19 +19,21 @@ module Gitlab::UsageDataCounters
|
|||
}.freeze
|
||||
|
||||
class << self
|
||||
def track_unique_project_event(project_id:, template:)
|
||||
def track_unique_project_event(project_id:, template:, config_source:)
|
||||
return if Feature.disabled?(:usage_data_track_ci_templates_unique_projects, default_enabled: :yaml)
|
||||
|
||||
if event = unique_project_event(template)
|
||||
if event = unique_project_event(template, config_source)
|
||||
Gitlab::UsageDataCounters::HLLRedisCounter.track_event(event, values: project_id)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def unique_project_event(template)
|
||||
def unique_project_event(template, config_source)
|
||||
if name = TEMPLATE_TO_EVENT[template]
|
||||
"p_#{REDIS_SLOT}_#{name}"
|
||||
prefix = 'implicit_' if config_source.to_s == 'auto_devops_source'
|
||||
|
||||
"p_#{REDIS_SLOT}_#{prefix}#{name}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
# Implicit Auto DevOps pipeline events
|
||||
- name: p_ci_templates_implicit_auto_devops
|
||||
category: ci_templates
|
||||
redis_slot: ci_templates
|
||||
aggregation: weekly
|
||||
feature_flag: usage_data_track_ci_templates_unique_projects
|
||||
|
||||
- name: p_ci_templates_implicit_auto_devops_build
|
||||
category: ci_templates
|
||||
redis_slot: ci_templates
|
||||
aggregation: weekly
|
||||
feature_flag: usage_data_track_ci_templates_unique_projects
|
||||
|
||||
- name: p_ci_templates_implicit_auto_devops_deploy
|
||||
category: ci_templates
|
||||
redis_slot: ci_templates
|
||||
aggregation: weekly
|
||||
feature_flag: usage_data_track_ci_templates_unique_projects
|
||||
|
||||
- name: p_ci_templates_implicit_security_sast
|
||||
category: ci_templates
|
||||
redis_slot: ci_templates
|
||||
aggregation: weekly
|
||||
feature_flag: usage_data_track_ci_templates_unique_projects
|
||||
|
||||
- name: p_ci_templates_implicit_security_secret_detection
|
||||
category: ci_templates
|
||||
redis_slot: ci_templates
|
||||
aggregation: weekly
|
||||
feature_flag: usage_data_track_ci_templates_unique_projects
|
||||
|
||||
# Explicit include:template pipeline events
|
||||
- name: p_ci_templates_5_min_production_app
|
||||
category: ci_templates
|
||||
redis_slot: ci_templates
|
||||
aggregation: weekly
|
||||
feature_flag: usage_data_track_ci_templates_unique_projects
|
||||
|
||||
- name: p_ci_templates_auto_devops
|
||||
category: ci_templates
|
||||
redis_slot: ci_templates
|
||||
aggregation: weekly
|
||||
feature_flag: usage_data_track_ci_templates_unique_projects
|
||||
|
||||
- name: p_ci_templates_aws_cf_deploy_ec2
|
||||
category: ci_templates
|
||||
redis_slot: ci_templates
|
||||
aggregation: weekly
|
||||
feature_flag: usage_data_track_ci_templates_unique_projects
|
||||
|
||||
- name: p_ci_templates_aws_deploy_ecs
|
||||
category: ci_templates
|
||||
redis_slot: ci_templates
|
||||
aggregation: weekly
|
||||
feature_flag: usage_data_track_ci_templates_unique_projects
|
||||
|
||||
- name: p_ci_templates_auto_devops_build
|
||||
category: ci_templates
|
||||
redis_slot: ci_templates
|
||||
aggregation: weekly
|
||||
feature_flag: usage_data_track_ci_templates_unique_projects
|
||||
|
||||
- name: p_ci_templates_auto_devops_deploy
|
||||
category: ci_templates
|
||||
redis_slot: ci_templates
|
||||
aggregation: weekly
|
||||
feature_flag: usage_data_track_ci_templates_unique_projects
|
||||
|
||||
- name: p_ci_templates_auto_devops_deploy_latest
|
||||
category: ci_templates
|
||||
redis_slot: ci_templates
|
||||
aggregation: weekly
|
||||
feature_flag: usage_data_track_ci_templates_unique_projects
|
||||
|
||||
- name: p_ci_templates_security_sast
|
||||
category: ci_templates
|
||||
redis_slot: ci_templates
|
||||
aggregation: weekly
|
||||
feature_flag: usage_data_track_ci_templates_unique_projects
|
||||
|
||||
- name: p_ci_templates_security_secret_detection
|
||||
category: ci_templates
|
||||
redis_slot: ci_templates
|
||||
aggregation: weekly
|
||||
feature_flag: usage_data_track_ci_templates_unique_projects
|
||||
|
||||
- name: p_ci_templates_terraform_base_latest
|
||||
category: ci_templates
|
||||
redis_slot: ci_templates
|
||||
aggregation: weekly
|
||||
feature_flag: usage_data_track_ci_templates_unique_projects
|
|
@ -466,57 +466,6 @@
|
|||
redis_slot: terraform
|
||||
aggregation: weekly
|
||||
feature_flag: usage_data_p_terraform_state_api_unique_users
|
||||
# CI templates
|
||||
- name: p_ci_templates_5_min_production_app
|
||||
category: ci_templates
|
||||
redis_slot: ci_templates
|
||||
aggregation: weekly
|
||||
feature_flag: usage_data_track_ci_templates_unique_projects
|
||||
- name: p_ci_templates_auto_devops
|
||||
category: ci_templates
|
||||
redis_slot: ci_templates
|
||||
aggregation: weekly
|
||||
feature_flag: usage_data_track_ci_templates_unique_projects
|
||||
- name: p_ci_templates_aws_cf_deploy_ec2
|
||||
category: ci_templates
|
||||
redis_slot: ci_templates
|
||||
aggregation: weekly
|
||||
feature_flag: usage_data_track_ci_templates_unique_projects
|
||||
- name: p_ci_templates_aws_deploy_ecs
|
||||
category: ci_templates
|
||||
redis_slot: ci_templates
|
||||
aggregation: weekly
|
||||
feature_flag: usage_data_track_ci_templates_unique_projects
|
||||
- name: p_ci_templates_auto_devops_build
|
||||
category: ci_templates
|
||||
redis_slot: ci_templates
|
||||
aggregation: weekly
|
||||
feature_flag: usage_data_track_ci_templates_unique_projects
|
||||
- name: p_ci_templates_auto_devops_deploy
|
||||
category: ci_templates
|
||||
redis_slot: ci_templates
|
||||
aggregation: weekly
|
||||
feature_flag: usage_data_track_ci_templates_unique_projects
|
||||
- name: p_ci_templates_auto_devops_deploy_latest
|
||||
category: ci_templates
|
||||
redis_slot: ci_templates
|
||||
aggregation: weekly
|
||||
feature_flag: usage_data_track_ci_templates_unique_projects
|
||||
- name: p_ci_templates_security_sast
|
||||
category: ci_templates
|
||||
redis_slot: ci_templates
|
||||
aggregation: weekly
|
||||
feature_flag: usage_data_track_ci_templates_unique_projects
|
||||
- name: p_ci_templates_security_secret_detection
|
||||
category: ci_templates
|
||||
redis_slot: ci_templates
|
||||
aggregation: weekly
|
||||
feature_flag: usage_data_track_ci_templates_unique_projects
|
||||
- name: p_ci_templates_terraform_base_latest
|
||||
category: ci_templates
|
||||
redis_slot: ci_templates
|
||||
aggregation: weekly
|
||||
feature_flag: usage_data_track_ci_templates_unique_projects
|
||||
# Pipeline Authoring
|
||||
- name: o_pipeline_authoring_unique_users_committing_ciconfigfile
|
||||
category: pipeline_authoring
|
||||
|
|
|
@ -28728,6 +28728,9 @@ msgstr ""
|
|||
msgid "Terraform"
|
||||
msgstr ""
|
||||
|
||||
msgid "Terraform|%{name} successfully removed"
|
||||
msgstr ""
|
||||
|
||||
msgid "Terraform|%{number} Terraform report failed to generate"
|
||||
msgid_plural "Terraform|%{number} Terraform reports failed to generate"
|
||||
msgstr[0] ""
|
||||
|
@ -28804,6 +28807,9 @@ msgstr ""
|
|||
msgid "Terraform|Remove state file and versions"
|
||||
msgstr ""
|
||||
|
||||
msgid "Terraform|Removing"
|
||||
msgstr ""
|
||||
|
||||
msgid "Terraform|Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete"
|
||||
msgstr ""
|
||||
|
||||
|
@ -30857,12 +30863,6 @@ msgstr ""
|
|||
msgid "Toggle thread"
|
||||
msgstr ""
|
||||
|
||||
msgid "ToggleButton|Toggle Status: OFF"
|
||||
msgstr ""
|
||||
|
||||
msgid "ToggleButton|Toggle Status: ON"
|
||||
msgstr ""
|
||||
|
||||
msgid "Toggled :%{name}: emoji award."
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -68,7 +68,7 @@ RSpec.describe 'Terraform', :js do
|
|||
fill_in "terraform-state-remove-input-#{additional_state.name}", with: additional_state.name
|
||||
click_button 'Remove'
|
||||
|
||||
expect(page).not_to have_content(additional_state.name)
|
||||
expect(page).to have_content("#{additional_state.name} successfully removed")
|
||||
expect { additional_state.reload }.to raise_error ActiveRecord::RecordNotFound
|
||||
end
|
||||
end
|
||||
|
|
|
@ -18,6 +18,8 @@ const createComponent = ({ issuableSymbol = '#', issuable = mockIssuable, slots
|
|||
slots,
|
||||
});
|
||||
|
||||
const MOCK_GITLAB_URL = 'http://0.0.0.0:3000';
|
||||
|
||||
describe('IssuableItem', () => {
|
||||
// The mock data is dependent that this is after our default date
|
||||
useFakeDate(2020, 11, 11);
|
||||
|
@ -28,7 +30,7 @@ describe('IssuableItem', () => {
|
|||
let wrapper;
|
||||
|
||||
beforeEach(() => {
|
||||
gon.gitlab_url = 'http://0.0.0.0:3000';
|
||||
gon.gitlab_url = MOCK_GITLAB_URL;
|
||||
wrapper = createComponent();
|
||||
});
|
||||
|
||||
|
@ -75,7 +77,7 @@ describe('IssuableItem', () => {
|
|||
it.each`
|
||||
issuableWebUrl | urlType | returnValue
|
||||
${'/gitlab-org/gitlab-test/-/issues/2'} | ${'relative'} | ${false}
|
||||
${'http://0.0.0.0:3000/gitlab-org/gitlab-test/-/issues/1'} | ${'absolute and internal'} | ${false}
|
||||
${`${MOCK_GITLAB_URL}/gitlab-org/gitlab-test/-/issues/1`} | ${'absolute and internal'} | ${false}
|
||||
${'http://jira.atlassian.net/browse/IG-1'} | ${'external'} | ${true}
|
||||
${'https://github.com/gitlabhq/gitlabhq/issues/1'} | ${'external'} | ${true}
|
||||
`(
|
||||
|
@ -217,14 +219,32 @@ describe('IssuableItem', () => {
|
|||
});
|
||||
|
||||
describe('template', () => {
|
||||
it('renders issuable title', () => {
|
||||
it.each`
|
||||
gitlabWebUrl | webUrl | expectedHref | expectedTarget
|
||||
${undefined} | ${`${MOCK_GITLAB_URL}/issue`} | ${`${MOCK_GITLAB_URL}/issue`} | ${undefined}
|
||||
${undefined} | ${'https://jira.com/issue'} | ${'https://jira.com/issue'} | ${'_blank'}
|
||||
${'/gitlab-org/issue'} | ${'https://jira.com/issue'} | ${'/gitlab-org/issue'} | ${undefined}
|
||||
`(
|
||||
'renders issuable title correctly when `gitlabWebUrl` is `$gitlabWebUrl` and webUrl is `$webUrl`',
|
||||
async ({ webUrl, gitlabWebUrl, expectedHref, expectedTarget }) => {
|
||||
wrapper.setProps({
|
||||
issuable: {
|
||||
...mockIssuable,
|
||||
webUrl,
|
||||
gitlabWebUrl,
|
||||
},
|
||||
});
|
||||
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
const titleEl = wrapper.find('[data-testid="issuable-title"]');
|
||||
|
||||
expect(titleEl.exists()).toBe(true);
|
||||
expect(titleEl.find(GlLink).attributes('href')).toBe(mockIssuable.webUrl);
|
||||
expect(titleEl.find(GlLink).attributes('target')).not.toBeDefined();
|
||||
expect(titleEl.find(GlLink).attributes('href')).toBe(expectedHref);
|
||||
expect(titleEl.find(GlLink).attributes('target')).toBe(expectedTarget);
|
||||
expect(titleEl.find(GlLink).text()).toBe(mockIssuable.title);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
it('renders checkbox when `showCheckbox` prop is true', async () => {
|
||||
wrapper.setProps({
|
||||
|
|
|
@ -14,6 +14,7 @@ localVue.use(VueApollo);
|
|||
describe('StatesTableActions', () => {
|
||||
let lockResponse;
|
||||
let removeResponse;
|
||||
let toast;
|
||||
let unlockResponse;
|
||||
let updateStateResponse;
|
||||
let wrapper;
|
||||
|
@ -59,10 +60,13 @@ describe('StatesTableActions', () => {
|
|||
const createComponent = (propsData = defaultProps) => {
|
||||
const apolloProvider = createMockApolloProvider();
|
||||
|
||||
toast = jest.fn();
|
||||
|
||||
wrapper = shallowMount(StateActions, {
|
||||
apolloProvider,
|
||||
localVue,
|
||||
propsData,
|
||||
mocks: { $toast: { show: toast } },
|
||||
stubs: { GlDropdown, GlModal, GlSprintf },
|
||||
});
|
||||
|
||||
|
@ -83,6 +87,7 @@ describe('StatesTableActions', () => {
|
|||
afterEach(() => {
|
||||
lockResponse = null;
|
||||
removeResponse = null;
|
||||
toast = null;
|
||||
unlockResponse = null;
|
||||
updateStateResponse = null;
|
||||
wrapper.destroy();
|
||||
|
@ -243,7 +248,6 @@ describe('StatesTableActions', () => {
|
|||
describe('when clicking the remove button', () => {
|
||||
beforeEach(() => {
|
||||
findRemoveBtn().vm.$emit('click');
|
||||
|
||||
return waitForPromises();
|
||||
});
|
||||
|
||||
|
@ -254,21 +258,70 @@ describe('StatesTableActions', () => {
|
|||
});
|
||||
|
||||
describe('when submitting the remove modal', () => {
|
||||
it('does not call the remove mutation when state name is missing', async () => {
|
||||
describe('when state name is missing', () => {
|
||||
beforeEach(() => {
|
||||
findRemoveModal().vm.$emit('ok');
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
expect(removeResponse).not.toHaveBeenCalledWith();
|
||||
return waitForPromises();
|
||||
});
|
||||
|
||||
it('calls the remove mutation when state name is present', async () => {
|
||||
it('does not call the remove mutation', () => {
|
||||
expect(removeResponse).not.toHaveBeenCalledWith();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when state name is present', () => {
|
||||
beforeEach(async () => {
|
||||
await wrapper.setData({ removeConfirmText: defaultProps.state.name });
|
||||
|
||||
findRemoveModal().vm.$emit('ok');
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
expect(removeResponse).toHaveBeenCalledWith({
|
||||
stateID: defaultProps.state.id,
|
||||
await waitForPromises();
|
||||
});
|
||||
|
||||
it('calls the remove mutation', () => {
|
||||
expect(removeResponse).toHaveBeenCalledWith({ stateID: defaultProps.state.id });
|
||||
});
|
||||
|
||||
it('calls the toast action', () => {
|
||||
expect(toast).toHaveBeenCalledWith(`${defaultProps.state.name} successfully removed`);
|
||||
});
|
||||
|
||||
it('calls mutations to set loading and errors', () => {
|
||||
// loading update
|
||||
expect(updateStateResponse).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
{},
|
||||
{
|
||||
terraformState: {
|
||||
...defaultProps.state,
|
||||
_showDetails: false,
|
||||
errorMessages: [],
|
||||
loadingLock: false,
|
||||
loadingRemove: true,
|
||||
},
|
||||
},
|
||||
// Apollo fields
|
||||
expect.any(Object),
|
||||
expect.any(Object),
|
||||
);
|
||||
|
||||
// final update
|
||||
expect(updateStateResponse).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
{},
|
||||
{
|
||||
terraformState: {
|
||||
...defaultProps.state,
|
||||
_showDetails: false,
|
||||
errorMessages: [],
|
||||
loadingLock: false,
|
||||
loadingRemove: false,
|
||||
},
|
||||
},
|
||||
// Apollo fields
|
||||
expect.any(Object),
|
||||
expect.any(Object),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { GlIcon, GlTooltip } from '@gitlab/ui';
|
||||
import { GlIcon, GlLoadingIcon, GlTooltip } from '@gitlab/ui';
|
||||
import { mount } from '@vue/test-utils';
|
||||
import { useFakeDate } from 'helpers/fake_date';
|
||||
import StatesTable from '~/terraform/components/states_table.vue';
|
||||
|
@ -92,6 +92,17 @@ describe('StatesTable', () => {
|
|||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
_showDetails: false,
|
||||
errorMessages: [],
|
||||
name: 'state-5',
|
||||
loadingLock: false,
|
||||
loadingRemove: true,
|
||||
lockedAt: null,
|
||||
lockedByUser: null,
|
||||
updatedAt: '2020-10-10T00:00:00Z',
|
||||
latestVersion: null,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
|
@ -112,14 +123,15 @@ describe('StatesTable', () => {
|
|||
});
|
||||
|
||||
it.each`
|
||||
name | toolTipText | locked | lineNumber
|
||||
${'state-1'} | ${'Locked by user-1 2 days ago'} | ${true} | ${0}
|
||||
${'state-2'} | ${'Locking state'} | ${false} | ${1}
|
||||
${'state-3'} | ${'Unlocking state'} | ${false} | ${2}
|
||||
${'state-4'} | ${'Locked by Unknown User 5 days ago'} | ${true} | ${3}
|
||||
name | toolTipText | locked | loading | lineNumber
|
||||
${'state-1'} | ${'Locked by user-1 2 days ago'} | ${true} | ${false} | ${0}
|
||||
${'state-2'} | ${'Locking state'} | ${false} | ${true} | ${1}
|
||||
${'state-3'} | ${'Unlocking state'} | ${false} | ${true} | ${2}
|
||||
${'state-4'} | ${'Locked by Unknown User 5 days ago'} | ${true} | ${false} | ${3}
|
||||
${'state-5'} | ${'Removing'} | ${false} | ${true} | ${4}
|
||||
`(
|
||||
'displays the name and locked information "$name" for line "$lineNumber"',
|
||||
({ name, toolTipText, locked, lineNumber }) => {
|
||||
({ name, toolTipText, locked, loading, lineNumber }) => {
|
||||
const states = wrapper.findAll('[data-testid="terraform-states-table-name"]');
|
||||
|
||||
const state = states.at(lineNumber);
|
||||
|
@ -127,6 +139,7 @@ describe('StatesTable', () => {
|
|||
|
||||
expect(state.text()).toContain(name);
|
||||
expect(state.find(GlIcon).exists()).toBe(locked);
|
||||
expect(state.find(GlLoadingIcon).exists()).toBe(loading);
|
||||
expect(toolTip.exists()).toBe(locked);
|
||||
|
||||
if (locked) {
|
||||
|
|
|
@ -1,96 +0,0 @@
|
|||
import { GlIcon } from '@gitlab/ui';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import ToggleButton from '~/vue_shared/components/toggle_button.vue';
|
||||
|
||||
describe('Toggle Button component', () => {
|
||||
let wrapper;
|
||||
|
||||
function createComponent(propsData = {}) {
|
||||
wrapper = shallowMount(ToggleButton, {
|
||||
propsData,
|
||||
});
|
||||
}
|
||||
|
||||
const findInput = () => wrapper.find('input');
|
||||
const findButton = () => wrapper.find('button');
|
||||
const findToggleIcon = () => wrapper.find(GlIcon);
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
wrapper = null;
|
||||
});
|
||||
|
||||
it('renders input with provided name', () => {
|
||||
createComponent({
|
||||
name: 'foo',
|
||||
});
|
||||
|
||||
expect(findInput().attributes('name')).toBe('foo');
|
||||
});
|
||||
|
||||
describe.each`
|
||||
value | iconName
|
||||
${true} | ${'status_success_borderless'}
|
||||
${false} | ${'status_failed_borderless'}
|
||||
`('when `value` prop is `$value`', ({ value, iconName }) => {
|
||||
beforeEach(() => {
|
||||
createComponent({
|
||||
value,
|
||||
name: 'foo',
|
||||
});
|
||||
});
|
||||
|
||||
it('renders input with correct value attribute', () => {
|
||||
expect(findInput().attributes('value')).toBe(`${value}`);
|
||||
});
|
||||
|
||||
it('renders correct icon', () => {
|
||||
const icon = findToggleIcon();
|
||||
expect(icon.isVisible()).toBe(true);
|
||||
expect(icon.props('name')).toBe(iconName);
|
||||
expect(findButton().classes('is-checked')).toBe(value);
|
||||
});
|
||||
|
||||
describe('when clicked', () => {
|
||||
it('emits `change` event with correct event', async () => {
|
||||
findButton().trigger('click');
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
expect(wrapper.emitted('change')).toStrictEqual([[!value]]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when `disabledInput` prop is `true`', () => {
|
||||
beforeEach(() => {
|
||||
createComponent({
|
||||
value: true,
|
||||
disabledInput: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('renders disabled button', () => {
|
||||
expect(findButton().classes()).toContain('is-disabled');
|
||||
});
|
||||
|
||||
it('does not emit change event when clicked', async () => {
|
||||
findButton().trigger('click');
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
expect(wrapper.emitted('change')).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when `isLoading` prop is `true`', () => {
|
||||
beforeEach(() => {
|
||||
createComponent({
|
||||
value: true,
|
||||
isLoading: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('renders loading class', () => {
|
||||
expect(findButton().classes()).toContain('is-loading');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,96 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Resolvers::GroupLabelsResolver do
|
||||
include GraphqlHelpers
|
||||
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
let_it_be(:current_user) { create(:user) }
|
||||
let_it_be(:group, reload: true) { create(:group, :private) }
|
||||
let_it_be(:subgroup, reload: true) { create(:group, :private, parent: group) }
|
||||
let_it_be(:sub_subgroup, reload: true) { create(:group, :private, parent: subgroup) }
|
||||
let_it_be(:project, reload: true) { create(:project, :private, group: sub_subgroup) }
|
||||
let_it_be(:label1) { create(:label, project: project, name: 'project feature') }
|
||||
let_it_be(:label2) { create(:label, project: project, name: 'new project feature') }
|
||||
let_it_be(:group_label1) { create(:group_label, group: group, name: 'group feature') }
|
||||
let_it_be(:group_label2) { create(:group_label, group: group, name: 'new group feature') }
|
||||
let_it_be(:subgroup_label1) { create(:group_label, group: subgroup, name: 'subgroup feature') }
|
||||
let_it_be(:subgroup_label2) { create(:group_label, group: subgroup, name: 'new subgroup feature') }
|
||||
let_it_be(:sub_subgroup_label1) { create(:group_label, group: sub_subgroup, name: 'sub_subgroup feature') }
|
||||
let_it_be(:sub_subgroup_label2) { create(:group_label, group: sub_subgroup, name: 'new sub_subgroup feature') }
|
||||
|
||||
specify do
|
||||
expect(described_class).to have_nullable_graphql_type(Types::LabelType.connection_type)
|
||||
end
|
||||
|
||||
describe '#resolve' do
|
||||
context 'with unauthorized user' do
|
||||
it 'raises error' do
|
||||
expect { resolve_labels(subgroup) }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with authorized user' do
|
||||
it 'does not raise error' do
|
||||
group.add_guest(current_user)
|
||||
|
||||
expect { resolve_labels(subgroup) }.not_to raise_error
|
||||
end
|
||||
end
|
||||
|
||||
context 'without parent' do
|
||||
it 'returns no labels' do
|
||||
expect(resolve_labels(nil)).to eq(Label.none)
|
||||
end
|
||||
end
|
||||
|
||||
context 'at group level' do
|
||||
before_all do
|
||||
group.add_developer(current_user)
|
||||
end
|
||||
|
||||
# because :include_ancestor_groups, :include_descendant_groups, :only_group_labels default to false
|
||||
# the `nil` value would be equivalent to passing in `false` so just check for `nil` option
|
||||
where(:include_ancestor_groups, :include_descendant_groups, :only_group_labels, :search_term, :test) do
|
||||
nil | nil | nil | nil | -> { expect(subject).to contain_exactly(subgroup_label1, subgroup_label2) }
|
||||
nil | nil | true | nil | -> { expect(subject).to contain_exactly(subgroup_label1, subgroup_label2) }
|
||||
nil | true | nil | nil | -> { expect(subject).to contain_exactly(subgroup_label1, subgroup_label2, sub_subgroup_label1, sub_subgroup_label2, label1, label2) }
|
||||
nil | true | true | nil | -> { expect(subject).to contain_exactly(subgroup_label1, subgroup_label2, sub_subgroup_label1, sub_subgroup_label2) }
|
||||
true | nil | nil | nil | -> { expect(subject).to contain_exactly(group_label1, group_label2, subgroup_label1, subgroup_label2) }
|
||||
true | nil | true | nil | -> { expect(subject).to contain_exactly(group_label1, group_label2, subgroup_label1, subgroup_label2) }
|
||||
true | true | nil | nil | -> { expect(subject).to contain_exactly(group_label1, group_label2, subgroup_label1, subgroup_label2, sub_subgroup_label1, sub_subgroup_label2, label1, label2) }
|
||||
true | true | true | nil | -> { expect(subject).to contain_exactly(group_label1, group_label2, subgroup_label1, subgroup_label2, sub_subgroup_label1, sub_subgroup_label2) }
|
||||
|
||||
nil | nil | nil | 'new' | -> { expect(subject).to contain_exactly(subgroup_label2) }
|
||||
nil | nil | true | 'new' | -> { expect(subject).to contain_exactly(subgroup_label2) }
|
||||
nil | true | nil | 'new' | -> { expect(subject).to contain_exactly(subgroup_label2, sub_subgroup_label2, label2) }
|
||||
nil | true | true | 'new' | -> { expect(subject).to contain_exactly(subgroup_label2, sub_subgroup_label2) }
|
||||
true | nil | nil | 'new' | -> { expect(subject).to contain_exactly(group_label2, subgroup_label2) }
|
||||
true | nil | true | 'new' | -> { expect(subject).to contain_exactly(group_label2, subgroup_label2) }
|
||||
true | true | nil | 'new' | -> { expect(subject).to contain_exactly(group_label2, subgroup_label2, sub_subgroup_label2, label2) }
|
||||
true | true | true | 'new' | -> { expect(subject).to contain_exactly(group_label2, subgroup_label2, sub_subgroup_label2) }
|
||||
end
|
||||
|
||||
with_them do
|
||||
let(:params) do
|
||||
{
|
||||
include_ancestor_groups: include_ancestor_groups,
|
||||
include_descendant_groups: include_descendant_groups,
|
||||
only_group_labels: only_group_labels,
|
||||
search_term: search_term
|
||||
}
|
||||
end
|
||||
|
||||
subject { resolve_labels(subgroup, params) }
|
||||
|
||||
it { self.instance_exec(&test) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def resolve_labels(parent, args = {}, context = { current_user: current_user })
|
||||
resolve(described_class, obj: parent, args: args, ctx: context)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,96 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Resolvers::LabelsResolver do
|
||||
include GraphqlHelpers
|
||||
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
let_it_be(:current_user) { create(:user) }
|
||||
let_it_be(:group, reload: true) { create(:group, :private) }
|
||||
let_it_be(:subgroup, reload: true) { create(:group, :private, parent: group) }
|
||||
let_it_be(:sub_subgroup, reload: true) { create(:group, :private, parent: subgroup) }
|
||||
let_it_be(:project, reload: true) { create(:project, :private, group: subgroup) }
|
||||
let_it_be(:label1) { create(:label, project: project, name: 'project feature') }
|
||||
let_it_be(:label2) { create(:label, project: project, name: 'new project feature') }
|
||||
let_it_be(:group_label1) { create(:group_label, group: group, name: 'group feature') }
|
||||
let_it_be(:group_label2) { create(:group_label, group: group, name: 'new group feature') }
|
||||
let_it_be(:subgroup_label1) { create(:group_label, group: subgroup, name: 'subgroup feature') }
|
||||
let_it_be(:subgroup_label2) { create(:group_label, group: subgroup, name: 'new subgroup feature') }
|
||||
let_it_be(:sub_subgroup_label1) { create(:group_label, group: sub_subgroup, name: 'sub_subgroup feature') }
|
||||
let_it_be(:sub_subgroup_label2) { create(:group_label, group: sub_subgroup, name: 'new sub_subgroup feature') }
|
||||
|
||||
specify do
|
||||
expect(described_class).to have_nullable_graphql_type(Types::LabelType.connection_type)
|
||||
end
|
||||
|
||||
describe '#resolve' do
|
||||
context 'with unauthorized user' do
|
||||
it 'returns no labels' do
|
||||
expect { resolve_labels(project) }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with authorized user' do
|
||||
it 'returns no labels' do
|
||||
group.add_guest(current_user)
|
||||
|
||||
expect { resolve_labels(project) }.not_to raise_error
|
||||
end
|
||||
end
|
||||
|
||||
context 'without parent' do
|
||||
it 'returns no labels' do
|
||||
expect(resolve_labels(nil)).to eq(Label.none)
|
||||
end
|
||||
end
|
||||
|
||||
context 'at project level' do
|
||||
before_all do
|
||||
group.add_developer(current_user)
|
||||
end
|
||||
|
||||
# because :include_ancestor_groups, :include_descendant_groups, :only_group_labels default to false
|
||||
# the `nil` value would be equivalent to passing in `false` so just check for `nil` option
|
||||
where(:include_ancestor_groups, :include_descendant_groups, :only_group_labels, :search_term, :test) do
|
||||
nil | nil | nil | nil | -> { expect(subject).to contain_exactly(label1, label2, subgroup_label1, subgroup_label2) }
|
||||
nil | nil | true | nil | -> { expect(subject).to contain_exactly(label1, label2, subgroup_label1, subgroup_label2) }
|
||||
nil | true | nil | nil | -> { expect(subject).to contain_exactly(label1, label2, subgroup_label1, subgroup_label2, sub_subgroup_label1, sub_subgroup_label2) }
|
||||
nil | true | true | nil | -> { expect(subject).to contain_exactly(label1, label2, subgroup_label1, subgroup_label2, sub_subgroup_label1, sub_subgroup_label2) }
|
||||
true | nil | nil | nil | -> { expect(subject).to contain_exactly(label1, label2, group_label1, group_label2, subgroup_label1, subgroup_label2) }
|
||||
true | nil | true | nil | -> { expect(subject).to contain_exactly(label1, label2, group_label1, group_label2, subgroup_label1, subgroup_label2) }
|
||||
true | true | nil | nil | -> { expect(subject).to contain_exactly(label1, label2, group_label1, group_label2, subgroup_label1, subgroup_label2, sub_subgroup_label1, sub_subgroup_label2) }
|
||||
true | true | true | nil | -> { expect(subject).to contain_exactly(label1, label2, group_label1, group_label2, subgroup_label1, subgroup_label2, sub_subgroup_label1, sub_subgroup_label2) }
|
||||
|
||||
nil | nil | nil | 'new' | -> { expect(subject).to contain_exactly(label2, subgroup_label2) }
|
||||
nil | nil | true | 'new' | -> { expect(subject).to contain_exactly(label2, subgroup_label2) }
|
||||
nil | true | nil | 'new' | -> { expect(subject).to contain_exactly(label2, subgroup_label2, sub_subgroup_label2) }
|
||||
nil | true | true | 'new' | -> { expect(subject).to contain_exactly(label2, subgroup_label2, sub_subgroup_label2) }
|
||||
true | nil | nil | 'new' | -> { expect(subject).to contain_exactly(label2, group_label2, subgroup_label2) }
|
||||
true | nil | true | 'new' | -> { expect(subject).to contain_exactly(label2, group_label2, subgroup_label2) }
|
||||
true | true | nil | 'new' | -> { expect(subject).to contain_exactly(label2, group_label2, subgroup_label2, sub_subgroup_label2) }
|
||||
true | true | true | 'new' | -> { expect(subject).to contain_exactly(label2, group_label2, subgroup_label2, sub_subgroup_label2) }
|
||||
end
|
||||
|
||||
with_them do
|
||||
let(:params) do
|
||||
{
|
||||
include_ancestor_groups: include_ancestor_groups,
|
||||
include_descendant_groups: include_descendant_groups,
|
||||
only_group_labels: only_group_labels,
|
||||
search_term: search_term
|
||||
}
|
||||
end
|
||||
|
||||
subject { resolve_labels(project, params) }
|
||||
|
||||
it { self.instance_exec(&test) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def resolve_labels(parent, args = {}, context = { current_user: current_user })
|
||||
resolve(described_class, obj: parent, args: args, ctx: context)
|
||||
end
|
||||
end
|
|
@ -38,5 +38,7 @@ RSpec.describe GitlabSchema.types['Group'] do
|
|||
it { is_expected.to have_graphql_resolver(Resolvers::GroupMembersResolver) }
|
||||
end
|
||||
|
||||
it_behaves_like 'a GraphQL type with labels'
|
||||
it_behaves_like 'a GraphQL type with labels' do
|
||||
let(:labels_resolver_arguments) { [:search_term, :includeAncestorGroups, :includeDescendantGroups, :onlyGroupLabels] }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -332,7 +332,9 @@ RSpec.describe GitlabSchema.types['Project'] do
|
|||
it { is_expected.to have_graphql_resolver(Resolvers::Terraform::StatesResolver) }
|
||||
end
|
||||
|
||||
it_behaves_like 'a GraphQL type with labels'
|
||||
it_behaves_like 'a GraphQL type with labels' do
|
||||
let(:labels_resolver_arguments) { [:search_term, :includeAncestorGroups] }
|
||||
end
|
||||
|
||||
describe 'jira_imports' do
|
||||
subject { resolve_field(:jira_imports, project) }
|
||||
|
|
|
@ -27,7 +27,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::TemplateUsage do
|
|||
%w(Template-1 Template-2).each do |expected_template|
|
||||
expect(Gitlab::UsageDataCounters::CiTemplateUniqueCounter).to(
|
||||
receive(:track_unique_project_event)
|
||||
.with(project_id: project.id, template: expected_template)
|
||||
.with(project_id: project.id, template: expected_template, config_source: pipeline.config_source)
|
||||
)
|
||||
end
|
||||
|
||||
|
|
|
@ -160,6 +160,47 @@ RSpec.describe Gitlab::UrlBlocker, :stub_invalid_dns_only do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when resolving runs into a timeout' do
|
||||
let(:import_url) { 'http://example.com' }
|
||||
|
||||
subject { described_class.validate!(import_url, dns_rebind_protection: dns_rebind_protection) }
|
||||
|
||||
before do
|
||||
skip 'timeout is not available' unless timeout_available?
|
||||
|
||||
stub_env('RSPEC_ALLOW_INVALID_URLS', 'false')
|
||||
stub_const("#{described_class}::GETADDRINFO_TIMEOUT_SECONDS", 0)
|
||||
end
|
||||
|
||||
context 'with dns rebinding enabled' do
|
||||
let(:dns_rebind_protection) { true }
|
||||
|
||||
it 'raises an error due to DNS timeout' do
|
||||
expect { subject }.to raise_error(described_class::BlockedUrlError)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with dns rebinding disabled' do
|
||||
let(:dns_rebind_protection) { false }
|
||||
|
||||
it_behaves_like 'validates URI and hostname' do
|
||||
let(:expected_uri) { import_url }
|
||||
let(:expected_hostname) { nil }
|
||||
end
|
||||
end
|
||||
|
||||
# Detect whether the timeout option is available.
|
||||
#
|
||||
# See https://bugs.ruby-lang.org/issues/15553
|
||||
def timeout_available?
|
||||
Addrinfo.getaddrinfo('localhost', nil, timeout: 0)
|
||||
|
||||
false
|
||||
rescue SocketError
|
||||
true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#blocked_url?' do
|
||||
|
|
|
@ -3,28 +3,88 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::UsageDataCounters::CiTemplateUniqueCounter do
|
||||
let(:project_id) { 1 }
|
||||
|
||||
describe '.track_unique_project_event' do
|
||||
described_class::TEMPLATE_TO_EVENT.keys.each do |template|
|
||||
context "when given template #{template}" do
|
||||
it_behaves_like 'tracking unique hll events' do
|
||||
subject(:request) { described_class.track_unique_project_event(project_id: project_id, template: template) }
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
let(:target_id) { "p_ci_templates_#{described_class::TEMPLATE_TO_EVENT[template]}" }
|
||||
where(:template, :config_source, :expected_event) do
|
||||
# Implicit Auto DevOps usage
|
||||
'Auto-DevOps.gitlab-ci.yml' | :auto_devops_source | 'p_ci_templates_implicit_auto_devops'
|
||||
'Jobs/Build.gitlab-ci.yml' | :auto_devops_source | 'p_ci_templates_implicit_auto_devops_build'
|
||||
'Jobs/Deploy.gitlab-ci.yml' | :auto_devops_source | 'p_ci_templates_implicit_auto_devops_deploy'
|
||||
'Security/SAST.gitlab-ci.yml' | :auto_devops_source | 'p_ci_templates_implicit_security_sast'
|
||||
'Security/Secret-Detection.gitlab-ci.yml' | :auto_devops_source | 'p_ci_templates_implicit_security_secret_detection'
|
||||
# Explicit include:template usage
|
||||
'5-Minute-Production-App.gitlab-ci.yml' | :repository_source | 'p_ci_templates_5_min_production_app'
|
||||
'Auto-DevOps.gitlab-ci.yml' | :repository_source | 'p_ci_templates_auto_devops'
|
||||
'AWS/CF-Provision-and-Deploy-EC2.gitlab-ci.yml' | :repository_source | 'p_ci_templates_aws_cf_deploy_ec2'
|
||||
'AWS/Deploy-ECS.gitlab-ci.yml' | :repository_source | 'p_ci_templates_aws_deploy_ecs'
|
||||
'Jobs/Build.gitlab-ci.yml' | :repository_source | 'p_ci_templates_auto_devops_build'
|
||||
'Jobs/Deploy.gitlab-ci.yml' | :repository_source | 'p_ci_templates_auto_devops_deploy'
|
||||
'Jobs/Deploy.latest.gitlab-ci.yml' | :repository_source | 'p_ci_templates_auto_devops_deploy_latest'
|
||||
'Security/SAST.gitlab-ci.yml' | :repository_source | 'p_ci_templates_security_sast'
|
||||
'Security/Secret-Detection.gitlab-ci.yml' | :repository_source | 'p_ci_templates_security_secret_detection'
|
||||
'Terraform/Base.latest.gitlab-ci.yml' | :repository_source | 'p_ci_templates_terraform_base_latest'
|
||||
end
|
||||
|
||||
with_them do
|
||||
it_behaves_like 'tracking unique hll events' do
|
||||
subject(:request) { described_class.track_unique_project_event(project_id: project_id, template: template, config_source: config_source) }
|
||||
|
||||
let(:project_id) { 1 }
|
||||
let(:target_id) { expected_event }
|
||||
let(:expected_type) { instance_of(Integer) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'known_events coverage tests' do
|
||||
let(:project_id) { 1 }
|
||||
let(:config_source) { :repository_source }
|
||||
|
||||
# These tests help guard against missing "explicit" events in known_events/ci_templates.yml
|
||||
context 'explicit include:template events' do
|
||||
described_class::TEMPLATE_TO_EVENT.keys.each do |template|
|
||||
it "does not raise error for #{template}" do
|
||||
expect do
|
||||
described_class.track_unique_project_event(project_id: project_id, template: template, config_source: config_source)
|
||||
end.not_to raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'does not track templates outside of TEMPLATE_TO_EVENT' do
|
||||
expect(Gitlab::UsageDataCounters::HLLRedisCounter).not_to(
|
||||
receive(:track_event)
|
||||
)
|
||||
# This test is to help guard against missing "implicit" events in known_events/ci_templates.yml
|
||||
it 'does not raise error for any template in an implicit Auto DevOps pipeline' do
|
||||
project = create(:project, :auto_devops)
|
||||
pipeline = double(project: project)
|
||||
command = double
|
||||
result = Gitlab::Ci::YamlProcessor.new(
|
||||
Gitlab::Ci::Pipeline::Chain::Config::Content::AutoDevops.new(pipeline, command).content,
|
||||
project: project,
|
||||
user: double,
|
||||
sha: double
|
||||
).execute
|
||||
|
||||
config_source = :auto_devops_source
|
||||
|
||||
result.included_templates.each do |template|
|
||||
expect do
|
||||
described_class.track_unique_project_event(project_id: project.id, template: template, config_source: config_source)
|
||||
end.not_to raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'templates outside of TEMPLATE_TO_EVENT' do
|
||||
let(:project_id) { 1 }
|
||||
let(:config_source) { :repository_source }
|
||||
|
||||
Dir.glob(File.join('lib', 'gitlab', 'ci', 'templates', '**'), base: Rails.root) do |template|
|
||||
next if described_class::TEMPLATE_TO_EVENT.key?(template)
|
||||
|
||||
described_class.track_unique_project_event(project_id: 1, template: template)
|
||||
it "does not track #{template}" do
|
||||
expect(Gitlab::UsageDataCounters::HLLRedisCounter).not_to(receive(:track_event))
|
||||
|
||||
described_class.track_unique_project_event(project_id: project_id, template: template, config_source: config_source)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -12,19 +12,38 @@ module DnsHelpers
|
|||
end
|
||||
|
||||
def stub_all_dns!
|
||||
allow(Addrinfo).to receive(:getaddrinfo).with(anything, anything, nil, :STREAM).and_return([])
|
||||
allow(Addrinfo).to receive(:getaddrinfo).with(anything, anything, nil, :STREAM, anything, anything).and_return([])
|
||||
allow(Addrinfo).to receive(:getaddrinfo).and_return([])
|
||||
end
|
||||
|
||||
def stub_invalid_dns!
|
||||
allow(Addrinfo).to receive(:getaddrinfo).with(/\Afoobar\.\w|(\d{1,3}\.){4,}\d{1,3}\z/i, anything, nil, :STREAM) do
|
||||
raise SocketError.new("getaddrinfo: Name or service not known")
|
||||
end
|
||||
invalid_addresses = %r{
|
||||
\A
|
||||
(?:
|
||||
foobar\.\w |
|
||||
(?:\d{1,3}\.){4,}\d{1,3}
|
||||
)
|
||||
\z
|
||||
}ix
|
||||
|
||||
allow(Addrinfo).to receive(:getaddrinfo)
|
||||
.with(invalid_addresses, any_args)
|
||||
.and_raise(SocketError, 'getaddrinfo: Name or service not known')
|
||||
end
|
||||
|
||||
def permit_local_dns!
|
||||
local_addresses = /\A(127|10)\.0\.0\.\d{1,3}|(192\.168|172\.16)\.\d{1,3}\.\d{1,3}|0\.0\.0\.0|localhost\z/i
|
||||
allow(Addrinfo).to receive(:getaddrinfo).with(local_addresses, anything, nil, :STREAM).and_call_original
|
||||
allow(Addrinfo).to receive(:getaddrinfo).with(local_addresses, anything, nil, :STREAM, anything, anything, any_args).and_call_original
|
||||
local_addresses = %r{
|
||||
\A
|
||||
(?:
|
||||
(?:127|10)\.0\.0\.\d{1,3} |
|
||||
(?:192\.168|172\.16)\.\d{1,3}\.\d{1,3} |
|
||||
0\.0\.0\.0 |
|
||||
localhost
|
||||
)
|
||||
\z
|
||||
}ix
|
||||
|
||||
allow(Addrinfo).to receive(:getaddrinfo)
|
||||
.with(local_addresses, any_args)
|
||||
.and_call_original
|
||||
end
|
||||
end
|
||||
|
|
|
@ -18,13 +18,12 @@ module StubRequests
|
|||
end
|
||||
|
||||
def stub_dns(url, ip_address:, port: 80)
|
||||
url = parse_url(url)
|
||||
url = URI(url)
|
||||
socket = Socket.sockaddr_in(port, ip_address)
|
||||
addr = Addrinfo.new(socket)
|
||||
|
||||
# See Gitlab::UrlBlocker
|
||||
allow(Addrinfo).to receive(:getaddrinfo)
|
||||
.with(url.hostname, url.port, nil, :STREAM)
|
||||
.with(url.hostname, url.port, any_args)
|
||||
.and_return([addr])
|
||||
end
|
||||
|
||||
|
@ -34,22 +33,14 @@ module StubRequests
|
|||
socket = Socket.sockaddr_in(port, ip_address)
|
||||
addr = Addrinfo.new(socket)
|
||||
|
||||
# See Gitlab::UrlBlocker
|
||||
allow(Addrinfo).to receive(:getaddrinfo).and_call_original
|
||||
allow(Addrinfo).to receive(:getaddrinfo)
|
||||
.with(url.hostname, anything, nil, :STREAM)
|
||||
.with(url.hostname, any_args)
|
||||
.and_return([addr])
|
||||
end
|
||||
|
||||
def stubbed_hostname(url, hostname: IP_ADDRESS_STUB)
|
||||
url = parse_url(url)
|
||||
url = URI(url)
|
||||
url.hostname = hostname
|
||||
url.to_s
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def parse_url(url)
|
||||
url.is_a?(URI) ? url : URI(url)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -18,7 +18,7 @@ RSpec.shared_examples 'a GraphQL type with labels' do
|
|||
subject { described_class.fields['labels'] }
|
||||
|
||||
it { is_expected.to have_graphql_type(Types::LabelType.connection_type) }
|
||||
it { is_expected.to have_graphql_arguments(:search_term) }
|
||||
it { is_expected.to have_graphql_arguments(labels_resolver_arguments) }
|
||||
end
|
||||
end
|
||||
|
||||
|
|
Loading…
Reference in New Issue