Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
9cd5033338
commit
d2eb61914a
32 changed files with 324 additions and 156 deletions
|
@ -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}`,
|
||||
});
|
||||
|
|
|
@ -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']),
|
||||
|
|
|
@ -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}`,
|
||||
};
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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}`,
|
||||
};
|
||||
|
|
|
@ -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}`,
|
||||
};
|
||||
|
|
|
@ -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>
|
|
@ -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';
|
||||
|
|
|
@ -39,6 +39,11 @@ export const temporaryConfig = {
|
|||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
__typename: 'LocalWorkItemWeight',
|
||||
type: 'WEIGHT',
|
||||
weight: 0,
|
||||
},
|
||||
];
|
||||
},
|
||||
},
|
||||
|
|
|
@ -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]
|
||||
}
|
||||
|
|
|
@ -14,6 +14,10 @@ query workItem($id: WorkItemID!) {
|
|||
webUrl
|
||||
}
|
||||
}
|
||||
... on LocalWorkItemWeight {
|
||||
type
|
||||
weight
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
26
app/controllers/concerns/zuora_csp.rb
Normal file
26
app/controllers/concerns/zuora_csp.rb
Normal file
|
@ -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
|
|
@ -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]
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
10
app/helpers/work_items_helper.rb
Normal file
10
app/helpers/work_items_helper.rb
Normal file
|
@ -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
|
|
@ -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) }
|
||||
|
|
|
@ -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
|
|
@ -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 }
|
||||
note || 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
|
||||
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)
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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 -->
|
||||
|
||||
|
|
|
@ -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
|
||||
## Create a project template for review
|
||||
|
||||
To contribute a new built-in project template to be distributed with GitLab:
|
||||
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.
|
||||
|
||||
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).
|
||||
## Add the template SVG icon to GitLab SVGs
|
||||
|
||||
## Add the SVG icon to 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.
|
||||
|
||||
If the template you're adding has an SVG icon, you need to first add it to
|
||||
<https://gitlab.com/gitlab-org/gitlab-svgs>:
|
||||
## 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.
|
||||
|
||||
## 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:
|
||||
To update an existing built-in project template:
|
||||
|
||||
- [`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.
|
||||
|
||||
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.
|
||||
|
|
|
@ -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:
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
20
spec/features/users/zuora_csp_spec.rb
Normal file
20
spec/features/users/zuora_csp_spec.rb
Normal file
|
@ -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
|
|
@ -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',
|
||||
},
|
||||
|
|
|
@ -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',
|
||||
});
|
||||
|
|
|
@ -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',
|
||||
});
|
||||
|
|
47
spec/frontend/work_items/components/work_item_weight_spec.js
Normal file
47
spec/frontend/work_items/components/work_item_weight_spec.js
Normal file
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue