diff options
36 files changed, 539 insertions, 412 deletions
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index b6148bc0a75..65ee0959841 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -1.66.0 +1.67.0 diff --git a/app/assets/javascripts/create_cluster/eks_cluster/components/cluster_form_dropdown.vue b/app/assets/javascripts/create_cluster/eks_cluster/components/cluster_form_dropdown.vue index f9465da6fda..3c6da43c4c4 100644 --- a/app/assets/javascripts/create_cluster/eks_cluster/components/cluster_form_dropdown.vue +++ b/app/assets/javascripts/create_cluster/eks_cluster/components/cluster_form_dropdown.vue @@ -3,6 +3,8 @@ import DropdownSearchInput from '~/vue_shared/components/dropdown/dropdown_searc import DropdownHiddenInput from '~/vue_shared/components/dropdown/dropdown_hidden_input.vue'; import DropdownButton from '~/vue_shared/components/dropdown/dropdown_button.vue'; +const findItem = (items, valueProp, value) => items.find(item => item[valueProp] === value); + export default { components: { DropdownButton, @@ -26,7 +28,7 @@ export default { default: '', }, value: { - type: Object, + type: [Object, String], required: false, default: () => null, }, @@ -93,8 +95,8 @@ export default { }, data() { return { + selectedItem: findItem(this.items, this.value), searchQuery: '', - selectedItem: null, }; }, computed: { @@ -127,10 +129,15 @@ export default { return (this.selectedItem && this.selectedItem[this.valueProperty]) || ''; }, }, + watch: { + value(value) { + this.selectedItem = findItem(this.items, this.valueProperty, value); + }, + }, methods: { select(item) { this.selectedItem = item; - this.$emit('input', item); + this.$emit('input', item[this.valueProperty]); }, }, }; diff --git a/app/assets/javascripts/create_cluster/eks_cluster/components/eks_cluster_configuration_form.vue b/app/assets/javascripts/create_cluster/eks_cluster/components/eks_cluster_configuration_form.vue index 6f6b9ad025a..94a446f1721 100644 --- a/app/assets/javascripts/create_cluster/eks_cluster/components/eks_cluster_configuration_form.vue +++ b/app/assets/javascripts/create_cluster/eks_cluster/components/eks_cluster_configuration_form.vue @@ -3,12 +3,15 @@ import { createNamespacedHelpers, mapState, mapActions } from 'vuex'; import { sprintf, s__ } from '~/locale'; import ClusterFormDropdown from './cluster_form_dropdown.vue'; import RegionDropdown from './region_dropdown.vue'; -import RoleNameDropdown from './role_name_dropdown.vue'; import SecurityGroupDropdown from './security_group_dropdown.vue'; +const { mapState: mapRolesState, mapActions: mapRolesActions } = createNamespacedHelpers('roles'); const { mapState: mapRegionsState, mapActions: mapRegionsActions } = createNamespacedHelpers( 'regions', ); +const { mapState: mapKeyPairsState, mapActions: mapKeyPairsActions } = createNamespacedHelpers( + 'keyPairs', +); const { mapState: mapVpcsState, mapActions: mapVpcActions } = createNamespacedHelpers('vpcs'); const { mapState: mapSubnetsState, mapActions: mapSubnetActions } = createNamespacedHelpers( 'subnets', @@ -18,16 +21,31 @@ export default { components: { ClusterFormDropdown, RegionDropdown, - RoleNameDropdown, SecurityGroupDropdown, }, computed: { - ...mapState(['selectedRegion', 'selectedVpc', 'selectedSubnet']), + ...mapState([ + 'selectedRegion', + 'selectedKeyPair', + 'selectedVpc', + 'selectedSubnet', + 'selectedRole', + ]), + ...mapRolesState({ + roles: 'items', + isLoadingRoles: 'isLoadingItems', + loadingRolesError: 'loadingItemsError', + }), ...mapRegionsState({ regions: 'items', isLoadingRegions: 'isLoadingItems', loadingRegionsError: 'loadingItemsError', }), + ...mapKeyPairsState({ + keyPairs: 'items', + isLoadingKeyPairs: 'isLoadingItems', + loadingKeyPairsError: 'loadingItemsError', + }), ...mapVpcsState({ vpcs: 'items', isLoadingVpcs: 'isLoadingItems', @@ -41,9 +59,38 @@ export default { vpcDropdownDisabled() { return !this.selectedRegion; }, + keyPairDropdownDisabled() { + return !this.selectedRegion; + }, subnetDropdownDisabled() { return !this.selectedVpc; }, + roleDropdownHelpText() { + return sprintf( + s__( + 'ClusterIntegration|Select the IAM Role to allow Amazon EKS and the Kubernetes control plane to manage AWS resources on your behalf. To use a new role name, first create one on %{startLink}Amazon Web Services%{endLink}.', + ), + { + startLink: + '<a href="https://console.aws.amazon.com/iam/home?#roles" target="_blank" rel="noopener noreferrer">', + endLink: '</a>', + }, + false, + ); + }, + keyPairDropdownHelpText() { + return sprintf( + s__( + 'ClusterIntegration|Select the key pair name that will be used to create EC2 nodes. To use a new key pair name, first create one on %{startLink}Amazon Web Services%{endLink}.', + ), + { + startLink: + '<a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-key-pairs.html#having-ec2-create-your-key-pair" target="_blank" rel="noopener noreferrer">', + endLink: '</a>', + }, + false, + ); + }, vpcDropdownHelpText() { return sprintf( s__( @@ -73,15 +120,19 @@ export default { }, mounted() { this.fetchRegions(); + this.fetchRoles(); }, methods: { - ...mapActions(['setRegion', 'setVpc', 'setSubnet']), + ...mapActions(['setRegion', 'setVpc', 'setSubnet', 'setRole', 'setKeyPair']), ...mapRegionsActions({ fetchRegions: 'fetchItems' }), ...mapVpcActions({ fetchVpcs: 'fetchItems' }), ...mapSubnetActions({ fetchSubnets: 'fetchItems' }), - setRegionAndFetchVpcs(region) { + ...mapRolesActions({ fetchRoles: 'fetchItems' }), + ...mapKeyPairsActions({ fetchKeyPairs: 'fetchItems' }), + setRegionAndFetchVpcsAndKeyPairs(region) { this.setRegion({ region }); this.fetchVpcs({ region }); + this.fetchKeyPairs({ region }); }, setVpcAndFetchSubnets(vpc) { this.setVpc({ vpc }); @@ -93,28 +144,58 @@ export default { <template> <form name="eks-cluster-configuration-form"> <div class="form-group"> - <label class="label-bold" name="role" for="eks-role">{{ - s__('ClusterIntegration|Role name') - }}</label> - <role-name-dropdown /> + <label class="label-bold" for="eks-role">{{ s__('ClusterIntegration|Role name') }}</label> + <cluster-form-dropdown + field-id="eks-role" + field-name="eks-role" + :input="selectedRole" + :items="roles" + :loading="isLoadingRoles" + :loading-text="s__('ClusterIntegration|Loading IAM Roles')" + :placeholder="s__('ClusterIntergation|Select role name')" + :search-field-placeholder="s__('ClusterIntegration|Search IAM Roles')" + :empty-text="s__('ClusterIntegration|No IAM Roles found')" + :has-errors="Boolean(loadingRolesError)" + :error-message="s__('ClusterIntegration|Could not load IAM roles')" + @input="setRole({ role: $event })" + /> + <p class="form-text text-muted" v-html="roleDropdownHelpText"></p> </div> <div class="form-group"> - <label class="label-bold" name="role" for="eks-role">{{ - s__('ClusterIntegration|Region') - }}</label> + <label class="label-bold" for="eks-role">{{ s__('ClusterIntegration|Region') }}</label> <region-dropdown :value="selectedRegion" :regions="regions" :error="loadingRegionsError" :loading="isLoadingRegions" - @input="setRegionAndFetchVpcs($event)" + @input="setRegionAndFetchVpcsAndKeyPairs($event)" /> </div> <div class="form-group"> - <label class="label-bold" name="eks-vpc" for="eks-vpc">{{ - s__('ClusterIntegration|VPC') + <label class="label-bold" for="eks-key-pair">{{ + s__('ClusterIntegration|Key pair name') }}</label> <cluster-form-dropdown + field-id="eks-key-pair" + field-name="eks-key-pair" + :input="selectedKeyPair" + :items="keyPairs" + :disabled="keyPairDropdownDisabled" + :disabled-text="s__('ClusterIntegration|Select a region to choose a Key Pair')" + :loading="isLoadingKeyPairs" + :loading-text="s__('ClusterIntegration|Loading Key Pairs')" + :placeholder="s__('ClusterIntergation|Select key pair')" + :search-field-placeholder="s__('ClusterIntegration|Search Key Pairs')" + :empty-text="s__('ClusterIntegration|No Key Pairs found')" + :has-errors="Boolean(loadingKeyPairsError)" + :error-message="s__('ClusterIntegration|Could not load Key Pairs')" + @input="setKeyPair({ keyPair: $event })" + /> + <p class="form-text text-muted" v-html="keyPairDropdownHelpText"></p> + </div> + <div class="form-group"> + <label class="label-bold" for="eks-vpc">{{ s__('ClusterIntegration|VPC') }}</label> + <cluster-form-dropdown field-id="eks-vpc" field-name="eks-vpc" :input="selectedVpc" @@ -126,16 +207,14 @@ export default { :placeholder="s__('ClusterIntergation|Select a VPC')" :search-field-placeholder="s__('ClusterIntegration|Search VPCs')" :empty-text="s__('ClusterIntegration|No VPCs found')" - :has-errors="loadingVpcsError" + :has-errors="Boolean(loadingVpcsError)" :error-message="s__('ClusterIntegration|Could not load VPCs for the selected region')" @input="setVpcAndFetchSubnets($event)" /> <p class="form-text text-muted" v-html="vpcDropdownHelpText"></p> </div> <div class="form-group"> - <label class="label-bold" name="eks-subnet" for="eks-subnet">{{ - s__('ClusterIntegration|Subnet') - }}</label> + <label class="label-bold" for="eks-role">{{ s__('ClusterIntegration|Subnet') }}</label> <cluster-form-dropdown field-id="eks-subnet" field-name="eks-subnet" @@ -148,7 +227,7 @@ export default { :placeholder="s__('ClusterIntergation|Select a subnet')" :search-field-placeholder="s__('ClusterIntegration|Search subnets')" :empty-text="s__('ClusterIntegration|No subnet found')" - :has-errors="loadingSubnetsError" + :has-errors="Boolean(loadingSubnetsError)" :error-message="s__('ClusterIntegration|Could not load subnets for the selected VPC')" @input="setSubnet({ subnet: $event })" /> diff --git a/app/assets/javascripts/create_cluster/eks_cluster/components/role_name_dropdown.vue b/app/assets/javascripts/create_cluster/eks_cluster/components/role_name_dropdown.vue deleted file mode 100644 index 70230b294ac..00000000000 --- a/app/assets/javascripts/create_cluster/eks_cluster/components/role_name_dropdown.vue +++ /dev/null @@ -1,53 +0,0 @@ -<script> -import { sprintf, s__ } from '~/locale'; - -import ClusterFormDropdown from './cluster_form_dropdown.vue'; - -export default { - components: { - ClusterFormDropdown, - }, - props: { - roles: { - type: Array, - required: false, - default: () => [], - }, - loading: { - type: Boolean, - required: false, - default: false, - }, - }, - computed: { - helpText() { - return sprintf( - s__( - 'ClusterIntegration|Select the IAM Role to allow Amazon EKS and the Kubernetes control plane to manage AWS resources on your behalf. To use a new role name, first create one on %{startLink}Amazon Web Services%{endLink}.', - ), - { - startLink: - '<a href="https://console.aws.amazon.com/iam/home?#roles" target="_blank" rel="noopener noreferrer">', - endLink: '</a>', - }, - false, - ); - }, - }, -}; -</script> -<template> - <div> - <cluster-form-dropdown - field-id="eks-role-name" - field-name="eks-role-name" - :items="roles" - :loading="loading" - :loading-text="s__('ClusterIntegration|Loading IAM Roles')" - :placeholder="s__('ClusterIntergation|Select role name')" - :search-field-placeholder="s__('ClusterIntegration|Search IAM Roles')" - :empty-text="s__('ClusterIntegration|No IAM Roles found')" - /> - <p class="form-text text-muted" v-html="helpText"></p> - </div> -</template> diff --git a/app/assets/javascripts/create_cluster/eks_cluster/services/aws_services_facade.js b/app/assets/javascripts/create_cluster/eks_cluster/services/aws_services_facade.js index 030b6b384b1..98ad33d6651 100644 --- a/app/assets/javascripts/create_cluster/eks_cluster/services/aws_services_facade.js +++ b/app/assets/javascripts/create_cluster/eks_cluster/services/aws_services_facade.js @@ -1,4 +1,39 @@ import EC2 from 'aws-sdk/clients/ec2'; +import IAM from 'aws-sdk/clients/iam'; + +export const fetchRoles = () => + new Promise((resolve, reject) => { + const iam = new IAM(); + + iam + .listRoles() + .on('success', ({ data: { Roles: roles } }) => { + const transformedRoles = roles.map(({ RoleName: name }) => ({ name })); + + resolve(transformedRoles); + }) + .on('error', error => { + reject(error); + }) + .send(); + }); + +export const fetchKeyPairs = () => + new Promise((resolve, reject) => { + const ec2 = new EC2(); + + ec2 + .describeKeyPairs() + .on('success', ({ data: { KeyPairs: keyPairs } }) => { + const transformedKeyPairs = keyPairs.map(({ RegionName: name }) => ({ name })); + + resolve(transformedKeyPairs); + }) + .on('error', error => { + reject(error); + }) + .send(); + }); export const fetchRegions = () => new Promise((resolve, reject) => { diff --git a/app/assets/javascripts/create_cluster/eks_cluster/store/actions.js b/app/assets/javascripts/create_cluster/eks_cluster/store/actions.js index 0809fc2dfc4..f2abc121f57 100644 --- a/app/assets/javascripts/create_cluster/eks_cluster/store/actions.js +++ b/app/assets/javascripts/create_cluster/eks_cluster/store/actions.js @@ -4,6 +4,10 @@ export const setRegion = ({ commit }, payload) => { commit(types.SET_REGION, payload); }; +export const setKeyPair = ({ commit }, payload) => { + commit(types.SET_KEY_PAIR, payload); +}; + export const setVpc = ({ commit }, payload) => { commit(types.SET_VPC, payload); }; @@ -12,4 +16,8 @@ export const setSubnet = ({ commit }, payload) => { commit(types.SET_SUBNET, payload); }; +export const setRole = ({ commit }, payload) => { + commit(types.SET_ROLE, payload); +}; + export default () => {}; diff --git a/app/assets/javascripts/create_cluster/eks_cluster/store/index.js b/app/assets/javascripts/create_cluster/eks_cluster/store/index.js index 622095f2cc8..584cd267d8c 100644 --- a/app/assets/javascripts/create_cluster/eks_cluster/store/index.js +++ b/app/assets/javascripts/create_cluster/eks_cluster/store/index.js @@ -15,10 +15,18 @@ const createStore = () => mutations, state: state(), modules: { + roles: { + namespaced: true, + ...clusterDropdownStore(awsServices.fetchRoles), + }, regions: { namespaced: true, ...clusterDropdownStore(awsServices.fetchRegions), }, + keyPairs: { + namespaced: true, + ...clusterDropdownStore(awsServices.fetchKeyPairs), + }, vpcs: { namespaced: true, ...clusterDropdownStore(awsServices.fetchVpcs), diff --git a/app/assets/javascripts/create_cluster/eks_cluster/store/mutation_types.js b/app/assets/javascripts/create_cluster/eks_cluster/store/mutation_types.js index ada76b21f18..b9af9c1d5a4 100644 --- a/app/assets/javascripts/create_cluster/eks_cluster/store/mutation_types.js +++ b/app/assets/javascripts/create_cluster/eks_cluster/store/mutation_types.js @@ -1,3 +1,5 @@ export const SET_REGION = 'SET_REGION'; export const SET_VPC = 'SET_VPC'; +export const SET_KEY_PAIR = 'SET_KEY_PAIR'; export const SET_SUBNET = 'SET_SUBNET'; +export const SET_ROLE = 'SET_ROLE'; diff --git a/app/assets/javascripts/create_cluster/eks_cluster/store/mutations.js b/app/assets/javascripts/create_cluster/eks_cluster/store/mutations.js index 346716bb0df..748a78e0b1e 100644 --- a/app/assets/javascripts/create_cluster/eks_cluster/store/mutations.js +++ b/app/assets/javascripts/create_cluster/eks_cluster/store/mutations.js @@ -4,10 +4,16 @@ export default { [types.SET_REGION](state, { region }) { state.selectedRegion = region; }, + [types.SET_KEY_PAIR](state, { keyPair }) { + state.selectedKeyPair = keyPair; + }, [types.SET_VPC](state, { vpc }) { state.selectedVpc = vpc; }, [types.SET_SUBNET](state, { subnet }) { state.selectedSubnet = subnet; }, + [types.SET_ROLE](state, { role }) { + state.selectedRole = role; + }, }; diff --git a/app/assets/javascripts/create_cluster/eks_cluster/store/state.js b/app/assets/javascripts/create_cluster/eks_cluster/store/state.js index 84880e15d9c..6ed174d247b 100644 --- a/app/assets/javascripts/create_cluster/eks_cluster/store/state.js +++ b/app/assets/javascripts/create_cluster/eks_cluster/store/state.js @@ -4,6 +4,7 @@ export default () => ({ selectedRegion: '', selectedRole: '', + selectedKeyPair: '', selectedVpc: '', selectedSubnet: '', selectedSecurityGroup: '', diff --git a/app/assets/javascripts/jobs/components/job_log.vue b/app/assets/javascripts/jobs/components/job_log.vue index a3fbe9338ee..20888c0af42 100644 --- a/app/assets/javascripts/jobs/components/job_log.vue +++ b/app/assets/javascripts/jobs/components/job_log.vue @@ -19,18 +19,13 @@ export default { updated() { this.$nextTick(() => { this.handleScrollDown(); - this.handleCollapsibleRows(); }); }, mounted() { this.$nextTick(() => { this.handleScrollDown(); - this.handleCollapsibleRows(); }); }, - destroyed() { - this.removeEventListener(); - }, methods: { ...mapActions(['scrollBottom']), /** @@ -47,53 +42,6 @@ export default { }, 0); } }, - removeEventListener() { - this.$el.querySelectorAll('.js-section-start').forEach(el => { - const titleSection = el.nextSibling; - titleSection.removeEventListener( - 'click', - this.handleHeaderClick.bind(this, el, el.dataset.section), - ); - el.removeEventListener('click', this.handleSectionClick); - }); - }, - /** - * The collapsible rows are sent in HTML from the backend - * We need tos add a onclick handler for the divs that match `.js-section-start` - * - */ - handleCollapsibleRows() { - this.$el.querySelectorAll('.js-section-start').forEach(el => { - const titleSection = el.nextSibling; - titleSection.addEventListener( - 'click', - this.handleHeaderClick.bind(this, el, el.dataset.section), - ); - el.addEventListener('click', this.handleSectionClick); - }); - }, - - handleHeaderClick(arrowElement, section) { - this.updateToggleSection(arrowElement, section); - }, - - updateToggleSection(arrow, section) { - // toggle the arrow class - arrow.classList.toggle('fa-caret-right'); - arrow.classList.toggle('fa-caret-down'); - - // hide the sections - const sibilings = this.$el.querySelectorAll(`.js-s-${section}:not(.js-section-header)`); - sibilings.forEach(row => row.classList.toggle('hidden')); - }, - /** - * On click, we toggle the hidden class of - * all the rows that match the `data-section` selector - */ - handleSectionClick(evt) { - const clickedArrow = evt.currentTarget; - this.updateToggleSection(clickedArrow, clickedArrow.dataset.section); - }, }, }; </script> diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss index 89fd160b575..c7d51a2093a 100644 --- a/app/assets/stylesheets/pages/builds.scss +++ b/app/assets/stylesheets/pages/builds.scss @@ -124,26 +124,6 @@ float: left; padding-left: $gl-padding-8; } - - .section-start { - display: inline; - } - - .section-start, - .section-header { - &:hover { - cursor: pointer; - - &::after { - content: ''; - background-color: rgba($white-light, 0.2); - left: 0; - right: 0; - position: absolute; - height: $job-log-highlight-height; - } - } - } } .build-header { diff --git a/app/finders/snippets_finder.rb b/app/finders/snippets_finder.rb index bf29f15642d..6e2bb1ded43 100644 --- a/app/finders/snippets_finder.rb +++ b/app/finders/snippets_finder.rb @@ -59,18 +59,20 @@ class SnippetsFinder < UnionFinder end def execute - base = - if project - snippets_for_a_single_project - else - snippets_for_multiple_projects - end - + base = init_collection base.with_optional_visibility(visibility_from_scope).fresh end private + def init_collection + if project + snippets_for_a_single_project + else + snippets_for_multiple_projects + end + end + # Produces a query that retrieves snippets from multiple projects. # # The resulting query will, depending on the user's permissions, include the @@ -115,7 +117,7 @@ class SnippetsFinder < UnionFinder # This method requires that `current_user` returns a `User` instead of `nil`, # and is optimised for this specific scenario. def snippets_of_authorized_projects - base = author ? snippets_for_author : Snippet.all + base = author ? author.snippets : Snippet.all base .only_include_projects_with_snippets_enabled(include_private: true) @@ -157,3 +159,5 @@ class SnippetsFinder < UnionFinder end end end + +SnippetsFinder.prepend_if_ee('EE::SnippetsFinder') diff --git a/app/models/concerns/notification_branch_selection.rb b/app/models/concerns/notification_branch_selection.rb index d8e18de7551..7f00b652530 100644 --- a/app/models/concerns/notification_branch_selection.rb +++ b/app/models/concerns/notification_branch_selection.rb @@ -21,7 +21,7 @@ module NotificationBranchSelection end is_default_branch = ref == project.default_branch - is_protected_branch = project.protected_branches.exists?(name: ref) + is_protected_branch = ProtectedBranch.protected?(project, ref) case branches_to_be_notified when "all" diff --git a/changelogs/unreleased/12739-incomplete-group-audit-logs-in-group-view.yml b/changelogs/unreleased/12739-incomplete-group-audit-logs-in-group-view.yml new file mode 100644 index 00000000000..206badd5ab2 --- /dev/null +++ b/changelogs/unreleased/12739-incomplete-group-audit-logs-in-group-view.yml @@ -0,0 +1,5 @@ +--- +title: Specify sort order explicitly for Group and Project audit events +merge_request: 17739 +author: +type: fixed diff --git a/changelogs/unreleased/32146-remove-fe-code.yml b/changelogs/unreleased/32146-remove-fe-code.yml new file mode 100644 index 00000000000..f7dd251de44 --- /dev/null +++ b/changelogs/unreleased/32146-remove-fe-code.yml @@ -0,0 +1,5 @@ +--- +title: Removes Collapsible Sections from Job Log +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/33582-fix-protected-branch-wildcard.yml b/changelogs/unreleased/33582-fix-protected-branch-wildcard.yml new file mode 100644 index 00000000000..091a88f80a5 --- /dev/null +++ b/changelogs/unreleased/33582-fix-protected-branch-wildcard.yml @@ -0,0 +1,5 @@ +--- +title: Fix protected branch detection used by notification service +merge_request: 18221 +author: +type: fixed diff --git a/changelogs/unreleased/fj-26123-narrow-snippet-search-scope-in-com.yml b/changelogs/unreleased/fj-26123-narrow-snippet-search-scope-in-com.yml new file mode 100644 index 00000000000..a5f670257e1 --- /dev/null +++ b/changelogs/unreleased/fj-26123-narrow-snippet-search-scope-in-com.yml @@ -0,0 +1,5 @@ +--- +title: Narrow snippet search scope in GitLab.com +merge_request: 17625 +author: +type: performance diff --git a/changelogs/unreleased/gitaly-version-v1.67.0.yml b/changelogs/unreleased/gitaly-version-v1.67.0.yml new file mode 100644 index 00000000000..03846e4d4d8 --- /dev/null +++ b/changelogs/unreleased/gitaly-version-v1.67.0.yml @@ -0,0 +1,5 @@ +--- +title: Upgrade to Gitaly v1.67.0 +merge_request: 18326 +author: +type: changed diff --git a/doc/development/internal_api.md b/doc/development/internal_api.md index a2665fac77a..63c441f2505 100644 --- a/doc/development/internal_api.md +++ b/doc/development/internal_api.md @@ -115,16 +115,6 @@ curl --request POST --header "Gitlab-Shared-Secret: <Base64 encoded token>" --da - GitLab-shell -## Get merge requests for a ref [NOT USED] - -``` -GET /internal/merge_request_urls -``` - -**Deprecated**: This used to be called from GitLab shell to fetch the -merge requests for a change to output them after a push, but this is -now handled in the `/internal/post_receive` call. - ## Authorized Keys Check This endpoint is called by the GitLab-shell authorized keys diff --git a/lib/api/internal/base.rb b/lib/api/internal/base.rb index a5ec3e12b99..7963adfd7f4 100644 --- a/lib/api/internal/base.rb +++ b/lib/api/internal/base.rb @@ -112,10 +112,6 @@ module API end # rubocop: enable CodeReuse/ActiveRecord - get "/merge_request_urls" do - merge_request_urls - end - # # Get a ssh key using the fingerprint # diff --git a/lib/gitlab/snippet_search_results.rb b/lib/gitlab/snippet_search_results.rb index 6763914287a..e955ccd35da 100644 --- a/lib/gitlab/snippet_search_results.rb +++ b/lib/gitlab/snippet_search_results.rb @@ -45,7 +45,7 @@ module Gitlab # rubocop: disable CodeReuse/ActiveRecord def snippets - SnippetsFinder.new(current_user) + SnippetsFinder.new(current_user, finder_params) .execute .includes(:author) .reorder(updated_at: :desc) @@ -67,5 +67,11 @@ module Gitlab def paginated_objects(relation, page) relation.page(page).per(per_page) end + + def finder_params + {} + end end end + +Gitlab::SnippetSearchResults.prepend_if_ee('::EE::Gitlab::SnippetSearchResults') diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 6fce25f2cb6..83fd5e28805 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -3417,6 +3417,12 @@ msgstr "" msgid "ClusterIntegration|Copy Service Token" msgstr "" +msgid "ClusterIntegration|Could not load IAM roles" +msgstr "" + +msgid "ClusterIntegration|Could not load Key Pairs" +msgstr "" + msgid "ClusterIntegration|Could not load VPCs for the selected region" msgstr "" @@ -3549,6 +3555,9 @@ msgstr "" msgid "ClusterIntegration|JupyterHub, a multi-user Hub, spawns, manages, and proxies multiple instances of the single-user Jupyter notebook server. JupyterHub can be used to serve notebooks to a class of students, a corporate data science group, or a scientific research group." msgstr "" +msgid "ClusterIntegration|Key pair name" +msgstr "" + msgid "ClusterIntegration|Knative" msgstr "" @@ -3609,6 +3618,9 @@ msgstr "" msgid "ClusterIntegration|Loading IAM Roles" msgstr "" +msgid "ClusterIntegration|Loading Key Pairs" +msgstr "" + msgid "ClusterIntegration|Loading Regions" msgstr "" @@ -3630,6 +3642,9 @@ msgstr "" msgid "ClusterIntegration|No IAM Roles found" msgstr "" +msgid "ClusterIntegration|No Key Pairs found" +msgstr "" + msgid "ClusterIntegration|No VPCs found" msgstr "" @@ -3717,6 +3732,9 @@ msgstr "" msgid "ClusterIntegration|Search IAM Roles" msgstr "" +msgid "ClusterIntegration|Search Key Pairs" +msgstr "" + msgid "ClusterIntegration|Search VPCs" msgstr "" @@ -3744,6 +3762,9 @@ msgstr "" msgid "ClusterIntegration|Select a VPC to use for your EKS Cluster resources. To use a new VPC, first create one on %{startLink}Amazon Web Services%{endLink}." msgstr "" +msgid "ClusterIntegration|Select a region to choose a Key Pair" +msgstr "" + msgid "ClusterIntegration|Select a region to choose a VPC" msgstr "" @@ -3762,6 +3783,9 @@ msgstr "" msgid "ClusterIntegration|Select the IAM Role to allow Amazon EKS and the Kubernetes control plane to manage AWS resources on your behalf. To use a new role name, first create one on %{startLink}Amazon Web Services%{endLink}." msgstr "" +msgid "ClusterIntegration|Select the key pair name that will be used to create EC2 nodes. To use a new key pair name, first create one on %{startLink}Amazon Web Services%{endLink}." +msgstr "" + msgid "ClusterIntegration|Select zone" msgstr "" @@ -3906,6 +3930,9 @@ msgstr "" msgid "ClusterIntergation|Select a subnet" msgstr "" +msgid "ClusterIntergation|Select key pair" +msgstr "" + msgid "ClusterIntergation|Select role name" msgstr "" @@ -14014,9 +14041,15 @@ msgstr "" msgid "SearchResults|Showing %{count} %{scope} for \"%{term}\"" msgstr "" +msgid "SearchResults|Showing %{count} %{scope} for \"%{term}\" in your personal and project snippets" +msgstr "" + msgid "SearchResults|Showing %{from} - %{to} of %{count} %{scope} for \"%{term}\"" msgstr "" +msgid "SearchResults|Showing %{from} - %{to} of %{count} %{scope} for \"%{term}\" in your personal and project snippets" +msgstr "" + msgid "SearchResults|We couldn't find any %{scope} matching %{term}" msgstr "" @@ -14207,6 +14240,12 @@ msgstr "" msgid "SecurityDashboard|Project" msgstr "" +msgid "SecurityDashboard|Projects added" +msgstr "" + +msgid "SecurityDashboard|Remove project from dashboard" +msgstr "" + msgid "SecurityDashboard|Report type" msgstr "" @@ -14216,6 +14255,9 @@ msgstr "" msgid "SecurityDashboard|Security Dashboard" msgstr "" +msgid "SecurityDashboard|Select a project to add by using the project search field above." +msgstr "" + msgid "SecurityDashboard|Severity" msgstr "" @@ -259,7 +259,6 @@ module QA module Milestone autoload :New, 'qa/page/project/milestone/new' autoload :Index, 'qa/page/project/milestone/index' - autoload :Show, 'qa/page/project/milestone/show' end module Operations diff --git a/qa/qa/page/project/milestone/show.rb b/qa/qa/page/project/milestone/show.rb deleted file mode 100644 index a6ad76cb33b..00000000000 --- a/qa/qa/page/project/milestone/show.rb +++ /dev/null @@ -1,14 +0,0 @@ -# frozen_string_literal: true - -module QA - module Page - module Project - module Milestone - class Show < Page::Base - end - end - end - end -end - -QA::Page::Project::Milestone::Show.prepend_if_ee('QA::EE::Page::Project::Milestone::Show') diff --git a/spec/features/projects/jobs/user_browses_job_spec.rb b/spec/features/projects/jobs/user_browses_job_spec.rb index 82ad08d0ff2..856c39df8b3 100644 --- a/spec/features/projects/jobs/user_browses_job_spec.rb +++ b/spec/features/projects/jobs/user_browses_job_spec.rb @@ -38,66 +38,6 @@ describe 'User browses a job', :js do expect(page).to have_content('Job has been erased') end - shared_examples 'has collapsible sections' do - it 'collapses the section clicked' do - wait_for_requests - text_to_hide = "Cloning into '/nolith/ci-tests'" - text_to_show = 'Waiting for pod' - - expect(page).to have_content(text_to_hide) - expect(page).to have_content(text_to_show) - - first('.js-section-start[data-section="get-sources"]').click - - expect(page).not_to have_content(text_to_hide) - expect(page).to have_content(text_to_show) - end - - it 'collapses the section header clicked' do - wait_for_requests - text_to_hide = "Cloning into '/nolith/ci-tests'" - text_to_show = 'Waiting for pod' - - expect(page).to have_content(text_to_hide) - expect(page).to have_content(text_to_show) - - first('.js-section-header.js-s-get-sources').click - - expect(page).not_to have_content(text_to_hide) - expect(page).to have_content(text_to_show) - end - end - - context 'when job trace contains sections' do - let!(:build) { create(:ci_build, :success, :trace_with_sections, :coverage, pipeline: pipeline) } - - it_behaves_like 'has collapsible sections' - end - - context 'when job trace contains duplicate sections' do - let!(:build) { create(:ci_build, :success, :trace_with_duplicate_sections, :coverage, pipeline: pipeline) } - - it_behaves_like 'has collapsible sections' - end - - context 'when job trace contains sections' do - let!(:build) { create(:ci_build, :success, :trace_with_duplicate_sections, :coverage, pipeline: pipeline) } - - it 'collapses a section' do - wait_for_requests - text_to_hide = "Cloning into '/nolith/ci-tests'" - text_to_show = 'Waiting for pod' - - expect(page).to have_content(text_to_hide) - expect(page).to have_content(text_to_show) - - first('.js-section-start[data-section="get-sources"]').click - - expect(page).not_to have_content(text_to_hide) - expect(page).to have_content(text_to_show) - end - end - context 'with a failed job' do let!(:build) { create(:ci_build, :failed, :trace_artifact, pipeline: pipeline) } diff --git a/spec/finders/snippets_finder_spec.rb b/spec/finders/snippets_finder_spec.rb index d367f9015c7..72de05b5131 100644 --- a/spec/finders/snippets_finder_spec.rb +++ b/spec/finders/snippets_finder_spec.rb @@ -150,6 +150,26 @@ describe SnippetsFinder do expect(snippets).to contain_exactly(private_project_snippet, internal_project_snippet, public_project_snippet) end + + context 'filter by author' do + let!(:other_user) { create(:user) } + let!(:other_private_project_snippet) { create(:project_snippet, :private, project: project, author: other_user) } + let!(:other_internal_project_snippet) { create(:project_snippet, :internal, project: project, author: other_user) } + let!(:other_public_project_snippet) { create(:project_snippet, :public, project: project, author: other_user) } + + it 'returns all snippets for project members' do + project.add_developer(user) + + snippets = described_class.new(user, author: other_user).execute + + expect(snippets) + .to contain_exactly( + other_private_project_snippet, + other_internal_project_snippet, + other_public_project_snippet + ) + end + end end context 'when the user cannot read cross project' do diff --git a/spec/frontend/create_cluster/eks_cluster/components/cluster_form_dropdown_spec.js b/spec/frontend/create_cluster/eks_cluster/components/cluster_form_dropdown_spec.js index e873ef0b2fa..366c2fc7b26 100644 --- a/spec/frontend/create_cluster/eks_cluster/components/cluster_form_dropdown_spec.js +++ b/spec/frontend/create_cluster/eks_cluster/components/cluster_form_dropdown_spec.js @@ -7,12 +7,22 @@ import DropdownHiddenInput from '~/vue_shared/components/dropdown/dropdown_hidde describe('ClusterFormDropdown', () => { let vm; + const firstItem = { name: 'item 1', value: 1 }; + const secondItem = { name: 'item 2', value: 2 }; + const items = [firstItem, secondItem, { name: 'item 3', value: 3 }]; beforeEach(() => { vm = shallowMount(ClusterFormDropdown); }); afterEach(() => vm.destroy()); + describe('when initial value is provided', () => { + it('sets selectedItem to initial value', () => { + vm.setProps({ items, value: secondItem.value }); + expect(vm.find(DropdownButton).props('toggleText')).toEqual(secondItem.name); + }); + }); + describe('when no item is selected', () => { it('displays placeholder text', () => { const placeholder = 'placeholder'; @@ -24,18 +34,19 @@ describe('ClusterFormDropdown', () => { }); describe('when an item is selected', () => { - const selectedItem = { name: 'Name', value: 'value' }; - beforeEach(() => { - vm.setData({ selectedItem }); + vm.setProps({ items }); + vm.findAll('.js-dropdown-item') + .at(1) + .trigger('click'); }); it('displays selected item label', () => { - expect(vm.find(DropdownButton).props('toggleText')).toEqual(selectedItem.name); + expect(vm.find(DropdownButton).props('toggleText')).toEqual(secondItem.name); }); it('sets selected value to dropdown hidden input', () => { - expect(vm.find(DropdownHiddenInput).props('value')).toEqual(selectedItem.value); + expect(vm.find(DropdownHiddenInput).props('value')).toEqual(secondItem.value); }); }); @@ -124,9 +135,7 @@ describe('ClusterFormDropdown', () => { }); it('it filters results by search query', () => { - const secondItem = { name: 'second item' }; - const items = [{ name: 'first item' }, secondItem]; - const searchQuery = 'second'; + const searchQuery = secondItem.name; vm.setProps({ items }); vm.setData({ searchQuery }); diff --git a/spec/frontend/create_cluster/eks_cluster/components/eks_cluster_configuration_form_spec.js b/spec/frontend/create_cluster/eks_cluster/components/eks_cluster_configuration_form_spec.js index cc7c6735a80..df214442369 100644 --- a/spec/frontend/create_cluster/eks_cluster/components/eks_cluster_configuration_form_spec.js +++ b/spec/frontend/create_cluster/eks_cluster/components/eks_cluster_configuration_form_spec.js @@ -14,12 +14,16 @@ describe('EksClusterConfigurationForm', () => { let store; let actions; let state; + let rolesState; let regionsState; let vpcsState; let subnetsState; + let keyPairsState; let vpcsActions; + let rolesActions; let regionsActions; let subnetsActions; + let keyPairsActions; let vm; beforeEach(() => { @@ -28,16 +32,27 @@ describe('EksClusterConfigurationForm', () => { setRegion: jest.fn(), setVpc: jest.fn(), setSubnet: jest.fn(), + setRole: jest.fn(), + setKeyPair: jest.fn(), }; regionsActions = { fetchItems: jest.fn(), }; + keyPairsActions = { + fetchItems: jest.fn(), + }; vpcsActions = { fetchItems: jest.fn(), }; subnetsActions = { fetchItems: jest.fn(), }; + rolesActions = { + fetchItems: jest.fn(), + }; + rolesState = { + ...clusterDropdownStoreState(), + }; regionsState = { ...clusterDropdownStoreState(), }; @@ -47,6 +62,9 @@ describe('EksClusterConfigurationForm', () => { subnetsState = { ...clusterDropdownStoreState(), }; + keyPairsState = { + ...clusterDropdownStoreState(), + }; store = new Vuex.Store({ state, actions, @@ -66,6 +84,16 @@ describe('EksClusterConfigurationForm', () => { state: subnetsState, actions: subnetsActions, }, + roles: { + namespaced: true, + state: rolesState, + actions: rolesActions, + }, + keyPairs: { + namespaced: true, + state: keyPairsState, + actions: keyPairsActions, + }, }, }); }); @@ -82,13 +110,37 @@ describe('EksClusterConfigurationForm', () => { }); const findRegionDropdown = () => vm.find(RegionDropdown); + const findKeyPairDropdown = () => vm.find('[field-id="eks-key-pair"]'); const findVpcDropdown = () => vm.find('[field-id="eks-vpc"]'); const findSubnetDropdown = () => vm.find('[field-id="eks-subnet"]'); + const findRoleDropdown = () => vm.find('[field-id="eks-role"]'); describe('when mounted', () => { it('fetches available regions', () => { expect(regionsActions.fetchItems).toHaveBeenCalled(); }); + + it('fetches available roles', () => { + expect(rolesActions.fetchItems).toHaveBeenCalled(); + }); + }); + + it('sets isLoadingRoles to RoleDropdown loading property', () => { + rolesState.isLoadingItems = true; + + return Vue.nextTick().then(() => { + expect(findRoleDropdown().props('loading')).toBe(rolesState.isLoadingItems); + }); + }); + + it('sets roles to RoleDropdown items property', () => { + expect(findRoleDropdown().props('items')).toBe(rolesState.items); + }); + + it('sets RoleDropdown hasErrors to true when loading roles failed', () => { + rolesState.loadingItemsError = new Error(); + + expect(findRoleDropdown().props('hasErrors')).toEqual(true); }); it('sets isLoadingRegions to RegionDropdown loading property', () => { @@ -107,6 +159,36 @@ describe('EksClusterConfigurationForm', () => { expect(findRegionDropdown().props('error')).toBe(regionsState.loadingItemsError); }); + it('disables KeyPairDropdown when no region is selected', () => { + expect(findKeyPairDropdown().props('disabled')).toBe(true); + }); + + it('enables KeyPairDropdown when no region is selected', () => { + state.selectedRegion = { name: 'west-1 ' }; + + return Vue.nextTick().then(() => { + expect(findKeyPairDropdown().props('disabled')).toBe(false); + }); + }); + + it('sets isLoadingKeyPairs to KeyPairDropdown loading property', () => { + keyPairsState.isLoadingItems = true; + + return Vue.nextTick().then(() => { + expect(findKeyPairDropdown().props('loading')).toBe(keyPairsState.isLoadingItems); + }); + }); + + it('sets keyPairs to KeyPairDropdown items property', () => { + expect(findKeyPairDropdown().props('items')).toBe(keyPairsState.items); + }); + + it('sets KeyPairDropdown hasErrors to true when loading key pairs fails', () => { + keyPairsState.loadingItemsError = new Error(); + + expect(findKeyPairDropdown().props('hasErrors')).toEqual(true); + }); + it('disables VpcDropdown when no region is selected', () => { expect(findVpcDropdown().props('disabled')).toBe(true); }); @@ -131,8 +213,10 @@ describe('EksClusterConfigurationForm', () => { expect(findVpcDropdown().props('items')).toBe(vpcsState.items); }); - it('sets loadingVpcsError to VpcDropdown hasErrors property', () => { - expect(findVpcDropdown().props('hasErrors')).toBe(vpcsState.loadingItemsError); + it('sets VpcDropdown hasErrors to true when loading vpcs fails', () => { + vpcsState.loadingItemsError = new Error(); + + expect(findVpcDropdown().props('hasErrors')).toEqual(true); }); it('disables SubnetDropdown when no vpc is selected', () => { @@ -159,8 +243,10 @@ describe('EksClusterConfigurationForm', () => { expect(findSubnetDropdown().props('items')).toBe(subnetsState.items); }); - it('sets loadingSubnetsError to SubnetDropdown hasErrors property', () => { - expect(findSubnetDropdown().props('hasErrors')).toBe(subnetsState.loadingItemsError); + it('sets SubnetDropdown hasErrors to true when loading subnets fails', () => { + subnetsState.loadingItemsError = new Error(); + + expect(findSubnetDropdown().props('hasErrors')).toEqual(true); }); describe('when region is selected', () => { @@ -177,6 +263,14 @@ describe('EksClusterConfigurationForm', () => { it('fetches available vpcs', () => { expect(vpcsActions.fetchItems).toHaveBeenCalledWith(expect.anything(), { region }, undefined); }); + + it('fetches available key pairs', () => { + expect(keyPairsActions.fetchItems).toHaveBeenCalledWith( + expect.anything(), + { region }, + undefined, + ); + }); }); describe('when vpc is selected', () => { @@ -206,4 +300,28 @@ describe('EksClusterConfigurationForm', () => { expect(actions.setSubnet).toHaveBeenCalledWith(expect.anything(), { subnet }, undefined); }); }); + + describe('when role is selected', () => { + const role = { name: 'admin' }; + + beforeEach(() => { + findRoleDropdown().vm.$emit('input', role); + }); + + it('dispatches setRole action', () => { + expect(actions.setRole).toHaveBeenCalledWith(expect.anything(), { role }, undefined); + }); + }); + + describe('when key pair is selected', () => { + const keyPair = { name: 'key pair' }; + + beforeEach(() => { + findKeyPairDropdown().vm.$emit('input', keyPair); + }); + + it('dispatches setKeyPair action', () => { + expect(actions.setKeyPair).toHaveBeenCalledWith(expect.anything(), { keyPair }, undefined); + }); + }); }); diff --git a/spec/frontend/create_cluster/eks_cluster/components/role_name_dropdown_spec.js b/spec/frontend/create_cluster/eks_cluster/components/role_name_dropdown_spec.js deleted file mode 100644 index 657637c1b56..00000000000 --- a/spec/frontend/create_cluster/eks_cluster/components/role_name_dropdown_spec.js +++ /dev/null @@ -1,43 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; - -import ClusterFormDropdown from '~/create_cluster/eks_cluster/components/cluster_form_dropdown.vue'; -import RoleNameDropdown from '~/create_cluster/eks_cluster/components/role_name_dropdown.vue'; - -describe('RoleNameDropdown', () => { - let vm; - - beforeEach(() => { - vm = shallowMount(RoleNameDropdown); - }); - afterEach(() => vm.destroy()); - - it('renders a cluster-form-dropdown', () => { - expect(vm.find(ClusterFormDropdown).exists()).toBe(true); - }); - - it('sets roles to cluster-form-dropdown items property', () => { - const roles = [{ name: 'basic' }]; - - vm.setProps({ roles }); - - expect(vm.find(ClusterFormDropdown).props('items')).toEqual(roles); - }); - - it('sets a loading text', () => { - expect(vm.find(ClusterFormDropdown).props('loadingText')).toEqual('Loading IAM Roles'); - }); - - it('sets a placeholder', () => { - expect(vm.find(ClusterFormDropdown).props('placeholder')).toEqual('Select role name'); - }); - - it('sets an empty results text', () => { - expect(vm.find(ClusterFormDropdown).props('emptyText')).toEqual('No IAM Roles found'); - }); - - it('sets a search field placeholder', () => { - expect(vm.find(ClusterFormDropdown).props('searchFieldPlaceholder')).toEqual( - 'Search IAM Roles', - ); - }); -}); diff --git a/spec/frontend/create_cluster/eks_cluster/store/actions_spec.js b/spec/frontend/create_cluster/eks_cluster/store/actions_spec.js index 893c657e699..aa7ced81e0d 100644 --- a/spec/frontend/create_cluster/eks_cluster/store/actions_spec.js +++ b/spec/frontend/create_cluster/eks_cluster/store/actions_spec.js @@ -2,24 +2,36 @@ import testAction from 'helpers/vuex_action_helper'; import createState from '~/create_cluster/eks_cluster/store/state'; import * as actions from '~/create_cluster/eks_cluster/store/actions'; -import { SET_REGION, SET_VPC, SET_SUBNET } from '~/create_cluster/eks_cluster/store/mutation_types'; +import { + SET_REGION, + SET_VPC, + SET_KEY_PAIR, + SET_SUBNET, + SET_ROLE, +} from '~/create_cluster/eks_cluster/store/mutation_types'; describe('EKS Cluster Store Actions', () => { let region; let vpc; let subnet; + let role; + let keyPair; beforeEach(() => { region = { name: 'regions-1' }; vpc = { name: 'vpc-1' }; subnet = { name: 'subnet-1' }; + role = { name: 'role-1' }; + keyPair = { name: 'key-pair-1' }; }); it.each` - action | mutation | payload | payloadDescription - ${'setRegion'} | ${SET_REGION} | ${{ region }} | ${'region'} - ${'setVpc'} | ${SET_VPC} | ${{ vpc }} | ${'vpc'} - ${'setSubnet'} | ${SET_SUBNET} | ${{ subnet }} | ${'subnet'} + action | mutation | payload | payloadDescription + ${'setRole'} | ${SET_ROLE} | ${{ role }} | ${'role'} + ${'setRegion'} | ${SET_REGION} | ${{ region }} | ${'region'} + ${'setKeyPair'} | ${SET_KEY_PAIR} | ${{ keyPair }} | ${'key pair'} + ${'setVpc'} | ${SET_VPC} | ${{ vpc }} | ${'vpc'} + ${'setSubnet'} | ${SET_SUBNET} | ${{ subnet }} | ${'subnet'} `(`$action commits $mutation with $payloadDescription payload`, data => { const { action, mutation, payload } = data; diff --git a/spec/frontend/create_cluster/eks_cluster/store/mutations_spec.js b/spec/frontend/create_cluster/eks_cluster/store/mutations_spec.js index 38199471f79..966406386ac 100644 --- a/spec/frontend/create_cluster/eks_cluster/store/mutations_spec.js +++ b/spec/frontend/create_cluster/eks_cluster/store/mutations_spec.js @@ -1,4 +1,10 @@ -import { SET_REGION, SET_VPC, SET_SUBNET } from '~/create_cluster/eks_cluster/store/mutation_types'; +import { + SET_REGION, + SET_VPC, + SET_KEY_PAIR, + SET_SUBNET, + SET_ROLE, +} from '~/create_cluster/eks_cluster/store/mutation_types'; import createState from '~/create_cluster/eks_cluster/store/state'; import mutations from '~/create_cluster/eks_cluster/store/mutations'; @@ -7,20 +13,26 @@ describe('Create EKS cluster store mutations', () => { let region; let vpc; let subnet; + let role; + let keyPair; beforeEach(() => { region = { name: 'regions-1' }; vpc = { name: 'vpc-1' }; subnet = { name: 'subnet-1' }; + role = { name: 'role-1' }; + keyPair = { name: 'key pair' }; state = createState(); }); it.each` - mutation | mutatedProperty | payload | expectedValue | expectedValueDescription - ${SET_REGION} | ${'selectedRegion'} | ${{ region }} | ${region} | ${'selected region payload'} - ${SET_VPC} | ${'selectedVpc'} | ${{ vpc }} | ${vpc} | ${'selected vpc payload'} - ${SET_SUBNET} | ${'selectedSubnet'} | ${{ subnet }} | ${subnet} | ${'selected sybnet payload'} + mutation | mutatedProperty | payload | expectedValue | expectedValueDescription + ${SET_ROLE} | ${'selectedRole'} | ${{ role }} | ${role} | ${'selected role payload'} + ${SET_REGION} | ${'selectedRegion'} | ${{ region }} | ${region} | ${'selected region payload'} + ${SET_KEY_PAIR} | ${'selectedKeyPair'} | ${{ keyPair }} | ${keyPair} | ${'selected key pair payload'} + ${SET_VPC} | ${'selectedVpc'} | ${{ vpc }} | ${vpc} | ${'selected vpc payload'} + ${SET_SUBNET} | ${'selectedSubnet'} | ${{ subnet }} | ${subnet} | ${'selected sybnet payload'} `(`$mutation sets $mutatedProperty to $expectedValueDescription`, data => { const { mutation, mutatedProperty, payload, expectedValue } = data; diff --git a/spec/javascripts/jobs/components/job_log_spec.js b/spec/javascripts/jobs/components/job_log_spec.js index 24bb6b9a48b..dd58f234394 100644 --- a/spec/javascripts/jobs/components/job_log_spec.js +++ b/spec/javascripts/jobs/components/job_log_spec.js @@ -3,7 +3,6 @@ import component from '~/jobs/components/job_log.vue'; import createStore from '~/jobs/store'; import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; import { resetStore } from '../store/helpers'; -import { logWithCollapsibleSections } from '../mock_data'; describe('Job Log', () => { const Component = Vue.extend(component); @@ -63,60 +62,4 @@ describe('Job Log', () => { expect(vm.$el.querySelector('.js-log-animation')).toBeNull(); }); }); - - describe('Collapsible sections', () => { - beforeEach(() => { - vm = mountComponentWithStore(Component, { - props: { - trace: logWithCollapsibleSections.html, - isComplete: true, - }, - store, - }); - }); - - it('renders open arrow', () => { - expect(vm.$el.querySelector('.fa-caret-down')).not.toBeNull(); - }); - - it('toggles hidden class to the sibilings rows when arrow is clicked', done => { - vm.$nextTick() - .then(() => { - const { section } = vm.$el.querySelector('.js-section-start').dataset; - vm.$el.querySelector('.js-section-start').click(); - - vm.$el.querySelectorAll(`.js-s-${section}:not(.js-section-header)`).forEach(el => { - expect(el.classList.contains('hidden')).toEqual(true); - }); - - vm.$el.querySelector('.js-section-start').click(); - - vm.$el.querySelectorAll(`.js-s-${section}:not(.js-section-header)`).forEach(el => { - expect(el.classList.contains('hidden')).toEqual(false); - }); - }) - .then(done) - .catch(done.fail); - }); - - it('toggles hidden class to the sibilings rows when header section is clicked', done => { - vm.$nextTick() - .then(() => { - const { section } = vm.$el.querySelector('.js-section-header').dataset; - vm.$el.querySelector('.js-section-header').click(); - - vm.$el.querySelectorAll(`.js-s-${section}:not(.js-section-header)`).forEach(el => { - expect(el.classList.contains('hidden')).toEqual(true); - }); - - vm.$el.querySelector('.js-section-header').click(); - - vm.$el.querySelectorAll(`.js-s-${section}:not(.js-section-header)`).forEach(el => { - expect(el.classList.contains('hidden')).toEqual(false); - }); - }) - .then(done) - .catch(done.fail); - }); - }); }); diff --git a/spec/javascripts/vue_shared/components/project_selector/project_selector_spec.js b/spec/javascripts/vue_shared/components/project_selector/project_selector_spec.js index f8271866ca1..9c2deca585b 100644 --- a/spec/javascripts/vue_shared/components/project_selector/project_selector_spec.js +++ b/spec/javascripts/vue_shared/components/project_selector/project_selector_spec.js @@ -4,9 +4,11 @@ import ProjectSelector from '~/vue_shared/components/project_selector/project_se import ProjectListItem from '~/vue_shared/components/project_selector/project_list_item.vue'; import { GlSearchBoxByType } from '@gitlab/ui'; -import { mount } from '@vue/test-utils'; +import { mount, createLocalVue } from '@vue/test-utils'; import { trimText } from 'spec/helpers/text_helper'; +const localVue = createLocalVue(); + describe('ProjectSelector component', () => { let wrapper; let vm; @@ -22,6 +24,7 @@ describe('ProjectSelector component', () => { jasmine.clock().install(); wrapper = mount(Vue.extend(ProjectSelector), { + localVue, propsData: { projectSearchResults: searchResults, selectedProjects: selected, diff --git a/spec/requests/api/internal/base_spec.rb b/spec/requests/api/internal/base_spec.rb index 4d13b32d4a1..2280d8ca9d4 100644 --- a/spec/requests/api/internal/base_spec.rb +++ b/spec/requests/api/internal/base_spec.rb @@ -770,47 +770,6 @@ describe API::Internal::Base do end end - describe 'GET /internal/merge_request_urls' do - let(:repo_name) { "#{project.full_path}" } - let(:changes) { URI.escape("#{Gitlab::Git::BLANK_SHA} 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/new_branch") } - - before do - project.add_developer(user) - end - - it 'returns link to create new merge request' do - get api("/internal/merge_request_urls?project=#{repo_name}&changes=#{changes}"), params: { secret_token: secret_token } - - expect(json_response).to match [{ - "branch_name" => "new_branch", - "url" => "http://#{Gitlab.config.gitlab.host}/#{project.full_path}/merge_requests/new?merge_request%5Bsource_branch%5D=new_branch", - "new_merge_request" => true - }] - end - - it 'returns empty array if printing_merge_request_link_enabled is false' do - project.update!(printing_merge_request_link_enabled: false) - - get api("/internal/merge_request_urls?project=#{repo_name}&changes=#{changes}"), params: { secret_token: secret_token } - - expect(json_response).to eq([]) - end - - context 'with a gl_repository parameter' do - let(:gl_repository) { "project-#{project.id}" } - - it 'returns link to create new merge request' do - get api("/internal/merge_request_urls?gl_repository=#{gl_repository}&changes=#{changes}"), params: { secret_token: secret_token } - - expect(json_response).to match [{ - "branch_name" => "new_branch", - "url" => "http://#{Gitlab.config.gitlab.host}/#{project.full_path}/merge_requests/new?merge_request%5Bsource_branch%5D=new_branch", - "new_merge_request" => true - }] - end - end - end - # TODO: Uncomment when the end-point is reenabled # describe 'POST /notify_post_receive' do # let(:valid_params) do @@ -951,6 +910,19 @@ describe API::Internal::Base do expect(json_response['messages']).to include(build_basic_message(message)) end + it 'returns the link to an existing merge request when it exists' do + merge_request = create(:merge_request, source_project: project, source_branch: branch_name, target_branch: 'master') + + post api('/internal/post_receive'), params: valid_params + + message = <<~MESSAGE.strip + View merge request for feature: + #{project_merge_request_url(project, merge_request)} + MESSAGE + + expect(json_response['messages']).to include(build_basic_message(message)) + end + it 'returns no merge request messages if printing_merge_request_link_enabled is false' do project.update!(printing_merge_request_link_enabled: false) diff --git a/spec/support/shared_examples/slack_mattermost_notifications_shared_examples.rb b/spec/support/shared_examples/slack_mattermost_notifications_shared_examples.rb index 1ead9ec2738..f15128d3e13 100644 --- a/spec/support/shared_examples/slack_mattermost_notifications_shared_examples.rb +++ b/spec/support/shared_examples/slack_mattermost_notifications_shared_examples.rb @@ -369,6 +369,48 @@ RSpec.shared_examples 'slack or mattermost notifications' do |service_name| end end + context 'on a protected branch with protected branches defined using wildcards' do + before do + create(:protected_branch, project: project, name: '*-stable') + end + + let(:data) do + Gitlab::DataBuilder::Push.build( + project: project, + user: user, + ref: '1-stable' + ) + end + + context 'pushing tags' do + let(:data) do + Gitlab::DataBuilder::Push.build( + project: project, + user: user, + ref: "#{Gitlab::Git::TAG_REF_PREFIX}test" + ) + end + + it_behaves_like "triggered #{service_name} service", event_type: "push" + end + + context 'notification enabled only for default branch' do + it_behaves_like "untriggered #{service_name} service", event_type: "push", branches_to_be_notified: "default" + end + + context 'notification enabled only for protected branches' do + it_behaves_like "triggered #{service_name} service", event_type: "push", branches_to_be_notified: "protected" + end + + context 'notification enabled only for default and protected branches' do + it_behaves_like "triggered #{service_name} service", event_type: "push", branches_to_be_notified: "default_and_protected" + end + + context 'notification enabled for all branches' do + it_behaves_like "triggered #{service_name} service", event_type: "push", branches_to_be_notified: "all" + end + end + context 'on a neither protected nor default branch' do let(:data) do Gitlab::DataBuilder::Push.build( @@ -570,6 +612,36 @@ RSpec.shared_examples 'slack or mattermost notifications' do |service_name| end end + context 'on a protected branch with protected branches defined usin wildcards' do + before do + create(:protected_branch, project: project, name: '*-stable') + end + + let(:pipeline) do + create(:ci_pipeline, + project: project, status: :failed, + sha: project.commit.sha, ref: '1-stable') + end + + let(:data) { Gitlab::DataBuilder::Pipeline.build(pipeline) } + + context 'notification enabled only for default branch' do + it_behaves_like "untriggered #{service_name} service", event_type: "pipeline", branches_to_be_notified: "default" + end + + context 'notification enabled only for protected branches' do + it_behaves_like "triggered #{service_name} service", event_type: "pipeline", branches_to_be_notified: "protected" + end + + context 'notification enabled only for default and protected branches' do + it_behaves_like "triggered #{service_name} service", event_type: "pipeline", branches_to_be_notified: "default_and_protected" + end + + context 'notification enabled for all branches' do + it_behaves_like "triggered #{service_name} service", event_type: "pipeline", branches_to_be_notified: "all" + end + end + context 'on a neither protected nor default branch' do let(:pipeline) do create(:ci_pipeline, |