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
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-08-10 00:10:17 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-08-10 00:10:17 +0300
commit413c91fda942270905873684a58041f8c65f878c (patch)
tree8053b007997cc1266b5d9c86794fd8f6beff0eae
parent7d112a9002182130f6a06f890bb17450b03406f9 (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.gitlab/CODEOWNERS2
-rw-r--r--.gitlab/issue_templates/Design Sprint.md2
-rw-r--r--app/assets/javascripts/organizations/groups_and_projects/components/groups_page.vue42
-rw-r--r--app/assets/javascripts/organizations/groups_and_projects/graphql/queries/groups.query.graphql22
-rw-r--r--app/assets/javascripts/organizations/groups_and_projects/graphql/resolvers.js12
-rw-r--r--app/assets/javascripts/organizations/groups_and_projects/utils.js6
-rw-r--r--app/assets/javascripts/pages/projects/pipeline_schedules/create/index.js3
-rw-r--r--app/assets/javascripts/pages/projects/pipeline_schedules/update/index.js3
-rw-r--r--app/assets/javascripts/users/profile/actions/components/user_actions_app.vue58
-rw-r--r--app/assets/javascripts/users/profile/actions/index.js13
-rw-r--r--app/assets/javascripts/users/profile/index.js2
-rw-r--r--app/assets/javascripts/vue_shared/components/badges/beta_badge.stories.js9
-rw-r--r--app/assets/javascripts/vue_shared/components/badges/beta_badge.vue13
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/comment_templates_dropdown.vue3
-rw-r--r--app/helpers/users_helper.rb18
-rw-r--r--app/services/metrics/dashboard/base_service.rb1
-rw-r--r--app/services/metrics/dashboard/predefined_dashboard_service.rb8
-rw-r--r--app/services/metrics/dashboard/system_dashboard_service.rb4
-rw-r--r--app/views/users/show.html.haml2
-rw-r--r--config/metrics/counts_28d/20210427102618_code_review_category_monthly_active_users.yml1
-rw-r--r--config/metrics/counts_28d/20210427103119_code_review_group_monthly_active_users.yml1
-rw-r--r--config/metrics/counts_28d/20230725194658_i_code_review_saved_replies_use_monthly.yml25
-rw-r--r--config/metrics/counts_7d/20210427103328_code_review_group_monthly_active_users.yml1
-rw-r--r--config/metrics/counts_7d/20210427103407_code_review_category_monthly_active_users.yml1
-rw-r--r--config/metrics/counts_7d/20230725194657_i_code_review_saved_replies_use_weekly.yml25
-rw-r--r--config/metrics/counts_all/20230725195335_i_code_review_saved_replies_count_use.yml25
-rw-r--r--doc/administration/settings/rate_limit_on_issues_creation.md26
-rw-r--r--lib/gitlab/metrics/dashboard/stages/custom_dashboard_metrics_inserter.rb24
-rw-r--r--locale/gitlab.pot5
-rw-r--r--qa/qa/resource/api_fabricator.rb21
-rw-r--r--spec/features/abuse_report_spec.rb119
-rw-r--r--spec/frontend/organizations/groups_and_projects/components/groups_page_spec.js88
-rw-r--r--spec/frontend/organizations/groups_and_projects/components/projects_page_spec.js2
-rw-r--r--spec/frontend/organizations/groups_and_projects/mock_data.js329
-rw-r--r--spec/frontend/organizations/groups_and_projects/utils_spec.js21
-rw-r--r--spec/frontend/users/profile/actions/components/user_actions_app_spec.js27
-rw-r--r--spec/frontend/vue_shared/components/badges/__snapshots__/beta_badge_spec.js.snap5
-rw-r--r--spec/frontend/vue_shared/components/badges/beta_badge_spec.js22
-rw-r--r--spec/frontend/vue_shared/components/markdown/comment_templates_dropdown_spec.js46
-rw-r--r--spec/helpers/users_helper_spec.rb54
-rw-r--r--spec/lib/gitlab/metrics/dashboard/processor_spec.rb71
-rw-r--r--spec/lib/gitlab/metrics/dashboard/stages/panel_ids_inserter_spec.rb88
42 files changed, 866 insertions, 384 deletions
diff --git a/.gitlab/CODEOWNERS b/.gitlab/CODEOWNERS
index f43db203219..9e0c52b9deb 100644
--- a/.gitlab/CODEOWNERS
+++ b/.gitlab/CODEOWNERS
@@ -2,7 +2,7 @@
# project here: https://gitlab.com/gitlab-org/gitlab/-/project_members
# As described in https://docs.gitlab.com/ee/user/project/code_owners.html
-* @gitlab-org/maintainers/rails-backend @gitlab-org/maintainers/frontend @gl-quality/qe-maintainers @gl-quality/tooling-maintainers @gitlab-org/delivery @gitlab-org/maintainers/cicd-templates @nolith @gitlab-org/tw-leadership
+* @gitlab-org/maintainers/rails-backend @gitlab-org/maintainers/frontend @gitlab-org/maintainers/database @gl-quality/qe-maintainers @gl-quality/tooling-maintainers @gitlab-org/delivery @gitlab-org/maintainers/cicd-templates @nolith @gitlab-org/tw-leadership
.gitlab/CODEOWNERS @gitlab-org/development-leaders @gitlab-org/tw-leadership
diff --git a/.gitlab/issue_templates/Design Sprint.md b/.gitlab/issue_templates/Design Sprint.md
index 76f3ae1c869..2ad3bac0a05 100644
--- a/.gitlab/issue_templates/Design Sprint.md
+++ b/.gitlab/issue_templates/Design Sprint.md
@@ -6,7 +6,7 @@ Please refer to the [Remote Design Sprint Handbook page](https://about.gitlab.co
## Design Sprint Focus
* [ ] Have you [determined that a Design Sprint is appropriate for this project](https://about.gitlab.com/handbook/product/ux/design-sprint/#when-to-opt-for-a-remote-design-sprint)?
-_What is the focus of the [Design Sprint](https://about.gitlab.com/handbook/product/product-processes/#design-sprint)? What problem area will you be solving for and who is the target user?_
+_What is the focus of the [Design Sprint](https://about.gitlab.com/handbook/product/product-processes/#remote-design-sprint)? What problem area will you be solving for and who is the target user?_
## Objectives
_What is the objective(s) this Design Sprint will entail?_
diff --git a/app/assets/javascripts/organizations/groups_and_projects/components/groups_page.vue b/app/assets/javascripts/organizations/groups_and_projects/components/groups_page.vue
index b723cd98ce4..20db38403f7 100644
--- a/app/assets/javascripts/organizations/groups_and_projects/components/groups_page.vue
+++ b/app/assets/javascripts/organizations/groups_and_projects/components/groups_page.vue
@@ -1,9 +1,43 @@
<script>
-export default {};
+import { GlLoadingIcon } from '@gitlab/ui';
+import { createAlert } from '~/alert';
+import { s__ } from '~/locale';
+import GroupsList from '~/vue_shared/components/groups_list/groups_list.vue';
+import groupsQuery from '../graphql/queries/groups.query.graphql';
+import { formatGroups } from '../utils';
+
+export default {
+ i18n: {
+ errorMessage: s__(
+ 'Organization|An error occurred loading the groups. Please refresh the page to try again.',
+ ),
+ },
+ components: { GlLoadingIcon, GroupsList },
+ data() {
+ return {
+ groups: [],
+ };
+ },
+ apollo: {
+ groups: {
+ query: groupsQuery,
+ update(data) {
+ return formatGroups(data.organization.groups.nodes);
+ },
+ error(error) {
+ createAlert({ message: this.$options.i18n.errorMessage, error, captureError: true });
+ },
+ },
+ },
+ computed: {
+ isLoading() {
+ return this.$apollo.queries.groups.loading;
+ },
+ },
+};
</script>
<template>
- <div>
- <!-- Intentionally empty. Will be implemented in future commits. -->
- </div>
+ <gl-loading-icon v-if="isLoading" class="gl-mt-5" size="md" />
+ <groups-list v-else :groups="groups" show-group-icon />
</template>
diff --git a/app/assets/javascripts/organizations/groups_and_projects/graphql/queries/groups.query.graphql b/app/assets/javascripts/organizations/groups_and_projects/graphql/queries/groups.query.graphql
new file mode 100644
index 00000000000..842c601e326
--- /dev/null
+++ b/app/assets/javascripts/organizations/groups_and_projects/graphql/queries/groups.query.graphql
@@ -0,0 +1,22 @@
+query getOrganizationGroups {
+ organization @client {
+ id
+ groups {
+ nodes {
+ id
+ fullName
+ parent
+ webUrl
+ descriptionHtml
+ avatarUrl
+ descendantGroupsCount
+ projectsCount
+ groupMembersCount
+ visibility
+ accessLevel {
+ integerValue
+ }
+ }
+ }
+ }
+}
diff --git a/app/assets/javascripts/organizations/groups_and_projects/graphql/resolvers.js b/app/assets/javascripts/organizations/groups_and_projects/graphql/resolvers.js
index e3e0529d6d9..8a375b28797 100644
--- a/app/assets/javascripts/organizations/groups_and_projects/graphql/resolvers.js
+++ b/app/assets/javascripts/organizations/groups_and_projects/graphql/resolvers.js
@@ -1,4 +1,8 @@
-import { organizationProjects } from 'jest/organizations/groups_and_projects/mock_data';
+import {
+ organization,
+ organizationProjects,
+ organizationGroups,
+} from 'jest/organizations/groups_and_projects/mock_data';
export default {
Query: {
@@ -8,7 +12,11 @@ export default {
setTimeout(resolve, 1000);
});
- return organizationProjects;
+ return {
+ ...organization,
+ projects: organizationProjects,
+ groups: organizationGroups,
+ };
},
},
};
diff --git a/app/assets/javascripts/organizations/groups_and_projects/utils.js b/app/assets/javascripts/organizations/groups_and_projects/utils.js
index 853a8543c1b..cbfa120503d 100644
--- a/app/assets/javascripts/organizations/groups_and_projects/utils.js
+++ b/app/assets/javascripts/organizations/groups_and_projects/utils.js
@@ -11,3 +11,9 @@ export const formatProjects = (projects) =>
},
},
}));
+
+export const formatGroups = (groups) =>
+ groups.map(({ id, ...group }) => ({
+ ...group,
+ id: getIdFromGraphQLId(id),
+ }));
diff --git a/app/assets/javascripts/pages/projects/pipeline_schedules/create/index.js b/app/assets/javascripts/pages/projects/pipeline_schedules/create/index.js
deleted file mode 100644
index 6dd21380bec..00000000000
--- a/app/assets/javascripts/pages/projects/pipeline_schedules/create/index.js
+++ /dev/null
@@ -1,3 +0,0 @@
-import initForm from '../shared/init_form';
-
-initForm();
diff --git a/app/assets/javascripts/pages/projects/pipeline_schedules/update/index.js b/app/assets/javascripts/pages/projects/pipeline_schedules/update/index.js
deleted file mode 100644
index 6dd21380bec..00000000000
--- a/app/assets/javascripts/pages/projects/pipeline_schedules/update/index.js
+++ /dev/null
@@ -1,3 +0,0 @@
-import initForm from '../shared/init_form';
-
-initForm();
diff --git a/app/assets/javascripts/users/profile/actions/components/user_actions_app.vue b/app/assets/javascripts/users/profile/actions/components/user_actions_app.vue
index a8ecc014a95..5dfa9c67852 100644
--- a/app/assets/javascripts/users/profile/actions/components/user_actions_app.vue
+++ b/app/assets/javascripts/users/profile/actions/components/user_actions_app.vue
@@ -1,10 +1,12 @@
<script>
import { GlDisclosureDropdown } from '@gitlab/ui';
import { sprintf, s__ } from '~/locale';
+import AbuseCategorySelector from '~/abuse_reports/components/abuse_category_selector.vue';
export default {
components: {
GlDisclosureDropdown,
+ AbuseCategorySelector,
},
props: {
userId: {
@@ -13,14 +15,22 @@ export default {
},
rssSubscriptionPath: {
type: String,
- required: true,
+ required: false,
+ default: '',
+ },
+ reportedUserId: {
+ type: Number,
+ required: false,
+ default: null,
+ },
+ reportedFromUrl: {
+ type: String,
+ required: false,
+ default: '',
},
},
data() {
return {
- // Only implement the copy function and RSS subscription in MR for now
- // https://gitlab.com/gitlab-org/gitlab/-/merge_requests/122971
- // The rest will be implemented in the upcoming MR.
defaultDropdownItems: [
{
action: this.onUserIdCopy,
@@ -30,38 +40,56 @@ export default {
},
},
],
+ open: false,
};
},
computed: {
dropdownItems() {
+ const dropdownItems = this.defaultDropdownItems.slice();
if (this.rssSubscriptionPath) {
- return [
- ...this.defaultDropdownItems,
- {
- href: this.rssSubscriptionPath,
- text: this.$options.i18n.rssSubscribe,
- extraAttrs: {
- 'data-testid': 'user-profile-rss-subscription-link',
- },
+ dropdownItems.push({
+ href: this.rssSubscriptionPath,
+ text: this.$options.i18n.rssSubscribe,
+ extraAttrs: {
+ 'data-testid': 'user-profile-rss-subscription-link',
},
- ];
+ });
+ }
+ if (this.reportedUserId) {
+ dropdownItems.push({
+ action: () => this.toggleDrawer(true),
+ text: this.$options.i18n.reportToAdmin,
+ });
}
- return this.defaultDropdownItems;
+ return dropdownItems;
},
},
methods: {
onUserIdCopy() {
this.$toast.show(this.$options.i18n.userIdCopied);
},
+ toggleDrawer(open) {
+ this.open = open;
+ },
},
i18n: {
userId: s__('UserProfile|Copy user ID: %{id}'),
userIdCopied: s__('UserProfile|User ID copied to clipboard'),
rssSubscribe: s__('UserProfile|Subscribe'),
+ reportToAdmin: s__('ReportAbuse|Report abuse to administrator'),
},
};
</script>
<template>
- <gl-disclosure-dropdown icon="ellipsis_v" category="tertiary" no-caret :items="dropdownItems" />
+ <span>
+ <gl-disclosure-dropdown icon="ellipsis_v" category="tertiary" no-caret :items="dropdownItems" />
+ <abuse-category-selector
+ v-if="reportedUserId"
+ :reported-user-id="reportedUserId"
+ :reported-from-url="reportedFromUrl"
+ :show-drawer="open"
+ @close-drawer="toggleDrawer(false)"
+ />
+ </span>
</template>
diff --git a/app/assets/javascripts/users/profile/actions/index.js b/app/assets/javascripts/users/profile/actions/index.js
index 22507165ece..e1f9352966b 100644
--- a/app/assets/javascripts/users/profile/actions/index.js
+++ b/app/assets/javascripts/users/profile/actions/index.js
@@ -7,18 +7,29 @@ export const initUserActionsApp = () => {
if (!mountingEl) return false;
- const { userId, rssSubscriptionPath } = mountingEl.dataset;
+ const {
+ userId,
+ rssSubscriptionPath,
+ reportAbusePath,
+ reportedUserId,
+ reportedFromUrl,
+ } = mountingEl.dataset;
Vue.use(GlToast);
return new Vue({
el: mountingEl,
name: 'UserActionsRoot',
+ provide: {
+ reportAbusePath,
+ },
render(createElement) {
return createElement(UserActionsApp, {
props: {
userId,
rssSubscriptionPath,
+ reportedUserId: reportedUserId ? parseInt(reportedUserId, 10) : null,
+ reportedFromUrl,
},
});
},
diff --git a/app/assets/javascripts/users/profile/index.js b/app/assets/javascripts/users/profile/index.js
index c6b85489785..3ae3cc2de98 100644
--- a/app/assets/javascripts/users/profile/index.js
+++ b/app/assets/javascripts/users/profile/index.js
@@ -13,7 +13,7 @@ export const initReportAbuse = () => {
name: 'ReportAbuseButtonRoot',
provide: {
reportAbusePath,
- reportedUserId: parseInt(reportedUserId, 10),
+ reportedUserId: reportedUserId ? parseInt(reportedUserId, 10) : null,
reportedFromUrl,
},
render(createElement) {
diff --git a/app/assets/javascripts/vue_shared/components/badges/beta_badge.stories.js b/app/assets/javascripts/vue_shared/components/badges/beta_badge.stories.js
index 3cef1bf4ba5..805a32273f4 100644
--- a/app/assets/javascripts/vue_shared/components/badges/beta_badge.stories.js
+++ b/app/assets/javascripts/vue_shared/components/badges/beta_badge.stories.js
@@ -7,13 +7,18 @@ export default {
const template = `
<div style="height:600px;" class="gl-display-flex gl-justify-content-center gl-align-items-center">
- <beta-badge />
+ <beta-badge :size="size" />
</div>
`;
-const Template = () => ({
+const Template = (args, { argTypes }) => ({
components: { BetaBadge },
+ data() {
+ return { value: args.value };
+ },
+ props: Object.keys(argTypes),
template,
});
export const Default = Template.bind({});
+Default.args = {};
diff --git a/app/assets/javascripts/vue_shared/components/badges/beta_badge.vue b/app/assets/javascripts/vue_shared/components/badges/beta_badge.vue
index 198b6580277..e8d33b5538e 100644
--- a/app/assets/javascripts/vue_shared/components/badges/beta_badge.vue
+++ b/app/assets/javascripts/vue_shared/components/badges/beta_badge.vue
@@ -12,11 +12,18 @@ export default {
"BetaBadge|A Beta feature is not production-ready, but is unlikely to change drastically before it's released. We encourage users to try Beta features and provide feedback.",
),
listIntroduction: s__('BetaBadge|A Beta feature:'),
- listItemStability: s__('BetaBadge|May have performance or stability issues.'),
+ listItemStability: s__('BetaBadge|May be unstable.'),
listItemDataLoss: s__('BetaBadge|Should not cause data loss.'),
listItemReasonableEffort: s__('BetaBadge|Is supported by a commercially reasonable effort.'),
listItemNearCompletion: s__('BetaBadge|Is complete or near completion.'),
},
+ props: {
+ size: {
+ type: String,
+ required: false,
+ default: 'md',
+ },
+ },
methods: {
target() {
/**
@@ -35,7 +42,9 @@ export default {
<template>
<div>
- <gl-badge ref="badge" class="gl-cursor-pointer">{{ $options.i18n.badgeLabel }}</gl-badge>
+ <gl-badge ref="badge" href="#" :size="size" variant="neutral" class="gl-cursor-pointer">{{
+ $options.i18n.badgeLabel
+ }}</gl-badge>
<gl-popover
triggers="hover focus click"
:show-close-button="true"
diff --git a/app/assets/javascripts/vue_shared/components/markdown/comment_templates_dropdown.vue b/app/assets/javascripts/vue_shared/components/markdown/comment_templates_dropdown.vue
index 966a5556d24..abfb92f772d 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/comment_templates_dropdown.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/comment_templates_dropdown.vue
@@ -1,6 +1,7 @@
<script>
import { GlCollapsibleListbox, GlTooltip, GlButton } from '@gitlab/ui';
import fuzzaldrinPlus from 'fuzzaldrin-plus';
+import { InternalEvents } from '~/tracking';
import savedRepliesQuery from './saved_replies.query.graphql';
export default {
@@ -18,6 +19,7 @@ export default {
GlButton,
GlTooltip,
},
+ mixins: [InternalEvents.mixin()],
props: {
newCommentTemplatePath: {
type: String,
@@ -55,6 +57,7 @@ export default {
const savedReply = this.savedReplies.find((r) => r.id === id);
if (savedReply) {
this.$emit('select', savedReply.content);
+ this.track_event('i_code_review_saved_replies_use');
}
},
},
diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb
index 95419c81a35..ac279904fd2 100644
--- a/app/helpers/users_helper.rb
+++ b/app/helpers/users_helper.rb
@@ -226,6 +226,24 @@ module UsersHelper
end
end
+ def user_profile_actions_data(user)
+ basic_actions_data = {
+ user_id: user.id
+ }
+
+ if can?(current_user, :read_user_profile, user)
+ basic_actions_data[:rss_subscription_path] = user_path(user, rss_url_options)
+ end
+
+ return basic_actions_data if !current_user || current_user == user
+
+ basic_actions_data.merge(
+ report_abuse_path: add_category_abuse_reports_path,
+ reported_user_id: user.id,
+ reported_from_url: user_url(user)
+ )
+ end
+
private
def admin_users_paths
diff --git a/app/services/metrics/dashboard/base_service.rb b/app/services/metrics/dashboard/base_service.rb
index 1b9f79b420f..e9b9c233b39 100644
--- a/app/services/metrics/dashboard/base_service.rb
+++ b/app/services/metrics/dashboard/base_service.rb
@@ -9,7 +9,6 @@ module Metrics
STAGES = ::Gitlab::Metrics::Dashboard::Stages
SEQUENCE = [
- STAGES::PanelIdsInserter,
STAGES::TrackPanelType,
STAGES::UrlValidator
].freeze
diff --git a/app/services/metrics/dashboard/predefined_dashboard_service.rb b/app/services/metrics/dashboard/predefined_dashboard_service.rb
index 32d34474a08..82f826531bf 100644
--- a/app/services/metrics/dashboard/predefined_dashboard_service.rb
+++ b/app/services/metrics/dashboard/predefined_dashboard_service.rb
@@ -9,10 +9,6 @@ module Metrics
DASHBOARD_PATH = nil
DASHBOARD_NAME = nil
- SEQUENCE = [
- STAGES::PanelIdsInserter
- ].freeze
-
class << self
def valid_params?(params)
matching_dashboard?(params[:dashboard_path])
@@ -52,10 +48,6 @@ module Metrics
load_yaml(yml)
end
-
- def sequence
- self.class::SEQUENCE
- end
end
end
end
diff --git a/app/services/metrics/dashboard/system_dashboard_service.rb b/app/services/metrics/dashboard/system_dashboard_service.rb
index 6086539e25f..e5508ef0b49 100644
--- a/app/services/metrics/dashboard/system_dashboard_service.rb
+++ b/app/services/metrics/dashboard/system_dashboard_service.rb
@@ -11,10 +11,6 @@ module Metrics
# SHA256 hash of dashboard content
DASHBOARD_VERSION = 'ce9ae27d2913f637de851d61099bc4151583eae68b1386a2176339ef6e653223'
- SEQUENCE = [
- STAGES::PanelIdsInserter
- ].freeze
-
class << self
def all_dashboard_paths(_project)
[{
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index 810a2ca3607..e2ddbb90213 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -26,7 +26,7 @@
= s_("UserProfile|Edit profile")
= render 'users/view_gpg_keys'
= render 'users/view_user_in_admin_area'
- .js-user-profile-actions{ data: { user_id: @user.id, rss_subscription_path: can?(current_user, :read_user_profile, @user) ? user_path(@user, rss_url_options) : '' } }
+ .js-user-profile-actions{ data: user_profile_actions_data(@user) }
- else
= render layout: 'users/cover_controls' do
- if @user == current_user
diff --git a/config/metrics/counts_28d/20210427102618_code_review_category_monthly_active_users.yml b/config/metrics/counts_28d/20210427102618_code_review_category_monthly_active_users.yml
index b8c5169e93a..8d1f00446d7 100644
--- a/config/metrics/counts_28d/20210427102618_code_review_category_monthly_active_users.yml
+++ b/config/metrics/counts_28d/20210427102618_code_review_category_monthly_active_users.yml
@@ -140,3 +140,4 @@ options:
- 'i_code_review_merge_request_widget_security_reports_expand_warning'
- 'i_code_review_merge_request_widget_security_reports_expand_failed'
- 'i_code_review_saved_replies_create'
+ - 'i_code_review_saved_replies_use'
diff --git a/config/metrics/counts_28d/20210427103119_code_review_group_monthly_active_users.yml b/config/metrics/counts_28d/20210427103119_code_review_group_monthly_active_users.yml
index 3ec0903e363..e19e0965e05 100644
--- a/config/metrics/counts_28d/20210427103119_code_review_group_monthly_active_users.yml
+++ b/config/metrics/counts_28d/20210427103119_code_review_group_monthly_active_users.yml
@@ -145,3 +145,4 @@ options:
- 'i_code_review_merge_request_widget_security_reports_expand_warning'
- 'i_code_review_merge_request_widget_security_reports_expand_failed'
- 'i_code_review_saved_replies_create'
+ - 'i_code_review_saved_replies_use'
diff --git a/config/metrics/counts_28d/20230725194658_i_code_review_saved_replies_use_monthly.yml b/config/metrics/counts_28d/20230725194658_i_code_review_saved_replies_use_monthly.yml
new file mode 100644
index 00000000000..5ccc10bb457
--- /dev/null
+++ b/config/metrics/counts_28d/20230725194658_i_code_review_saved_replies_use_monthly.yml
@@ -0,0 +1,25 @@
+---
+key_path: redis_hll_counters.code_review.i_code_review_saved_replies_use_monthly
+description: Number of unique users per month who use a saved reply
+product_section: dev
+product_stage: create
+product_group: code_review
+value_type: number
+status: active
+milestone: "16.3"
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/127442
+time_frame: 28d
+data_source: redis_hll
+data_category: optional
+instrumentation_class: RedisHLLMetric
+performance_indicator_type: []
+distribution:
+- ce
+- ee
+tier:
+- free
+- premium
+- ultimate
+options:
+ events:
+ - i_code_review_saved_replies_use
diff --git a/config/metrics/counts_7d/20210427103328_code_review_group_monthly_active_users.yml b/config/metrics/counts_7d/20210427103328_code_review_group_monthly_active_users.yml
index 6a0a14bdc26..94cf3bcc63c 100644
--- a/config/metrics/counts_7d/20210427103328_code_review_group_monthly_active_users.yml
+++ b/config/metrics/counts_7d/20210427103328_code_review_group_monthly_active_users.yml
@@ -143,3 +143,4 @@ options:
- 'i_code_review_merge_request_widget_security_reports_expand_warning'
- 'i_code_review_merge_request_widget_security_reports_expand_failed'
- 'i_code_review_saved_replies_create'
+ - 'i_code_review_saved_replies_use'
diff --git a/config/metrics/counts_7d/20210427103407_code_review_category_monthly_active_users.yml b/config/metrics/counts_7d/20210427103407_code_review_category_monthly_active_users.yml
index 18d69d8e2f7..a15273b156b 100644
--- a/config/metrics/counts_7d/20210427103407_code_review_category_monthly_active_users.yml
+++ b/config/metrics/counts_7d/20210427103407_code_review_category_monthly_active_users.yml
@@ -138,3 +138,4 @@ options:
- 'i_code_review_merge_request_widget_security_reports_expand_warning'
- 'i_code_review_merge_request_widget_security_reports_expand_failed'
- 'i_code_review_saved_replies_create'
+ - 'i_code_review_saved_replies_use'
diff --git a/config/metrics/counts_7d/20230725194657_i_code_review_saved_replies_use_weekly.yml b/config/metrics/counts_7d/20230725194657_i_code_review_saved_replies_use_weekly.yml
new file mode 100644
index 00000000000..6b2684eec2a
--- /dev/null
+++ b/config/metrics/counts_7d/20230725194657_i_code_review_saved_replies_use_weekly.yml
@@ -0,0 +1,25 @@
+---
+key_path: redis_hll_counters.code_review.i_code_review_saved_replies_use_weekly
+description: Number of unique users per week who use a saved reply
+product_section: dev
+product_stage: create
+product_group: code_review
+value_type: number
+status: active
+milestone: "16.3"
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/127442
+time_frame: 7d
+data_source: redis_hll
+data_category: optional
+instrumentation_class: RedisHLLMetric
+performance_indicator_type: []
+distribution:
+- ce
+- ee
+tier:
+- free
+- premium
+- ultimate
+options:
+ events:
+ - i_code_review_saved_replies_use
diff --git a/config/metrics/counts_all/20230725195335_i_code_review_saved_replies_count_use.yml b/config/metrics/counts_all/20230725195335_i_code_review_saved_replies_count_use.yml
new file mode 100644
index 00000000000..40599af67c1
--- /dev/null
+++ b/config/metrics/counts_all/20230725195335_i_code_review_saved_replies_count_use.yml
@@ -0,0 +1,25 @@
+---
+key_path: counts.i_code_review_saved_replies_count_use
+description: Total number of times a saved reply comment was used
+product_section: dev
+product_stage: create
+product_group: code_review
+value_type: number
+status: active
+milestone: "16.3"
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/127442
+time_frame: all
+data_source: redis
+data_category: optional
+instrumentation_class: RedisMetric
+performance_indicator_type: []
+distribution:
+- ce
+- ee
+tier:
+- free
+- premium
+- ultimate
+options:
+ event: use
+ prefix: i_code_review_saved_replies
diff --git a/doc/administration/settings/rate_limit_on_issues_creation.md b/doc/administration/settings/rate_limit_on_issues_creation.md
index d8bc04fcdd3..f7e7d05a0b5 100644
--- a/doc/administration/settings/rate_limit_on_issues_creation.md
+++ b/doc/administration/settings/rate_limit_on_issues_creation.md
@@ -5,12 +5,18 @@ group: Project Management
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
---
-# Rate limits on issue creation **(FREE SELF)**
+# Rate limits on issue and epic creation **(FREE SELF)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/28129) in GitLab 12.10.
-This setting allows you to rate limit the requests to the issue and epic creation endpoints.
-To can change its value:
+Rate limits control the pace at which new epics and issues can be created.
+For example, if you set the limit to `300`, the
+[Projects::IssuesController#create](https://gitlab.com/gitlab-org/gitlab/blob/master/app/controllers/projects/issues_controller.rb)
+action blocks requests that exceed a rate of 300 per minute. Access to the endpoint is available after one minute.
+
+## Set the rate limit
+
+To limit the number of requests made to the issue and epic creation endpoints:
1. On the left sidebar, expand the top-most chevron (**{chevron-down}**).
1. Select **Admin Area**.
@@ -19,18 +25,12 @@ To can change its value:
1. Under **Max requests per minute**, enter the new value.
1. Select **Save changes**.
-For example, if you set a limit of 300, requests using the
-[Projects::IssuesController#create](https://gitlab.com/gitlab-org/gitlab/blob/master/app/controllers/projects/issues_controller.rb)
-action exceeding a rate of 300 per minute are blocked. Access to the endpoint is allowed after one minute.
-
-When using [epics](../../user/group/epics/index.md), epic creation shares this rate limit with issues.
-
![Rate limits on issues creation](img/rate_limit_on_issues_creation_v14_2.png)
-This limit is:
+The limit for [epic](../../user/group/epics/index.md) creation is the same limit applied to issue creation. The rate limit:
-- Applied independently per project and per user.
-- Not applied per IP address.
-- Disabled by default. To enable it, set the option to any value other than `0`.
+- Is applied independently per project and per user.
+- Is not applied per IP address.
+- Can be set to `0` to disable the rate limit.
Requests over the rate limit are logged into the `auth.log` file.
diff --git a/lib/gitlab/metrics/dashboard/stages/custom_dashboard_metrics_inserter.rb b/lib/gitlab/metrics/dashboard/stages/custom_dashboard_metrics_inserter.rb
deleted file mode 100644
index 5ed4466f440..00000000000
--- a/lib/gitlab/metrics/dashboard/stages/custom_dashboard_metrics_inserter.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Metrics
- module Dashboard
- module Stages
- # Acts on metrics which have been ingested from source controlled dashboards
- class CustomDashboardMetricsInserter < BaseStage
- # For each metric in the dashboard config, attempts to
- # find a corresponding database record. If found, includes
- # the record's id in the dashboard config.
- def transform!
- database_metrics = ::PrometheusMetricsFinder.new(common: false, group: :custom, project: project).execute
-
- for_metrics do |metric|
- metric_record = database_metrics.find { |m| m.identifier == metric[:id] }
- metric[:metric_id] = metric_record.id if metric_record
- end
- end
- end
- end
- end
- end
-end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 3ef465686d5..6179270fd0a 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -7351,7 +7351,7 @@ msgstr ""
msgid "BetaBadge|Is supported by a commercially reasonable effort."
msgstr ""
-msgid "BetaBadge|May have performance or stability issues."
+msgid "BetaBadge|May be unstable."
msgstr ""
msgid "BetaBadge|Should not cause data loss."
@@ -32794,6 +32794,9 @@ msgstr ""
msgid "Organizations"
msgstr ""
+msgid "Organization|An error occurred loading the groups. Please refresh the page to try again."
+msgstr ""
+
msgid "Organization|An error occurred loading the projects. Please refresh the page to try again."
msgstr ""
diff --git a/qa/qa/resource/api_fabricator.rb b/qa/qa/resource/api_fabricator.rb
index 3f9d2b92a0a..5f431103df3 100644
--- a/qa/qa/resource/api_fabricator.rb
+++ b/qa/qa/resource/api_fabricator.rb
@@ -26,7 +26,10 @@ module QA
raise NotImplementedError, "Resource #{self.class.name} does not support fabrication via the API!"
end
- resource_web_url(api_post)
+ resource_web_url = resource_web_url(api_post)
+ wait_for_resource_availability(resource_web_url)
+
+ resource_web_url
end
def reload!
@@ -222,6 +225,22 @@ module QA
v.is_a?(Hash) ? a.merge(flatten_hash(v)) : a.merge(k.to_sym => v)
end
end
+
+ # Given a URL, wait for the given URL to return 200
+ # @param [String] resource_web_url the URL to check
+ # @example
+ # wait_for_resource_availability('https://gitlab.com/api/v4/projects/1234')
+ # @example
+ # wait_for_resource_availability(resource_web_url(Resource::Issue.fabricate_via_api!))
+ def wait_for_resource_availability(resource_web_url)
+ return unless Runtime::Address.valid?(resource_web_url)
+
+ Support::Retrier.retry_until(sleep_interval: 3, max_attempts: 5, raise_on_failure: false) do
+ response_check = get(resource_web_url)
+ Runtime::Logger.debug("Resource availability check ... #{response_check.code}")
+ response_check.code == HTTP_STATUS_OK
+ end
+ end
end
end
end
diff --git a/spec/features/abuse_report_spec.rb b/spec/features/abuse_report_spec.rb
index ae3859280b1..0312efeb9ce 100644
--- a/spec/features/abuse_report_spec.rb
+++ b/spec/features/abuse_report_spec.rb
@@ -13,10 +13,19 @@ RSpec.describe 'Abuse reports', :js, feature_category: :insider_threat do
before do
sign_in(reporter1)
stub_feature_flags(moved_mr_sidebar: false)
- stub_feature_flags(user_profile_overflow_menu_vue: false)
end
describe 'report abuse to administrator' do
+ shared_examples 'cancel report' do
+ it 'redirects backs to user profile when cancel button is clicked' do
+ fill_and_submit_abuse_category_form
+
+ click_link 'Cancel'
+
+ expect(page).to have_current_path(user_path(abusive_user))
+ end
+ end
+
context 'when reporting an issue for abuse' do
before do
visit project_issue_path(project, issue)
@@ -46,54 +55,102 @@ RSpec.describe 'Abuse reports', :js, feature_category: :insider_threat do
it_behaves_like 'reports the user with an abuse category'
end
- context 'when reporting a user profile for abuse' do
- let_it_be(:reporter2) { create(:user) }
+ describe 'when user_profile_overflow_menu FF turned on' do
+ context 'when reporting a user profile for abuse' do
+ let_it_be(:reporter2) { create(:user) }
- before do
- visit user_path(abusive_user)
- end
+ before do
+ visit user_path(abusive_user)
+ find('[data-testid="base-dropdown-toggle"').click
+ end
- it_behaves_like 'reports the user with an abuse category'
+ it_behaves_like 'reports the user with an abuse category'
- it 'allows the reporter to report the same user for different abuse categories' do
- visit user_path(abusive_user)
+ it 'allows the reporter to report the same user for different abuse categories' do
+ visit user_path(abusive_user)
- fill_and_submit_abuse_category_form
- fill_and_submit_report_abuse_form
+ find('[data-testid="base-dropdown-toggle"').click
+ fill_and_submit_abuse_category_form
+ fill_and_submit_report_abuse_form
- expect(page).to have_content 'Thank you for your report'
+ expect(page).to have_content 'Thank you for your report'
- visit user_path(abusive_user)
+ visit user_path(abusive_user)
- fill_and_submit_abuse_category_form("They're being offensive or abusive.")
- fill_and_submit_report_abuse_form
+ find('[data-testid="base-dropdown-toggle"').click
+ fill_and_submit_abuse_category_form("They're being offensive or abusive.")
+ fill_and_submit_report_abuse_form
- expect(page).to have_content 'Thank you for your report'
- end
+ expect(page).to have_content 'Thank you for your report'
+ end
- it 'allows multiple users to report the same user' do
- fill_and_submit_abuse_category_form
- fill_and_submit_report_abuse_form
+ it 'allows multiple users to report the same user' do
+ fill_and_submit_abuse_category_form
+ fill_and_submit_report_abuse_form
- expect(page).to have_content 'Thank you for your report'
+ expect(page).to have_content 'Thank you for your report'
- gitlab_sign_out
- gitlab_sign_in(reporter2)
+ gitlab_sign_out
+ gitlab_sign_in(reporter2)
- visit user_path(abusive_user)
+ visit user_path(abusive_user)
- fill_and_submit_abuse_category_form
- fill_and_submit_report_abuse_form
+ find('[data-testid="base-dropdown-toggle"').click
+ fill_and_submit_abuse_category_form
+ fill_and_submit_report_abuse_form
- expect(page).to have_content 'Thank you for your report'
+ expect(page).to have_content 'Thank you for your report'
+ end
+
+ it_behaves_like 'cancel report'
end
+ end
- it 'redirects backs to user profile when cancel button is clicked' do
- fill_and_submit_abuse_category_form
+ describe 'when user_profile_overflow_menu FF turned off' do
+ context 'when reporting a user profile for abuse' do
+ let_it_be(:reporter2) { create(:user) }
- click_link 'Cancel'
+ before do
+ stub_feature_flags(user_profile_overflow_menu_vue: false)
+ visit user_path(abusive_user)
+ end
- expect(page).to have_current_path(user_path(abusive_user))
+ it_behaves_like 'reports the user with an abuse category'
+
+ it 'allows the reporter to report the same user for different abuse categories' do
+ visit user_path(abusive_user)
+
+ fill_and_submit_abuse_category_form
+ fill_and_submit_report_abuse_form
+
+ expect(page).to have_content 'Thank you for your report'
+
+ visit user_path(abusive_user)
+
+ fill_and_submit_abuse_category_form("They're being offensive or abusive.")
+ fill_and_submit_report_abuse_form
+
+ expect(page).to have_content 'Thank you for your report'
+ end
+
+ it 'allows multiple users to report the same user' do
+ fill_and_submit_abuse_category_form
+ fill_and_submit_report_abuse_form
+
+ expect(page).to have_content 'Thank you for your report'
+
+ gitlab_sign_out
+ gitlab_sign_in(reporter2)
+
+ visit user_path(abusive_user)
+
+ fill_and_submit_abuse_category_form
+ fill_and_submit_report_abuse_form
+
+ expect(page).to have_content 'Thank you for your report'
+ end
+
+ it_behaves_like 'cancel report'
end
end
diff --git a/spec/frontend/organizations/groups_and_projects/components/groups_page_spec.js b/spec/frontend/organizations/groups_and_projects/components/groups_page_spec.js
new file mode 100644
index 00000000000..537f8114fcf
--- /dev/null
+++ b/spec/frontend/organizations/groups_and_projects/components/groups_page_spec.js
@@ -0,0 +1,88 @@
+import VueApollo from 'vue-apollo';
+import Vue from 'vue';
+import { GlLoadingIcon } from '@gitlab/ui';
+import GroupsPage from '~/organizations/groups_and_projects/components/groups_page.vue';
+import { formatGroups } from '~/organizations/groups_and_projects/utils';
+import resolvers from '~/organizations/groups_and_projects/graphql/resolvers';
+import GroupsList from '~/vue_shared/components/groups_list/groups_list.vue';
+import { createAlert } from '~/alert';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import { organizationGroups } from '../mock_data';
+
+jest.mock('~/alert');
+
+Vue.use(VueApollo);
+jest.useFakeTimers();
+
+describe('GroupsPage', () => {
+ let wrapper;
+ let mockApollo;
+
+ const createComponent = ({ mockResolvers = resolvers } = {}) => {
+ mockApollo = createMockApollo([], mockResolvers);
+
+ wrapper = shallowMountExtended(GroupsPage, { apolloProvider: mockApollo });
+ };
+
+ afterEach(() => {
+ mockApollo = null;
+ });
+
+ describe('when API call is loading', () => {
+ beforeEach(() => {
+ const mockResolvers = {
+ Query: {
+ organization: jest.fn().mockReturnValueOnce(new Promise(() => {})),
+ },
+ };
+
+ createComponent({ mockResolvers });
+ });
+
+ it('renders loading icon', () => {
+ expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true);
+ });
+ });
+
+ describe('when API call is successful', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('renders `GroupsList` component and passes correct props', async () => {
+ jest.runAllTimers();
+ await waitForPromises();
+
+ expect(wrapper.findComponent(GroupsList).props()).toEqual({
+ groups: formatGroups(organizationGroups.nodes),
+ showGroupIcon: true,
+ });
+ });
+ });
+
+ describe('when API call is not successful', () => {
+ const error = new Error();
+
+ beforeEach(() => {
+ const mockResolvers = {
+ Query: {
+ organization: jest.fn().mockRejectedValueOnce(error),
+ },
+ };
+
+ createComponent({ mockResolvers });
+ });
+
+ it('displays error alert', async () => {
+ await waitForPromises();
+
+ expect(createAlert).toHaveBeenCalledWith({
+ message: GroupsPage.i18n.errorMessage,
+ error,
+ captureError: true,
+ });
+ });
+ });
+});
diff --git a/spec/frontend/organizations/groups_and_projects/components/projects_page_spec.js b/spec/frontend/organizations/groups_and_projects/components/projects_page_spec.js
index 07f9f0da7c7..7cadcab5021 100644
--- a/spec/frontend/organizations/groups_and_projects/components/projects_page_spec.js
+++ b/spec/frontend/organizations/groups_and_projects/components/projects_page_spec.js
@@ -56,7 +56,7 @@ describe('ProjectsPage', () => {
await waitForPromises();
expect(wrapper.findComponent(ProjectsList).props()).toEqual({
- projects: formatProjects(organizationProjects.projects.nodes),
+ projects: formatProjects(organizationProjects.nodes),
showProjectIcon: true,
});
});
diff --git a/spec/frontend/organizations/groups_and_projects/mock_data.js b/spec/frontend/organizations/groups_and_projects/mock_data.js
index c3276450745..b1f91e2f42f 100644
--- a/spec/frontend/organizations/groups_and_projects/mock_data.js
+++ b/spec/frontend/organizations/groups_and_projects/mock_data.js
@@ -1,98 +1,247 @@
-export const organizationProjects = {
+export const organization = {
id: 'gid://gitlab/Organization/1',
__typename: 'Organization',
- projects: {
- nodes: [
- {
- id: 'gid://gitlab/Project/8',
- nameWithNamespace: 'Twitter / Typeahead.Js',
- webUrl: 'http://127.0.0.1:3000/twitter/Typeahead.Js',
- topics: ['JavaScript', 'Vue.js'],
- forksCount: 4,
- avatarUrl: null,
- starCount: 0,
- visibility: 'public',
- openIssuesCount: 48,
- descriptionHtml:
- '<p data-sourcepos="1:1-1:59" dir="auto">Optio et reprehenderit enim doloremque deserunt et commodi.</p>',
- issuesAccessLevel: 'enabled',
- forkingAccessLevel: 'enabled',
- accessLevel: {
- integerValue: 30,
- },
+};
+
+export const organizationProjects = {
+ nodes: [
+ {
+ id: 'gid://gitlab/Project/8',
+ nameWithNamespace: 'Twitter / Typeahead.Js',
+ webUrl: 'http://127.0.0.1:3000/twitter/Typeahead.Js',
+ topics: ['JavaScript', 'Vue.js'],
+ forksCount: 4,
+ avatarUrl: null,
+ starCount: 0,
+ visibility: 'public',
+ openIssuesCount: 48,
+ descriptionHtml:
+ '<p data-sourcepos="1:1-1:59" dir="auto">Optio et reprehenderit enim doloremque deserunt et commodi.</p>',
+ issuesAccessLevel: 'enabled',
+ forkingAccessLevel: 'enabled',
+ accessLevel: {
+ integerValue: 30,
+ },
+ },
+ {
+ id: 'gid://gitlab/Project/7',
+ nameWithNamespace: 'Flightjs / Flight',
+ webUrl: 'http://127.0.0.1:3000/flightjs/Flight',
+ topics: [],
+ forksCount: 0,
+ avatarUrl: null,
+ starCount: 0,
+ visibility: 'private',
+ openIssuesCount: 37,
+ descriptionHtml:
+ '<p data-sourcepos="1:1-1:49" dir="auto">Dolor dicta rerum et ut eius voluptate earum qui.</p>',
+ issuesAccessLevel: 'enabled',
+ forkingAccessLevel: 'enabled',
+ accessLevel: {
+ integerValue: 20,
+ },
+ },
+ {
+ id: 'gid://gitlab/Project/6',
+ nameWithNamespace: 'Jashkenas / Underscore',
+ webUrl: 'http://127.0.0.1:3000/jashkenas/Underscore',
+ topics: [],
+ forksCount: 0,
+ avatarUrl: null,
+ starCount: 0,
+ visibility: 'private',
+ openIssuesCount: 34,
+ descriptionHtml:
+ '<p data-sourcepos="1:1-1:52" dir="auto">Incidunt est aliquam autem nihil eveniet quis autem.</p>',
+ issuesAccessLevel: 'enabled',
+ forkingAccessLevel: 'enabled',
+ accessLevel: {
+ integerValue: 40,
+ },
+ },
+ {
+ id: 'gid://gitlab/Project/5',
+ nameWithNamespace: 'Commit451 / Lab Coat',
+ webUrl: 'http://127.0.0.1:3000/Commit451/lab-coat',
+ topics: [],
+ forksCount: 0,
+ avatarUrl: null,
+ starCount: 0,
+ visibility: 'internal',
+ openIssuesCount: 49,
+ descriptionHtml:
+ '<p data-sourcepos="1:1-1:34" dir="auto">Sint eos dolorem impedit rerum et.</p>',
+ issuesAccessLevel: 'enabled',
+ forkingAccessLevel: 'enabled',
+ accessLevel: {
+ integerValue: 10,
+ },
+ },
+ {
+ id: 'gid://gitlab/Project/1',
+ nameWithNamespace: 'Toolbox / Gitlab Smoke Tests',
+ webUrl: 'http://127.0.0.1:3000/toolbox/gitlab-smoke-tests',
+ topics: [],
+ forksCount: 0,
+ avatarUrl: null,
+ starCount: 0,
+ visibility: 'internal',
+ openIssuesCount: 34,
+ descriptionHtml:
+ '<p data-sourcepos="1:1-1:40" dir="auto">Veritatis error laboriosam libero autem.</p>',
+ issuesAccessLevel: 'enabled',
+ forkingAccessLevel: 'enabled',
+ accessLevel: {
+ integerValue: 30,
+ },
+ },
+ ],
+};
+
+export const organizationGroups = {
+ nodes: [
+ {
+ id: 'gid://gitlab/Group/29',
+ fullName: 'Commit451',
+ parent: null,
+ webUrl: 'http://127.0.0.1:3000/groups/Commit451',
+ descriptionHtml:
+ '<p data-sourcepos="1:1-1:52" dir="auto">Autem praesentium vel ut ratione itaque ullam culpa.</p>',
+ avatarUrl: null,
+ descendantGroupsCount: 0,
+ projectsCount: 3,
+ groupMembersCount: 2,
+ visibility: 'public',
+ accessLevel: {
+ integerValue: 30,
+ },
+ },
+ {
+ id: 'gid://gitlab/Group/33',
+ fullName: 'Flightjs',
+ parent: null,
+ webUrl: 'http://127.0.0.1:3000/groups/flightjs',
+ descriptionHtml:
+ '<p data-sourcepos="1:1-1:60" dir="auto">Ipsa reiciendis deleniti officiis illum nostrum quo aliquam.</p>',
+ avatarUrl: null,
+ descendantGroupsCount: 4,
+ projectsCount: 3,
+ groupMembersCount: 1,
+ visibility: 'private',
+ accessLevel: {
+ integerValue: 20,
+ },
+ },
+ {
+ id: 'gid://gitlab/Group/24',
+ fullName: 'Gitlab Org',
+ parent: null,
+ webUrl: 'http://127.0.0.1:3000/groups/gitlab-org',
+ descriptionHtml:
+ '<p data-sourcepos="1:1-1:64" dir="auto">Dolorem dolorem omnis impedit cupiditate pariatur officia velit.</p>',
+ avatarUrl: null,
+ descendantGroupsCount: 1,
+ projectsCount: 1,
+ groupMembersCount: 2,
+ visibility: 'internal',
+ accessLevel: {
+ integerValue: 10,
+ },
+ },
+ {
+ id: 'gid://gitlab/Group/27',
+ fullName: 'Gnuwget',
+ parent: null,
+ webUrl: 'http://127.0.0.1:3000/groups/gnuwgetf',
+ descriptionHtml:
+ '<p data-sourcepos="1:1-1:47" dir="auto">Culpa soluta aut eius dolores est vel sapiente.</p>',
+ avatarUrl: null,
+ descendantGroupsCount: 4,
+ projectsCount: 2,
+ groupMembersCount: 3,
+ visibility: 'public',
+ accessLevel: {
+ integerValue: 40,
+ },
+ },
+ {
+ id: 'gid://gitlab/Group/31',
+ fullName: 'Jashkenas',
+ parent: null,
+ webUrl: 'http://127.0.0.1:3000/groups/jashkenas',
+ descriptionHtml: '<p data-sourcepos="1:1-1:25" dir="auto">Ut ut id aliquid nostrum.</p>',
+ avatarUrl: null,
+ descendantGroupsCount: 3,
+ projectsCount: 3,
+ groupMembersCount: 10,
+ visibility: 'private',
+ accessLevel: {
+ integerValue: 10,
+ },
+ },
+ {
+ id: 'gid://gitlab/Group/22',
+ fullName: 'Toolbox',
+ parent: null,
+ webUrl: 'http://127.0.0.1:3000/groups/toolbox',
+ descriptionHtml:
+ '<p data-sourcepos="1:1-1:46" dir="auto">Quo voluptatem magnam facere voluptates alias.</p>',
+ avatarUrl: null,
+ descendantGroupsCount: 2,
+ projectsCount: 3,
+ groupMembersCount: 40,
+ visibility: 'internal',
+ accessLevel: {
+ integerValue: 30,
},
- {
- id: 'gid://gitlab/Project/7',
- nameWithNamespace: 'Flightjs / Flight',
- webUrl: 'http://127.0.0.1:3000/flightjs/Flight',
- topics: [],
- forksCount: 0,
- avatarUrl: null,
- starCount: 0,
- visibility: 'private',
- openIssuesCount: 37,
- descriptionHtml:
- '<p data-sourcepos="1:1-1:49" dir="auto">Dolor dicta rerum et ut eius voluptate earum qui.</p>',
- issuesAccessLevel: 'enabled',
- forkingAccessLevel: 'enabled',
- accessLevel: {
- integerValue: 20,
- },
+ },
+ {
+ id: 'gid://gitlab/Group/35',
+ fullName: 'Twitter',
+ parent: null,
+ webUrl: 'http://127.0.0.1:3000/groups/twitter',
+ descriptionHtml:
+ '<p data-sourcepos="1:1-1:40" dir="auto">Quae nulla consequatur assumenda id quo.</p>',
+ avatarUrl: null,
+ descendantGroupsCount: 20,
+ projectsCount: 30,
+ groupMembersCount: 100,
+ visibility: 'public',
+ accessLevel: {
+ integerValue: 40,
},
- {
- id: 'gid://gitlab/Project/6',
- nameWithNamespace: 'Jashkenas / Underscore',
- webUrl: 'http://127.0.0.1:3000/jashkenas/Underscore',
- topics: [],
- forksCount: 0,
- avatarUrl: null,
- starCount: 0,
- visibility: 'private',
- openIssuesCount: 34,
- descriptionHtml:
- '<p data-sourcepos="1:1-1:52" dir="auto">Incidunt est aliquam autem nihil eveniet quis autem.</p>',
- issuesAccessLevel: 'enabled',
- forkingAccessLevel: 'enabled',
- accessLevel: {
- integerValue: 40,
- },
+ },
+ {
+ id: 'gid://gitlab/Group/73',
+ fullName: 'test',
+ parent: null,
+ webUrl: 'http://127.0.0.1:3000/groups/test',
+ descriptionHtml: '',
+ avatarUrl: null,
+ descendantGroupsCount: 1,
+ projectsCount: 1,
+ groupMembersCount: 1,
+ visibility: 'private',
+ accessLevel: {
+ integerValue: 30,
},
- {
- id: 'gid://gitlab/Project/5',
- nameWithNamespace: 'Commit451 / Lab Coat',
- webUrl: 'http://127.0.0.1:3000/Commit451/lab-coat',
- topics: [],
- forksCount: 0,
- avatarUrl: null,
- starCount: 0,
- visibility: 'internal',
- openIssuesCount: 49,
- descriptionHtml:
- '<p data-sourcepos="1:1-1:34" dir="auto">Sint eos dolorem impedit rerum et.</p>',
- issuesAccessLevel: 'enabled',
- forkingAccessLevel: 'enabled',
- accessLevel: {
- integerValue: 10,
- },
+ },
+ {
+ id: 'gid://gitlab/Group/74',
+ fullName: 'Twitter / test subgroup',
+ parent: {
+ id: 'gid://gitlab/Group/35',
},
- {
- id: 'gid://gitlab/Project/1',
- nameWithNamespace: 'Toolbox / Gitlab Smoke Tests',
- webUrl: 'http://127.0.0.1:3000/toolbox/gitlab-smoke-tests',
- topics: [],
- forksCount: 0,
- avatarUrl: null,
- starCount: 0,
- visibility: 'internal',
- openIssuesCount: 34,
- descriptionHtml:
- '<p data-sourcepos="1:1-1:40" dir="auto">Veritatis error laboriosam libero autem.</p>',
- issuesAccessLevel: 'enabled',
- forkingAccessLevel: 'enabled',
- accessLevel: {
- integerValue: 30,
- },
+ webUrl: 'http://127.0.0.1:3000/groups/twitter/test-subgroup',
+ descriptionHtml: '',
+ avatarUrl: null,
+ descendantGroupsCount: 4,
+ projectsCount: 4,
+ groupMembersCount: 4,
+ visibility: 'internal',
+ accessLevel: {
+ integerValue: 20,
},
- ],
- },
+ },
+ ],
};
diff --git a/spec/frontend/organizations/groups_and_projects/utils_spec.js b/spec/frontend/organizations/groups_and_projects/utils_spec.js
index 5aae26802ac..fe745116f33 100644
--- a/spec/frontend/organizations/groups_and_projects/utils_spec.js
+++ b/spec/frontend/organizations/groups_and_projects/utils_spec.js
@@ -1,11 +1,11 @@
-import { formatProjects } from '~/organizations/groups_and_projects/utils';
+import { formatProjects, formatGroups } from '~/organizations/groups_and_projects/utils';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
-import { organizationProjects } from './mock_data';
+import { organizationProjects, organizationGroups } from './mock_data';
describe('formatProjects', () => {
it('correctly formats the projects', () => {
- const [firstMockProject] = organizationProjects.projects.nodes;
- const formattedProjects = formatProjects(organizationProjects.projects.nodes);
+ const [firstMockProject] = organizationProjects.nodes;
+ const formattedProjects = formatProjects(organizationProjects.nodes);
const [firstFormattedProject] = formattedProjects;
expect(firstFormattedProject).toMatchObject({
@@ -17,6 +17,17 @@ describe('formatProjects', () => {
},
},
});
- expect(formattedProjects.length).toBe(organizationProjects.projects.nodes.length);
+ expect(formattedProjects.length).toBe(organizationProjects.nodes.length);
+ });
+});
+
+describe('formatGroups', () => {
+ it('correctly formats the groups', () => {
+ const [firstMockGroup] = organizationGroups.nodes;
+ const formattedGroups = formatGroups(organizationGroups.nodes);
+ const [firstFormattedGroup] = formattedGroups;
+
+ expect(firstFormattedGroup.id).toBe(getIdFromGraphQLId(firstMockGroup.id));
+ expect(formattedGroups.length).toBe(organizationGroups.nodes.length);
});
});
diff --git a/spec/frontend/users/profile/actions/components/user_actions_app_spec.js b/spec/frontend/users/profile/actions/components/user_actions_app_spec.js
index c2ae3d8364f..a33474375e6 100644
--- a/spec/frontend/users/profile/actions/components/user_actions_app_spec.js
+++ b/spec/frontend/users/profile/actions/components/user_actions_app_spec.js
@@ -1,6 +1,7 @@
import { GlDisclosureDropdown } from '@gitlab/ui';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import UserActionsApp from '~/users/profile/actions/components/user_actions_app.vue';
+import AbuseCategorySelector from '~/abuse_reports/components/abuse_category_selector.vue';
describe('User Actions App', () => {
let wrapper;
@@ -40,6 +41,15 @@ describe('User Actions App', () => {
});
expect(findActions()).toHaveLength(2);
});
+
+ it('should show items with report abuse', () => {
+ createWrapper({
+ rssSubscriptionPath: '/test/path',
+ reportedUserId: 1,
+ reportedFromUrl: '/report/path',
+ });
+ expect(findActions()).toHaveLength(3);
+ });
});
it('shows copy user id action', () => {
@@ -60,4 +70,21 @@ describe('User Actions App', () => {
expect(rssLink.attributes('href')).toBe(testSubscriptionPath);
expect(rssLink.text()).toBe('Subscribe');
});
+
+ it('shows report abuse action when reported user id was presented', () => {
+ const reportUrl = '/path/to/report';
+ const reportUserId = 1;
+ createWrapper({
+ rssSubscriptionPath: '/test/path',
+ reportedUserId: reportUserId,
+ reportedFromUrl: reportUrl,
+ });
+ const abuseCategorySelector = wrapper.findComponent(AbuseCategorySelector);
+ expect(abuseCategorySelector.exists()).toBe(true);
+ expect(abuseCategorySelector.props()).toEqual({
+ reportedUserId: reportUserId,
+ reportedFromUrl: reportUrl,
+ showDrawer: false,
+ });
+ });
});
diff --git a/spec/frontend/vue_shared/components/badges/__snapshots__/beta_badge_spec.js.snap b/spec/frontend/vue_shared/components/badges/__snapshots__/beta_badge_spec.js.snap
index 5c49ea1b9f4..24b2c54f20b 100644
--- a/spec/frontend/vue_shared/components/badges/__snapshots__/beta_badge_spec.js.snap
+++ b/spec/frontend/vue_shared/components/badges/__snapshots__/beta_badge_spec.js.snap
@@ -4,9 +4,10 @@ exports[`Beta badge component renders the badge 1`] = `
<div>
<gl-badge-stub
class="gl-cursor-pointer"
+ href="#"
iconsize="md"
size="md"
- variant="muted"
+ variant="neutral"
>
Beta
</gl-badge-stub>
@@ -33,7 +34,7 @@ exports[`Beta badge component renders the badge 1`] = `
class="gl-pl-4"
>
<li>
- May have performance or stability issues.
+ May be unstable.
</li>
<li>
diff --git a/spec/frontend/vue_shared/components/badges/beta_badge_spec.js b/spec/frontend/vue_shared/components/badges/beta_badge_spec.js
index 6109fad9310..c930c6d5708 100644
--- a/spec/frontend/vue_shared/components/badges/beta_badge_spec.js
+++ b/spec/frontend/vue_shared/components/badges/beta_badge_spec.js
@@ -1,12 +1,32 @@
import { shallowMount } from '@vue/test-utils';
+import { GlBadge } from '@gitlab/ui';
import BetaBadge from '~/vue_shared/components/badges/beta_badge.vue';
describe('Beta badge component', () => {
let wrapper;
+ const findBadge = () => wrapper.findComponent(GlBadge);
+ const createWrapper = (props = {}) => {
+ wrapper = shallowMount(BetaBadge, {
+ propsData: { ...props },
+ });
+ };
+
it('renders the badge', () => {
- wrapper = shallowMount(BetaBadge);
+ createWrapper();
expect(wrapper.element).toMatchSnapshot();
});
+
+ it('passes default size to badge', () => {
+ createWrapper();
+
+ expect(findBadge().props('size')).toBe('md');
+ });
+
+ it('passes given size to badge', () => {
+ createWrapper({ size: 'sm' });
+
+ expect(findBadge().props('size')).toBe('sm');
+ });
});
diff --git a/spec/frontend/vue_shared/components/markdown/comment_templates_dropdown_spec.js b/spec/frontend/vue_shared/components/markdown/comment_templates_dropdown_spec.js
index 2bef6dd15df..e4689a84900 100644
--- a/spec/frontend/vue_shared/components/markdown/comment_templates_dropdown_spec.js
+++ b/spec/frontend/vue_shared/components/markdown/comment_templates_dropdown_spec.js
@@ -1,6 +1,8 @@
+import { GlCollapsibleListbox } from '@gitlab/ui';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import savedRepliesResponse from 'test_fixtures/graphql/comment_templates/saved_replies.query.graphql.json';
+import { mockTracking } from 'helpers/tracking_helper';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
@@ -31,6 +33,10 @@ function createComponent(options = {}) {
});
}
+function findDropdownComponent() {
+ return wrapper.findComponent(GlCollapsibleListbox);
+}
+
describe('Comment templates dropdown', () => {
it('fetches data when dropdown gets opened', async () => {
const mockApollo = createMockApolloProvider(savedRepliesResponse);
@@ -43,16 +49,42 @@ describe('Comment templates dropdown', () => {
expect(savedRepliesResp).toHaveBeenCalled();
});
- it('adds emits a select event on selecting a comment', async () => {
- const mockApollo = createMockApolloProvider(savedRepliesResponse);
- wrapper = createComponent({ mockApollo });
+ describe('when selecting a comment', () => {
+ let trackingSpy;
+ let mockApollo;
- wrapper.find('.js-comment-template-toggle').trigger('click');
+ beforeEach(() => {
+ trackingSpy = mockTracking(undefined, window.document, jest.spyOn);
+ mockApollo = createMockApolloProvider(savedRepliesResponse);
+ wrapper = createComponent({ mockApollo });
+ });
- await waitForPromises();
+ it('emits a select event', async () => {
+ wrapper.find('.js-comment-template-toggle').trigger('click');
+
+ await waitForPromises();
+
+ wrapper.find('.gl-new-dropdown-item').trigger('click');
+
+ expect(wrapper.emitted().select[0]).toEqual(['Saved Reply Content']);
+ });
+
+ it('tracks the usage of the saved comment', async () => {
+ const dropdown = findDropdownComponent();
+
+ dropdown.vm.$emit('shown');
+
+ await waitForPromises();
+
+ dropdown.vm.$emit('select', savedRepliesResponse.data.currentUser.savedReplies.nodes[0].id);
- wrapper.find('.gl-new-dropdown-item').trigger('click');
+ await waitForPromises();
- expect(wrapper.emitted().select[0]).toEqual(['Saved Reply Content']);
+ expect(trackingSpy).toHaveBeenCalledWith(
+ expect.any(String),
+ 'i_code_review_saved_replies_use',
+ expect.any(Object),
+ );
+ });
});
});
diff --git a/spec/helpers/users_helper_spec.rb b/spec/helpers/users_helper_spec.rb
index 6745dba2f3c..ad8aef276bb 100644
--- a/spec/helpers/users_helper_spec.rb
+++ b/spec/helpers/users_helper_spec.rb
@@ -682,4 +682,58 @@ RSpec.describe UsersHelper do
it { is_expected.to eq('Active') }
end
end
+
+ describe '#user_profile_actions_data' do
+ let(:user_1) { create(:user) }
+ let(:user_2) { create(:user) }
+ let(:user_path) { '/users/root' }
+
+ subject { helper.user_profile_actions_data(user_1) }
+
+ before do
+ allow(helper).to receive(:user_path).and_return(user_path)
+ allow(helper).to receive(:user_url).and_return(user_path)
+ end
+
+ shared_examples 'user cannot report' do
+ it 'returns data without reporting related data' do
+ is_expected.to match({
+ user_id: user_1.id,
+ rss_subscription_path: user_path
+ })
+ end
+ end
+
+ context 'user is current user' do
+ before do
+ allow(helper).to receive(:current_user).and_return(user_1)
+ end
+
+ it_behaves_like 'user cannot report'
+ end
+
+ context 'user is not current user' do
+ before do
+ allow(helper).to receive(:current_user).and_return(user_2)
+ end
+
+ it 'returns data for reporting related data' do
+ is_expected.to match({
+ user_id: user_1.id,
+ rss_subscription_path: user_path,
+ report_abuse_path: add_category_abuse_reports_path,
+ reported_user_id: user_1.id,
+ reported_from_url: user_path
+ })
+ end
+ end
+
+ context 'when logged out' do
+ before do
+ allow(helper).to receive(:current_user).and_return(nil)
+ end
+
+ it_behaves_like 'user cannot report'
+ end
+ end
end
diff --git a/spec/lib/gitlab/metrics/dashboard/processor_spec.rb b/spec/lib/gitlab/metrics/dashboard/processor_spec.rb
index ae884c58f86..11b587e4905 100644
--- a/spec/lib/gitlab/metrics/dashboard/processor_spec.rb
+++ b/spec/lib/gitlab/metrics/dashboard/processor_spec.rb
@@ -12,7 +12,6 @@ RSpec.describe Gitlab::Metrics::Dashboard::Processor do
describe 'process' do
let(:sequence) do
[
- Gitlab::Metrics::Dashboard::Stages::PanelIdsInserter,
Gitlab::Metrics::Dashboard::Stages::UrlValidator
]
end
@@ -20,12 +19,6 @@ RSpec.describe Gitlab::Metrics::Dashboard::Processor do
let(:process_params) { [project, dashboard_yml, sequence, { environment: environment }] }
let(:dashboard) { described_class.new(*process_params).process }
- it 'includes an id for each dashboard panel' do
- expect(all_panels).to satisfy_all do |panel|
- panel[:id].present?
- end
- end
-
context 'when the dashboard is not present' do
let(:dashboard_yml) { nil }
@@ -33,69 +26,5 @@ RSpec.describe Gitlab::Metrics::Dashboard::Processor do
expect(dashboard).to be_nil
end
end
-
- shared_examples_for 'errors with message' do |expected_message|
- it 'raises a DashboardLayoutError' do
- error_class = Gitlab::Metrics::Dashboard::Errors::DashboardProcessingError
-
- expect { dashboard }.to raise_error(error_class, expected_message)
- end
- end
-
- context 'when the dashboard is missing panel_groups' do
- let(:dashboard_yml) { {} }
-
- it_behaves_like 'errors with message', 'Top-level key :panel_groups must be an array'
- end
-
- context 'when the dashboard contains a panel_group which is missing panels' do
- let(:dashboard_yml) { { panel_groups: [{}] } }
-
- it_behaves_like 'errors with message', 'Each "panel_group" must define an array :panels'
- end
- end
-
- private
-
- def all_metrics
- all_panels.flat_map { |panel| panel[:metrics] }
- end
-
- def all_panels
- dashboard[:panel_groups].flat_map { |group| group[:panels] }
- end
-
- def get_metric_details(metric)
- {
- query_range: metric.query,
- unit: metric.unit,
- label: metric.legend,
- metric_id: metric.id,
- edit_path: edit_metric_path(metric)
- }
- end
-
- def prometheus_path(query)
- Gitlab::Routing.url_helpers.prometheus_api_project_environment_path(
- project,
- environment,
- proxy_path: :query_range,
- query: query
- )
- end
-
- def sample_metrics_path(metric)
- Gitlab::Routing.url_helpers.sample_metrics_project_environment_path(
- project,
- environment,
- identifier: metric
- )
- end
-
- def edit_metric_path(metric)
- Gitlab::Routing.url_helpers.edit_project_prometheus_metric_path(
- project,
- metric.id
- )
end
end
diff --git a/spec/lib/gitlab/metrics/dashboard/stages/panel_ids_inserter_spec.rb b/spec/lib/gitlab/metrics/dashboard/stages/panel_ids_inserter_spec.rb
deleted file mode 100644
index 7a3a9021f86..00000000000
--- a/spec/lib/gitlab/metrics/dashboard/stages/panel_ids_inserter_spec.rb
+++ /dev/null
@@ -1,88 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Gitlab::Metrics::Dashboard::Stages::PanelIdsInserter do
- include MetricsDashboardHelpers
-
- let(:project) { build_stubbed(:project) }
-
- def fetch_panel_ids(dashboard_hash)
- dashboard_hash[:panel_groups].flat_map { |group| group[:panels].flat_map { |panel| panel[:id] } }
- end
-
- describe '#transform!' do
- subject(:transform!) { described_class.new(project, dashboard, nil).transform! }
-
- let(:dashboard) { load_sample_dashboard.deep_symbolize_keys }
-
- context 'when dashboard panels are present' do
- it 'assigns unique ids to each panel using PerformanceMonitoring::PrometheusPanel', :aggregate_failures do
- dashboard.fetch(:panel_groups).each do |group|
- group.fetch(:panels).each do |panel|
- panel_double = instance_double(::PerformanceMonitoring::PrometheusPanel)
-
- expect(::PerformanceMonitoring::PrometheusPanel).to receive(:new).with(panel).and_return(panel_double)
- expect(panel_double).to receive(:id).with(group[:group]).and_return(FFaker::Lorem.unique.characters(125))
- end
- end
-
- transform!
-
- expect(fetch_panel_ids(dashboard)).not_to include nil
- end
- end
-
- context 'when dashboard panels has duplicated ids' do
- it 'no panel has assigned id' do
- panel_double = instance_double(::PerformanceMonitoring::PrometheusPanel)
- allow(::PerformanceMonitoring::PrometheusPanel).to receive(:new).and_return(panel_double)
- allow(panel_double).to receive(:id).and_return('duplicated id')
-
- transform!
-
- expect(fetch_panel_ids(dashboard)).to all be_nil
- expect(fetch_panel_ids(dashboard)).not_to include 'duplicated id'
- end
- end
-
- context 'when there are no panels in the dashboard' do
- it 'raises a processing error' do
- dashboard[:panel_groups][0].delete(:panels)
-
- expect { transform! }.to(
- raise_error(::Gitlab::Metrics::Dashboard::Errors::DashboardProcessingError)
- )
- end
- end
-
- context 'when there are no panel_groups in the dashboard' do
- it 'raises a processing error' do
- dashboard.delete(:panel_groups)
-
- expect { transform! }.to(
- raise_error(::Gitlab::Metrics::Dashboard::Errors::DashboardProcessingError)
- )
- end
- end
-
- context 'when dashboard panels has unknown schema attributes' do
- before do
- error = ActiveModel::UnknownAttributeError.new(double, 'unknown_panel_attribute')
- allow(::PerformanceMonitoring::PrometheusPanel).to receive(:new).and_raise(error)
- end
-
- it 'no panel has assigned id' do
- transform!
-
- expect(fetch_panel_ids(dashboard)).to all be_nil
- end
-
- it 'logs the failure' do
- expect(Gitlab::ErrorTracking).to receive(:log_exception)
-
- transform!
- end
- end
- end
-end