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>2021-10-08 09:11:46 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-10-08 09:11:46 +0300
commita2b477802dd0cee675d93347a698d24c31ad7ff5 (patch)
tree0339757d48cf367157eac3331d664196a82452c9 /app
parentd27e35761a665e32a7a83fc76ca19613a7a1c7bb (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/cycle_analytics/utils.js30
-rw-r--r--app/assets/javascripts/lib/utils/datetime/date_format_utility.js29
-rw-r--r--app/assets/javascripts/snippets/components/snippet_header.vue70
-rw-r--r--app/assets/javascripts/snippets/index.js2
-rw-r--r--app/assets/javascripts/user_popovers.js1
-rw-r--r--app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue22
-rw-r--r--app/models/clusters/agents/group_authorization.rb4
-rw-r--r--app/models/clusters/agents/implicit_authorization.rb5
-rw-r--r--app/models/clusters/agents/project_authorization.rb4
-rw-r--r--app/models/users/credit_card_validation.rb8
-rw-r--r--app/views/admin/users/show.html.haml3
-rw-r--r--app/views/projects/snippets/show.html.haml2
-rw-r--r--app/views/snippets/show.html.haml2
13 files changed, 119 insertions, 63 deletions
diff --git a/app/assets/javascripts/cycle_analytics/utils.js b/app/assets/javascripts/cycle_analytics/utils.js
index fa02fdf914a..3c6267bac06 100644
--- a/app/assets/javascripts/cycle_analytics/utils.js
+++ b/app/assets/javascripts/cycle_analytics/utils.js
@@ -1,13 +1,10 @@
import dateFormat from 'dateformat';
-import { unescape } from 'lodash';
import { dateFormats } from '~/analytics/shared/constants';
import { hideFlash } from '~/flash';
-import { sanitize } from '~/lib/dompurify';
-import { roundToNearestHalf } from '~/lib/utils/common_utils';
import { getDateInPast } from '~/lib/utils/datetime/date_calculation_utility';
import { parseSeconds } from '~/lib/utils/datetime_utility';
+import { formatTimeAsSummary } from '~/lib/utils/datetime/date_format_utility';
import { slugify } from '~/lib/utils/text_utility';
-import { s__, sprintf } from '../locale';
export const removeFlash = (type = 'alert') => {
const flashEl = document.querySelector(`.flash-${type}`);
@@ -45,29 +42,6 @@ export const transformStagesForPathNavigation = ({
return formattedStages;
};
-export const timeSummaryForPathNavigation = ({ seconds, hours, days, minutes, weeks, months }) => {
- if (months) {
- return sprintf(s__('ValueStreamAnalytics|%{value}M'), {
- value: roundToNearestHalf(months),
- });
- } else if (weeks) {
- return sprintf(s__('ValueStreamAnalytics|%{value}w'), {
- value: roundToNearestHalf(weeks),
- });
- } else if (days) {
- return sprintf(s__('ValueStreamAnalytics|%{value}d'), {
- value: roundToNearestHalf(days),
- });
- } else if (hours) {
- return sprintf(s__('ValueStreamAnalytics|%{value}h'), { value: hours });
- } else if (minutes) {
- return sprintf(s__('ValueStreamAnalytics|%{value}m'), { value: minutes });
- } else if (seconds) {
- return unescape(sanitize(s__('ValueStreamAnalytics|&lt;1m'), { ALLOWED_TAGS: [] }));
- }
- return '-';
-};
-
/**
* Takes a raw median value in seconds and converts it to a string representation
* ie. converts 172800 => 2d (2 days)
@@ -76,7 +50,7 @@ export const timeSummaryForPathNavigation = ({ seconds, hours, days, minutes, we
* @returns {String} String representation ie 2w
*/
export const medianTimeToParsedSeconds = (value) =>
- timeSummaryForPathNavigation({
+ formatTimeAsSummary({
...parseSeconds(value, { daysPerWeek: 7, hoursPerDay: 24 }),
seconds: value,
});
diff --git a/app/assets/javascripts/lib/utils/datetime/date_format_utility.js b/app/assets/javascripts/lib/utils/datetime/date_format_utility.js
index 0a35efb0ac8..3c446c21865 100644
--- a/app/assets/javascripts/lib/utils/datetime/date_format_utility.js
+++ b/app/assets/javascripts/lib/utils/datetime/date_format_utility.js
@@ -1,6 +1,8 @@
import dateFormat from 'dateformat';
-import { isString, mapValues, reduce, isDate } from 'lodash';
-import { s__, n__, __ } from '../../../locale';
+import { isString, mapValues, reduce, isDate, unescape } from 'lodash';
+import { roundToNearestHalf } from '~/lib/utils/common_utils';
+import { sanitize } from '~/lib/dompurify';
+import { s__, n__, __, sprintf } from '../../../locale';
/**
* Returns i18n month names array.
@@ -361,3 +363,26 @@ export const dateToTimeInputValue = (date) => {
hour12: false,
});
};
+
+export const formatTimeAsSummary = ({ seconds, hours, days, minutes, weeks, months }) => {
+ if (months) {
+ return sprintf(s__('ValueStreamAnalytics|%{value}M'), {
+ value: roundToNearestHalf(months),
+ });
+ } else if (weeks) {
+ return sprintf(s__('ValueStreamAnalytics|%{value}w'), {
+ value: roundToNearestHalf(weeks),
+ });
+ } else if (days) {
+ return sprintf(s__('ValueStreamAnalytics|%{value}d'), {
+ value: roundToNearestHalf(days),
+ });
+ } else if (hours) {
+ return sprintf(s__('ValueStreamAnalytics|%{value}h'), { value: hours });
+ } else if (minutes) {
+ return sprintf(s__('ValueStreamAnalytics|%{value}m'), { value: minutes });
+ } else if (seconds) {
+ return unescape(sanitize(s__('ValueStreamAnalytics|&lt;1m'), { ALLOWED_TAGS: [] }));
+ }
+ return '-';
+};
diff --git a/app/assets/javascripts/snippets/components/snippet_header.vue b/app/assets/javascripts/snippets/components/snippet_header.vue
index 466b273cae4..a5c98a7ad90 100644
--- a/app/assets/javascripts/snippets/components/snippet_header.vue
+++ b/app/assets/javascripts/snippets/components/snippet_header.vue
@@ -11,15 +11,26 @@ import {
GlButton,
GlTooltipDirective,
} from '@gitlab/ui';
+import { isEmpty } from 'lodash';
import CanCreateProjectSnippet from 'shared_queries/snippet/project_permissions.query.graphql';
import CanCreatePersonalSnippet from 'shared_queries/snippet/user_permissions.query.graphql';
import { fetchPolicies } from '~/lib/graphql';
+import axios from '~/lib/utils/axios_utils';
import { joinPaths } from '~/lib/utils/url_utility';
-import { __ } from '~/locale';
+import { __, s__, sprintf } from '~/locale';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
+import createFlash, { FLASH_TYPES } from '~/flash';
import DeleteSnippetMutation from '../mutations/deleteSnippet.mutation.graphql';
+export const i18n = {
+ snippetSpamSuccess: sprintf(
+ s__('Snippets|%{spammable_titlecase} was submitted to Akismet successfully.'),
+ { spammable_titlecase: __('Snippet') },
+ ),
+ snippetSpamFailure: s__('Snippets|Error with Akismet. Please check the logs for more info.'),
+};
+
export default {
components: {
GlAvatar,
@@ -54,7 +65,7 @@ export default {
},
},
},
- inject: ['reportAbusePath'],
+ inject: ['reportAbusePath', 'canReportSpam'],
props: {
snippet: {
type: Object,
@@ -63,7 +74,8 @@ export default {
},
data() {
return {
- isDeleting: false,
+ isLoading: false,
+ isSubmittingSpam: false,
errorMessage: '',
canCreateSnippet: false,
};
@@ -105,10 +117,11 @@ export default {
category: 'secondary',
},
{
- condition: this.reportAbusePath,
+ condition: this.canReportSpam && !isEmpty(this.reportAbusePath),
text: __('Submit as spam'),
- href: this.reportAbusePath,
+ click: this.submitAsSpam,
title: __('Submit as spam'),
+ loading: this.isSubmittingSpam,
},
];
},
@@ -157,7 +170,7 @@ export default {
this.$refs.deleteModal.show();
},
deleteSnippet() {
- this.isDeleting = true;
+ this.isLoading = true;
this.$apollo
.mutate({
mutation: DeleteSnippetMutation,
@@ -167,17 +180,34 @@ export default {
if (data?.destroySnippet?.errors.length) {
throw new Error(data?.destroySnippet?.errors[0]);
}
- this.isDeleting = false;
this.errorMessage = undefined;
this.closeDeleteModal();
this.redirectToSnippets();
})
.catch((err) => {
- this.isDeleting = false;
+ this.isLoading = false;
this.errorMessage = err.message;
+ })
+ .finally(() => {
+ this.isLoading = false;
+ });
+ },
+ async submitAsSpam() {
+ try {
+ this.isSubmittingSpam = true;
+ await axios.post(this.reportAbusePath);
+ createFlash({
+ message: this.$options.i18n.snippetSpamSuccess,
+ type: FLASH_TYPES.SUCCESS,
});
+ } catch (error) {
+ createFlash({ message: this.$options.i18n.snippetSpamFailure });
+ } finally {
+ this.isSubmittingSpam = false;
+ }
},
},
+ i18n,
};
</script>
<template>
@@ -189,9 +219,7 @@ export default {
:title="snippetVisibilityLevelDescription"
data-container="body"
>
- <span class="sr-only">
- {{ s__(`VisibilityLevel|${visibility}`) }}
- </span>
+ <span class="sr-only">{{ s__(`VisibilityLevel|${visibility}`) }}</span>
<gl-icon :name="visibilityLevelIcon" :size="14" />
</div>
<div class="creator" data-testid="authored-message">
@@ -233,6 +261,7 @@ export default {
>
<gl-button
:disabled="action.disabled"
+ :loading="action.loading"
:variant="action.variant"
:category="action.category"
:class="action.cssClass"
@@ -240,9 +269,8 @@ export default {
data-qa-selector="snippet_action_button"
:data-qa-action="action.text"
@click="action.click ? action.click() : undefined"
+ >{{ action.text }}</gl-button
>
- {{ action.text }}
- </gl-button>
</div>
</template>
</div>
@@ -266,14 +294,14 @@ export default {
<gl-modal ref="deleteModal" modal-id="delete-modal" title="Example title">
<template #modal-title>{{ __('Delete snippet?') }}</template>
- <gl-alert v-if="errorMessage" variant="danger" class="mb-2" @dismiss="errorMessage = ''">{{
- errorMessage
- }}</gl-alert>
+ <gl-alert v-if="errorMessage" variant="danger" class="mb-2" @dismiss="errorMessage = ''">
+ {{ errorMessage }}
+ </gl-alert>
<gl-sprintf :message="__('Are you sure you want to delete %{name}?')">
- <template #name
- ><strong>{{ snippet.title }}</strong></template
- >
+ <template #name>
+ <strong>{{ snippet.title }}</strong>
+ </template>
</gl-sprintf>
<template #modal-footer>
@@ -281,11 +309,11 @@ export default {
<gl-button
variant="danger"
category="primary"
- :disabled="isDeleting"
+ :disabled="isLoading"
data-qa-selector="delete_snippet_button"
@click="deleteSnippet"
>
- <gl-loading-icon v-if="isDeleting" size="sm" inline />
+ <gl-loading-icon v-if="isLoading" size="sm" inline />
{{ __('Delete snippet') }}
</gl-button>
</template>
diff --git a/app/assets/javascripts/snippets/index.js b/app/assets/javascripts/snippets/index.js
index dec8dcec179..8e7368ef804 100644
--- a/app/assets/javascripts/snippets/index.js
+++ b/app/assets/javascripts/snippets/index.js
@@ -27,6 +27,7 @@ export default function appFactory(el, Component) {
visibilityLevels = '[]',
selectedLevel,
multipleLevelsRestricted,
+ canReportSpam,
reportAbusePath,
...restDataset
} = el.dataset;
@@ -39,6 +40,7 @@ export default function appFactory(el, Component) {
selectedLevel: SNIPPET_LEVELS_MAP[selectedLevel] ?? SNIPPET_VISIBILITY_PRIVATE,
multipleLevelsRestricted: 'multipleLevelsRestricted' in el.dataset,
reportAbusePath,
+ canReportSpam,
},
render(createElement) {
return createElement(Component, {
diff --git a/app/assets/javascripts/user_popovers.js b/app/assets/javascripts/user_popovers.js
index 7a7518bcf83..4544373d8aa 100644
--- a/app/assets/javascripts/user_popovers.js
+++ b/app/assets/javascripts/user_popovers.js
@@ -41,6 +41,7 @@ const populateUserInfo = (user) => {
workInformation: userData.work_information,
websiteUrl: userData.website_url,
pronouns: userData.pronouns,
+ localTime: userData.local_time,
loaded: true,
});
}
diff --git a/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue b/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue
index 74616763f8f..05e0c3b0be3 100644
--- a/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue
+++ b/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue
@@ -93,19 +93,27 @@ export default {
</div>
<div class="gl-text-gray-500">
<div v-if="user.bio" class="gl-display-flex gl-mb-2">
- <gl-icon name="profile" class="gl-text-gray-400 gl-flex-shrink-0" />
+ <gl-icon name="profile" class="gl-flex-shrink-0" />
<span ref="bio" class="gl-ml-2 gl-overflow-hidden">{{ user.bio }}</span>
</div>
<div v-if="user.workInformation" class="gl-display-flex gl-mb-2">
- <gl-icon name="work" class="gl-text-gray-400 gl-flex-shrink-0" />
+ <gl-icon name="work" class="gl-flex-shrink-0" />
<span ref="workInformation" class="gl-ml-2">{{ user.workInformation }}</span>
</div>
+ <div v-if="user.location" class="gl-display-flex gl-mb-2">
+ <gl-icon name="location" class="gl-flex-shrink-0" />
+ <span class="gl-ml-2">{{ user.location }}</span>
+ </div>
+ <div
+ v-if="user.localTime && !user.bot"
+ class="gl-display-flex gl-mb-2"
+ data-testid="user-popover-local-time"
+ >
+ <gl-icon name="clock" class="gl-flex-shrink-0" />
+ <span class="gl-ml-2">{{ user.localTime }}</span>
+ </div>
</div>
- <div v-if="user.location" class="js-location gl-text-gray-500 gl-display-flex">
- <gl-icon name="location" class="gl-text-gray-400 flex-shrink-0" />
- <span class="gl-ml-2">{{ user.location }}</span>
- </div>
- <div v-if="statusHtml" class="js-user-status gl-mt-3">
+ <div v-if="statusHtml" class="gl-mb-2" data-testid="user-popover-status">
<span v-safe-html:[$options.safeHtmlConfig]="statusHtml"></span>
</div>
<div v-if="user.bot" class="gl-text-blue-500">
diff --git a/app/models/clusters/agents/group_authorization.rb b/app/models/clusters/agents/group_authorization.rb
index 74c0cec3b7e..28a711aaf17 100644
--- a/app/models/clusters/agents/group_authorization.rb
+++ b/app/models/clusters/agents/group_authorization.rb
@@ -10,7 +10,9 @@ module Clusters
validates :config, json_schema: { filename: 'cluster_agent_authorization_configuration' }
- delegate :project, to: :agent
+ def config_project
+ agent.project
+ end
end
end
end
diff --git a/app/models/clusters/agents/implicit_authorization.rb b/app/models/clusters/agents/implicit_authorization.rb
index 967cc686045..9f7f653ed65 100644
--- a/app/models/clusters/agents/implicit_authorization.rb
+++ b/app/models/clusters/agents/implicit_authorization.rb
@@ -6,12 +6,15 @@ module Clusters
attr_reader :agent
delegate :id, to: :agent, prefix: true
- delegate :project, to: :agent
def initialize(agent:)
@agent = agent
end
+ def config_project
+ agent.project
+ end
+
def config
nil
end
diff --git a/app/models/clusters/agents/project_authorization.rb b/app/models/clusters/agents/project_authorization.rb
index 1c71a0a432a..f6d19086751 100644
--- a/app/models/clusters/agents/project_authorization.rb
+++ b/app/models/clusters/agents/project_authorization.rb
@@ -9,6 +9,10 @@ module Clusters
belongs_to :project, class_name: '::Project', optional: false
validates :config, json_schema: { filename: 'cluster_agent_authorization_configuration' }
+
+ def config_project
+ agent.project
+ end
end
end
end
diff --git a/app/models/users/credit_card_validation.rb b/app/models/users/credit_card_validation.rb
index 55f56c6277a..a4cc43d1f13 100644
--- a/app/models/users/credit_card_validation.rb
+++ b/app/models/users/credit_card_validation.rb
@@ -12,5 +12,13 @@ module Users
validates :last_digits, allow_nil: true, numericality: {
greater_than_or_equal_to: 0, less_than_or_equal_to: 9999
}
+
+ def similar_records
+ self.class.where(
+ expiration_date: expiration_date,
+ last_digits: last_digits,
+ holder_name: holder_name
+ ).order(credit_card_validated_at: :desc).includes(:user)
+ end
end
end
diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml
index ad8d9d1f04f..2a9b4694e7b 100644
--- a/app/views/admin/users/show.html.haml
+++ b/app/views/admin/users/show.html.haml
@@ -61,7 +61,6 @@
= _('Disabled')
= render_if_exists 'admin/namespace_plan_info', namespace: @user.namespace
- = render_if_exists 'admin/users/credit_card_info', user: @user
%li
%span.light= _('External User:')
@@ -139,6 +138,8 @@
= render_if_exists 'namespaces/shared_runner_status', namespace: @user.namespace
+ = render_if_exists 'admin/users/credit_card_info', user: @user, link_to_match_page: true
+
= render 'shared/custom_attributes', custom_attributes: @user.custom_attributes
-# Rendered on desktop only so order of cards can be different on desktop vs mobile
diff --git a/app/views/projects/snippets/show.html.haml b/app/views/projects/snippets/show.html.haml
index 8ef53c40b11..3e6acdb130a 100644
--- a/app/views/projects/snippets/show.html.haml
+++ b/app/views/projects/snippets/show.html.haml
@@ -3,7 +3,7 @@
- breadcrumb_title @snippet.to_reference
- page_title "#{@snippet.title} (#{@snippet.to_reference})", _("Snippets")
-#js-snippet-view{ data: {'qa-selector': 'snippet_view', 'snippet-gid': @snippet.to_global_id, 'report-abuse-path': snippet_report_abuse_path(@snippet) } }
+#js-snippet-view{ data: {'qa-selector': 'snippet_view', 'snippet-gid': @snippet.to_global_id, 'report-abuse-path': snippet_report_abuse_path(@snippet), 'can-report-spam': @snippet.submittable_as_spam_by?(current_user).to_s } }
.row-content-block.top-block.content-component-block
= render 'award_emoji/awards_block', awardable: @snippet, inline: true, api_awards_path: project_snippets_award_api_path(@snippet)
diff --git a/app/views/snippets/show.html.haml b/app/views/snippets/show.html.haml
index ca52a1f8f46..f1093a3b730 100644
--- a/app/views/snippets/show.html.haml
+++ b/app/views/snippets/show.html.haml
@@ -12,7 +12,7 @@
- content_for :prefetch_asset_tags do
- webpack_preload_asset_tag('monaco', prefetch: true)
-#js-snippet-view{ data: {'qa-selector': 'snippet_view', 'snippet-gid': @snippet.to_global_id, 'report-abuse-path': snippet_report_abuse_path(@snippet) } }
+#js-snippet-view{ data: {'qa-selector': 'snippet_view', 'snippet-gid': @snippet.to_global_id, 'report-abuse-path': snippet_report_abuse_path(@snippet), 'can-report-spam': @snippet.submittable_as_spam_by?(current_user).to_s } }
.row-content-block.top-block.content-component-block
= render 'award_emoji/awards_block', awardable: @snippet, inline: true