diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-04-04 00:09:37 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-04-04 00:09:37 +0000 |
commit | e3bdfa1a13d7e6c92716324c78b5b20c07eeb7c6 (patch) | |
tree | e8776263096b027d32d4be5118cccc87b00de2bc | |
parent | c1a50b8195f4e36fda9b233acbde57a449bcf6c3 (diff) | |
download | gitlab-ce-e3bdfa1a13d7e6c92716324c78b5b20c07eeb7c6.tar.gz |
Add latest changes from gitlab-org/gitlab@master
36 files changed, 618 insertions, 188 deletions
diff --git a/app/assets/javascripts/clusters/clusters_bundle.js b/app/assets/javascripts/clusters/clusters_bundle.js index da34c5030f9..1b11ec355bb 100644 --- a/app/assets/javascripts/clusters/clusters_bundle.js +++ b/app/assets/javascripts/clusters/clusters_bundle.js @@ -259,7 +259,7 @@ export default class Clusters { eventHub.$on('installApplication', this.installApplication); eventHub.$on('updateApplication', data => this.updateApplication(data)); eventHub.$on('saveKnativeDomain', data => this.saveKnativeDomain(data)); - eventHub.$on('setKnativeHostname', data => this.setKnativeHostname(data)); + eventHub.$on('setKnativeDomain', data => this.setKnativeDomain(data)); eventHub.$on('uninstallApplication', data => this.uninstallApplication(data)); eventHub.$on('setCrossplaneProviderStack', data => this.setCrossplaneProviderStack(data)); eventHub.$on('setIngressModSecurityEnabled', data => this.setIngressModSecurityEnabled(data)); @@ -275,7 +275,7 @@ export default class Clusters { eventHub.$off('installApplication', this.installApplication); eventHub.$off('updateApplication', this.updateApplication); eventHub.$off('saveKnativeDomain'); - eventHub.$off('setKnativeHostname'); + eventHub.$off('setKnativeDomain'); eventHub.$off('setCrossplaneProviderStack'); eventHub.$off('uninstallApplication'); eventHub.$off('setIngressModSecurityEnabled'); @@ -521,10 +521,10 @@ export default class Clusters { }); } - setKnativeHostname(data) { - const appId = data.id; - this.store.updateAppProperty(appId, 'isEditingHostName', true); - this.store.updateAppProperty(appId, 'hostname', data.hostname); + setKnativeDomain({ id: appId, domain, domainId }) { + this.store.updateAppProperty(appId, 'isEditingDomain', true); + this.store.updateAppProperty(appId, 'hostname', domain); + this.store.updateAppProperty(appId, 'pagesDomain', domainId ? { id: domainId, domain } : null); } setCrossplaneProviderStack(data) { diff --git a/app/assets/javascripts/clusters/components/applications.vue b/app/assets/javascripts/clusters/components/applications.vue index 219825b1c01..723030c5b8b 100644 --- a/app/assets/javascripts/clusters/components/applications.vue +++ b/app/assets/javascripts/clusters/components/applications.vue @@ -240,16 +240,20 @@ Crossplane runs inside your Kubernetes cluster and supports secure connectivity this.helmInstallIllustration = helmInstallIllustration; }, methods: { - saveKnativeDomain(hostname) { + saveKnativeDomain() { eventHub.$emit('saveKnativeDomain', { id: 'knative', - params: { hostname }, + params: { + hostname: this.applications.knative.hostname, + pages_domain_id: this.applications.knative.pagesDomain?.id, + }, }); }, - setKnativeHostname(hostname) { - eventHub.$emit('setKnativeHostname', { + setKnativeDomain({ domainId, domain }) { + eventHub.$emit('setKnativeDomain', { id: 'knative', - hostname, + domainId, + domain, }); }, setCrossplaneProviderStack(stack) { @@ -591,7 +595,10 @@ Crossplane runs inside your Kubernetes cluster and supports secure connectivity :request-reason="applications.knative.requestReason" :installed="applications.knative.installed" :install-failed="applications.knative.installFailed" - :install-application-request-params="{ hostname: applications.knative.hostname }" + :install-application-request-params="{ + hostname: applications.knative.hostname, + pages_domain_id: applications.knative.pagesDomain && applications.knative.pagesDomain.id, + }" :installed-via="installedVia" :uninstallable="applications.knative.uninstallable" :uninstall-successful="applications.knative.uninstallSuccessful" @@ -628,7 +635,7 @@ Crossplane runs inside your Kubernetes cluster and supports secure connectivity :knative="knative" :ingress-dns-help-path="ingressDnsHelpPath" @save="saveKnativeDomain" - @set="setKnativeHostname" + @set="setKnativeDomain" /> </div> </application-row> diff --git a/app/assets/javascripts/clusters/components/knative_domain_editor.vue b/app/assets/javascripts/clusters/components/knative_domain_editor.vue index 30efbe2e0f7..8136704d13b 100644 --- a/app/assets/javascripts/clusters/components/knative_domain_editor.vue +++ b/app/assets/javascripts/clusters/components/knative_domain_editor.vue @@ -1,5 +1,12 @@ <script> -import { GlLoadingIcon } from '@gitlab/ui'; +import { + GlDropdown, + GlDropdownDivider, + GlDropdownItem, + GlLoadingIcon, + GlSearchBoxByType, + GlSprintf, +} from '@gitlab/ui'; import LoadingButton from '~/vue_shared/components/loading_button.vue'; import ClipboardButton from '../../vue_shared/components/clipboard_button.vue'; import { __, s__ } from '~/locale'; @@ -13,6 +20,11 @@ export default { LoadingButton, ClipboardButton, GlLoadingIcon, + GlDropdown, + GlDropdownDivider, + GlDropdownItem, + GlSearchBoxByType, + GlSprintf, }, props: { knative: { @@ -25,6 +37,11 @@ export default { required: false, }, }, + data() { + return { + searchQuery: '', + }; + }, computed: { saveButtonDisabled() { return [UNINSTALLING, UPDATING].includes(this.knative.status); @@ -49,9 +66,22 @@ export default { return this.knative.hostname; }, set(hostname) { - this.$emit('set', hostname); + this.selectCustomDomain(hostname); }, }, + domainDropdownText() { + return this.knativeHostname || s__('ClusterIntegration|Select existing domain or use new'); + }, + availableDomains() { + return this.knative.availableDomains || []; + }, + filteredDomains() { + const query = this.searchQuery.toLowerCase(); + return this.availableDomains.filter(({ domain }) => domain.toLowerCase().includes(query)); + }, + showDomainsDropdown() { + return this.availableDomains.length > 0; + }, }, watch: { knativeUpdateSuccessful(updateSuccessful) { @@ -60,6 +90,14 @@ export default { } }, }, + methods: { + selectDomain({ id, domain }) { + this.$emit('set', { domain, domainId: id }); + }, + selectCustomDomain(domain) { + this.$emit('set', { domain, domainId: null }); + }, + }, }; </script> @@ -72,22 +110,55 @@ export default { {{ s__('ClusterIntegration|Something went wrong while updating Knative domain name.') }} </div> - <template> - <div - :class="{ 'col-md-6': knativeInstalled, 'col-12': !knativeInstalled }" - class="form-group col-sm-12 mb-0" + <div + :class="{ 'col-md-6': knativeInstalled, 'col-12': !knativeInstalled }" + class="form-group col-sm-12 mb-0" + > + <label for="knative-domainname"> + <strong>{{ s__('ClusterIntegration|Knative Domain Name:') }}</strong> + </label> + + <gl-dropdown + v-if="showDomainsDropdown" + :text="domainDropdownText" + toggle-class="dropdown-menu-toggle" + class="w-100 mb-2" > - <label for="knative-domainname"> - <strong>{{ s__('ClusterIntegration|Knative Domain Name:') }}</strong> - </label> - <input - id="knative-domainname" - v-model="knativeHostname" - type="text" - class="form-control js-knative-domainname" + <gl-search-box-by-type + v-model.trim="searchQuery" + :placeholder="s__('ClusterIntegration|Search domains')" + class="m-2" /> - </div> - </template> + <gl-dropdown-item + v-for="domain in filteredDomains" + :key="domain.id" + @click="selectDomain(domain)" + > + <span class="ml-1">{{ domain.domain }}</span> + </gl-dropdown-item> + <template v-if="searchQuery"> + <gl-dropdown-divider /> + <gl-dropdown-item key="custom-domain" @click="selectCustomDomain(searchQuery)"> + <span class="ml-1"> + <gl-sprintf :message="s__('ClusterIntegration|Use %{query}')"> + <template #query> + <code>{{ searchQuery }}</code> + </template> + </gl-sprintf> + </span> + </gl-dropdown-item> + </template> + </gl-dropdown> + + <input + v-else + id="knative-domainname" + v-model="knativeHostname" + type="text" + class="form-control js-knative-domainname" + /> + </div> + <template v-if="knativeInstalled"> <div class="form-group col-sm-12 col-md-6 pl-md-0 mb-0 mt-3 mt-md-0"> <label for="knative-endpoint"> @@ -144,7 +215,7 @@ export default { :loading="saving" :disabled="saveButtonDisabled" :label="saveButtonLabel" - @click="$emit('save', knativeHostname)" + @click="$emit('save')" /> </template> </div> diff --git a/app/assets/javascripts/clusters/stores/clusters_store.js b/app/assets/javascripts/clusters/stores/clusters_store.js index 8685e3decc5..b09fd6800b6 100644 --- a/app/assets/javascripts/clusters/stores/clusters_store.js +++ b/app/assets/javascripts/clusters/stores/clusters_store.js @@ -93,7 +93,7 @@ export default class ClusterStore { ...applicationInitialState, title: s__('ClusterIntegration|Knative'), hostname: null, - isEditingHostName: false, + isEditingDomain: false, externalIp: null, externalHostname: null, updateSuccessful: false, @@ -234,7 +234,12 @@ export default class ClusterStore { 'jupyter', ); } else if (appId === KNATIVE) { - if (!this.state.applications.knative.isEditingHostName) { + if (serverAppEntry.available_domains) { + this.state.applications.knative.availableDomains = serverAppEntry.available_domains; + } + if (!this.state.applications.knative.isEditingDomain) { + this.state.applications.knative.pagesDomain = + serverAppEntry.pages_domain || this.state.applications.knative.pagesDomain; this.state.applications.knative.hostname = serverAppEntry.hostname || this.state.applications.knative.hostname; } diff --git a/app/assets/javascripts/lib/utils/url_utility.js b/app/assets/javascripts/lib/utils/url_utility.js index 1ff4f7bab97..a495d2040d3 100644 --- a/app/assets/javascripts/lib/utils/url_utility.js +++ b/app/assets/javascripts/lib/utils/url_utility.js @@ -318,3 +318,11 @@ export const setUrlParams = (params, url = window.location.href, clearParams = f export function urlIsDifferent(url, compare = String(window.location)) { return url !== compare; } + +export function getHTTPProtocol(url) { + if (!url) { + return window.location.protocol.slice(0, -1); + } + const protocol = url.split(':'); + return protocol.length > 1 ? protocol[0] : undefined; +} diff --git a/app/assets/javascripts/vue_shared/components/clone_dropdown.vue b/app/assets/javascripts/vue_shared/components/clone_dropdown.vue new file mode 100644 index 00000000000..3b9b9f37f52 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/clone_dropdown.vue @@ -0,0 +1,89 @@ +<script> +import { + GlNewDropdown, + GlNewDropdownHeader, + GlFormInputGroup, + GlNewButton, + GlIcon, + GlTooltipDirective, +} from '@gitlab/ui'; +import { __, sprintf } from '~/locale'; +import { getHTTPProtocol } from '~/lib/utils/url_utility'; + +export default { + components: { + GlNewDropdown, + GlNewDropdownHeader, + GlFormInputGroup, + GlNewButton, + GlIcon, + }, + directives: { + GlTooltip: GlTooltipDirective, + }, + props: { + sshLink: { + type: String, + required: false, + default: '', + }, + httpLink: { + type: String, + required: false, + default: '', + }, + }, + computed: { + httpLabel() { + const protocol = this.httpLink ? getHTTPProtocol(this.httpLink)?.toUpperCase() : ''; + return sprintf(__('Clone with %{protocol}'), { protocol }); + }, + }, + labels: { + defaultLabel: __('Clone'), + ssh: __('Clone with SSH'), + }, + copyURLTooltip: __('Copy URL'), +}; +</script> +<template> + <gl-new-dropdown :text="$options.labels.defaultLabel" category="primary" variant="info"> + <div class="pb-2 mx-1"> + <template v-if="sshLink"> + <gl-new-dropdown-header>{{ $options.labels.ssh }}</gl-new-dropdown-header> + + <div class="mx-3"> + <gl-form-input-group :value="sshLink" readonly select-on-click> + <template #append> + <gl-new-button + v-gl-tooltip.hover + :title="$options.copyURLTooltip" + :data-clipboard-text="sshLink" + > + <gl-icon name="copy-to-clipboard" :title="$options.copyURLTooltip" /> + </gl-new-button> + </template> + </gl-form-input-group> + </div> + </template> + + <template v-if="httpLink"> + <gl-new-dropdown-header>{{ httpLabel }}</gl-new-dropdown-header> + + <div class="mx-3"> + <gl-form-input-group :value="httpLink" readonly select-on-click> + <template #append> + <gl-new-button + v-gl-tooltip.hover + :title="$options.copyURLTooltip" + :data-clipboard-text="httpLink" + > + <gl-icon name="copy-to-clipboard" :title="$options.copyURLTooltip" /> + </gl-new-button> + </template> + </gl-form-input-group> + </div> + </template> + </div> + </gl-new-dropdown> +</template> diff --git a/app/controllers/admin/integrations_controller.rb b/app/controllers/admin/integrations_controller.rb index 0d79032233f..4f3be43d14d 100644 --- a/app/controllers/admin/integrations_controller.rb +++ b/app/controllers/admin/integrations_controller.rb @@ -5,6 +5,12 @@ class Admin::IntegrationsController < Admin::ApplicationController private + def find_or_initialize_integration(name) + if name.in?(Service.available_services_names) + "#{name}_service".camelize.constantize.find_or_initialize_by(instance: true) # rubocop:disable CodeReuse/ActiveRecord + end + end + def integrations_enabled? Feature.enabled?(:instance_level_integrations) end diff --git a/app/controllers/clusters/applications_controller.rb b/app/controllers/clusters/applications_controller.rb index ba62cfeea7e..3ebd248c29e 100644 --- a/app/controllers/clusters/applications_controller.rb +++ b/app/controllers/clusters/applications_controller.rb @@ -47,7 +47,7 @@ class Clusters::ApplicationsController < Clusters::BaseController end def cluster_application_params - params.permit(:application, :hostname, :email, :stack, :modsecurity_enabled, :modsecurity_mode) + params.permit(:application, :hostname, :pages_domain_id, :email, :stack, :modsecurity_enabled, :modsecurity_mode) end def cluster_application_destroy_params diff --git a/app/controllers/concerns/integrations_actions.rb b/app/controllers/concerns/integrations_actions.rb index ffb5d7a8086..4c998055a5d 100644 --- a/app/controllers/concerns/integrations_actions.rb +++ b/app/controllers/concerns/integrations_actions.rb @@ -37,11 +37,7 @@ module IntegrationsActions end def test - if integration.can_test? - render json: service_test_response, status: :ok - else - render json: {}, status: :not_found - end + render json: {}, status: :ok end private @@ -50,17 +46,11 @@ module IntegrationsActions false end - # TODO: Use actual integrations on the group / instance level - # To be completed in https://gitlab.com/groups/gitlab-org/-/epics/2430 - def project - Project.first - end - def integration # Using instance variable `@service` still required as it's used in ServiceParams # and app/views/shared/_service_settings.html.haml. Should be removed once # those 2 are refactored to use `@integration`. - @integration = @service ||= project.find_or_initialize_service(params[:id]) # rubocop:disable Gitlab/ModuleWithInstanceVariables + @integration = @service ||= find_or_initialize_integration(params[:id]) # rubocop:disable Gitlab/ModuleWithInstanceVariables end def success_message @@ -74,21 +64,4 @@ module IntegrationsActions .as_json(only: integration.json_fields) .merge(errors: integration.errors.as_json) end - - def service_test_response - unless integration.update(service_params[:service]) - return { error: true, message: _('Validations failed.'), service_response: integration.errors.full_messages.join(','), test_failed: false } - end - - data = integration.test_data(project, current_user) - outcome = integration.test(data) - - unless outcome[:success] - return { error: true, message: _('Test failed.'), service_response: outcome[:result].to_s, test_failed: true } - end - - {} - rescue Gitlab::HTTP::BlockedUrlError => e - { error: true, message: _('Test failed.'), service_response: e.message, test_failed: true } - end end diff --git a/app/controllers/groups/settings/integrations_controller.rb b/app/controllers/groups/settings/integrations_controller.rb index 43f8a7118d4..0ba030f26bc 100644 --- a/app/controllers/groups/settings/integrations_controller.rb +++ b/app/controllers/groups/settings/integrations_controller.rb @@ -9,6 +9,12 @@ module Groups private + # TODO: Make this compatible with group-level integration + # https://gitlab.com/groups/gitlab-org/-/epics/2543 + def find_or_initialize_integration(name) + Project.first.find_or_initialize_service(name) + end + def integrations_enabled? Feature.enabled?(:group_level_integrations, group) end diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb index 2766ba11c97..eaddac9cce3 100644 --- a/app/models/project_services/jira_service.rb +++ b/app/models/project_services/jira_service.rb @@ -201,8 +201,7 @@ class JiraService < IssueTrackerService end # Jira does not need test data. - # We are requesting the project that belongs to the project key. - def test_data(user = nil, project = nil) + def test_data(_, _) nil end @@ -221,7 +220,6 @@ class JiraService < IssueTrackerService def test_settings return unless client_url.present? - # Test settings by getting the project jira_request { client.ServerInfo.all.attrs } end diff --git a/app/models/project_services/pipelines_email_service.rb b/app/models/project_services/pipelines_email_service.rb index c3ed958242b..b5e5afb6ea5 100644 --- a/app/models/project_services/pipelines_email_service.rb +++ b/app/models/project_services/pipelines_email_service.rb @@ -53,7 +53,7 @@ class PipelinesEmailService < Service end def can_test? - project.ci_pipelines.any? + project&.ci_pipelines&.any? end def test_data(project, user) diff --git a/app/models/service.rb b/app/models/service.rb index 6bb3a94ce81..017c15468a2 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -184,8 +184,10 @@ class Service < ApplicationRecord { success: result.present?, result: result } end + # Disable test for instance-level services. + # https://gitlab.com/gitlab-org/gitlab/-/issues/213138 def can_test? - true + !instance? end # Provide convenient accessor methods diff --git a/changelogs/unreleased/209035-consume-link-lfs-objects-projects.yml b/changelogs/unreleased/209035-consume-link-lfs-objects-projects.yml new file mode 100644 index 00000000000..4fa4b88e37b --- /dev/null +++ b/changelogs/unreleased/209035-consume-link-lfs-objects-projects.yml @@ -0,0 +1,5 @@ +--- +title: Consume remaining LinkLFsObjectsProjects jobs +merge_request: 27558 +author: +type: other diff --git a/changelogs/unreleased/212654-can-we-remove-the-index-index_ci_builds_on_name_for_security_repor.yml b/changelogs/unreleased/212654-can-we-remove-the-index-index_ci_builds_on_name_for_security_repor.yml new file mode 100644 index 00000000000..b6d083a3b2e --- /dev/null +++ b/changelogs/unreleased/212654-can-we-remove-the-index-index_ci_builds_on_name_for_security_repor.yml @@ -0,0 +1,5 @@ +--- +title: Remove unnecessary index index_ci_builds_on_name_for_security_reports_values +merge_request: 28224 +author: +type: performance diff --git a/remove-health-status-epic.yml b/changelogs/unreleased/remove-health-status-epic.yml index c87d84fc5b3..c87d84fc5b3 100644 --- a/remove-health-status-epic.yml +++ b/changelogs/unreleased/remove-health-status-epic.yml diff --git a/db/migrate/20200330123739_remove_index_ci_builds_on_name_for_security_reports_values.rb b/db/migrate/20200330123739_remove_index_ci_builds_on_name_for_security_reports_values.rb new file mode 100644 index 00000000000..7ab57183e18 --- /dev/null +++ b/db/migrate/20200330123739_remove_index_ci_builds_on_name_for_security_reports_values.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +class RemoveIndexCiBuildsOnNameForSecurityReportsValues < ActiveRecord::Migration[6.0] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + INDEX_NAME = 'index_ci_builds_on_name_for_security_reports_values' + + def up + remove_concurrent_index_by_name :ci_builds, INDEX_NAME + end + + def down + add_concurrent_index :ci_builds, + :name, + name: INDEX_NAME, + where: "((name)::text = ANY (ARRAY[('container_scanning'::character varying)::text, ('dast'::character varying)::text, ('dependency_scanning'::character varying)::text, ('license_management'::character varying)::text, ('sast'::character varying)::text, ('license_scanning'::character varying)::text]))" + end +end diff --git a/db/post_migrate/20200319071702_consume_remaining_link_lfs_objects_projects_jobs.rb b/db/post_migrate/20200319071702_consume_remaining_link_lfs_objects_projects_jobs.rb new file mode 100644 index 00000000000..63fa0234a5b --- /dev/null +++ b/db/post_migrate/20200319071702_consume_remaining_link_lfs_objects_projects_jobs.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +class ConsumeRemainingLinkLfsObjectsProjectsJobs < ActiveRecord::Migration[6.0] + DOWNTIME = false + + disable_ddl_transaction! + + def up + Gitlab::BackgroundMigration.steal('LinkLfsObjectsProjects') + end + + def down + # no-op as there is no need to do anything if this gets rolled back + end +end diff --git a/db/structure.sql b/db/structure.sql index 94d98c68f48..25855aeadc0 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -8672,8 +8672,6 @@ CREATE INDEX index_ci_builds_on_commit_id_and_type_and_ref ON public.ci_builds U CREATE INDEX index_ci_builds_on_name_and_security_type_eq_ci_build ON public.ci_builds USING btree (name, id) WHERE (((name)::text = ANY (ARRAY[('container_scanning'::character varying)::text, ('dast'::character varying)::text, ('dependency_scanning'::character varying)::text, ('license_management'::character varying)::text, ('sast'::character varying)::text, ('license_scanning'::character varying)::text])) AND ((type)::text = 'Ci::Build'::text)); -CREATE INDEX index_ci_builds_on_name_for_security_reports_values ON public.ci_builds USING btree (name) WHERE ((name)::text = ANY (ARRAY[('container_scanning'::character varying)::text, ('dast'::character varying)::text, ('dependency_scanning'::character varying)::text, ('license_management'::character varying)::text, ('sast'::character varying)::text, ('license_scanning'::character varying)::text])); - CREATE INDEX index_ci_builds_on_project_id_and_id ON public.ci_builds USING btree (project_id, id); CREATE INDEX index_ci_builds_on_project_id_and_name_and_ref ON public.ci_builds USING btree (project_id, name, ref) WHERE (((type)::text = 'Ci::Build'::text) AND ((status)::text = 'success'::text) AND ((retried = false) OR (retried IS NULL))); @@ -12902,6 +12900,7 @@ COPY "schema_migrations" (version) FROM STDIN; 20200318164448 20200318165448 20200318175008 +20200319071702 20200319123041 20200319203901 20200320112455 @@ -12927,6 +12926,7 @@ COPY "schema_migrations" (version) FROM STDIN; 20200326145443 20200330074719 20200330121000 +20200330123739 20200330132913 20200331220930 \. diff --git a/locale/gitlab.pot b/locale/gitlab.pot index c385e1e1a91..983a142590f 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -4034,6 +4034,9 @@ msgstr "" msgid "Clone with %{http_label}" msgstr "" +msgid "Clone with %{protocol}" +msgstr "" + msgid "Clone with KRB5" msgstr "" @@ -4691,6 +4694,9 @@ msgstr "" msgid "ClusterIntegration|Search VPCs" msgstr "" +msgid "ClusterIntegration|Search domains" +msgstr "" + msgid "ClusterIntegration|Search instance types" msgstr "" @@ -4748,6 +4754,9 @@ msgstr "" msgid "ClusterIntegration|Select a zone to choose a network" msgstr "" +msgid "ClusterIntegration|Select existing domain or use new" +msgstr "" + msgid "ClusterIntegration|Select machine type" msgstr "" @@ -4868,6 +4877,9 @@ msgstr "" msgid "ClusterIntegration|Update failed. Please check the logs and try again." msgstr "" +msgid "ClusterIntegration|Use %{query}" +msgstr "" + msgid "ClusterIntegration|Uses the Cloud Run, Istio, and HTTP Load Balancing addons for this cluster." msgstr "" diff --git a/package.json b/package.json index 350d63d9296..56356002bd3 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "@babel/preset-env": "^7.8.4", "@gitlab/at.js": "1.5.5", "@gitlab/svgs": "1.117.0", - "@gitlab/ui": "11.0.0", + "@gitlab/ui": "11.0.1", "@gitlab/visual-review-tools": "1.5.1", "@sentry/browser": "^5.10.2", "@sourcegraph/code-host-integration": "0.0.34", diff --git a/spec/controllers/admin/integrations_controller_spec.rb b/spec/controllers/admin/integrations_controller_spec.rb index 50748918893..8e48ecddd0f 100644 --- a/spec/controllers/admin/integrations_controller_spec.rb +++ b/spec/controllers/admin/integrations_controller_spec.rb @@ -3,7 +3,6 @@ require 'spec_helper' describe Admin::IntegrationsController do - let_it_be(:project) { create(:project) } let(:admin) { create(:admin) } before do @@ -34,7 +33,7 @@ describe Admin::IntegrationsController do end describe '#update' do - let(:integration) { create(:jira_service, project: project) } + let(:integration) { create(:jira_service, :instance) } before do put :update, params: { id: integration.class.to_param, service: { url: url } } @@ -52,34 +51,9 @@ describe Admin::IntegrationsController do context 'invalid params' do let(:url) { 'https://jira.localhost' } - it 'does not update the integration' do - expect(response).to have_gitlab_http_status(:ok) - expect(response).to render_template(:edit) - expect(integration.reload.url).not_to eq(url) - end - end - end - - describe '#test' do - context 'testable' do - let(:integration) { create(:jira_service, project: project) } - - it 'returns ok' do - allow_any_instance_of(integration.class).to receive(:test) { { success: true } } - - put :test, params: { id: integration.class.to_param } - - expect(response).to have_gitlab_http_status(:ok) - end - end - - context 'not testable' do - let(:integration) { create(:alerts_service, project: project) } - - it 'returns not found' do - put :test, params: { id: integration.class.to_param } - - expect(response).to have_gitlab_http_status(:not_found) + it 'updates the integration' do + expect(response).to have_gitlab_http_status(:found) + expect(integration.reload.url).to eq(url) end end end diff --git a/spec/controllers/groups/settings/integrations_controller_spec.rb b/spec/controllers/groups/settings/integrations_controller_spec.rb index bbf215a4bb9..eee65476dab 100644 --- a/spec/controllers/groups/settings/integrations_controller_spec.rb +++ b/spec/controllers/groups/settings/integrations_controller_spec.rb @@ -67,40 +67,11 @@ describe Groups::Settings::IntegrationsController do end context 'invalid params' do - let(:url) { 'ftp://jira.localhost' } + let(:url) { 'https://jira.localhost' } it 'does not update the integration' do - expect(response).to have_gitlab_http_status(:ok) - expect(response).to render_template(:edit) - expect(integration.reload.url).not_to eq(url) - end - end - end - - describe '#test' do - context 'testable' do - let(:integration) { create(:jira_service, project: project) } - - before do - group.add_owner(user) - end - - it 'returns ok' do - allow_any_instance_of(integration.class).to receive(:test) { { success: true } } - - put :test, params: { group_id: group, id: integration.class.to_param } - - expect(response).to have_gitlab_http_status(:ok) - end - end - - context 'not testable' do - let(:integration) { create(:alerts_service, project: project) } - - it 'returns not found' do - put :test, params: { group_id: group, id: integration.class.to_param } - - expect(response).to have_gitlab_http_status(:not_found) + expect(response).to have_gitlab_http_status(:found) + expect(integration.reload.url).to eq(url) end end end diff --git a/spec/frontend/__mocks__/lodash/debounce.js b/spec/frontend/__mocks__/lodash/debounce.js new file mode 100644 index 00000000000..97fdb39097a --- /dev/null +++ b/spec/frontend/__mocks__/lodash/debounce.js @@ -0,0 +1,11 @@ +// `lodash/debounce` has a non-trivial implementation which can lead to +// [flaky spec errors][1]. This mock simply makes `debounce` calls synchronous. +// +// In the future we could enhance this by injecting some test values in +// the function passed to it. See [this issue][2] for more information. +// +// [1]: https://gitlab.com/gitlab-org/gitlab/-/issues/212532 +// [2]: https://gitlab.com/gitlab-org/gitlab/-/issues/213378 +// Further reference: https://github.com/facebook/jest/issues/3465 + +export default fn => fn; diff --git a/spec/frontend/clusters/components/applications_spec.js b/spec/frontend/clusters/components/applications_spec.js index db5d5c84820..782e5215ad8 100644 --- a/spec/frontend/clusters/components/applications_spec.js +++ b/spec/frontend/clusters/components/applications_spec.js @@ -400,6 +400,10 @@ describe('Applications', () => { }); describe('Knative application', () => { + const availableDomain = { + id: 4, + domain: 'newhostname.com', + }; const propsData = { applications: { ...APPLICATIONS_MOCK_STATE, @@ -409,10 +413,11 @@ describe('Applications', () => { status: 'installed', externalIp: '1.1.1.1', installed: true, + availableDomains: [availableDomain], + pagesDomain: null, }, }, }; - const newHostname = 'newhostname.com'; let wrapper; let knativeDomainEditor; @@ -428,20 +433,44 @@ describe('Applications', () => { }); it('emits saveKnativeDomain event when knative domain editor emits save event', () => { - knativeDomainEditor.vm.$emit('save', newHostname); + propsData.applications.knative.hostname = availableDomain.domain; + propsData.applications.knative.pagesDomain = availableDomain; + knativeDomainEditor.vm.$emit('save'); + + expect(eventHub.$emit).toHaveBeenCalledWith('saveKnativeDomain', { + id: 'knative', + params: { + hostname: availableDomain.domain, + pages_domain_id: availableDomain.id, + }, + }); + }); + + it('emits saveKnativeDomain event when knative domain editor emits save event with custom domain', () => { + const newHostName = 'someothernewhostname.com'; + propsData.applications.knative.hostname = newHostName; + propsData.applications.knative.pagesDomain = null; + knativeDomainEditor.vm.$emit('save'); expect(eventHub.$emit).toHaveBeenCalledWith('saveKnativeDomain', { id: 'knative', - params: { hostname: newHostname }, + params: { + hostname: newHostName, + pages_domain_id: undefined, + }, }); }); it('emits setKnativeHostname event when knative domain editor emits change event', () => { - wrapper.find(KnativeDomainEditor).vm.$emit('set', newHostname); + wrapper.find(KnativeDomainEditor).vm.$emit('set', { + domain: availableDomain.domain, + domainId: availableDomain.id, + }); - expect(eventHub.$emit).toHaveBeenCalledWith('setKnativeHostname', { + expect(eventHub.$emit).toHaveBeenCalledWith('setKnativeDomain', { id: 'knative', - hostname: newHostname, + domain: availableDomain.domain, + domainId: availableDomain.id, }); }); }); diff --git a/spec/frontend/clusters/components/knative_domain_editor_spec.js b/spec/frontend/clusters/components/knative_domain_editor_spec.js index 6514d883c0d..2de04f7da1f 100644 --- a/spec/frontend/clusters/components/knative_domain_editor_spec.js +++ b/spec/frontend/clusters/components/knative_domain_editor_spec.js @@ -1,4 +1,5 @@ import { shallowMount } from '@vue/test-utils'; +import { GlDropdownItem } from '@gitlab/ui'; import KnativeDomainEditor from '~/clusters/components/knative_domain_editor.vue'; import LoadingButton from '~/vue_shared/components/loading_button.vue'; import { APPLICATION_STATUS } from '~/clusters/constants'; @@ -80,7 +81,7 @@ describe('KnativeDomainEditor', () => { it('triggers save event and pass current knative hostname', () => { wrapper.find(LoadingButton).vm.$emit('click'); return wrapper.vm.$nextTick().then(() => { - expect(wrapper.emitted('save')[0]).toEqual([knative.hostname]); + expect(wrapper.emitted('save').length).toEqual(1); }); }); }); @@ -104,14 +105,43 @@ describe('KnativeDomainEditor', () => { describe('when knative domain name input changes', () => { it('emits "set" event with updated domain name', () => { - createComponent({ knative }); + const newDomain = { + id: 4, + domain: 'newhostname.com', + }; + + createComponent({ knative: { ...knative, availableDomains: [newDomain] } }); + jest.spyOn(wrapper.vm, 'selectDomain'); + + wrapper.find(GlDropdownItem).vm.$emit('click'); + return wrapper.vm.$nextTick().then(() => { + expect(wrapper.vm.selectDomain).toHaveBeenCalledWith(newDomain); + expect(wrapper.emitted('set')[0]).toEqual([ + { + domain: newDomain.domain, + domainId: newDomain.id, + }, + ]); + }); + }); + + it('emits "set" event with updated custom domain name', () => { const newHostname = 'newhostname.com'; + createComponent({ knative }); + jest.spyOn(wrapper.vm, 'selectCustomDomain'); + wrapper.setData({ knativeHostname: newHostname }); return wrapper.vm.$nextTick().then(() => { - expect(wrapper.emitted('set')[0]).toEqual([newHostname]); + expect(wrapper.vm.selectCustomDomain).toHaveBeenCalledWith(newHostname); + expect(wrapper.emitted('set')[0]).toEqual([ + { + domain: newHostname, + domainId: null, + }, + ]); }); }); }); diff --git a/spec/frontend/clusters/stores/clusters_store_spec.js b/spec/frontend/clusters/stores/clusters_store_spec.js index 0207fda84c4..9fafc688af9 100644 --- a/spec/frontend/clusters/stores/clusters_store_spec.js +++ b/spec/frontend/clusters/stores/clusters_store_spec.js @@ -140,7 +140,7 @@ describe('Clusters Store', () => { statusReason: mockResponseData.applications[5].status_reason, requestReason: null, hostname: null, - isEditingHostName: false, + isEditingDomain: false, externalIp: null, externalHostname: null, installed: false, diff --git a/spec/frontend/ide/components/branches/search_list_spec.js b/spec/frontend/ide/components/branches/search_list_spec.js index 826d51b24f1..fe142d70698 100644 --- a/spec/frontend/ide/components/branches/search_list_spec.js +++ b/spec/frontend/ide/components/branches/search_list_spec.js @@ -9,8 +9,6 @@ import { branches } from '../../mock_data'; const localVue = createLocalVue(); localVue.use(Vuex); -jest.mock('lodash/debounce', () => jest.fn); - describe('IDE branches search list', () => { let wrapper; const fetchBranchesMock = jest.fn(); diff --git a/spec/frontend/ide/components/merge_requests/list_spec.js b/spec/frontend/ide/components/merge_requests/list_spec.js index ae94ee4efa7..e2c6ac49e07 100644 --- a/spec/frontend/ide/components/merge_requests/list_spec.js +++ b/spec/frontend/ide/components/merge_requests/list_spec.js @@ -134,9 +134,7 @@ describe('IDE merge requests list', () => { createComponent(defaultStateWithMergeRequests); const input = findTokenedInput(); input.vm.$emit('input', 'something'); - fetchMergeRequestsMock.mockClear(); - jest.runAllTimers(); return wrapper.vm.$nextTick().then(() => { expect(fetchMergeRequestsMock).toHaveBeenCalledWith( expect.any(Object), diff --git a/spec/frontend/lib/utils/url_utility_spec.js b/spec/frontend/lib/utils/url_utility_spec.js index d0abf2c03a9..4960895890f 100644 --- a/spec/frontend/lib/utils/url_utility_spec.js +++ b/spec/frontend/lib/utils/url_utility_spec.js @@ -485,4 +485,30 @@ describe('URL utility', () => { ); }); }); + + describe('getHTTPProtocol', () => { + const httpProtocol = 'http:'; + const httpsProtocol = 'https:'; + + it.each([[httpProtocol], [httpsProtocol]])( + 'when no url passed, returns correct protocol for %i from window location', + protocol => { + setWindowLocation({ + protocol, + }); + expect(urlUtils.getHTTPProtocol()).toBe(protocol.slice(0, -1)); + }, + ); + + it.each` + url | expectation + ${'not-a-url'} | ${undefined} + ${'wss://example.com'} | ${'wss'} + ${'https://foo.bar'} | ${'https'} + ${'http://foo.bar'} | ${'http'} + ${'http://foo.bar:8080'} | ${'http'} + `('returns correct protocol for $url', ({ url, expectation }) => { + expect(urlUtils.getHTTPProtocol(url)).toBe(expectation); + }); + }); }); diff --git a/spec/frontend/test_setup.js b/spec/frontend/test_setup.js index fff76f158dd..e216f49630f 100644 --- a/spec/frontend/test_setup.js +++ b/spec/frontend/test_setup.js @@ -20,7 +20,7 @@ afterEach(() => // give Promises a bit more time so they fail the right test new Promise(setImmediate).then(() => { // wait for pending setTimeout()s - jest.runAllTimers(); + jest.runOnlyPendingTimers(); }), ); diff --git a/spec/frontend/vue_shared/components/__snapshots__/clone_dropdown_spec.js.snap b/spec/frontend/vue_shared/components/__snapshots__/clone_dropdown_spec.js.snap new file mode 100644 index 00000000000..d837c793784 --- /dev/null +++ b/spec/frontend/vue_shared/components/__snapshots__/clone_dropdown_spec.js.snap @@ -0,0 +1,115 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Clone Dropdown Button rendering matches the snapshot 1`] = ` +<gl-new-dropdown-stub + category="primary" + headertext="" + size="medium" + text="Clone" + variant="info" +> + <div + class="pb-2 mx-1" + > + <gl-new-dropdown-header-stub> + Clone with SSH + </gl-new-dropdown-header-stub> + + <div + class="mx-3" + > + <div + readonly="readonly" + > + <b-input-group-stub + tag="div" + > + <b-input-group-prepend-stub + tag="div" + > + + <!----> + </b-input-group-prepend-stub> + + <b-form-input-stub + class="gl-form-input" + debounce="0" + readonly="true" + type="text" + value="ssh://foo.bar" + /> + + <b-input-group-append-stub + tag="div" + > + <gl-new-button-stub + category="tertiary" + data-clipboard-text="ssh://foo.bar" + icon="" + size="medium" + title="Copy URL" + variant="default" + > + <gl-icon-stub + name="copy-to-clipboard" + size="16" + title="Copy URL" + /> + </gl-new-button-stub> + </b-input-group-append-stub> + </b-input-group-stub> + </div> + </div> + + <gl-new-dropdown-header-stub> + Clone with HTTP + </gl-new-dropdown-header-stub> + + <div + class="mx-3" + > + <div + readonly="readonly" + > + <b-input-group-stub + tag="div" + > + <b-input-group-prepend-stub + tag="div" + > + + <!----> + </b-input-group-prepend-stub> + + <b-form-input-stub + class="gl-form-input" + debounce="0" + readonly="true" + type="text" + value="http://foo.bar" + /> + + <b-input-group-append-stub + tag="div" + > + <gl-new-button-stub + category="tertiary" + data-clipboard-text="http://foo.bar" + icon="" + size="medium" + title="Copy URL" + variant="default" + > + <gl-icon-stub + name="copy-to-clipboard" + size="16" + title="Copy URL" + /> + </gl-new-button-stub> + </b-input-group-append-stub> + </b-input-group-stub> + </div> + </div> + </div> +</gl-new-dropdown-stub> +`; diff --git a/spec/frontend/vue_shared/components/clone_dropdown_spec.js b/spec/frontend/vue_shared/components/clone_dropdown_spec.js new file mode 100644 index 00000000000..38e0cadfe83 --- /dev/null +++ b/spec/frontend/vue_shared/components/clone_dropdown_spec.js @@ -0,0 +1,74 @@ +import CloneDropdown from '~/vue_shared/components/clone_dropdown.vue'; +import { shallowMount } from '@vue/test-utils'; +import { GlFormInputGroup, GlNewDropdownHeader } from '@gitlab/ui'; + +describe('Clone Dropdown Button', () => { + let wrapper; + const sshLink = 'ssh://foo.bar'; + const httpLink = 'http://foo.bar'; + const httpsLink = 'https://foo.bar'; + const defaultPropsData = { + sshLink, + httpLink, + }; + + const createComponent = (propsData = defaultPropsData) => { + wrapper = shallowMount(CloneDropdown, { + propsData, + stubs: { + 'gl-form-input-group': GlFormInputGroup, + }, + }); + }; + + afterEach(() => { + wrapper.destroy(); + wrapper = null; + }); + + describe('rendering', () => { + it('matches the snapshot', () => { + createComponent(); + expect(wrapper.element).toMatchSnapshot(); + }); + + it.each` + name | index | value + ${'SSH'} | ${0} | ${sshLink} + ${'HTTP'} | ${1} | ${httpLink} + `('renders correct link and a copy-button for $name', ({ index, value }) => { + createComponent(); + const group = wrapper.findAll(GlFormInputGroup).at(index); + expect(group.props('value')).toBe(value); + expect(group.contains(GlFormInputGroup)).toBe(true); + }); + + it.each` + name | value + ${'sshLink'} | ${sshLink} + ${'httpLink'} | ${httpLink} + `('does not fail if only $name is set', ({ name, value }) => { + createComponent({ [name]: value }); + + expect(wrapper.find(GlFormInputGroup).props('value')).toBe(value); + expect(wrapper.findAll(GlNewDropdownHeader).length).toBe(1); + }); + }); + + describe('functionality', () => { + it.each` + name | value + ${'sshLink'} | ${null} + ${'httpLink'} | ${null} + `('allows null values for the props', ({ name, value }) => { + createComponent({ ...defaultPropsData, [name]: value }); + + expect(wrapper.findAll(GlNewDropdownHeader).length).toBe(1); + }); + + it('correctly calculates httpLabel for HTTPS protocol', () => { + createComponent({ httpLink: httpsLink }); + expect(wrapper.find(GlNewDropdownHeader).text()).toContain('HTTPS'); + }); + }); +}); diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb index d1a4924c07e..cb8122b6573 100644 --- a/spec/models/service_spec.rb +++ b/spec/models/service_spec.rb @@ -104,21 +104,29 @@ describe Service do describe "Test Button" do describe '#can_test?' do + subject { service.can_test? } + let(:service) { create(:service, project: project) } context 'when repository is not empty' do let(:project) { create(:project, :repository) } - it 'returns true' do - expect(service.can_test?).to be true - end + it { is_expected.to be true } end context 'when repository is empty' do let(:project) { create(:project) } - it 'returns true' do - expect(service.can_test?).to be true + it { is_expected.to be true } + end + + context 'when instance-level service' do + Service.available_services_types.each do |service_type| + let(:service) do + service_type.constantize.new(instance: true) + end + + it { is_expected.to be_falsey } end end end diff --git a/spec/services/metrics/dashboard/update_dashboard_service_spec.rb b/spec/services/metrics/dashboard/update_dashboard_service_spec.rb index 72d36959bce..fce027688d9 100644 --- a/spec/services/metrics/dashboard/update_dashboard_service_spec.rb +++ b/spec/services/metrics/dashboard/update_dashboard_service_spec.rb @@ -27,14 +27,7 @@ describe Metrics::Dashboard::UpdateDashboardService, :use_clean_rails_memory_sto end context 'user does not have push right to repository' do - it 'returns an appropriate message and status code', :aggregate_failures do - result = service_call - - expect(result.keys).to contain_exactly(:message, :http_status, :status, :last_step) - expect(result[:status]).to eq(:error) - expect(result[:http_status]).to eq(:forbidden) - expect(result[:message]).to eq("You are not allowed to push into this branch. Create another branch or open a merge request.") - end + it_behaves_like 'misconfigured dashboard service response with stepable', :forbidden, 'You are not allowed to push into this branch. Create another branch or open a merge request.' end context 'with rights to push to the repository' do @@ -46,27 +39,13 @@ describe Metrics::Dashboard::UpdateDashboardService, :use_clean_rails_memory_sto context 'with a yml extension' do let(:file_name) { 'config/prometheus/../database.yml' } - it 'returns an appropriate message and status code', :aggregate_failures do - result = service_call - - expect(result.keys).to contain_exactly(:message, :http_status, :status, :last_step) - expect(result[:status]).to eq(:error) - expect(result[:http_status]).to eq(:bad_request) - expect(result[:message]).to eq("A file with this name doesn't exist") - end + it_behaves_like 'misconfigured dashboard service response with stepable', :bad_request, "A file with this name doesn't exist" end context 'without a yml extension' do let(:file_name) { '../../..../etc/passwd' } - it 'returns an appropriate message and status code', :aggregate_failures do - result = service_call - - expect(result.keys).to contain_exactly(:message, :http_status, :status, :last_step) - expect(result[:status]).to eq(:error) - expect(result[:http_status]).to eq(:bad_request) - expect(result[:message]).to eq("The file name should have a .yml extension") - end + it_behaves_like 'misconfigured dashboard service response with stepable', :bad_request, 'The file name should have a .yml extension' end end @@ -81,14 +60,7 @@ describe Metrics::Dashboard::UpdateDashboardService, :use_clean_rails_memory_sto project.repository.add_branch(user, branch, 'master') end - it 'returns an appropriate message and status code', :aggregate_failures do - result = service_call - - expect(result.keys).to contain_exactly(:message, :http_status, :status, :last_step) - expect(result[:status]).to eq(:error) - expect(result[:http_status]).to eq(:bad_request) - expect(result[:message]).to eq("There was an error updating the dashboard, branch named: existing_branch already exists.") - end + it_behaves_like 'misconfigured dashboard service response with stepable', :bad_request, 'There was an error updating the dashboard, branch named: existing_branch already exists.' end context 'Files::UpdateService success' do diff --git a/yarn.lock b/yarn.lock index 2a75187b06a..79a8892a499 100644 --- a/yarn.lock +++ b/yarn.lock @@ -786,10 +786,10 @@ resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.117.0.tgz#05239ddcf529c62ca29e1ec1a25a7e24efb98207" integrity sha512-dGy/VWuRAFCTZX3Yqu1+RnAHTSUWafteIk/RMfUCN9B/EMbYzjhYsNy0NLVoZ23Rj/KGv1bUGHvyQCoPP6VzpA== -"@gitlab/ui@11.0.0": - version "11.0.0" - resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-11.0.0.tgz#2dc6c4e92201911b80c1c24ff4ea6c5247810451" - integrity sha512-Cu50RQMbNGxEfMIxr3iv6i09hNs3deRS01CnkxNEdxmmokyKr86a2TItHwrAKyacjkb8IEfkn0Q/yoBMTpfPAw== +"@gitlab/ui@11.0.1": + version "11.0.1" + resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-11.0.1.tgz#7d9fdb823590c72c232b7dee06b86c3e8766ba28" + integrity sha512-JlZULrpmm2jELsVHfcMpE0uiam+hA+5tL4+xZxiHoG+i9UlTQCAteMHOgJVT7pQYvjPAoSnw9XzTATEEcHVcOw== dependencies: "@babel/standalone" "^7.0.0" "@gitlab/vue-toasted" "^1.3.0" |