diff options
Diffstat (limited to 'app')
11 files changed, 113 insertions, 139 deletions
diff --git a/app/assets/javascripts/issuable/components/related_issuable_item.vue b/app/assets/javascripts/issuable/components/related_issuable_item.vue index 8894e8f63b8..254248ef1d4 100644 --- a/app/assets/javascripts/issuable/components/related_issuable_item.vue +++ b/app/assets/javascripts/issuable/components/related_issuable_item.vue @@ -141,6 +141,7 @@ export default { <gl-link :href="computedPath" class="sortable-link gl-font-weight-normal" + target="_blank" @click="handleTitleClick" > {{ title }} diff --git a/app/assets/javascripts/pipeline_schedules/components/pipeline_schedules.vue b/app/assets/javascripts/pipeline_schedules/components/pipeline_schedules.vue index 4a08a82275a..b055b89c528 100644 --- a/app/assets/javascripts/pipeline_schedules/components/pipeline_schedules.vue +++ b/app/assets/javascripts/pipeline_schedules/components/pipeline_schedules.vue @@ -1,6 +1,8 @@ <script> -import { GlAlert, GlLoadingIcon, GlModal } from '@gitlab/ui'; +import { GlAlert, GlBadge, GlButton, GlLoadingIcon, GlModal, GlTabs, GlTab } from '@gitlab/ui'; import { s__, __ } from '~/locale'; +import { limitedCounterWithDelimiter } from '~/lib/utils/text_utility'; +import { queryToObject } from '~/lib/utils/url_utility'; import deletePipelineScheduleMutation from '../graphql/mutations/delete_pipeline_schedule.mutation.graphql'; import getPipelineSchedulesQuery from '../graphql/queries/get_pipeline_schedules.query.graphql'; import PipelineSchedulesTable from './table/pipeline_schedules_table.vue'; @@ -11,6 +13,7 @@ export default { scheduleDeleteError: s__( 'PipelineSchedules|There was a problem deleting the pipeline schedule.', ), + newSchedule: s__('PipelineSchedules|New schedule'), }, modal: { id: 'delete-pipeline-schedule-modal', @@ -28,8 +31,12 @@ export default { }, components: { GlAlert, + GlBadge, + GlButton, GlLoadingIcon, GlModal, + GlTabs, + GlTab, PipelineSchedulesTable, }, inject: { @@ -43,10 +50,16 @@ export default { variables() { return { projectPath: this.fullPath, + status: this.scope, }; }, - update({ project }) { - return project?.pipelineSchedules?.nodes || []; + update(data) { + const { pipelineSchedules: { nodes: list = [], count } = {} } = data.project || {}; + + return { + list, + count, + }; }, error() { this.reportError(this.$options.i18n.schedulesFetchError); @@ -54,18 +67,58 @@ export default { }, }, data() { + const { scope } = queryToObject(window.location.search); return { - schedules: [], + schedules: { + list: [], + }, + scope, hasError: false, errorMessage: '', scheduleToDeleteId: null, showModal: false, + count: 0, }; }, computed: { isLoading() { return this.$apollo.queries.schedules.loading; }, + schedulesCount() { + return this.schedules.count; + }, + tabs() { + return [ + { + text: s__('PipelineSchedules|All'), + count: limitedCounterWithDelimiter(this.count), + scope: null, + showBadge: true, + attrs: { 'data-testid': 'pipeline-schedules-all-tab' }, + }, + { + text: s__('PipelineSchedules|Active'), + scope: 'ACTIVE', + showBadge: false, + attrs: { 'data-testid': 'pipeline-schedules-active-tab' }, + }, + { + text: s__('PipelineSchedules|Inactive'), + scope: 'INACTIVE', + showBadge: false, + attrs: { 'data-testid': 'pipeline-schedules-inactive-tab' }, + }, + ]; + }, + }, + watch: { + // this watcher ensures that the count on the all tab + // is not updated when switching to other tabs + schedulesCount(newCount) { + if (!this.scope) { + this.count = newCount; + } + }, }, methods: { reportError(error) { @@ -100,6 +153,10 @@ export default { this.reportError(this.$options.i18n.scheduleDeleteError); } }, + fetchPipelineSchedulesByStatus(scope) { + this.scope = scope; + this.$apollo.queries.schedules.refetch(); + }, }, }; </script> @@ -110,12 +167,45 @@ export default { {{ errorMessage }} </gl-alert> - <gl-loading-icon v-if="isLoading" size="lg" /> + <template v-else> + <gl-tabs + sync-active-tab-with-query-params + query-param-name="scope" + nav-class="gl-flex-grow-1 gl-align-items-center" + > + <gl-tab + v-for="tab in tabs" + :key="tab.text" + :title-link-attributes="tab.attrs" + :query-param-value="tab.scope" + @click="fetchPipelineSchedulesByStatus(tab.scope)" + > + <template #title> + <span>{{ tab.text }}</span> - <!-- Tabs will be addressed in #371989 --> + <template v-if="tab.showBadge"> + <gl-loading-icon v-if="tab.scope === scope && isLoading" class="gl-ml-2" /> - <template v-else> - <pipeline-schedules-table :schedules="schedules" @showDeleteModal="showDeleteModal" /> + <gl-badge v-else-if="tab.count" size="sm" class="gl-tab-counter-badge"> + {{ tab.count }} + </gl-badge> + </template> + </template> + + <gl-loading-icon v-if="isLoading" size="lg" /> + <pipeline-schedules-table + v-else + :schedules="schedules.list" + @showDeleteModal="showDeleteModal" + /> + </gl-tab> + + <template #tabs-end> + <gl-button variant="confirm" class="gl-ml-auto" data-testid="new-schedule-button"> + {{ $options.i18n.newSchedule }} + </gl-button> + </template> + </gl-tabs> <gl-modal :visible="showModal" diff --git a/app/assets/javascripts/pipeline_schedules/components/table/pipeline_schedules_table.vue b/app/assets/javascripts/pipeline_schedules/components/table/pipeline_schedules_table.vue index d54008b81b2..da2157a8851 100644 --- a/app/assets/javascripts/pipeline_schedules/components/table/pipeline_schedules_table.vue +++ b/app/assets/javascripts/pipeline_schedules/components/table/pipeline_schedules_table.vue @@ -12,31 +12,37 @@ export default { { key: 'description', label: s__('PipelineSchedules|Description'), + thClass: 'gl-border-t-none!', columnClass: 'gl-w-40p', }, { key: 'target', label: s__('PipelineSchedules|Target'), + thClass: 'gl-border-t-none!', columnClass: 'gl-w-10p', }, { key: 'pipeline', label: s__('PipelineSchedules|Last Pipeline'), + thClass: 'gl-border-t-none!', columnClass: 'gl-w-10p', }, { key: 'next', label: s__('PipelineSchedules|Next Run'), + thClass: 'gl-border-t-none!', columnClass: 'gl-w-15p', }, { key: 'owner', label: s__('PipelineSchedules|Owner'), + thClass: 'gl-border-t-none!', columnClass: 'gl-w-10p', }, { key: 'actions', label: '', + thClass: 'gl-border-t-none!', columnClass: 'gl-w-15p', }, ], diff --git a/app/assets/javascripts/pipeline_schedules/graphql/queries/get_pipeline_schedules.query.graphql b/app/assets/javascripts/pipeline_schedules/graphql/queries/get_pipeline_schedules.query.graphql index 7d9d658b1b6..9f6cb429cca 100644 --- a/app/assets/javascripts/pipeline_schedules/graphql/queries/get_pipeline_schedules.query.graphql +++ b/app/assets/javascripts/pipeline_schedules/graphql/queries/get_pipeline_schedules.query.graphql @@ -1,7 +1,8 @@ -query getPipelineSchedulesQuery($projectPath: ID!) { +query getPipelineSchedulesQuery($projectPath: ID!, $status: PipelineScheduleStatus) { project(fullPath: $projectPath) { id - pipelineSchedules { + pipelineSchedules(status: $status) { + count nodes { id description diff --git a/app/controllers/jira_connect/subscriptions_controller.rb b/app/controllers/jira_connect/subscriptions_controller.rb index 9305f46c39e..751481f78e2 100644 --- a/app/controllers/jira_connect/subscriptions_controller.rb +++ b/app/controllers/jira_connect/subscriptions_controller.rb @@ -64,7 +64,7 @@ class JiraConnect::SubscriptionsController < JiraConnect::ApplicationController private def allow_self_managed_content_security_policy - return unless Feature.enabled?(:jira_connect_oauth_self_managed) + return unless Feature.enabled?(:jira_connect_oauth_self_managed_setting) return unless current_jira_installation.instance_url? diff --git a/app/models/namespace.rb b/app/models/namespace.rb index 42f362876bb..12b96f34316 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -40,9 +40,9 @@ class Namespace < ApplicationRecord PATH_TRAILING_VIOLATIONS = %w[.git .atom .].freeze - # The first date in https://docs.gitlab.com/ee/user/usage_quotas.html#namespace-storage-limit-enforcement-schedule - # Determines when we start enforcing namespace storage - MIN_STORAGE_ENFORCEMENT_DATE = Date.new(2022, 10, 19) + # This date is just a placeholder until namespace storage enforcement timeline is confirmed at which point + # this should be replaced, see https://about.gitlab.com/pricing/faq-efficient-free-tier/#user-limits-on-gitlab-saas-free-tier + MIN_STORAGE_ENFORCEMENT_DATE = 3.months.from_now.to_date # https://gitlab.com/gitlab-org/gitlab/-/issues/367531 MIN_STORAGE_ENFORCEMENT_USAGE = 5.gigabytes diff --git a/app/models/personal_access_token.rb b/app/models/personal_access_token.rb index f0ed1822da6..3126dba9d6d 100644 --- a/app/models/personal_access_token.rb +++ b/app/models/personal_access_token.rb @@ -11,8 +11,6 @@ class PersonalAccessToken < ApplicationRecord add_authentication_token_field :token, digest: true - REDIS_EXPIRY_TIME = 3.minutes - # PATs are 20 characters + optional configurable settings prefix (0..20) TOKEN_LENGTH_RANGE = (20..40).freeze @@ -34,8 +32,6 @@ class PersonalAccessToken < ApplicationRecord scope :for_user, -> (user) { where(user: user) } scope :for_users, -> (users) { where(user: users) } scope :preload_users, -> { preload(:user) } - scope :order_expires_at_asc, -> { reorder(expires_at: :asc) } - scope :order_expires_at_desc, -> { reorder(expires_at: :desc) } scope :order_expires_at_asc_id_desc, -> { reorder(expires_at: :asc, id: :desc) } scope :project_access_token, -> { includes(:user).where(user: { user_type: :project_bot }) } scope :owner_is_human, -> { includes(:user).where(user: { user_type: :human }) } @@ -55,35 +51,10 @@ class PersonalAccessToken < ApplicationRecord !revoked? && !expired? end - def self.redis_getdel(user_id) - Gitlab::Redis::SharedState.with do |redis| - redis_key = redis_shared_state_key(user_id) - encrypted_token = redis.get(redis_key) - redis.del(redis_key) - - begin - Gitlab::CryptoHelper.aes256_gcm_decrypt(encrypted_token) - rescue StandardError => e - logger.warn "Failed to decrypt #{self.name} value stored in Redis for key ##{redis_key}: #{e.class}" - encrypted_token - end - end - end - - def self.redis_store!(user_id, token) - encrypted_token = Gitlab::CryptoHelper.aes256_gcm_encrypt(token) - - Gitlab::Redis::SharedState.with do |redis| - redis.set(redis_shared_state_key(user_id), encrypted_token, ex: REDIS_EXPIRY_TIME) - end - end - override :simple_sorts def self.simple_sorts super.merge( { - 'expires_at_asc' => -> { order_expires_at_asc }, - 'expires_at_desc' => -> { order_expires_at_desc }, 'expires_at_asc_id_desc' => -> { order_expires_at_asc_id_desc } } ) @@ -121,10 +92,6 @@ class PersonalAccessToken < ApplicationRecord self.scopes = Gitlab::Auth::DEFAULT_SCOPES if self.scopes.empty? end - - def self.redis_shared_state_key(user_id) - "gitlab:personal_access_token:#{user_id}" - end end PersonalAccessToken.prepend_mod_with('PersonalAccessToken') diff --git a/app/views/admin/application_settings/general.html.haml b/app/views/admin/application_settings/general.html.haml index ec5d1ef4a34..db407ae35c4 100644 --- a/app/views/admin/application_settings/general.html.haml +++ b/app/views/admin/application_settings/general.html.haml @@ -123,4 +123,4 @@ = render 'admin/application_settings/eks' = render 'admin/application_settings/floc' = render_if_exists 'admin/application_settings/add_license' -= render 'admin/application_settings/jira_connect_application_key' if Feature.enabled?(:jira_connect_oauth, current_user) += render 'admin/application_settings/jira_connect_application_key' if Feature.enabled?(:jira_connect_oauth_self_managed_setting, current_user) diff --git a/app/views/ci/variables/_url_query_variable_row.html.haml b/app/views/ci/variables/_url_query_variable_row.html.haml deleted file mode 100644 index 77bcacdb94b..00000000000 --- a/app/views/ci/variables/_url_query_variable_row.html.haml +++ /dev/null @@ -1,28 +0,0 @@ -- form_field = local_assigns.fetch(:form_field, nil) -- variable = local_assigns.fetch(:variable, nil) - -- key = variable[0] -- value = variable[1] -- variable_type = variable[2] || "env_var" - -- destroy_input_name = "#{form_field}[variables_attributes][][_destroy]" -- variable_type_input_name = "#{form_field}[variables_attributes][][variable_type]" -- key_input_name = "#{form_field}[variables_attributes][][key]" -- value_input_name = "#{form_field}[variables_attributes][][secret_value]" - -%li.js-row.ci-variable-row - .ci-variable-row-body.border-bottom - %input.js-ci-variable-input-destroy{ type: "hidden", name: destroy_input_name } - %select.js-ci-variable-input-variable-type.ci-variable-body-item.form-control.select-control.custom-select.table-section.section-15{ name: variable_type_input_name } - = options_for_select(ci_variable_type_options, variable_type) - %input.js-ci-variable-input-key.ci-variable-body-item.form-control.table-section.section-15{ type: "text", - name: key_input_name, - value: key, - placeholder: s_('CiVariables|Input variable key') } - .ci-variable-body-item.gl-show-field-errors.table-section.section-15.border-top-0.p-0 - %textarea.js-ci-variable-input-value.js-secret-value.form-control{ rows: 1, - name: value_input_name, - placeholder: s_('CiVariables|Input variable value') } - = value - %button.gl-button.btn.btn-default.btn-icon.btn-item-remove.js-row-remove-button.ci-variable-row-remove-button.table-section{ type: 'button', 'aria-label': s_('CiVariables|Remove variable row') } - = sprite_icon('close') diff --git a/app/views/shared/access_tokens/_created_container.html.haml b/app/views/shared/access_tokens/_created_container.html.haml deleted file mode 100644 index c0aaa46e761..00000000000 --- a/app/views/shared/access_tokens/_created_container.html.haml +++ /dev/null @@ -1,12 +0,0 @@ -.created-personal-access-token-container - %h5.gl-mt-0 - = _('Your new %{type}') % { type: type } - .form-group - .input-group - = text_field_tag 'created-personal-access-token', new_token_value, readonly: true, class: 'form-control js-select-on-focus', data: { qa_selector: 'created_access_token_field' }, 'aria-describedby' => 'created-token-help-block' - %span.input-group-append - = clipboard_button(text: new_token_value, title: _('Copy %{type}') % { type: type }, placement: 'left', class: 'input-group-text btn-default btn-clipboard') - %span#created-token-help-block.form-text.text-muted.text-danger - = _("Make sure you save it - you won't be able to access it again.") - -%hr diff --git a/app/views/shared/access_tokens/_table.html.haml b/app/views/shared/access_tokens/_table.html.haml deleted file mode 100644 index 53c6800f93d..00000000000 --- a/app/views/shared/access_tokens/_table.html.haml +++ /dev/null @@ -1,51 +0,0 @@ -- no_active_tokens_message = local_assigns.fetch(:no_active_tokens_message, _('This user has no active %{type}.') % { type: type_plural }) -- impersonation = local_assigns.fetch(:impersonation, false) -- resource = local_assigns.fetch(:resource, false) - -%hr - -%h5 - = _('Active %{type} (%{token_length})') % { type: type_plural, token_length: active_tokens.length } - -- if impersonation - %p.profile-settings-content - = _("To see all the user's personal access tokens you must impersonate them first.") - -- if active_tokens.present? - .table-responsive - %table.table.active-tokens - %thead - %tr - %th= _('Token name') - %th= _('Scopes') - %th= s_('AccessTokens|Created') - %th - = _('Last Used') - = link_to sprite_icon('question-o'), help_page_path('user/profile/personal_access_tokens.md', anchor: 'view-the-last-time-a-token-was-used'), target: '_blank', rel: 'noopener noreferrer' - %th= _('Expires') - - if resource - %th= _('Role') - %th - %tbody - - active_tokens.each do |token| - %tr - %td= token.name - %td= token.scopes.present? ? token.scopes.join(', ') : _('no scopes selected') - %td= token.created_at.to_date.to_s(:medium) - %td - - if token.last_used_at? - %span.token-last-used-label= _(time_ago_with_tooltip(token.last_used_at)) - - else - %span.token-never-used-label= _('Never') - %td - - if token.expires? - %span{ class: ('text-warning' if token.expires_soon?) } - = time_ago_with_tooltip(token.expires_at) - - else - %span.token-never-expires-label= _('Never') - - if resource - %td= resource.member(token.user).human_access - %td= link_to _('Revoke'), revoke_route_helper.call(token), method: :put, class: "gl-button btn btn-danger btn-sm float-right #{'btn-danger-secondary' unless token.expires?}", aria: { label: _('Revoke') }, data: { confirm: _('Are you sure you want to revoke this %{type}? This action cannot be undone.') % { type: type }, 'confirm-btn-variant': 'danger', qa_selector: 'revoke_button' } -- else - .settings-message.text-center - = no_active_tokens_message |