diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-06-02 15:09:21 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-06-02 15:09:21 +0300 |
commit | d2eb61914a7ad4667136815d2120a619b6045b58 (patch) | |
tree | e1ccb573a1004f8e08230d778d75fec6a4feaa6e | |
parent | 9cd5033338348e41c06b09211161a32ed9a8b18a (diff) |
Add latest changes from gitlab-org/gitlab@master
32 files changed, 324 insertions, 156 deletions
diff --git a/app/assets/javascripts/issues/show/components/description.vue b/app/assets/javascripts/issues/show/components/description.vue index a1e83591694..d6ea06d172e 100644 --- a/app/assets/javascripts/issues/show/components/description.vue +++ b/app/assets/javascripts/issues/show/components/description.vue @@ -23,6 +23,7 @@ import workItemQuery from '~/work_items/graphql/work_item.query.graphql'; import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import WorkItemDetailModal from '~/work_items/components/work_item_detail_modal.vue'; +import { TRACKING_CATEGORY_SHOW } from '~/work_items/constants'; import CreateWorkItem from '~/work_items/pages/create_work_item.vue'; import animateMixin from '../mixins/animate'; import { convertDescriptionWithNewSort } from '../utils'; @@ -314,7 +315,7 @@ export default { this.workItemId = workItemId; this.updateWorkItemIdUrlQuery(issue); this.track('viewed_work_item_from_modal', { - category: 'workItems:show', + category: TRACKING_CATEGORY_SHOW, label: 'work_item_view', property: `type_${referenceType}`, }); diff --git a/app/assets/javascripts/issues/show/index.js b/app/assets/javascripts/issues/show/index.js index 6b0b26ef2e3..3f149e39c4e 100644 --- a/app/assets/javascripts/issues/show/index.js +++ b/app/assets/javascripts/issues/show/index.js @@ -83,7 +83,7 @@ export function initIssueApp(issueData, store) { bootstrapApollo({ ...issueState, issueType: el.dataset.issueType }); - const { canCreateIncident, ...issueProps } = issueData; + const { canCreateIncident, hasIssueWeightsFeature, ...issueProps } = issueData; return new Vue({ el, @@ -93,6 +93,7 @@ export function initIssueApp(issueData, store) { provide: { canCreateIncident, fullPath, + hasIssueWeightsFeature, }, computed: { ...mapGetters(['getNoteableData']), diff --git a/app/assets/javascripts/work_items/components/work_item_description.vue b/app/assets/javascripts/work_items/components/work_item_description.vue index 60ba71e3260..a4118cc48c4 100644 --- a/app/assets/javascripts/work_items/components/work_item_description.vue +++ b/app/assets/javascripts/work_items/components/work_item_description.vue @@ -1,6 +1,7 @@ <script> import { GlSafeHtmlDirective } from '@gitlab/ui'; import Tracking from '~/tracking'; +import { TRACKING_CATEGORY_SHOW } from '../constants'; export default { directives: { @@ -21,7 +22,7 @@ export default { computed: { tracking() { return { - category: 'workItems:show', + category: TRACKING_CATEGORY_SHOW, label: 'item_description', property: `type_${this.workItem.workItemType.name}`, }; diff --git a/app/assets/javascripts/work_items/components/work_item_detail.vue b/app/assets/javascripts/work_items/components/work_item_detail.vue index a512f87c00a..82ef907bb70 100644 --- a/app/assets/javascripts/work_items/components/work_item_detail.vue +++ b/app/assets/javascripts/work_items/components/work_item_detail.vue @@ -1,7 +1,12 @@ <script> import { GlAlert, GlSkeletonLoader } from '@gitlab/ui'; import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; -import { i18n, WIDGET_TYPE_ASSIGNEE, WIDGET_TYPE_DESCRIPTION } from '../constants'; +import { + i18n, + WIDGET_TYPE_ASSIGNEE, + WIDGET_TYPE_DESCRIPTION, + WIDGET_TYPE_WEIGHT, +} from '../constants'; import workItemQuery from '../graphql/work_item.query.graphql'; import workItemTitleSubscription from '../graphql/work_item_title.subscription.graphql'; import WorkItemActions from './work_item_actions.vue'; @@ -10,6 +15,7 @@ import WorkItemTitle from './work_item_title.vue'; import WorkItemDescription from './work_item_description.vue'; import WorkItemLinks from './work_item_links/work_item_links.vue'; import WorkItemAssignees from './work_item_assignees.vue'; +import WorkItemWeight from './work_item_weight.vue'; export default { i18n, @@ -22,6 +28,7 @@ export default { WorkItemTitle, WorkItemState, WorkItemLinks, + WorkItemWeight, }, mixins: [glFeatureFlagMixin()], props: { @@ -77,12 +84,15 @@ export default { workItemDescription() { return this.workItem?.widgets?.find((widget) => widget.type === WIDGET_TYPE_DESCRIPTION); }, - workItemAssigneesEnabled() { - return this.glFeatures.workItemAssignees; + workItemsMvc2Enabled() { + return this.glFeatures.workItemsMvc2; }, workItemAssignees() { return this.workItem?.mockWidgets?.find((widget) => widget.type === WIDGET_TYPE_ASSIGNEE); }, + workItemWeight() { + return this.workItem?.mockWidgets?.find((widget) => widget.type === WIDGET_TYPE_WEIGHT); + }, }, }; </script> @@ -117,10 +127,10 @@ export default { @error="error = $event" /> </div> - <work-item-assignees - v-if="workItemAssigneesEnabled && workItemAssignees" - :assignees="workItemAssignees.nodes" - /> + <template v-if="workItemsMvc2Enabled"> + <work-item-assignees v-if="workItemAssignees" :assignees="workItemAssignees.nodes" /> + <work-item-weight v-if="workItemWeight" :weight="workItemWeight.weight" /> + </template> <work-item-state :work-item="workItem" @error="error = $event" diff --git a/app/assets/javascripts/work_items/components/work_item_state.vue b/app/assets/javascripts/work_items/components/work_item_state.vue index 51db4c804eb..7e506ba748c 100644 --- a/app/assets/javascripts/work_items/components/work_item_state.vue +++ b/app/assets/javascripts/work_items/components/work_item_state.vue @@ -7,6 +7,7 @@ import { STATE_CLOSED, STATE_EVENT_CLOSE, STATE_EVENT_REOPEN, + TRACKING_CATEGORY_SHOW, } from '../constants'; import updateWorkItemMutation from '../graphql/update_work_item.mutation.graphql'; import ItemState from './item_state.vue'; @@ -33,7 +34,7 @@ export default { }, tracking() { return { - category: 'workItems:show', + category: TRACKING_CATEGORY_SHOW, label: 'item_state', property: `type_${this.workItemType}`, }; diff --git a/app/assets/javascripts/work_items/components/work_item_title.vue b/app/assets/javascripts/work_items/components/work_item_title.vue index d2e6d3c0bbf..cd5363d36c4 100644 --- a/app/assets/javascripts/work_items/components/work_item_title.vue +++ b/app/assets/javascripts/work_items/components/work_item_title.vue @@ -1,6 +1,6 @@ <script> import Tracking from '~/tracking'; -import { i18n } from '../constants'; +import { i18n, TRACKING_CATEGORY_SHOW } from '../constants'; import updateWorkItemMutation from '../graphql/update_work_item.mutation.graphql'; import ItemTitle from './item_title.vue'; @@ -29,7 +29,7 @@ export default { computed: { tracking() { return { - category: 'workItems:show', + category: TRACKING_CATEGORY_SHOW, label: 'item_title', property: `type_${this.workItemType}`, }; diff --git a/app/assets/javascripts/work_items/components/work_item_weight.vue b/app/assets/javascripts/work_items/components/work_item_weight.vue new file mode 100644 index 00000000000..b0f2b3aa14a --- /dev/null +++ b/app/assets/javascripts/work_items/components/work_item_weight.vue @@ -0,0 +1,26 @@ +<script> +import { __ } from '~/locale'; + +export default { + inject: ['hasIssueWeightsFeature'], + props: { + weight: { + type: Number, + required: false, + default: undefined, + }, + }, + computed: { + weightText() { + return this.weight ?? __('None'); + }, + }, +}; +</script> + +<template> + <div v-if="hasIssueWeightsFeature" class="gl-mb-5"> + <span class="gl-display-inline-block gl-font-weight-bold gl-w-15">{{ __('Weight') }}</span> + {{ weightText }} + </div> +</template> diff --git a/app/assets/javascripts/work_items/constants.js b/app/assets/javascripts/work_items/constants.js index 7e5beafa8db..d3bc314fa58 100644 --- a/app/assets/javascripts/work_items/constants.js +++ b/app/assets/javascripts/work_items/constants.js @@ -6,6 +6,8 @@ export const STATE_CLOSED = 'CLOSED'; export const STATE_EVENT_REOPEN = 'REOPEN'; export const STATE_EVENT_CLOSE = 'CLOSE'; +export const TRACKING_CATEGORY_SHOW = 'workItems:show'; + export const i18n = { fetchError: s__('WorkItem|Something went wrong when fetching the work item. Please try again.'), updateError: s__('WorkItem|Something went wrong while updating the work item. Please try again.'), @@ -15,3 +17,4 @@ export const DEFAULT_MODAL_TYPE = 'Task'; export const WIDGET_TYPE_ASSIGNEE = 'ASSIGNEES'; export const WIDGET_TYPE_DESCRIPTION = 'DESCRIPTION'; +export const WIDGET_TYPE_WEIGHT = 'WEIGHT'; diff --git a/app/assets/javascripts/work_items/graphql/provider.js b/app/assets/javascripts/work_items/graphql/provider.js index aa30094f345..e393354705b 100644 --- a/app/assets/javascripts/work_items/graphql/provider.js +++ b/app/assets/javascripts/work_items/graphql/provider.js @@ -39,6 +39,11 @@ export const temporaryConfig = { }, ], }, + { + __typename: 'LocalWorkItemWeight', + type: 'WEIGHT', + weight: 0, + }, ]; }, }, diff --git a/app/assets/javascripts/work_items/graphql/typedefs.graphql b/app/assets/javascripts/work_items/graphql/typedefs.graphql index 0e2c8593a04..68a0986f411 100644 --- a/app/assets/javascripts/work_items/graphql/typedefs.graphql +++ b/app/assets/javascripts/work_items/graphql/typedefs.graphql @@ -1,5 +1,6 @@ enum LocalWidgetType { ASSIGNEES + WEIGHT } interface LocalWorkItemWidget { @@ -11,6 +12,11 @@ type LocalWorkItemAssignees implements LocalWorkItemWidget { nodes: [UserCore] } +type LocalWorkItemWeight implements LocalWorkItemWidget { + type: LocalWidgetType! + weight: Int +} + extend type WorkItem { mockWidgets: [LocalWorkItemWidget] } diff --git a/app/assets/javascripts/work_items/graphql/work_item.query.graphql b/app/assets/javascripts/work_items/graphql/work_item.query.graphql index ff9341c4c0c..30bc61f5c59 100644 --- a/app/assets/javascripts/work_items/graphql/work_item.query.graphql +++ b/app/assets/javascripts/work_items/graphql/work_item.query.graphql @@ -14,6 +14,10 @@ query workItem($id: WorkItemID!) { webUrl } } + ... on LocalWorkItemWeight { + type + weight + } } } } diff --git a/app/assets/javascripts/work_items/index.js b/app/assets/javascripts/work_items/index.js index e39b0d6a353..33e28831b54 100644 --- a/app/assets/javascripts/work_items/index.js +++ b/app/assets/javascripts/work_items/index.js @@ -1,11 +1,12 @@ import Vue from 'vue'; +import { parseBoolean } from '~/lib/utils/common_utils'; import App from './components/app.vue'; import { createRouter } from './router'; import { createApolloProvider } from './graphql/provider'; export const initWorkItemsRoot = () => { const el = document.querySelector('#js-work-items'); - const { fullPath, issuesListPath } = el.dataset; + const { fullPath, hasIssueWeightsFeature, issuesListPath } = el.dataset; return new Vue({ el, @@ -13,6 +14,7 @@ export const initWorkItemsRoot = () => { apolloProvider: createApolloProvider(), provide: { fullPath, + hasIssueWeightsFeature: parseBoolean(hasIssueWeightsFeature), issuesListPath, }, render(createElement) { diff --git a/app/controllers/concerns/zuora_csp.rb b/app/controllers/concerns/zuora_csp.rb new file mode 100644 index 00000000000..5f9be11d7b9 --- /dev/null +++ b/app/controllers/concerns/zuora_csp.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module ZuoraCSP + extend ActiveSupport::Concern + + ZUORA_URL = 'https://*.zuora.com' + + included do + content_security_policy do |policy| + next if policy.directives.blank? + + default_script_src = policy.directives['script-src'] || policy.directives['default-src'] + script_src_values = Array.wrap(default_script_src) | ["'self'", "'unsafe-eval'", ZUORA_URL] + + default_frame_src = policy.directives['frame-src'] || policy.directives['default-src'] + frame_src_values = Array.wrap(default_frame_src) | ["'self'", ZUORA_URL] + + default_child_src = policy.directives['child-src'] || policy.directives['default-src'] + child_src_values = Array.wrap(default_child_src) | ["'self'", ZUORA_URL] + + policy.script_src(*script_src_values) + policy.frame_src(*frame_src_values) + policy.child_src(*child_src_values) + end + end +end diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index acca85c0bf9..3f4337a5dd9 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -49,7 +49,7 @@ class Projects::IssuesController < Projects::ApplicationController push_frontend_feature_flag(:paginated_issue_discussions, project) push_frontend_feature_flag(:realtime_labels, project) push_force_frontend_feature_flag(:work_items, project&.work_items_feature_flag_enabled?) - push_frontend_feature_flag(:work_item_assignees) + push_frontend_feature_flag(:work_items_mvc_2) end around_action :allow_gitaly_ref_name_caching, only: [:discussions] diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb index cab055cebca..ac9a9a376dd 100644 --- a/app/controllers/projects/pipelines_controller.rb +++ b/app/controllers/projects/pipelines_controller.rb @@ -4,6 +4,7 @@ class Projects::PipelinesController < Projects::ApplicationController include ::Gitlab::Utils::StrongMemoize include RedisTracking include ProjectStatsRefreshConflictsGuard + include ZuoraCSP urgency :low, [ :index, :new, :builds, :show, :failures, :create, @@ -43,23 +44,6 @@ class Projects::PipelinesController < Projects::ApplicationController POLLING_INTERVAL = 10_000 - content_security_policy do |policy| - next if policy.directives.blank? - - default_script_src = policy.directives['script-src'] || policy.directives['default-src'] - script_src_values = Array.wrap(default_script_src) | ["'self'", "'unsafe-eval'", 'https://*.zuora.com'] - - default_frame_src = policy.directives['frame-src'] || policy.directives['default-src'] - frame_src_values = Array.wrap(default_frame_src) | ["'self'", 'https://*.zuora.com'] - - default_child_src = policy.directives['child-src'] || policy.directives['default-src'] - child_src_values = Array.wrap(default_child_src) | ["'self'", 'https://*.zuora.com'] - - policy.script_src(*script_src_values) - policy.frame_src(*frame_src_values) - policy.child_src(*child_src_values) - end - feature_category :continuous_integration, [ :charts, :show, :config_variables, :stage, :cancel, :retry, :builds, :dag, :failures, :status, diff --git a/app/controllers/projects/work_items_controller.rb b/app/controllers/projects/work_items_controller.rb index 6db83a19d43..ba23af41bb0 100644 --- a/app/controllers/projects/work_items_controller.rb +++ b/app/controllers/projects/work_items_controller.rb @@ -3,7 +3,7 @@ class Projects::WorkItemsController < Projects::ApplicationController before_action do push_force_frontend_feature_flag(:work_items, project&.work_items_feature_flag_enabled?) - push_frontend_feature_flag(:work_item_assignees) + push_frontend_feature_flag(:work_items_mvc_2) push_frontend_feature_flag(:work_items_hierarchy, project) end diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 5e1d80a4b09..56b919dddba 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -42,7 +42,7 @@ class ProjectsController < Projects::ApplicationController push_licensed_feature(:file_locks) if @project.present? && @project.licensed_feature_available?(:file_locks) push_licensed_feature(:security_orchestration_policies) if @project.present? && @project.licensed_feature_available?(:security_orchestration_policies) push_force_frontend_feature_flag(:work_items, @project&.work_items_feature_flag_enabled?) - push_frontend_feature_flag(:work_item_assignees) + push_frontend_feature_flag(:work_items_mvc_2) push_frontend_feature_flag(:package_registry_access_level) end diff --git a/app/helpers/work_items_helper.rb b/app/helpers/work_items_helper.rb new file mode 100644 index 00000000000..2c61fc20ca8 --- /dev/null +++ b/app/helpers/work_items_helper.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module WorkItemsHelper + def work_items_index_data(project) + { + full_path: project.full_path, + issues_list_path: project_issues_path(project) + } + end +end diff --git a/app/views/projects/work_items/index.html.haml b/app/views/projects/work_items/index.html.haml index 356f93c6ed5..1f36afc48aa 100644 --- a/app/views/projects/work_items/index.html.haml +++ b/app/views/projects/work_items/index.html.haml @@ -1,3 +1,3 @@ - page_title s_('WorkItem|Work Items') -#js-work-items{ data: { full_path: @project.full_path, issues_list_path: project_issues_path(@project) } } +#js-work-items{ data: work_items_index_data(@project) } diff --git a/config/feature_flags/development/work_item_assignees.yml b/config/feature_flags/development/work_items_mvc_2.yml index 16b4f7c2dd1..871c3d3a82c 100644 --- a/config/feature_flags/development/work_item_assignees.yml +++ b/config/feature_flags/development/work_items_mvc_2.yml @@ -1,6 +1,6 @@ --- -name: work_item_assignees -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/88003 +name: work_items_mvc_2 +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/89028 rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/363030 milestone: '15.1' type: development diff --git a/danger/roulette/Dangerfile b/danger/roulette/Dangerfile index ba2d50657e0..527cdf58391 100644 --- a/danger/roulette/Dangerfile +++ b/danger/roulette/Dangerfile @@ -60,29 +60,22 @@ NOT_AVAILABLE_TEMPLATES = { integrations_fe: group_not_available_template('#g_ecosystem_integrations', '@gitlab-org/ecosystem-stage/integrations') }.freeze -def note_for_spins_role(spins, role, category) +def note_for_spin_role(spin, role, category) template = NOT_AVAILABLE_TEMPLATES[category] || NOT_AVAILABLE_TEMPLATES[:default] - spins.each do |spin| - note = note_for_spin_role(spin, role) + note = + if spin.optional_role == role + OPTIONAL_REVIEW_TEMPLATE % { role: role.capitalize, category: helper.label_for_category(spin.category) } + else + spin.public_send(role)&.markdown_name(author: roulette.team_mr_author) # rubocop:disable GitlabSecurity/PublicSend + end - return note if note - end - - template % { role: role } -end - -def note_for_spin_role(spin, role) - if spin.optional_role == role - return OPTIONAL_REVIEW_TEMPLATE % { role: role.capitalize, category: helper.label_for_category(spin.category) } - end - - spin.public_send(role)&.markdown_name(author: roulette.team_mr_author) # rubocop:disable GitlabSecurity/PublicSend + note || template % { role: role } end -def markdown_row_for_spins(category, spins_array) - maintainer_note = note_for_spins_role(spins_array, :maintainer, category) - reviewer_note = note_for_spins_role(spins_array, :reviewer, category) +def markdown_row_for_spin(category, spin) + maintainer_note = note_for_spin_role(spin, :maintainer, category) + reviewer_note = note_for_spin_role(spin, :reviewer, category) "| #{helper.label_for_category(category)} | #{reviewer_note} | #{maintainer_note} |" end @@ -115,7 +108,7 @@ if changes.any? random_roulette_spins = roulette.spin(nil, categories, timezone_experiment: false) rows = random_roulette_spins.map do |spin| - markdown_row_for_spins(spin.category, [spin]) + markdown_row_for_spin(spin.category, spin) end markdown(REVIEW_ROULETTE_SECTION) diff --git a/doc/architecture/blueprints/ci_data_decay/index.md b/doc/architecture/blueprints/ci_data_decay/index.md index ed202d00700..8808a526df0 100644 --- a/doc/architecture/blueprints/ci_data_decay/index.md +++ b/doc/architecture/blueprints/ci_data_decay/index.md @@ -230,6 +230,22 @@ All three tracks can be worked on in parallel: In progress. +## Timeline + +- 2021-01-21: Parent [CI Scaling](../ci_scale/) blueprint [merge request](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/52203) created. +- 2021-04-26: CI Scaling blueprint approved and merged. +- 2021-09-10: CI/CD data time decay blueprint discussions started. +- 2022-01-07: CI/CD data time decay blueprint [merged](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/70052). +- 2022-02-01: Blueprint [updated](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/79110) with new content and links to epics. +- 2022-02-08: Pipeline partitioning PoC [merge request](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/80186) started. +- 2022-02-23: Pipeline partitioning PoC [successful](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/80186#note_852704724) +- 2022-03-07: A way to attach an existing table as a partition [found and proven](https://gitlab.com/gitlab-org/gitlab/-/issues/353380#note_865237214). +- 2022-03-23: Pipeline partitioning design [Google Doc](https://docs.google.com/document/d/1ARdoTZDy4qLGf6Z1GIHh83-stG_ZLpqsibjKr_OXMgc) started. +- 2022-03-29: Pipeline partitioning PoC [concluded](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/80186#note_892674358). +- 2022-04-15: Partitioned pipeline data associations PoC [shipped](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/84071). +- 2022-04-30: Additional [benchmarking started](https://gitlab.com/gitlab-org/gitlab/-/issues/361019) to evaluate impact. +- 2022-06-31: [Pipeline partitioning design](pipeline_partitioning.md) document [merge request](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/87683) merged. + ## Who Proposal: diff --git a/doc/ci/pipelines/settings.md b/doc/ci/pipelines/settings.md index 16fbadee2d7..e409b63e098 100644 --- a/doc/ci/pipelines/settings.md +++ b/doc/ci/pipelines/settings.md @@ -287,6 +287,7 @@ Use this regex for commonly used test tools. - .NET (OpenCover). Example: `(Visited Points).*\((.*)\)`. - .NET (`dotnet test` line coverage). Example: `Total\s*\|\s*(\d+(?:\.\d+)?)`. - tarpaulin (Rust). Example: `^\d+.\d+% coverage`. +- Pester (PowerShell). Example: `Covered (\d+\.\d+%)`. <!-- vale gitlab.Spelling = YES --> diff --git a/doc/development/project_templates.md b/doc/development/project_templates.md index 74ded9c93fc..f688d54ad4f 100644 --- a/doc/development/project_templates.md +++ b/doc/development/project_templates.md @@ -4,59 +4,56 @@ group: Workspace info: "To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments" --- -# Contribute to GitLab project templates +# Contribute a built-in project template -Thanks for considering a contribution to the GitLab -[built-in project templates](../user/project/working_with_projects.md#create-a-project-from-a-built-in-template). +This page provides instructions about how to contribute a +[built-in project template](../user/project/working_with_projects.md#create-a-project-from-a-built-in-template). + +To contribute a built-in project template, you must complete the following tasks: + +1. [Create a project template for GitLab review](#create-a-project-template-for-review) +1. [Add the template SVG icon to GitLab SVGs](#add-the-template-svg-icon-to-gitlab-svgs) +1. [Create a merge request with vendor details](#create-a-merge-request-with-vendor-details) + +You can contribute the following types of project templates: + +- Enterprise: For users with GitLab Premium and above. +- Non-enterprise: For users with GitLab Free and above. ## Prerequisites -To add a new or update an existing template, you must have the following tools +To add or update an existing template, you must have the following tools installed: - `wget` - `tar` -- `jq` - -## Create a new project -To contribute a new built-in project template to be distributed with GitLab: +## Create a project template for review -1. Create a new public project with the project content you'd like to contribute - in a namespace of your choosing. You can [view a working example](https://gitlab.com/gitlab-org/project-templates/dotnetcore). - Projects should be as simple as possible and free of any unnecessary assets or dependencies. -1. When the project is ready for review, [create a new issue](https://gitlab.com/gitlab-org/gitlab/issues) with a link to your project. - In your issue, `@` mention the relevant Backend Engineering Manager and Product - Manager for the [Templates feature](https://about.gitlab.com/handbook/product/categories/#source-code-group). +1. In your selected namespace, create a public project. +1. Add the project content you want to use in the template. Do not include unnecessary assets or dependencies. For an example, +[see this project](https://gitlab.com/gitlab-org/project-templates/dotnetcore). +1. When the project is ready for review, [create an issue](https://gitlab.com/gitlab-org/gitlab/issues) with a link to your project. + In your issue, mention the relevant [Backend Engineering Manager and Product Manager](https://about.gitlab.com/handbook/product/categories/#source-code-group) + for the Templates feature. -## Add the SVG icon to GitLab SVGs +## Add the template SVG icon to GitLab SVGs -If the template you're adding has an SVG icon, you need to first add it to -<https://gitlab.com/gitlab-org/gitlab-svgs>: +If the project template has an SVG icon, you must add it to the +[GitLab SVGs project](https://gitlab.com/gitlab-org/gitlab-svgs/-/blob/main/README.md#adding-icons-or-illustrations) +before you can create a merge request with vendor details. -1. Follow the steps outlined in the - [GitLab SVGs project](https://gitlab.com/gitlab-org/gitlab-svgs/-/blob/main/README.md#adding-icons-or-illustrations) - and submit a merge request. -1. When the merge request is merged, `gitlab-bot` will pull the new changes in - the `gitlab-org/gitlab` project. -1. You can now continue on the vendoring process. +## Create a merge request with vendor details -## Vendoring process - -To make the project template available when creating a new project, the vendoring -process will have to be completed: +Before GitLab can implement the project template, you must [create a merge request](../user/project/merge_requests/creating_merge_requests.md) in [`gitlab-org/gitlab`](https://gitlab.com/gitlab-org/gitlab) that includes vendor details about the project. 1. [Export the project](../user/project/settings/import_export.md#export-a-project-and-its-data) - you created in the previous step and save the file as `<name>.tar.gz`, where - `<name>` is the short name of the project. -1. Edit the following files to include the project template. Two types of built-in - templates are available within GitLab: - - **Normal templates**: Available in GitLab Free and above (this is the most common type of built-in template). - See MR [!25318](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/25318) for an example. - - To add a normal template: - - 1. Open `lib/gitlab/project_template.rb` and add details of the template + and save the file as `<name>.tar.gz`, where `<name>` is the short name of the project. + Move this file to the root directory of `gitlab-org/gitlab`. +1. In `gitlab-org/gitlab`, create and checkout a new branch. +1. Edit the following files to include the project template: + - For **non-Enterprise** project templates: + - In `lib/gitlab/project_template.rb`, add details about the template in the `localized_templates_table` method. In the following example, the short name of the project is `hugo`: @@ -64,11 +61,11 @@ process will have to be completed: ProjectTemplate.new('hugo', 'Pages/Hugo', _('Everything you need to create a GitLab Pages site using Hugo'), 'https://gitlab.com/pages/hugo', 'illustrations/logos/hugo.svg'), ``` - If the vendored project doesn't have an SVG icon, omit `, 'illustrations/logos/hugo.svg'`. + If the project doesn't have an SVG icon, exclude `, 'illustrations/logos/hugo.svg'`. - 1. Open `spec/lib/gitlab/project_template_spec.rb` and add the short name - of the template in the `.all` test. - 1. Open `app/assets/javascripts/projects/default_project_templates.js` and + - In `spec/support/helpers/project_template_test_helper.rb`, append the short name + of the template in the `all_templates` method. + - In `app/assets/javascripts/projects/default_project_templates.js`, add details of the template. For example: ```javascript @@ -78,25 +75,19 @@ process will have to be completed: }, ``` - If the vendored project doesn't have an SVG icon, use `.icon-gitlab_logo` + If the project doesn't have an SVG icon, use `.icon-gitlab_logo` instead. - - - **Enterprise templates**: Introduced in GitLab 12.10, that are available only in GitLab Premium and above. - See MR [!28187](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/28187) for an example. - - To add an Enterprise template: - - 1. Open `ee/lib/ee/gitlab/project_template.rb` and add details of the template - in the `localized_ee_templates_table` method. For example: + - For **Enterprise** project templates: + - In `ee/lib/ee/gitlab/project_template.rb`, in the `localized_ee_templates_table` method, add details about the template. For example: ```ruby ::Gitlab::ProjectTemplate.new('hipaa_audit_protocol', 'HIPAA Audit Protocol', _('A project containing issues for each audit inquiry in the HIPAA Audit Protocol published by the U.S. Department of Health & Human Services'), 'https://gitlab.com/gitlab-org/project-templates/hipaa-audit-protocol', 'illustrations/logos/asklepian.svg') ``` - 1. Open `ee/spec/lib/gitlab/project_template_spec.rb` and add the short name + - In `ee/spec/lib/gitlab/project_template_spec.rb`, add the short name of the template in the `.all` test. - 1. Open `ee/app/assets/javascripts/projects/default_project_templates.js` and - add details of the template. For example: + - In `ee/app/assets/javascripts/projects/default_project_templates.js`, + add the template details. For example: ```javascript hipaa_audit_protocol: { @@ -105,10 +96,11 @@ process will have to be completed: }, ``` -1. Run the `vendor_template` script. Make sure to pass the correct arguments: +1. Run the following Rake task, where `<path>/<name>` is the + name you gave the template in `lib/gitlab/project_template.rb`: ```shell - scripts/vendor_template <git_repo_url> <name> <comment> + bin/rake gitlab:update_project_templates\[<path>/<name>\] ``` 1. Regenerate `gitlab.pot`: @@ -117,41 +109,24 @@ process will have to be completed: bin/rake gettext:regenerate ``` -1. By now, there should be one new file under `vendor/project_templates/` and - 4 changed files. Commit all of them in a new branch and create a merge - request. +1. After you run the scripts, there is one new file in `vendor/project_templates/` and four changed files. Commit all changes and push your branch to update the merge request. For an example, see this [merge request](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/25318). -## Test with GDK +## Test your built-in project with the GitLab Development Kit -If you are using the GitLab Development Kit (GDK) you must disable `praefect` -and regenerate the Procfile, as the Rake task is not currently compatible with it: +Complete the following steps to test the project template in your own GitLab Development Kit instance: -```yaml -# gitlab-development-kit/gdk.yml -praefect: - enabled: false -``` - -1. Follow the steps described in the [vendoring process](#vendoring-process). -1. Run the following Rake task where `<path>/<name>` is the +1. Run the following Rake task, where `<path>/<name>` is the name you gave the template in `lib/gitlab/project_template.rb`: ```shell - bin/rake gitlab:update_project_templates[<path>/<name>] + bin/rake gitlab:update_project_templates\[<path>/<name>\] ``` -You can now test to create a new project by importing the new template in GDK. - ## Contribute an improvement to an existing template -Existing templates are imported from the following groups: - -- [`project-templates`](https://gitlab.com/gitlab-org/project-templates) -- [`pages`](htps://gitlab.com/pages) - -To contribute a change, open a merge request in the relevant project -and mention `@gitlab-org/manage/import/backend` when you are ready for a review. +To update an existing built-in project template: -Then, if your merge request gets accepted, either [open an issue](https://gitlab.com/gitlab-org/gitlab/-/issues) -to ask for it to get updated, or open a merge request updating -the [vendored template](#vendoring-process). +1. Create a merge request in the relevant project of the `project-templates` and `pages` group and mention `@gitlab-org/manage/import/backend` when you are ready for a review. +1. If your merge request is accepted, either: + - [Create an issue](https://gitlab.com/gitlab-org/gitlab/-/issues) to ask for the template to get updated. + - [Create a merge request with vendor details](#create-a-merge-request-with-vendor-details) to update the template. diff --git a/doc/user/project/working_with_projects.md b/doc/user/project/working_with_projects.md index 87e3c7f3311..c239b28411b 100644 --- a/doc/user/project/working_with_projects.md +++ b/doc/user/project/working_with_projects.md @@ -115,7 +115,7 @@ Built-in templates are sourced from the following groups: - [`project-templates`](https://gitlab.com/gitlab-org/project-templates) - [`pages`](https://gitlab.com/pages) -Anyone can contribute a built-in template by following [these steps](https://about.gitlab.com/community/contribute/project-templates/). +Anyone can [contribute a built-in template](../../development/project_templates.md). To create a project from a built-in template: diff --git a/qa/tasks/knapsack.rake b/qa/tasks/knapsack.rake index 2ffc18eb3d5..fe9a9c4586f 100644 --- a/qa/tasks/knapsack.rake +++ b/qa/tasks/knapsack.rake @@ -34,7 +34,7 @@ namespace :knapsack do desc "Report long running spec files" task :notify_long_running_specs do - QA::Support::LongRunningSpecReporter.execute + QA::Tools::LongRunningSpecReporter.execute end end # rubocop:enable Rails/RakeEnvironment diff --git a/spec/features/users/zuora_csp_spec.rb b/spec/features/users/zuora_csp_spec.rb new file mode 100644 index 00000000000..f3fd27d6495 --- /dev/null +++ b/spec/features/users/zuora_csp_spec.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Zuora content security policy' do + let(:user) { create(:user) } + let(:project) { create(:project) } + let(:pipeline) { create(:ci_pipeline, project: project) } + + before do + project.add_developer(user) + sign_in(user) + end + + it 'has proper Content Security Policy headers' do + visit pipeline_path(pipeline) + + expect(response_headers['Content-Security-Policy']).to include('https://*.zuora.com') + end +end diff --git a/spec/frontend/issues/show/components/description_spec.js b/spec/frontend/issues/show/components/description_spec.js index 1ae04531a6b..2cc27309e59 100644 --- a/spec/frontend/issues/show/components/description_spec.js +++ b/spec/frontend/issues/show/components/description_spec.js @@ -17,6 +17,7 @@ import { updateHistory } from '~/lib/utils/url_utility'; import workItemQuery from '~/work_items/graphql/work_item.query.graphql'; import TaskList from '~/task_list'; import WorkItemDetailModal from '~/work_items/components/work_item_detail_modal.vue'; +import { TRACKING_CATEGORY_SHOW } from '~/work_items/constants'; import CreateWorkItem from '~/work_items/pages/create_work_item.vue'; import { descriptionProps as initialProps, @@ -370,10 +371,10 @@ describe('Description component', () => { await findTaskLink().trigger('click'); expect(trackingSpy).toHaveBeenCalledWith( - 'workItems:show', + TRACKING_CATEGORY_SHOW, 'viewed_work_item_from_modal', { - category: 'workItems:show', + category: TRACKING_CATEGORY_SHOW, label: 'work_item_view', property: 'type_task', }, diff --git a/spec/frontend/work_items/components/work_item_state_spec.js b/spec/frontend/work_items/components/work_item_state_spec.js index 9e48f56d9e9..0e4b73933b1 100644 --- a/spec/frontend/work_items/components/work_item_state_spec.js +++ b/spec/frontend/work_items/components/work_item_state_spec.js @@ -12,6 +12,7 @@ import { STATE_CLOSED, STATE_EVENT_CLOSE, STATE_EVENT_REOPEN, + TRACKING_CATEGORY_SHOW, } from '~/work_items/constants'; import updateWorkItemMutation from '~/work_items/graphql/update_work_item.mutation.graphql'; import { updateWorkItemMutationResponse, workItemQueryResponse } from '../mock_data'; @@ -107,8 +108,8 @@ describe('WorkItemState component', () => { findItemState().vm.$emit('changed', STATE_CLOSED); await waitForPromises(); - expect(trackingSpy).toHaveBeenCalledWith('workItems:show', 'updated_state', { - category: 'workItems:show', + expect(trackingSpy).toHaveBeenCalledWith(TRACKING_CATEGORY_SHOW, 'updated_state', { + category: TRACKING_CATEGORY_SHOW, label: 'item_state', property: 'type_Task', }); diff --git a/spec/frontend/work_items/components/work_item_title_spec.js b/spec/frontend/work_items/components/work_item_title_spec.js index 19b56362ac0..168a742090b 100644 --- a/spec/frontend/work_items/components/work_item_title_spec.js +++ b/spec/frontend/work_items/components/work_item_title_spec.js @@ -6,7 +6,7 @@ import { mockTracking } from 'helpers/tracking_helper'; import waitForPromises from 'helpers/wait_for_promises'; import ItemTitle from '~/work_items/components/item_title.vue'; import WorkItemTitle from '~/work_items/components/work_item_title.vue'; -import { i18n } from '~/work_items/constants'; +import { i18n, TRACKING_CATEGORY_SHOW } from '~/work_items/constants'; import updateWorkItemMutation from '~/work_items/graphql/update_work_item.mutation.graphql'; import { updateWorkItemMutationResponse, workItemQueryResponse } from '../mock_data'; @@ -91,8 +91,8 @@ describe('WorkItemTitle component', () => { findItemTitle().vm.$emit('title-changed', 'new title'); await waitForPromises(); - expect(trackingSpy).toHaveBeenCalledWith('workItems:show', 'updated_title', { - category: 'workItems:show', + expect(trackingSpy).toHaveBeenCalledWith(TRACKING_CATEGORY_SHOW, 'updated_title', { + category: TRACKING_CATEGORY_SHOW, label: 'item_title', property: 'type_Task', }); diff --git a/spec/frontend/work_items/components/work_item_weight_spec.js b/spec/frontend/work_items/components/work_item_weight_spec.js new file mode 100644 index 00000000000..80a1d032ad7 --- /dev/null +++ b/spec/frontend/work_items/components/work_item_weight_spec.js @@ -0,0 +1,47 @@ +import { shallowMount } from '@vue/test-utils'; +import WorkItemWeight from '~/work_items/components/work_item_weight.vue'; + +describe('WorkItemAssignees component', () => { + let wrapper; + + const createComponent = ({ weight, hasIssueWeightsFeature = true } = {}) => { + wrapper = shallowMount(WorkItemWeight, { + propsData: { + weight, + }, + provide: { + hasIssueWeightsFeature, + }, + }); + }; + + describe('weight licensed feature', () => { + describe.each` + description | hasIssueWeightsFeature | exists + ${'when available'} | ${true} | ${true} + ${'when not available'} | ${false} | ${false} + `('$description', ({ hasIssueWeightsFeature, exists }) => { + it(hasIssueWeightsFeature ? 'renders component' : 'does not render component', () => { + createComponent({ hasIssueWeightsFeature }); + + expect(wrapper.find('div').exists()).toBe(exists); + }); + }); + }); + + describe('weight text', () => { + describe.each` + description | weight | text + ${'renders 1'} | ${1} | ${'1'} + ${'renders 0'} | ${0} | ${'0'} + ${'renders None'} | ${null} | ${'None'} + ${'renders None'} | ${undefined} | ${'None'} + `('when weight is $weight', ({ description, weight, text }) => { + it(description, () => { + createComponent({ weight }); + + expect(wrapper.text()).toContain(text); + }); + }); + }); +}); diff --git a/spec/frontend/work_items/pages/work_item_detail_spec.js b/spec/frontend/work_items/pages/work_item_detail_spec.js index fe13f8945f5..33cf2636dd5 100644 --- a/spec/frontend/work_items/pages/work_item_detail_spec.js +++ b/spec/frontend/work_items/pages/work_item_detail_spec.js @@ -9,6 +9,7 @@ import WorkItemDescription from '~/work_items/components/work_item_description.v import WorkItemState from '~/work_items/components/work_item_state.vue'; import WorkItemTitle from '~/work_items/components/work_item_title.vue'; import WorkItemAssignees from '~/work_items/components/work_item_assignees.vue'; +import WorkItemWeight from '~/work_items/components/work_item_weight.vue'; import { i18n } from '~/work_items/constants'; import workItemQuery from '~/work_items/graphql/work_item.query.graphql'; import workItemTitleSubscription from '~/work_items/graphql/work_item_title.subscription.graphql'; @@ -29,13 +30,14 @@ describe('WorkItemDetail component', () => { const findWorkItemState = () => wrapper.findComponent(WorkItemState); const findWorkItemDescription = () => wrapper.findComponent(WorkItemDescription); const findWorkItemAssignees = () => wrapper.findComponent(WorkItemAssignees); + const findWorkItemWeight = () => wrapper.findComponent(WorkItemWeight); const createComponent = ({ workItemId = workItemQueryResponse.data.workItem.id, handler = successHandler, subscriptionHandler = initialSubscriptionHandler, - assigneesEnabled = false, - includeAssigneesWidget = false, + workItemsMvc2Enabled = false, + includeWidgets = false, } = {}) => { wrapper = shallowMount(WorkItemDetail, { apolloProvider: createMockApollo( @@ -45,13 +47,13 @@ describe('WorkItemDetail component', () => { ], {}, { - typePolicies: includeAssigneesWidget ? temporaryConfig.cacheConfig.typePolicies : {}, + typePolicies: includeWidgets ? temporaryConfig.cacheConfig.typePolicies : {}, }, ), propsData: { workItemId }, provide: { glFeatures: { - workItemAssignees: assigneesEnabled, + workItemsMvc2: workItemsMvc2Enabled, }, }, }); @@ -153,11 +155,11 @@ describe('WorkItemDetail component', () => { expect(wrapper.emitted('workItemUpdated')).toEqual([[], []]); }); - describe('when assignees feature flag is enabled', () => { + describe('when work_items_mvc_2 feature flag is enabled', () => { it('renders assignees component when assignees widget is returned from the API', async () => { createComponent({ - assigneesEnabled: true, - includeAssigneesWidget: true, + workItemsMvc2Enabled: true, + includeWidgets: true, }); await waitForPromises(); @@ -166,8 +168,8 @@ describe('WorkItemDetail component', () => { it('does not render assignees component when assignees widget is not returned from the API', async () => { createComponent({ - assigneesEnabled: true, - includeAssigneesWidget: false, + workItemsMvc2Enabled: true, + includeWidgets: false, }); await waitForPromises(); @@ -181,4 +183,36 @@ describe('WorkItemDetail component', () => { expect(findWorkItemAssignees().exists()).toBe(false); }); + + describe('weight widget', () => { + describe('when work_items_mvc_2 feature flag is enabled', () => { + describe.each` + description | includeWidgets | exists + ${'when widget is returned from API'} | ${true} | ${true} + ${'when widget is not returned from API'} | ${false} | ${false} + `('$description', ({ includeWidgets, exists }) => { + it(`${includeWidgets ? 'renders' : 'does not render'} weight component`, async () => { + createComponent({ includeWidgets, workItemsMvc2Enabled: true }); + await waitForPromises(); + + expect(findWorkItemWeight().exists()).toBe(exists); + }); + }); + }); + + describe('when work_items_mvc_2 feature flag is disabled', () => { + describe.each` + description | includeWidgets | exists + ${'when widget is returned from API'} | ${true} | ${false} + ${'when widget is not returned from API'} | ${false} | ${false} + `('$description', ({ includeWidgets, exists }) => { + it(`${includeWidgets ? 'renders' : 'does not render'} weight component`, async () => { + createComponent({ includeWidgets, workItemsMvc2Enabled: false }); + await waitForPromises(); + + expect(findWorkItemWeight().exists()).toBe(exists); + }); + }); + }); + }); }); |