Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-05-07 03:11:11 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-05-07 03:11:11 +0300
commitd4f8f25db649b973f1ae344cb0f8a407862d106b (patch)
treef71f2d2243dc768a1ec44e79556d8020bff51dc7 /app
parent5f0e3773e9695fd0c9e92ea9180c8a1f5cfaa5c5 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/pages/projects/pipelines/index/index.js1
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines.vue33
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_filtered_search.vue69
-rw-r--r--app/assets/javascripts/pipelines/components/tokens/pipeline_trigger_author_token.vue78
-rw-r--r--app/assets/javascripts/pipelines/constants.js1
-rw-r--r--app/assets/javascripts/pipelines/services/pipelines_service.js10
-rw-r--r--app/assets/javascripts/vue_shared/mixins/ci_pagination_api_mixin.js24
-rw-r--r--app/controllers/projects/pipelines_controller.rb9
-rw-r--r--app/finders/releases_finder.rb20
-rw-r--r--app/graphql/resolvers/release_resolver.rb25
-rw-r--r--app/graphql/resolvers/releases_resolver.rb21
-rw-r--r--app/graphql/types/project_type.rb14
-rw-r--r--app/graphql/types/release_type.rb49
-rw-r--r--app/helpers/services_helper.rb2
-rw-r--r--app/models/project_services/youtrack_service.rb4
-rw-r--r--app/views/admin/application_settings/integrations.html.haml2
-rw-r--r--app/views/groups/settings/integrations/index.html.haml2
-rw-r--r--app/views/projects/pipelines/index.html.haml1
-rw-r--r--app/views/projects/services/_index.html.haml30
-rw-r--r--app/views/projects/settings/integrations/show.html.haml4
-rw-r--r--app/views/shared/integrations/_index.html.haml (renamed from app/views/shared/integrations/_integrations.html.haml)9
21 files changed, 357 insertions, 51 deletions
diff --git a/app/assets/javascripts/pages/projects/pipelines/index/index.js b/app/assets/javascripts/pages/projects/pipelines/index/index.js
index 894185fa8c2..1226fbb670a 100644
--- a/app/assets/javascripts/pages/projects/pipelines/index/index.js
+++ b/app/assets/javascripts/pages/projects/pipelines/index/index.js
@@ -51,6 +51,7 @@ document.addEventListener(
hasGitlabCi: parseBoolean(this.dataset.hasGitlabCi),
ciLintPath: this.dataset.ciLintPath,
resetCachePath: this.dataset.resetCachePath,
+ projectId: this.dataset.projectId,
},
});
},
diff --git a/app/assets/javascripts/pipelines/components/pipelines.vue b/app/assets/javascripts/pipelines/components/pipelines.vue
index d4f23697e09..ad78f5232ef 100644
--- a/app/assets/javascripts/pipelines/components/pipelines.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines.vue
@@ -9,14 +9,18 @@ import NavigationTabs from '../../vue_shared/components/navigation_tabs.vue';
import NavigationControls from './nav_controls.vue';
import { getParameterByName } from '../../lib/utils/common_utils';
import CIPaginationMixin from '../../vue_shared/mixins/ci_pagination_api_mixin';
+import PipelinesFilteredSearch from './pipelines_filtered_search.vue';
+import { ANY_TRIGGER_AUTHOR } from '../constants';
+import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
export default {
components: {
TablePagination,
NavigationTabs,
NavigationControls,
+ PipelinesFilteredSearch,
},
- mixins: [pipelinesMixin, CIPaginationMixin],
+ mixins: [pipelinesMixin, CIPaginationMixin, glFeatureFlagsMixin()],
props: {
store: {
type: Object,
@@ -78,6 +82,10 @@ export default {
required: false,
default: null,
},
+ projectId: {
+ type: String,
+ required: true,
+ },
},
data() {
return {
@@ -209,6 +217,9 @@ export default {
},
];
},
+ canFilterPipelines() {
+ return this.glFeatures.filterPipelinesSearch;
+ },
},
created() {
this.service = new PipelinesService(this.endpoint);
@@ -238,6 +249,19 @@ export default {
createFlash(s__('Pipelines|Something went wrong while cleaning runners cache.'));
});
},
+ filterPipelines(filters) {
+ filters.forEach(filter => {
+ this.requestData[filter.type] = filter.value.data;
+ });
+
+ // set query params back to default if filtering by Any author
+ // or input is cleared on submit
+ if (this.requestData.username === ANY_TRIGGER_AUTHOR || filters.length === 0) {
+ this.requestData = { page: this.page, scope: this.scope };
+ }
+
+ this.updateContent(this.requestData);
+ },
},
};
</script>
@@ -267,6 +291,13 @@ export default {
/>
</div>
+ <pipelines-filtered-search
+ v-if="canFilterPipelines"
+ :pipelines="state.pipelines"
+ :project-id="projectId"
+ @filterPipelines="filterPipelines"
+ />
+
<div class="content-list pipelines">
<gl-loading-icon
v-if="stateToRender === $options.stateMap.loading"
diff --git a/app/assets/javascripts/pipelines/components/pipelines_filtered_search.vue b/app/assets/javascripts/pipelines/components/pipelines_filtered_search.vue
new file mode 100644
index 00000000000..178f08d84cc
--- /dev/null
+++ b/app/assets/javascripts/pipelines/components/pipelines_filtered_search.vue
@@ -0,0 +1,69 @@
+<script>
+import { GlFilteredSearch } from '@gitlab/ui';
+import { __, s__ } from '~/locale';
+import PipelineTriggerAuthorToken from './tokens/pipeline_trigger_author_token.vue';
+import Api from '~/api';
+import createFlash from '~/flash';
+
+export default {
+ components: {
+ GlFilteredSearch,
+ },
+ props: {
+ pipelines: {
+ type: Array,
+ required: true,
+ },
+ projectId: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ projectUsers: null,
+ };
+ },
+ computed: {
+ tokens() {
+ return [
+ {
+ type: 'username',
+ icon: 'user',
+ title: s__('Pipeline|Trigger author'),
+ dataType: 'username',
+ unique: true,
+ token: PipelineTriggerAuthorToken,
+ operators: [{ value: '=', description: __('is'), default: 'true' }],
+ triggerAuthors: this.projectUsers,
+ },
+ ];
+ },
+ },
+ created() {
+ Api.projectUsers(this.projectId)
+ .then(users => {
+ this.projectUsers = users;
+ })
+ .catch(err => {
+ createFlash(__('There was a problem fetching project users.'));
+ throw err;
+ });
+ },
+ methods: {
+ onSubmit(filters) {
+ this.$emit('filterPipelines', filters);
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="row-content-block">
+ <gl-filtered-search
+ :placeholder="__('Filter pipelines')"
+ :available-tokens="tokens"
+ @submit="onSubmit"
+ />
+ </div>
+</template>
diff --git a/app/assets/javascripts/pipelines/components/tokens/pipeline_trigger_author_token.vue b/app/assets/javascripts/pipelines/components/tokens/pipeline_trigger_author_token.vue
new file mode 100644
index 00000000000..55b783024d3
--- /dev/null
+++ b/app/assets/javascripts/pipelines/components/tokens/pipeline_trigger_author_token.vue
@@ -0,0 +1,78 @@
+<script>
+import {
+ GlFilteredSearchToken,
+ GlAvatar,
+ GlFilteredSearchSuggestion,
+ GlDropdownDivider,
+} from '@gitlab/ui';
+import { ANY_TRIGGER_AUTHOR } from '../../constants';
+
+export default {
+ anyTriggerAuthor: ANY_TRIGGER_AUTHOR,
+ components: {
+ GlFilteredSearchToken,
+ GlAvatar,
+ GlFilteredSearchSuggestion,
+ GlDropdownDivider,
+ },
+ props: {
+ config: {
+ type: Object,
+ required: true,
+ },
+ value: {
+ type: Object,
+ required: true,
+ },
+ },
+ computed: {
+ currentValue() {
+ return this.value.data.toLowerCase();
+ },
+ filteredTriggerAuthors() {
+ return this.config.triggerAuthors.filter(user => {
+ return user.username.toLowerCase().includes(this.currentValue);
+ });
+ },
+ activeUser() {
+ return this.config.triggerAuthors.find(user => {
+ return user.username.toLowerCase() === this.currentValue;
+ });
+ },
+ },
+};
+</script>
+
+<template>
+ <gl-filtered-search-token :config="config" v-bind="{ ...$props, ...$attrs }" v-on="$listeners">
+ <template #view="{inputValue}">
+ <gl-avatar
+ v-if="activeUser"
+ :size="16"
+ :src="activeUser.avatar_url"
+ shape="circle"
+ class="gl-mr-2"
+ />
+ <span>{{ activeUser ? activeUser.name : inputValue }}</span>
+ </template>
+ <template #suggestions>
+ <gl-filtered-search-suggestion :value="$options.anyTriggerAuthor">{{
+ $options.anyTriggerAuthor
+ }}</gl-filtered-search-suggestion>
+ <gl-dropdown-divider />
+ <gl-filtered-search-suggestion
+ v-for="user in filteredTriggerAuthors"
+ :key="user.username"
+ :value="user.username"
+ >
+ <div class="d-flex">
+ <gl-avatar :size="32" :src="user.avatar_url" />
+ <div>
+ <div>{{ user.name }}</div>
+ <div>@{{ user.username }}</div>
+ </div>
+ </div>
+ </gl-filtered-search-suggestion>
+ </template>
+ </gl-filtered-search-token>
+</template>
diff --git a/app/assets/javascripts/pipelines/constants.js b/app/assets/javascripts/pipelines/constants.js
index c9655d18a04..d334e867101 100644
--- a/app/assets/javascripts/pipelines/constants.js
+++ b/app/assets/javascripts/pipelines/constants.js
@@ -1,6 +1,7 @@
export const CANCEL_REQUEST = 'CANCEL_REQUEST';
export const PIPELINES_TABLE = 'PIPELINES_TABLE';
export const LAYOUT_CHANGE_DELAY = 300;
+export const ANY_TRIGGER_AUTHOR = 'Any';
export const TestStatus = {
FAILED: 'failed',
diff --git a/app/assets/javascripts/pipelines/services/pipelines_service.js b/app/assets/javascripts/pipelines/services/pipelines_service.js
index 3c755db23dc..82d082a226f 100644
--- a/app/assets/javascripts/pipelines/services/pipelines_service.js
+++ b/app/assets/javascripts/pipelines/services/pipelines_service.js
@@ -19,13 +19,19 @@ export default class PipelinesService {
}
getPipelines(data = {}) {
- const { scope, page } = data;
+ const { scope, page, username } = data;
const { CancelToken } = axios;
+ const queryParams = { scope, page };
+
+ if (username) {
+ queryParams.username = username;
+ }
+
this.cancelationSource = CancelToken.source();
return axios.get(this.endpoint, {
- params: { scope, page },
+ params: queryParams,
cancelToken: this.cancelationSource.token,
});
}
diff --git a/app/assets/javascripts/vue_shared/mixins/ci_pagination_api_mixin.js b/app/assets/javascripts/vue_shared/mixins/ci_pagination_api_mixin.js
index f9e3f3df0cc..69e9839380f 100644
--- a/app/assets/javascripts/vue_shared/mixins/ci_pagination_api_mixin.js
+++ b/app/assets/javascripts/vue_shared/mixins/ci_pagination_api_mixin.js
@@ -9,21 +9,41 @@ import { historyPushState, buildUrlWithCurrentLocation } from '../../lib/utils/c
export default {
methods: {
onChangeTab(scope) {
- this.updateContent({ scope, page: '1' });
+ let params = {
+ scope,
+ page: '1',
+ };
+
+ params = this.onChangeWithFilter(params);
+
+ this.updateContent(params);
},
onChangePage(page) {
/* URLS parameters are strings, we need to parse to match types */
- const params = {
+ let params = {
page: Number(page).toString(),
};
if (this.scope) {
params.scope = this.scope;
}
+
+ params = this.onChangeWithFilter(params);
+
this.updateContent(params);
},
+ onChangeWithFilter(params) {
+ const { username } = this.requestData;
+
+ if (username) {
+ return { ...params, username };
+ }
+
+ return params;
+ },
+
updateInternalState(parameters) {
// stop polling
this.poll.stop();
diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb
index c578925e04c..a7b52121d2b 100644
--- a/app/controllers/projects/pipelines_controller.rb
+++ b/app/controllers/projects/pipelines_controller.rb
@@ -24,9 +24,8 @@ class Projects::PipelinesController < Projects::ApplicationController
POLLING_INTERVAL = 10_000
def index
- @scope = params[:scope]
@pipelines = Ci::PipelinesFinder
- .new(project, current_user, scope: @scope)
+ .new(project, current_user, index_params)
.execute
.page(params[:page])
.per(30)
@@ -256,7 +255,7 @@ class Projects::PipelinesController < Projects::ApplicationController
end
def limited_pipelines_count(project, scope = nil)
- finder = Ci::PipelinesFinder.new(project, current_user, scope: scope)
+ finder = Ci::PipelinesFinder.new(project, current_user, index_params.merge(scope: scope))
view_context.limited_counter_with_delimiter(finder.execute)
end
@@ -268,6 +267,10 @@ class Projects::PipelinesController < Projects::ApplicationController
end
end
end
+
+ def index_params
+ params.permit(:scope, :username)
+ end
end
Projects::PipelinesController.prepend_if_ee('EE::Projects::PipelinesController')
diff --git a/app/finders/releases_finder.rb b/app/finders/releases_finder.rb
index e58a90922a5..6a754fdb5a1 100644
--- a/app/finders/releases_finder.rb
+++ b/app/finders/releases_finder.rb
@@ -1,17 +1,31 @@
# frozen_string_literal: true
class ReleasesFinder
- def initialize(project, current_user = nil)
+ attr_reader :project, :current_user, :params
+
+ def initialize(project, current_user = nil, params = {})
@project = project
@current_user = current_user
+ @params = params
end
def execute(preload: true)
- return Release.none unless Ability.allowed?(@current_user, :read_release, @project)
+ return Release.none unless Ability.allowed?(current_user, :read_release, project)
# See https://gitlab.com/gitlab-org/gitlab/-/issues/211988
- releases = @project.releases.where.not(tag: nil) # rubocop:disable CodeReuse/ActiveRecord
+ releases = project.releases.where.not(tag: nil) # rubocop:disable CodeReuse/ActiveRecord
+ releases = by_tag(releases)
releases = releases.preloaded if preload
releases.sorted
end
+
+ private
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def by_tag(releases)
+ return releases unless params[:tag].present?
+
+ releases.where(tag: params[:tag])
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/graphql/resolvers/release_resolver.rb b/app/graphql/resolvers/release_resolver.rb
new file mode 100644
index 00000000000..9bae8b8cd13
--- /dev/null
+++ b/app/graphql/resolvers/release_resolver.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module Resolvers
+ class ReleaseResolver < BaseResolver
+ type Types::ReleaseType, null: true
+
+ argument :tag_name, GraphQL::STRING_TYPE,
+ required: true,
+ description: 'The name of the tag associated to the release'
+
+ alias_method :project, :object
+
+ def self.single
+ self
+ end
+
+ def resolve(tag_name:)
+ ReleasesFinder.new(
+ project,
+ current_user,
+ { tag: tag_name }
+ ).execute.first
+ end
+ end
+end
diff --git a/app/graphql/resolvers/releases_resolver.rb b/app/graphql/resolvers/releases_resolver.rb
new file mode 100644
index 00000000000..b2afbb92684
--- /dev/null
+++ b/app/graphql/resolvers/releases_resolver.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module Resolvers
+ class ReleasesResolver < BaseResolver
+ type Types::ReleaseType.connection_type, null: true
+
+ alias_method :project, :object
+
+ # This resolver has a custom singular resolver
+ def self.single
+ Resolvers::ReleaseResolver
+ end
+
+ def resolve(**args)
+ ReleasesFinder.new(
+ project,
+ current_user
+ ).execute
+ end
+ end
+end
diff --git a/app/graphql/types/project_type.rb b/app/graphql/types/project_type.rb
index ebde6c4c98d..e7a83446610 100644
--- a/app/graphql/types/project_type.rb
+++ b/app/graphql/types/project_type.rb
@@ -217,6 +217,20 @@ module Types
null: true,
description: 'A single Alert Management alert of the project',
resolver: Resolvers::AlertManagementAlertResolver.single
+
+ field :releases,
+ Types::ReleaseType.connection_type,
+ null: true,
+ description: 'Releases of the project',
+ resolver: Resolvers::ReleasesResolver,
+ feature_flag: :graphql_release_data
+
+ field :release,
+ Types::ReleaseType,
+ null: true,
+ description: 'A single release of the project',
+ resolver: Resolvers::ReleasesResolver.single,
+ feature_flag: :graphql_release_data
end
end
diff --git a/app/graphql/types/release_type.rb b/app/graphql/types/release_type.rb
new file mode 100644
index 00000000000..26b47c191ca
--- /dev/null
+++ b/app/graphql/types/release_type.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+module Types
+ class ReleaseType < BaseObject
+ graphql_name 'Release'
+
+ authorize :read_release
+
+ alias_method :release, :object
+
+ present_using ReleasePresenter
+
+ field :tag_name, GraphQL::STRING_TYPE, null: false, method: :tag,
+ description: 'Name of the tag associated with the release'
+ field :tag_path, GraphQL::STRING_TYPE, null: true,
+ description: 'Relative web path to the tag associated with the release'
+ field :description, GraphQL::STRING_TYPE, null: true,
+ description: 'Description (also known as "release notes") of the release'
+ markdown_field :description_html, null: true
+ field :name, GraphQL::STRING_TYPE, null: true,
+ description: 'Name of the release'
+ field :evidence_sha, GraphQL::STRING_TYPE, null: true,
+ description: "SHA of the release's evidence"
+ field :created_at, Types::TimeType, null: true,
+ description: 'Timestamp of when the release was created'
+ field :released_at, Types::TimeType, null: true,
+ description: 'Timestamp of when the release was released'
+ field :milestones, Types::MilestoneType.connection_type, null: true,
+ description: 'Milestones associated to the release'
+
+ field :author, Types::UserType, null: true,
+ description: 'User that created the release'
+
+ def author
+ Gitlab::Graphql::Loaders::BatchModelLoader.new(User, release.author_id).find
+ end
+
+ field :commit, Types::CommitType, null: true,
+ complexity: 10, calls_gitaly: true,
+ description: 'The commit associated with the release',
+ authorize: :reporter_access
+
+ def commit
+ return if release.sha.nil?
+
+ release.project.commit_by(oid: release.sha)
+ end
+ end
+end
diff --git a/app/helpers/services_helper.rb b/app/helpers/services_helper.rb
index ec60c62794a..dcdf6bbd3da 100644
--- a/app/helpers/services_helper.rb
+++ b/app/helpers/services_helper.rb
@@ -80,7 +80,7 @@ module ServicesHelper
def scoped_edit_integration_path(integration)
if @project.present?
- edit_project_settings_integration_path(@project, integration)
+ edit_project_service_path(@project, integration)
elsif @group.present?
edit_group_settings_integration_path(@group, integration)
else
diff --git a/app/models/project_services/youtrack_service.rb b/app/models/project_services/youtrack_service.rb
index 0815e27850d..40203ad692d 100644
--- a/app/models/project_services/youtrack_service.rb
+++ b/app/models/project_services/youtrack_service.rb
@@ -27,8 +27,8 @@ class YoutrackService < IssueTrackerService
def fields
[
{ type: 'text', name: 'description', placeholder: description },
- { type: 'text', name: 'project_url', placeholder: 'Project url', required: true },
- { type: 'text', name: 'issues_url', placeholder: 'Issue url', required: true }
+ { type: 'text', name: 'project_url', title: 'Project URL', placeholder: 'Project URL', required: true },
+ { type: 'text', name: 'issues_url', title: 'Issue URL', placeholder: 'Issue URL', required: true }
]
end
end
diff --git a/app/views/admin/application_settings/integrations.html.haml b/app/views/admin/application_settings/integrations.html.haml
index 2b01160a230..a8eff26b94c 100644
--- a/app/views/admin/application_settings/integrations.html.haml
+++ b/app/views/admin/application_settings/integrations.html.haml
@@ -18,7 +18,7 @@
%p
= s_('AdminSettings|Integrations configured here will automatically apply to all projects on this instance.')
= link_to _('Learn more'), '#'
- = render 'shared/integrations/integrations', integrations: @integrations
+ = render 'shared/integrations/index', integrations: @integrations
- else
= render_if_exists 'admin/application_settings/elasticsearch_form'
diff --git a/app/views/groups/settings/integrations/index.html.haml b/app/views/groups/settings/integrations/index.html.haml
index 78825cc72b0..96bd6d69a96 100644
--- a/app/views/groups/settings/integrations/index.html.haml
+++ b/app/views/groups/settings/integrations/index.html.haml
@@ -6,4 +6,4 @@
%p
= s_('GroupSettings|Integrations configured here will automatically apply to all projects in this group.')
= link_to _('Learn more'), '#'
-= render 'shared/integrations/integrations', integrations: @integrations
+= render 'shared/integrations/index', integrations: @integrations
diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml
index f64f07487fd..64789c7c263 100644
--- a/app/views/projects/pipelines/index.html.haml
+++ b/app/views/projects/pipelines/index.html.haml
@@ -3,6 +3,7 @@
= render_if_exists "shared/shared_runners_minutes_limit_flash_message"
#pipelines-list-vue{ data: { endpoint: project_pipelines_path(@project, format: :json),
+ project_id: @project.id,
"help-page-path" => help_page_path('ci/quick_start/README'),
"help-auto-devops-path" => help_page_path('topics/autodevops/index.md'),
"empty-state-svg-path" => image_path('illustrations/pipelines_empty.svg'),
diff --git a/app/views/projects/services/_index.html.haml b/app/views/projects/services/_index.html.haml
deleted file mode 100644
index dca324ac846..00000000000
--- a/app/views/projects/services/_index.html.haml
+++ /dev/null
@@ -1,30 +0,0 @@
-.row.prepend-top-default
- .col-lg-4
- %h4.prepend-top-0
- = _('Integrations')
- %p= _('Integrations allow you to integrate GitLab with other applications')
- .col-lg-8
- %table.table
- %colgroup
- %col
- %col
- %col
- %col{ width: "120" }
- %thead
- %tr
- %th
- %th= _('Integration')
- %th.d-none.d-sm-block= _("Description")
- %th= s_("ProjectService|Last edit")
- - @services.sort_by(&:title).each do |service|
- %tr
- %td{ "aria-label" => (service.activated? ? s_("ProjectService|%{service_title}: status on") : s_("ProjectService|%{service_title}: status off")) % { service_title: service.title } }
- = boolean_to_icon service.activated?
- %td
- = link_to edit_project_service_path(@project, service.to_param), { data: { qa_selector: "#{service.title.downcase.gsub(/[\s\(\)]/,'_')}_link" } } do
- %strong= service.title
- %td.d-none.d-sm-block
- = service.description
- %td.light
- - if service.updated_at.present?
- = time_ago_with_tooltip service.updated_at
diff --git a/app/views/projects/settings/integrations/show.html.haml b/app/views/projects/settings/integrations/show.html.haml
index f603f23a2c7..4372763fcf7 100644
--- a/app/views/projects/settings/integrations/show.html.haml
+++ b/app/views/projects/settings/integrations/show.html.haml
@@ -12,4 +12,6 @@
.gl-alert-actions
= link_to _('Go to Webhooks'), project_hooks_path(@project), class: 'btn gl-alert-action btn-info new-gl-button'
-= render 'projects/services/index'
+%h4= s_('Integrations')
+%p= s_('Integrations allow you to integrate GitLab with other applications')
+= render 'shared/integrations/index', integrations: @services
diff --git a/app/views/shared/integrations/_integrations.html.haml b/app/views/shared/integrations/_index.html.haml
index b2359aca016..176f899d677 100644
--- a/app/views/shared/integrations/_integrations.html.haml
+++ b/app/views/shared/integrations/_index.html.haml
@@ -3,7 +3,7 @@
%col
%col
%col.d-none.d-sm-table-column
- %col{ width: 120 }
+ %col{ width: 130 }
%thead{ role: 'rowgroup' }
%tr{ role: 'row' }
%th{ role: 'columnheader', scope: 'col', 'aria-colindex': 1 }
@@ -13,13 +13,14 @@
%tbody{ role: 'rowgroup' }
- integrations.each do |integration|
+ - activated_label = (integration.activated? ? s_("ProjectService|%{service_title}: status on") : s_("ProjectService|%{service_title}: status off")) % { service_title: integration.title }
%tr{ role: 'row' }
- %td{ role: 'cell', 'aria-colindex': 1 }
+ %td{ role: 'cell', 'aria-colindex': 1, 'aria-label': activated_label }
= boolean_to_icon integration.activated?
%td{ role: 'cell', 'aria-colindex': 2 }
- = link_to scoped_edit_integration_path(integration) do
+ = link_to scoped_edit_integration_path(integration), { data: { qa_selector: "#{integration.to_param}_link" } } do
%strong= integration.title
- %td.d-none.d-sm-block{ role: 'cell', 'aria-colindex': 3 }
+ %td.d-none.d-sm-table-cell{ role: 'cell', 'aria-colindex': 3 }
= integration.description
%td{ role: 'cell', 'aria-colindex': 4 }
- if integration.updated_at.present?