diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-07-28 21:11:01 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-07-28 21:11:01 +0300 |
commit | 7c5f1bfac791045e54386b9c9bb56ee24afc68ca (patch) | |
tree | a11c8dff3994899c25acacb383c0a70522a24cd1 /app | |
parent | d62fd6e04c272d48dccde4033529ca97c27502f6 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
19 files changed, 316 insertions, 90 deletions
diff --git a/app/assets/javascripts/clusters/forms/components/integration_form.vue b/app/assets/javascripts/clusters/forms/components/integration_form.vue index b2a8381f937..cd82465f2f0 100644 --- a/app/assets/javascripts/clusters/forms/components/integration_form.vue +++ b/app/assets/javascripts/clusters/forms/components/integration_form.vue @@ -74,7 +74,6 @@ export default { v-gl-tooltip:tooltipcontainer name="cluster[enabled]" class="gl-mb-0 js-project-feature-toggle" - data-qa-selector="integration_status_toggle" aria-describedby="toggleCluster" :disabled="!editable" :label="$options.i18n.toggleLabel" @@ -111,7 +110,6 @@ export default { id="cluster_base_domain" v-model="baseDomainField" name="cluster[base_domain]" - data-qa-selector="base_domain_field" class="col-md-6" type="text" /> @@ -144,7 +142,6 @@ export default { type="submit" :disabled="!canSubmit" :aria-disabled="!canSubmit" - data-qa-selector="save_changes_button" >{{ s__('ClusterIntegration|Save changes') }}</gl-button > </div> diff --git a/app/assets/javascripts/clusters_list/components/clusters_actions.vue b/app/assets/javascripts/clusters_list/components/clusters_actions.vue index 7b97a5af373..c388d3fee71 100644 --- a/app/assets/javascripts/clusters_list/components/clusters_actions.vue +++ b/app/assets/javascripts/clusters_list/components/clusters_actions.vue @@ -92,11 +92,7 @@ export default { <!--TODO: Replace button-group workaround once `split` option for new dropdowns is implemented.--> <!-- See issue at https://gitlab.com/gitlab-org/gitlab-ui/-/issues/2263--> - <gl-button-group - ref="actions" - data-qa-selector="clusters_actions_button" - class="gl-w-full gl-mb-3 gl-md-w-auto gl-md-mb-0" - > + <gl-button-group ref="actions" class="gl-w-full gl-mb-3 gl-md-w-auto gl-md-mb-0"> <gl-button v-gl-modal-directive="shouldTriggerModal && $options.INSTALL_AGENT_MODAL_ID" :href="defaultActionUrl" diff --git a/app/assets/javascripts/observability/client.js b/app/assets/javascripts/observability/client.js index 251c165e7dd..667bbc107dc 100644 --- a/app/assets/javascripts/observability/client.js +++ b/app/assets/javascripts/observability/client.js @@ -1,4 +1,5 @@ import axios from '~/lib/utils/axios_utils'; +import mockData from './mock_traces.json'; function enableTraces() { // TODO remove mocks https://gitlab.com/gitlab-org/opstrace/opstrace/-/issues/2271 @@ -19,6 +20,16 @@ function isTracingEnabled() { }); } +async function fetchTrace(tracingUrl, traceId) { + const data = mockData; + const trace = data.traces.find((t) => t.trace_id === traceId); + const duration = trace.spans.reduce((acc, cur) => acc + cur.duration_nano, 0); + return { + ...trace, + duration: duration / 1000, + }; +} + async function fetchTraces(tracingUrl) { const { data } = await axios.get(tracingUrl, { withCredentials: true }); if (!Array.isArray(data.traces)) { @@ -39,5 +50,6 @@ export function buildClient({ provisioningUrl, tracingUrl }) { enableTraces: () => enableTraces(provisioningUrl), isTracingEnabled: () => isTracingEnabled(provisioningUrl), fetchTraces: () => fetchTraces(tracingUrl), + fetchTrace: (traceId) => fetchTrace(tracingUrl, traceId), }; } diff --git a/app/assets/javascripts/pages/projects/tracing/show/index.js b/app/assets/javascripts/pages/projects/tracing/show/index.js new file mode 100644 index 00000000000..107c004aa5f --- /dev/null +++ b/app/assets/javascripts/pages/projects/tracing/show/index.js @@ -0,0 +1,4 @@ +import { initSimpleApp } from '~/helpers/init_simple_app_helper'; +import DetailsIndex from '~/tracing/details_index.vue'; + +initSimpleApp('#js-tracing-details', DetailsIndex); diff --git a/app/assets/javascripts/tracing/components/tracing_details.vue b/app/assets/javascripts/tracing/components/tracing_details.vue new file mode 100644 index 00000000000..d8b2cbc9469 --- /dev/null +++ b/app/assets/javascripts/tracing/components/tracing_details.vue @@ -0,0 +1,90 @@ +<script> +import { GlLoadingIcon } from '@gitlab/ui'; +import { s__ } from '~/locale'; +import { createAlert } from '~/alert'; +import { visitUrl, isSafeURL } from '~/lib/utils/url_utility'; + +export default { + components: { + GlLoadingIcon, + }, + i18n: { + error: s__('Tracing|Failed to load trace details.'), + }, + props: { + observabilityClient: { + required: true, + type: Object, + }, + traceId: { + required: true, + type: String, + }, + tracingIndexUrl: { + required: true, + type: String, + validator: (val) => isSafeURL(val), + }, + }, + data() { + return { + trace: null, + loading: false, + }; + }, + created() { + this.validateAndFetch(); + }, + methods: { + async validateAndFetch() { + if (!this.traceId) { + createAlert({ + message: this.$options.i18n.error, + }); + } + this.loading = true; + try { + const enabled = await this.observabilityClient.isTracingEnabled(); + if (enabled) { + await this.fetchTrace(); + } else { + this.goToTracingIndex(); + } + } catch (e) { + createAlert({ + message: this.$options.i18n.error, + }); + } finally { + this.loading = false; + } + }, + async fetchTrace() { + this.loading = true; + try { + this.trace = await this.observabilityClient.fetchTrace(this.traceId); + } catch (e) { + createAlert({ + message: this.$options.i18n.error, + }); + } finally { + this.loading = false; + } + }, + goToTracingIndex() { + visitUrl(this.tracingIndexUrl); + }, + }, +}; +</script> + +<template> + <div v-if="loading" class="gl-py-5"> + <gl-loading-icon size="lg" /> + </div> + + <!-- TODO Replace with actual trace-details component--> + <div v-else-if="trace" data-testid="trace-details"> + <p>{{ tracingIndexUrl }}</p> + <p>{{ trace }}</p> + </div> +</template> diff --git a/app/assets/javascripts/tracing/components/tracing_empty_state.vue b/app/assets/javascripts/tracing/components/tracing_empty_state.vue index 4cb3bd6d9f0..3cdb280ef9e 100644 --- a/app/assets/javascripts/tracing/components/tracing_empty_state.vue +++ b/app/assets/javascripts/tracing/components/tracing_empty_state.vue @@ -1,15 +1,15 @@ <script> import EMPTY_TRACING_SVG from '@gitlab/svgs/dist/illustrations/monitoring/tracing.svg?url'; import { GlEmptyState, GlButton } from '@gitlab/ui'; -import { __ } from '~/locale'; +import { s__ } from '~/locale'; export default { EMPTY_TRACING_SVG, name: 'TracingEmptyState', i18n: { - title: __('Get started with Tracing'), - description: __('Monitor your applications with GitLab Distributed Tracing.'), - enableButtonText: __('Enable'), + title: s__('Tracing|Get started with Tracing'), + description: s__('Tracing|Monitor your applications with GitLab Distributed Tracing.'), + enableButtonText: s__('Tracing|Enable'), }, components: { GlEmptyState, diff --git a/app/assets/javascripts/tracing/components/tracing_list.vue b/app/assets/javascripts/tracing/components/tracing_list.vue index 294e520d7ac..d247e78bb0b 100644 --- a/app/assets/javascripts/tracing/components/tracing_list.vue +++ b/app/assets/javascripts/tracing/components/tracing_list.vue @@ -1,7 +1,8 @@ <script> import { GlLoadingIcon } from '@gitlab/ui'; -import { __ } from '~/locale'; +import { s__ } from '~/locale'; import { createAlert } from '~/alert'; +import { visitUrl, joinPaths } from '~/lib/utils/url_utility'; import TracingEmptyState from './tracing_empty_state.vue'; import TracingTableList from './tracing_table_list.vue'; @@ -28,7 +29,7 @@ export default { traces: [], }; }, - async created() { + created() { this.checkEnabled(); }, methods: { @@ -41,7 +42,7 @@ export default { } } catch (e) { createAlert({ - message: __('Failed to load page.'), + message: s__('Tracing|Failed to load page.'), }); } finally { this.loading = false; @@ -55,7 +56,7 @@ export default { await this.fetchTraces(); } catch (e) { createAlert({ - message: __('Failed to enable tracing.'), + message: s__('Tracing|Failed to enable tracing.'), }); } finally { this.loading = false; @@ -68,12 +69,15 @@ export default { this.traces = traces; } catch (e) { createAlert({ - message: __('Failed to load traces.'), + message: s__('Tracing|Failed to load traces.'), }); } finally { this.loading = false; } }, + selectTrace(trace) { + visitUrl(joinPaths(window.location.pathname, trace.trace_id)); + }, }, }; </script> @@ -87,7 +91,12 @@ export default { <template v-else-if="tracingEnabled !== null"> <tracing-empty-state v-if="tracingEnabled === false" :enable-tracing="enableTracing" /> - <tracing-table-list v-else :traces="traces" @reload="fetchTraces" /> + <tracing-table-list + v-else + :traces="traces" + @reload="fetchTraces" + @trace-selected="selectTrace" + /> </template> </div> </template> diff --git a/app/assets/javascripts/tracing/components/tracing_table_list.vue b/app/assets/javascripts/tracing/components/tracing_table_list.vue index bbed5520b40..59604890c86 100644 --- a/app/assets/javascripts/tracing/components/tracing_table_list.vue +++ b/app/assets/javascripts/tracing/components/tracing_table_list.vue @@ -1,37 +1,37 @@ <script> import { GlTable, GlLink } from '@gitlab/ui'; -import { __ } from '~/locale'; +import { s__ } from '~/locale'; export const tableDataClass = 'gl-display-flex gl-md-display-table-cell gl-align-items-center'; export default { name: 'TracingTableList', i18n: { - title: __('Traces'), - emptyText: __('No traces to display.'), - emptyLinkText: __('Check again'), + title: s__('Tracing|Traces'), + emptyText: s__('Tracing|No traces to display.'), + emptyLinkText: s__('Tracing|Check again'), }, fields: [ { key: 'timestamp', - label: __('Date'), + label: s__('Tracing|Date'), tdClass: tableDataClass, sortable: true, }, { key: 'service_name', - label: __('Service'), + label: s__('Tracing|Service'), tdClass: tableDataClass, sortable: true, }, { key: 'operation', - label: __('Operation'), + label: s__('Tracing|Operation'), tdClass: tableDataClass, sortable: true, }, { key: 'duration', - label: __('Duration'), + label: s__('Tracing|Duration'), thClass: 'gl-w-15p', tdClass: tableDataClass, sortable: true, @@ -47,6 +47,13 @@ export default { type: Array, }, }, + methods: { + onSelect(items) { + if (items[0]) { + this.$emit('trace-selected', items[0]); + } + }, + }, }; </script> @@ -64,6 +71,10 @@ export default { fixed stacked="md" tbody-tr-class="table-row" + selectable + select-mode="single" + selected-variant="" + @row-selected="onSelect" > <template #cell(timestamp)="data"> {{ data.item.timestamp }} diff --git a/app/assets/javascripts/tracing/details_index.vue b/app/assets/javascripts/tracing/details_index.vue new file mode 100644 index 00000000000..5702a88766c --- /dev/null +++ b/app/assets/javascripts/tracing/details_index.vue @@ -0,0 +1,49 @@ +<script> +import ObservabilityContainer from '~/observability/components/observability_container.vue'; +import TracingDetails from './components/tracing_details.vue'; + +export default { + components: { + ObservabilityContainer, + TracingDetails, + }, + props: { + traceId: { + type: String, + required: true, + }, + oauthUrl: { + type: String, + required: true, + }, + tracingUrl: { + type: String, + required: true, + }, + provisioningUrl: { + type: String, + required: true, + }, + tracingIndexUrl: { + required: true, + type: String, + }, + }, +}; +</script> + +<template> + <observability-container + :oauth-url="oauthUrl" + :tracing-url="tracingUrl" + :provisioning-url="provisioningUrl" + > + <template #default="{ observabilityClient }"> + <tracing-details + :trace-id="traceId" + :tracing-index-url="tracingIndexUrl" + :observability-client="observabilityClient" + /> + </template> + </observability-container> +</template> diff --git a/app/graphql/mutations/work_items/subscribe.rb b/app/graphql/mutations/work_items/subscribe.rb new file mode 100644 index 00000000000..a29c3416c3d --- /dev/null +++ b/app/graphql/mutations/work_items/subscribe.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +module Mutations + module WorkItems + class Subscribe < BaseMutation + graphql_name 'WorkItemSubscribe' + + argument :id, ::Types::GlobalIDType[::WorkItem], + required: true, + description: 'Global ID of the work item.' + + argument :subscribed, + GraphQL::Types::Boolean, + required: true, + description: 'Desired state of the subscription.' + + field :work_item, Types::WorkItemType, + null: true, + description: 'Work item after mutation.' + + authorize :update_subscription + + def resolve(args) + work_item = authorized_find!(id: args[:id]) + + update_subscription(work_item, args[:subscribed]) + + { + work_item: work_item, + errors: [] + } + end + + private + + def update_subscription(work_item, subscribed_state) + work_item.set_subscription(current_user, subscribed_state, work_item.project) + end + end + end +end diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb index 16c46d172f3..9a6b3861103 100644 --- a/app/graphql/types/mutation_type.rb +++ b/app/graphql/types/mutation_type.rb @@ -183,6 +183,7 @@ module Types mount_mutation Mutations::SavedReplies::Destroy mount_mutation Mutations::Uploads::Delete mount_mutation Mutations::Users::SetNamespaceCommitEmail + mount_mutation Mutations::WorkItems::Subscribe, alpha: { milestone: '16.3' } end end diff --git a/app/models/application_record.rb b/app/models/application_record.rb index 291375f647c..8e5f3f030fb 100644 --- a/app/models/application_record.rb +++ b/app/models/application_record.rb @@ -95,7 +95,7 @@ class ApplicationRecord < ActiveRecord::Base end def self.underscore - Gitlab::SafeRequestStore.fetch("model:#{self}:underscore") { to_s.underscore } + @underscore ||= to_s.underscore end def self.where_exists(query) diff --git a/app/models/todo.rb b/app/models/todo.rb index 2f2e731fc7e..d159b51a0eb 100644 --- a/app/models/todo.rb +++ b/app/models/todo.rb @@ -225,6 +225,10 @@ class Todo < ApplicationRecord action == MEMBER_ACCESS_REQUESTED end + def review_submitted? + action == REVIEW_SUBMITTED + end + def member_access_type target.class.name.downcase end diff --git a/app/services/ci/pipeline_schedules/base_save_service.rb b/app/services/ci/pipeline_schedules/base_save_service.rb new file mode 100644 index 00000000000..45d70e5a65d --- /dev/null +++ b/app/services/ci/pipeline_schedules/base_save_service.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +module Ci + module PipelineSchedules + class BaseSaveService + include Gitlab::Utils::StrongMemoize + + def execute + schedule.assign_attributes(params) + + return forbidden_to_save unless allowed_to_save? + return forbidden_to_save_variables unless allowed_to_save_variables? + + if schedule.save + ServiceResponse.success(payload: schedule) + else + ServiceResponse.error(payload: schedule, message: schedule.errors.full_messages) + end + end + + private + + attr_reader :project, :user, :params, :schedule + + def allowed_to_save? + user.can?(self.class::AUTHORIZE, schedule) + end + + def forbidden_to_save + # We add the error to the base object too + # because model errors are used in the API responses and the `form_errors` helper. + schedule.errors.add(:base, authorize_message) + + ServiceResponse.error(payload: schedule, message: [authorize_message], reason: :forbidden) + end + + def allowed_to_save_variables? + return true if params[:variables_attributes].blank? + + user.can?(:set_pipeline_variables, project) + end + + def forbidden_to_save_variables + message = _('The current user is not authorized to set pipeline schedule variables') + + # We add the error to the base object too + # because model errors are used in the API responses and the `form_errors` helper. + schedule.errors.add(:base, message) + + ServiceResponse.error(payload: schedule, message: [message], reason: :forbidden) + end + end + end +end diff --git a/app/services/ci/pipeline_schedules/create_service.rb b/app/services/ci/pipeline_schedules/create_service.rb index c1825865bc0..23775e68399 100644 --- a/app/services/ci/pipeline_schedules/create_service.rb +++ b/app/services/ci/pipeline_schedules/create_service.rb @@ -2,46 +2,22 @@ module Ci module PipelineSchedules - class CreateService - def initialize(project, user, params) - @project = project - @user = user - @params = params + class CreateService < BaseSaveService + AUTHORIZE = :create_pipeline_schedule + def initialize(project, user, params) @schedule = project.pipeline_schedules.new - end - - def execute - return forbidden unless allowed? - - schedule.assign_attributes(params.merge(owner: user)) - - if schedule.save - ServiceResponse.success(payload: schedule) - else - ServiceResponse.error(payload: schedule, message: schedule.errors.full_messages) - end + @user = user + @project = project + @params = params.merge(owner: user) end private - attr_reader :project, :user, :params, :schedule - - def allowed? - user.can?(:create_pipeline_schedule, schedule) - end - - def forbidden - # We add the error to the base object too - # because model errors are used in the API responses and the `form_errors` helper. - schedule.errors.add(:base, forbidden_message) - - ServiceResponse.error(payload: schedule, message: [forbidden_message], reason: :forbidden) - end - - def forbidden_message + def authorize_message _('The current user is not authorized to create the pipeline schedule') end + strong_memoize_attr :authorize_message end end end diff --git a/app/services/ci/pipeline_schedules/update_service.rb b/app/services/ci/pipeline_schedules/update_service.rb index 28c22e0a868..2fd1173ecce 100644 --- a/app/services/ci/pipeline_schedules/update_service.rb +++ b/app/services/ci/pipeline_schedules/update_service.rb @@ -2,44 +2,22 @@ module Ci module PipelineSchedules - class UpdateService + class UpdateService < BaseSaveService + AUTHORIZE = :update_pipeline_schedule + def initialize(schedule, user, params) @schedule = schedule @user = user + @project = schedule.project @params = params end - def execute - return forbidden unless allowed? - - schedule.assign_attributes(params) - - if schedule.save - ServiceResponse.success(payload: schedule) - else - ServiceResponse.error(message: schedule.errors.full_messages) - end - end - private - attr_reader :schedule, :user, :params - - def allowed? - user.can?(:update_pipeline_schedule, schedule) - end - - def forbidden - # We add the error to the base object too - # because model errors are used in the API responses and the `form_errors` helper. - schedule.errors.add(:base, forbidden_message) - - ServiceResponse.error(message: [forbidden_message], reason: :forbidden) - end - - def forbidden_message + def authorize_message _('The current user is not authorized to update the pipeline schedule') end + strong_memoize_attr :authorize_message end end end diff --git a/app/services/todos/destroy/group_private_service.rb b/app/services/todos/destroy/group_private_service.rb index d7ecbb952aa..60599ca9ca4 100644 --- a/app/services/todos/destroy/group_private_service.rb +++ b/app/services/todos/destroy/group_private_service.rb @@ -24,7 +24,10 @@ module Todos override :authorized_users def authorized_users - group.direct_and_indirect_users.select(:id) + User.from_union([ + group.project_users_with_descendants.select(:id), + group.members_with_parents.select(:user_id) + ], remove_duplicates: false) end override :todos_to_remove? diff --git a/app/views/clusters/clusters/user/_form.html.haml b/app/views/clusters/clusters/user/_form.html.haml index 893271ea982..4ecef4b76ce 100644 --- a/app/views/clusters/clusters/user/_form.html.haml +++ b/app/views/clusters/clusters/user/_form.html.haml @@ -47,7 +47,7 @@ .form-group .form-check - = platform_kubernetes_field.check_box :authorization_type, { data: { qa_selector: 'rbac_checkbox'}, inline: true, class: 'form-check-input' }, 'rbac', 'abac' + = platform_kubernetes_field.check_box :authorization_type, { inline: true, class: 'form-check-input' }, 'rbac', 'abac' = platform_kubernetes_field.label :authorization_type, s_('ClusterIntegration|RBAC-enabled cluster'), class: 'form-check-label label-bold' %small.form-text.text-muted = '%{help_text} %{help_link}'.html_safe % { help_text: rbac_help_text, help_link: rbac_help_link } @@ -73,4 +73,4 @@ = render('clusters/clusters/namespace', platform_field: platform_kubernetes_field) .form-group - = field.submit s_('ClusterIntegration|Add Kubernetes cluster'), pajamas_button: true, data: { qa_selector: 'add_kubernetes_cluster_button' } + = field.submit s_('ClusterIntegration|Add Kubernetes cluster'), pajamas_button: true diff --git a/app/views/dashboard/todos/_todo.html.haml b/app/views/dashboard/todos/_todo.html.haml index e20fccc218a..1cd8015934e 100644 --- a/app/views/dashboard/todos/_todo.html.haml +++ b/app/views/dashboard/todos/_todo.html.haml @@ -48,6 +48,7 @@ = first_line_in_markdown(todo, :body, 125, is_todo: true, project: todo.project, group: todo.group) = render_if_exists "dashboard/todos/diff_summary", local_assigns: { todo: todo } + = render_if_exists "dashboard/todos/review_summary", local_assigns: { todo: todo } .todo-timestamp.gl-white-space-nowrap.gl-sm-ml-3.gl-mt-2.gl-mb-2.gl-sm-my-0.gl-px-2.gl-sm-px-0 %span.todo-timestamp.gl-font-sm.gl-text-secondary |