diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-10-08 09:11:46 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-10-08 09:11:46 +0300 |
commit | a2b477802dd0cee675d93347a698d24c31ad7ff5 (patch) | |
tree | 0339757d48cf367157eac3331d664196a82452c9 /app | |
parent | d27e35761a665e32a7a83fc76ca19613a7a1c7bb (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r-- | app/assets/javascripts/cycle_analytics/utils.js | 30 | ||||
-rw-r--r-- | app/assets/javascripts/lib/utils/datetime/date_format_utility.js | 29 | ||||
-rw-r--r-- | app/assets/javascripts/snippets/components/snippet_header.vue | 70 | ||||
-rw-r--r-- | app/assets/javascripts/snippets/index.js | 2 | ||||
-rw-r--r-- | app/assets/javascripts/user_popovers.js | 1 | ||||
-rw-r--r-- | app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue | 22 | ||||
-rw-r--r-- | app/models/clusters/agents/group_authorization.rb | 4 | ||||
-rw-r--r-- | app/models/clusters/agents/implicit_authorization.rb | 5 | ||||
-rw-r--r-- | app/models/clusters/agents/project_authorization.rb | 4 | ||||
-rw-r--r-- | app/models/users/credit_card_validation.rb | 8 | ||||
-rw-r--r-- | app/views/admin/users/show.html.haml | 3 | ||||
-rw-r--r-- | app/views/projects/snippets/show.html.haml | 2 | ||||
-rw-r--r-- | app/views/snippets/show.html.haml | 2 |
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|<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|<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 |