Commit 606f6caa authored by Alexandru Croitor's avatar Alexandru Croitor

Rolldown issue and merge request templates to projects in the group

Description templates are a variation of file templates, however
description templates were only visible at the project level in which
those were defined.

This implements the ability to inherit description templates from
projects that define description templates and are set as template
repository at group level.

This will allow to display both inherited description templates
from parent group(s) and instance down to project's own own
description templates

Edit issue is using a different FE(vue app) to be redered vs new issue
form. Update templates controller to return the right templates and in
corresponding format, i.e. hash instead of array.

Fix the edge case when templtes in different groups have the same name,
so that it would be correctly resolved to the correct template.
parent 00d64e00
...@@ -51,7 +51,7 @@ export const NO_ISSUE_TEMPLATE_SELECTED = { key: '', name: __('No template selec ...@@ -51,7 +51,7 @@ export const NO_ISSUE_TEMPLATE_SELECTED = { key: '', name: __('No template selec
export const TAKING_INCIDENT_ACTION_DOCS_LINK = export const TAKING_INCIDENT_ACTION_DOCS_LINK =
'/help/operations/metrics/alerts#trigger-actions-from-alerts'; '/help/operations/metrics/alerts#trigger-actions-from-alerts';
export const ISSUE_TEMPLATES_DOCS_LINK = export const ISSUE_TEMPLATES_DOCS_LINK =
'/help/user/project/description_templates#creating-issue-templates'; '/help/user/project/description_templates#create-an-issue-template';
/* PagerDuty integration settings constants */ /* PagerDuty integration settings constants */
......
...@@ -307,7 +307,7 @@ export default { ...@@ -307,7 +307,7 @@ export default {
}); });
}, },
updateAndShowForm(templates = []) { updateAndShowForm(templates = {}) {
if (!this.showForm) { if (!this.showForm) {
this.showForm = true; this.showForm = true;
this.store.setFormState({ this.store.setFormState({
......
...@@ -13,9 +13,9 @@ export default { ...@@ -13,9 +13,9 @@ export default {
required: true, required: true,
}, },
issuableTemplates: { issuableTemplates: {
type: Array, type: [Object, Array],
required: false, required: false,
default: () => [], default: () => {},
}, },
projectPath: { projectPath: {
type: String, type: String,
......
...@@ -26,9 +26,9 @@ export default { ...@@ -26,9 +26,9 @@ export default {
required: true, required: true,
}, },
issuableTemplates: { issuableTemplates: {
type: Array, type: [Object, Array],
required: false, required: false,
default: () => [], default: () => {},
}, },
issuableType: { issuableType: {
type: String, type: String,
...@@ -72,7 +72,7 @@ export default { ...@@ -72,7 +72,7 @@ export default {
}, },
computed: { computed: {
hasIssuableTemplates() { hasIssuableTemplates() {
return this.issuableTemplates.length; return Object.values(Object(this.issuableTemplates)).length;
}, },
showLockedWarning() { showLockedWarning() {
return this.formState.lockedWarningVisible && !this.formState.updateLoading; return this.formState.lockedWarningVisible && !this.formState.updateLoading;
......
...@@ -11,7 +11,7 @@ export default class Store { ...@@ -11,7 +11,7 @@ export default class Store {
lockedWarningVisible: false, lockedWarningVisible: false,
updateLoading: false, updateLoading: false,
lock_version: 0, lock_version: 0,
issuableTemplates: [], issuableTemplates: {},
}; };
} }
......
...@@ -25,7 +25,7 @@ class Projects::TemplatesController < Projects::ApplicationController ...@@ -25,7 +25,7 @@ class Projects::TemplatesController < Projects::ApplicationController
def names def names
respond_to do |format| respond_to do |format|
format.json { render json: TemplateFinder.all_template_names_array(project, params[:template_type].to_s.pluralize) } format.json { render json: TemplateFinder.all_template_names_hash_or_array(project, params[:template_type].to_s) }
end end
end end
......
...@@ -22,16 +22,26 @@ class TemplateFinder ...@@ -22,16 +22,26 @@ class TemplateFinder
end end
end end
# This is temporary and will be removed once we introduce group level inherited templates and
# remove the inherited_issuable_templates FF
def all_template_names_hash_or_array(project, issuable_type)
if project.inherited_issuable_templates_enabled?
all_template_names(project, issuable_type.pluralize)
else
all_template_names_array(project, issuable_type.pluralize)
end
end
def all_template_names(project, type) def all_template_names(project, type)
return {} if !VENDORED_TEMPLATES.key?(type.to_s) && type.to_s != 'licenses' return {} if !VENDORED_TEMPLATES.key?(type.to_s) && type.to_s != 'licenses'
build(type, project).template_names build(type, project).template_names
end end
# This is issues and merge requests description templates only. # This is for issues and merge requests description templates only.
# This will be removed once we introduce group level inherited templates # This will be removed once we introduce group level inherited templates and remove the inherited_issuable_templates FF
def all_template_names_array(project, type) def all_template_names_array(project, type)
all_template_names(project, type).values.flatten.uniq all_template_names(project, type).values.flatten.select { |tmpl| tmpl[:project_id] == project.id }.compact.uniq
end end
end end
......
...@@ -5,7 +5,8 @@ module IssuablesDescriptionTemplatesHelper ...@@ -5,7 +5,8 @@ module IssuablesDescriptionTemplatesHelper
include GitlabRoutingHelper include GitlabRoutingHelper
def template_dropdown_tag(issuable, &block) def template_dropdown_tag(issuable, &block)
title = selected_template(issuable) || "Choose a template" selected_template = selected_template(issuable)
title = selected_template || "Choose a template"
options = { options = {
toggle_class: 'js-issuable-selector', toggle_class: 'js-issuable-selector',
title: title, title: title,
...@@ -15,7 +16,7 @@ module IssuablesDescriptionTemplatesHelper ...@@ -15,7 +16,7 @@ module IssuablesDescriptionTemplatesHelper
data: { data: {
data: issuable_templates(ref_project, issuable.to_ability_name), data: issuable_templates(ref_project, issuable.to_ability_name),
field_name: 'issuable_template', field_name: 'issuable_template',
selected: selected_template(issuable), selected: selected_template,
project_id: ref_project.id project_id: ref_project.id
} }
} }
...@@ -28,15 +29,21 @@ module IssuablesDescriptionTemplatesHelper ...@@ -28,15 +29,21 @@ module IssuablesDescriptionTemplatesHelper
def issuable_templates(project, issuable_type) def issuable_templates(project, issuable_type)
@template_types ||= {} @template_types ||= {}
@template_types[project.id] ||= {} @template_types[project.id] ||= {}
@template_types[project.id][issuable_type] ||= TemplateFinder.all_template_names_array(project, issuable_type.pluralize) @template_types[project.id][issuable_type] ||= TemplateFinder.all_template_names_hash_or_array(project, issuable_type)
end end
def issuable_templates_names(issuable) def issuable_templates_names(issuable)
issuable_templates(ref_project, issuable.to_ability_name).map { |template| template[:name] } all_templates = issuable_templates(ref_project, issuable.to_ability_name)
if ref_project.inherited_issuable_templates_enabled?
all_templates.values.flatten.map { |tpl| tpl[:name] if tpl[:project_id] == ref_project.id }.compact.uniq
else
all_templates.map { |template| template[:name] }
end
end end
def selected_template(issuable) def selected_template(issuable)
params[:issuable_template] if issuable_templates(ref_project, issuable.to_ability_name).any? { |template| template[:name] == params[:issuable_template] } params[:issuable_template] if issuable_templates_names(issuable).any? { |tmpl_name| tmpl_name == params[:issuable_template] }
end end
def template_names_path(parent, issuable) def template_names_path(parent, issuable)
......
...@@ -2532,6 +2532,10 @@ class Project < ApplicationRecord ...@@ -2532,6 +2532,10 @@ class Project < ApplicationRecord
Projects::GitGarbageCollectWorker Projects::GitGarbageCollectWorker
end end
def inherited_issuable_templates_enabled?
Feature.enabled?(:inherited_issuable_templates, self, default_enabled: :yaml)
end
private private
def find_service(services, name) def find_service(services, name)
......
---
name: inherited_issuable_templates
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/52360
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/321247
milestone: '13.9'
type: development
group: group::project management
default_enabled: false
...@@ -100,7 +100,7 @@ GET /projects/:id/templates/:type/:name ...@@ -100,7 +100,7 @@ GET /projects/:id/templates/:type/:name
| Attribute | Type | Required | Description | | Attribute | Type | Required | Description |
| ---------- | ------ | -------- | ----------- | | ---------- | ------ | -------- | ----------- |
| `id` | integer / string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) | | `id` | integer / string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
| `type` | string | yes| The type `(dockerfiles|gitignores|gitlab_ci_ymls|licenses|issues|merge_requests)` of the template | | `type` | string | yes| The type of the template. One of: `dockerfiles`, `gitignores`, `gitlab_ci_ymls`, `licenses`, `issues`, or `merge_requests`. |
| `name` | string | yes | The key of the template, as obtained from the collection endpoint | | `name` | string | yes | The key of the template, as obtained from the collection endpoint |
| `source_template_project_id` | integer | no | The project ID where a given template is being stored. This is useful when multiple templates from different projects have the same name. If multiple templates have the same name, the match from `closest ancestor` is returned if `source_template_project_id` is not specified | | `source_template_project_id` | integer | no | The project ID where a given template is being stored. This is useful when multiple templates from different projects have the same name. If multiple templates have the same name, the match from `closest ancestor` is returned if `source_template_project_id` is not specified |
| `project` | string | no | The project name to use when expanding placeholders in the template. Only affects licenses | | `project` | string | no | The project name to use when expanding placeholders in the template. Only affects licenses |
......
...@@ -10,8 +10,8 @@ We have implemented standard features that depend on configuration files in the ...@@ -10,8 +10,8 @@ We have implemented standard features that depend on configuration files in the
When implementing new features, please refer to these existing features to avoid conflicts: When implementing new features, please refer to these existing features to avoid conflicts:
- [Custom Dashboards](../operations/metrics/dashboards/index.md#add-a-new-dashboard-to-your-project): `.gitlab/dashboards/`. - [Custom Dashboards](../operations/metrics/dashboards/index.md#add-a-new-dashboard-to-your-project): `.gitlab/dashboards/`.
- [Issue Templates](../user/project/description_templates.md#creating-issue-templates): `.gitlab/issue_templates/`. - [Issue Templates](../user/project/description_templates.md#create-an-issue-template): `.gitlab/issue_templates/`.
- [Merge Request Templates](../user/project/description_templates.md#creating-merge-request-templates): `.gitlab/merge_request_templates/`. - [Merge Request Templates](../user/project/description_templates.md#create-a-merge-request-template): `.gitlab/merge_request_templates/`.
- [GitLab Kubernetes Agents](https://gitlab.com/gitlab-org/cluster-integration/gitlab-agent/-/blob/master/doc/configuration_repository.md#layout): `.gitlab/agents/`. - [GitLab Kubernetes Agents](https://gitlab.com/gitlab-org/cluster-integration/gitlab-agent/-/blob/master/doc/configuration_repository.md#layout): `.gitlab/agents/`.
- [CODEOWNERS](../user/project/code_owners.md#how-to-set-up-code-owners): `.gitlab/CODEOWNERS`. - [CODEOWNERS](../user/project/code_owners.md#how-to-set-up-code-owners): `.gitlab/CODEOWNERS`.
- [Route Maps](../ci/review_apps/#route-maps): `.gitlab/route-map.yml`. - [Route Maps](../ci/review_apps/#route-maps): `.gitlab/route-map.yml`.
......
...@@ -54,7 +54,7 @@ With Maintainer or higher [permissions](../../user/permissions.md), you can enab ...@@ -54,7 +54,7 @@ With Maintainer or higher [permissions](../../user/permissions.md), you can enab
1. Navigate to **Settings > Operations > Incidents** and expand **Incidents**. 1. Navigate to **Settings > Operations > Incidents** and expand **Incidents**.
1. Check the **Create an incident** checkbox. 1. Check the **Create an incident** checkbox.
1. To customize the incident, select an 1. To customize the incident, select an
[issue template](../../user/project/description_templates.md#creating-issue-templates). [issue template](../../user/project/description_templates.md#create-an-issue-template).
1. To send [an email notification](paging.md#email-notifications) to users 1. To send [an email notification](paging.md#email-notifications) to users
with [Developer permissions](../../user/permissions.md), select with [Developer permissions](../../user/permissions.md), select
**Send a separate email notification to Developers**. Email notifications are **Send a separate email notification to Developers**. Email notifications are
......
...@@ -20,7 +20,7 @@ the tiers are no longer mentioned in GitLab documentation: ...@@ -20,7 +20,7 @@ the tiers are no longer mentioned in GitLab documentation:
[per-group charts](../user/project/milestones/index.md#group-burndown-charts) [per-group charts](../user/project/milestones/index.md#group-burndown-charts)
- [Code owners](../user/project/code_owners.md) - [Code owners](../user/project/code_owners.md)
- Description templates: - Description templates:
- [Setting a default template for merge requests and issues](../user/project/description_templates.md#setting-a-default-template-for-merge-requests-and-issues) - [Setting a default template for merge requests and issues](../user/project/description_templates.md#set-a-default-template-for-merge-requests-and-issues)
- [Email from GitLab](../tools/email.md) - [Email from GitLab](../tools/email.md)
- Groups: - Groups:
- [Creating group memberships via CN](../user/group/index.md#creating-group-links-via-cn) - [Creating group memberships via CN](../user/group/index.md#creating-group-links-via-cn)
......
...@@ -37,10 +37,10 @@ To learn how to create templates for various file types in groups, visit ...@@ -37,10 +37,10 @@ To learn how to create templates for various file types in groups, visit
images guidelines, link to the related issue, reviewer name, and so on. images guidelines, link to the related issue, reviewer name, and so on.
- You can also create issues and merge request templates for different - You can also create issues and merge request templates for different
stages of your workflow, for example, feature proposal, feature improvement, or a bug report. stages of your workflow, for example, feature proposal, feature improvement, or a bug report.
- You can use an [issue description template](#creating-issue-templates) as a - You can use an [issue description template](#create-an-issue-template) as a
[Service Desk email template](service_desk.md#new-service-desk-issues). [Service Desk email template](service_desk.md#new-service-desk-issues).
## Creating issue templates ## Create an issue template
Create a new Markdown (`.md`) file inside the `.gitlab/issue_templates/` Create a new Markdown (`.md`) file inside the `.gitlab/issue_templates/`
directory in your repository. Commit and push to your default branch. directory in your repository. Commit and push to your default branch.
...@@ -65,13 +65,13 @@ To create the `.gitlab/issue_templates` directory: ...@@ -65,13 +65,13 @@ To create the `.gitlab/issue_templates` directory:
To check if this has worked correctly, [create a new issue](issues/managing_issues.md#create-a-new-issue) To check if this has worked correctly, [create a new issue](issues/managing_issues.md#create-a-new-issue)
and see if you can choose a description template. and see if you can choose a description template.
## Creating merge request templates ## Create a merge request template
Similarly to issue templates, create a new Markdown (`.md`) file inside the Similarly to issue templates, create a new Markdown (`.md`) file inside the
`.gitlab/merge_request_templates/` directory in your repository. Commit and `.gitlab/merge_request_templates/` directory in your repository. Commit and
push to your default branch. push to your default branch.
## Using the templates ## Use the templates
Let's take for example that you've created the file `.gitlab/issue_templates/Bug.md`. Let's take for example that you've created the file `.gitlab/issue_templates/Bug.md`.
This enables the `Bug` dropdown option when creating or editing issues. When This enables the `Bug` dropdown option when creating or editing issues. When
...@@ -85,9 +85,45 @@ For example: `https://gitlab.com/gitlab-org/gitlab/-/issues/new?issuable_templat ...@@ -85,9 +85,45 @@ For example: `https://gitlab.com/gitlab-org/gitlab/-/issues/new?issuable_templat
![Description templates](img/description_templates.png) ![Description templates](img/description_templates.png)
## Setting a default template for merge requests and issues **(PREMIUM)** ### Set an issue and merge request description template at group level **(PREMIUM)**
> - Moved to GitLab Premium in 13.9. > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/52360) in GitLab 13.9.
> - It's [deployed behind a feature flag](../feature_flags.md), disabled by default.
> - It's disabled by default on GitLab.com.
> - To use it in GitLab self-managed instances, ask a GitLab administrator to
[enable it](#enable-or-disable-issue-and-merge-request-description-templates-at-group-and-instance-level).
Templates can be useful because you can create a template once and use it multiple times.
To re-use templates [you've created](../project/description_templates.md#create-an-issue-template):
1. Go to the group's **Settings > General > Templates**.
1. From the dropdown, select your template project as the template repository at group level.
![Group template settings](../group/img/group_file_template_settings.png)
### Set an issue and merge request description template at instance level **(PREMIUM ONLY)**
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/52360) in GitLab 13.9.
> - It's [deployed behind a feature flag](../feature_flags.md), disabled by default.
> - It's disabled by default on GitLab.com.
> - To use it in GitLab self-managed instances, ask a GitLab administrator to
[enable it](#enable-or-disable-issue-and-merge-request-description-templates-at-group-and-instance-level).
Similar to group templates, issue and merge request templates can also be set up at the instance level.
This results in those templates being available in all projects within the instance.
Only instance administrators can set instance-level templates.
To set the instance-level description template repository:
1. Select the **Admin Area** icon (**{admin}**).
1. Go to **Settings > Templates**.
1. From the dropdown, select your template project as the template repository at instance level.
Learn more about [instance template repository](../admin_area/settings/instance_template_repository.md).
![Setting templates in the Admin Area](../admin_area/settings/img/file_template_admin_area.png)
### Set a default template for merge requests and issues **(PREMIUM)**
The visibility of issues or merge requests should be set to either "Everyone The visibility of issues or merge requests should be set to either "Everyone
with access" or "Only Project Members" in your project's **Settings / Visibility, project features, permissions** section, otherwise the with access" or "Only Project Members" in your project's **Settings / Visibility, project features, permissions** section, otherwise the
...@@ -159,3 +195,28 @@ it's very hard to read otherwise.) ...@@ -159,3 +195,28 @@ it's very hard to read otherwise.)
/cc @project-manager /cc @project-manager
/assign @qa-tester /assign @qa-tester
``` ```
## Enable or disable issue and merge request description templates at group and instance level
Setting issue and merge request description templates at group and instance levels
is under development and not ready for production use. It is deployed behind a
feature flag that is **disabled by default**.
[GitLab administrators with access to the GitLab Rails console](../../administration/feature_flags.md)
can enable it.
To enable it:
```ruby
Feature.enable(:inherited_issuable_templates)
```
To disable it:
```ruby
Feature.disable(:inherited_issuable_templates)
```
The feature flag affects these features:
- Setting a templates project as issue and merge request description templates source at group level.
- Setting a templates project as issue and merge request description templates source at instance level.
...@@ -184,7 +184,7 @@ You can then see issue statuses in the [issue list](#issues-list) and the ...@@ -184,7 +184,7 @@ You can then see issue statuses in the [issue list](#issues-list) and the
## Other Issue actions ## Other Issue actions
- [Create an issue from a template](../../project/description_templates.md#using-the-templates) - [Create an issue from a template](../../project/description_templates.md#use-the-templates)
- [Set a due date](due_dates.md) - [Set a due date](due_dates.md)
- [Bulk edit issues](../bulk_editing.md) - From the Issues List, select multiple issues - [Bulk edit issues](../bulk_editing.md) - From the Issues List, select multiple issues
in order to change their status, assignee, milestone, or labels in bulk. in order to change their status, assignee, milestone, or labels in bulk.
......
...@@ -137,13 +137,13 @@ You can use these placeholders to be automatically replaced in each email: ...@@ -137,13 +137,13 @@ You can use these placeholders to be automatically replaced in each email:
#### New Service Desk issues #### New Service Desk issues
You can select one [issue description template](description_templates.md#creating-issue-templates) You can select one [issue description template](description_templates.md#create-an-issue-template)
**per project** to be appended to every new Service Desk issue's description. **per project** to be appended to every new Service Desk issue's description.
Issue description templates should reside in your repository's `.gitlab/issue_templates/` directory. Issue description templates should reside in your repository's `.gitlab/issue_templates/` directory.
To use a custom issue template with Service Desk, in your project: To use a custom issue template with Service Desk, in your project:
1. [Create a description template](description_templates.md#creating-issue-templates) 1. [Create a description template](description_templates.md#create-an-issue-template)
1. Go to **Settings > General > Service Desk**. 1. Go to **Settings > General > Service Desk**.
1. From the dropdown **Template to append to all Service Desk issues**, select your template. 1. From the dropdown **Template to append to all Service Desk issues**, select your template.
......
...@@ -102,7 +102,7 @@ To edit a file: ...@@ -102,7 +102,7 @@ To edit a file:
in the bottom-right corner. in the bottom-right corner.
1. When you're done, click **Submit changes...**. 1. When you're done, click **Submit changes...**.
1. (Optional) Adjust the default title and description of the merge request, to submit 1. (Optional) Adjust the default title and description of the merge request, to submit
with your changes. Alternatively, select a [merge request template](../../../user/project/description_templates.md#creating-merge-request-templates) with your changes. Alternatively, select a [merge request template](../../../user/project/description_templates.md#create-a-merge-request-template)
from the dropdown menu and edit it accordingly. from the dropdown menu and edit it accordingly.
1. Click **Submit changes**. 1. Click **Submit changes**.
1. A new merge request is automatically created and you can assign a colleague for review. 1. A new merge request is automatically created and you can assign a colleague for review.
......
...@@ -17,8 +17,8 @@ module EE ...@@ -17,8 +17,8 @@ module EE
def initialize(type, project, *args, &blk) def initialize(type, project, *args, &blk)
super super
if CUSTOM_TEMPLATES.key?(type) if custom_templates_mapping.key?(type)
finder = CUSTOM_TEMPLATES.fetch(type) finder = custom_templates_mapping.fetch(type)
@custom_templates = ::Gitlab::CustomFileTemplates.new(finder, project) @custom_templates = ::Gitlab::CustomFileTemplates.new(finder, project)
end end
end end
...@@ -42,5 +42,18 @@ module EE ...@@ -42,5 +42,18 @@ module EE
# from ancestor group levels # from ancestor group levels
custom_templates.all_template_names.merge(super) custom_templates.all_template_names.merge(super)
end end
# This method is going to be removed once we remove the `inherited_issuable_templates` FF and
# issues and merge_requests entries will go into CUSTOM_TEMPLATES
def custom_templates_mapping
if project&.inherited_issuable_templates_enabled?
CUSTOM_TEMPLATES.merge(
issues: ::Gitlab::Template::IssueTemplate,
merge_requests: ::Gitlab::Template::MergeRequestTemplate
)
else
CUSTOM_TEMPLATES
end
end
end end
end end
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
.settings-header .settings-header
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only= _('Default description template for issues') %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only= _('Default description template for issues')
%button.gl-button.btn.btn-default.js-settings-toggle{ type: 'button' }= expanded ? _('Collapse') : _('Expand') %button.gl-button.btn.btn-default.js-settings-toggle{ type: 'button' }= expanded ? _('Collapse') : _('Expand')
- link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: help_page_path('user/project/description_templates', anchor: 'setting-a-default-template-for-merge-requests-and-issues') } - link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: help_page_path('user/project/description_templates', anchor: 'set-a-default-template-for-merge-requests-and-issues') }
%p#issue-settings-default-template-label= _('Set a default description template to be used for new issues. %{link_start}What are description templates?%{link_end}').html_safe % { link_start: link_start, link_end: '</a>'.html_safe } %p#issue-settings-default-template-label= _('Set a default description template to be used for new issues. %{link_start}What are description templates?%{link_end}').html_safe % { link_start: link_start, link_end: '</a>'.html_safe }
.settings-content .settings-content
......
...@@ -116,7 +116,7 @@ module Gitlab ...@@ -116,7 +116,7 @@ module Gitlab
def translate(template, project, category:) def translate(template, project, category:)
return unless template return unless template
template.category = category template.category = category if category
# License templates require special handling as the "vendored" licenses # License templates require special handling as the "vendored" licenses
# are actually in a gem, not on disk like the rest of the templates. So, # are actually in a gem, not on disk like the rest of the templates. So,
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Service Desk Setting', :js, :clean_gitlab_redis_cache do
let_it_be(:issuable_project_template_files) do
{
'.gitlab/issue_templates/project-issue-bar.md' => 'Project Issue Template Bar',
'.gitlab/issue_templates/project-issue-foo.md' => 'Project Issue Template Foo'
}
end
let_it_be(:issuable_group_template_files) do
{
'.gitlab/issue_templates/group-issue-bar.md' => 'Group Issue Template Bar',
'.gitlab/issue_templates/group-issue-foo.md' => 'Group Issue Template Foo'
}
end
let_it_be_with_reload(:group) { create(:group)}
let_it_be_with_reload(:project) { create(:project, :custom_repo, group: group, files: issuable_project_template_files) }
let_it_be(:group_template_repo) { create(:project, :custom_repo, group: group, files: issuable_group_template_files) }
let_it_be(:user) { create(:user) }
let_it_be(:presenter) { project.present(current_user: user) }
before do
stub_licensed_features(custom_file_templates_for_namespace: true, custom_file_templates: true)
project.add_maintainer(user)
sign_in(user)
allow(::Gitlab::IncomingEmail).to receive(:enabled?) { true }
allow(::Gitlab::IncomingEmail).to receive(:supports_wildcard?) { true }
allow(::Gitlab::ServiceDeskEmail).to receive(:enabled?) { true }
allow(::Gitlab::ServiceDeskEmail).to receive(:address_for_key) { 'address-suffix@example.com' }
allow_next_instance_of(Project) do |proj_instance|
expect(proj_instance).to receive(:present).with(current_user: user).and_return(presenter)
end
group.update_columns(file_template_project_id: group_template_repo.id)
end
context 'when inherited_issuable_templates enabled' do
before do
stub_feature_flags(inherited_issuable_templates: true)
visit edit_project_path(project)
end
it_behaves_like 'issue description templates from current project only'
end
context 'when inherited_issuable_templates disabled' do
before do
stub_feature_flags(inherited_issuable_templates: false)
visit edit_project_path(project)
end
it_behaves_like 'issue description templates from current project only'
end
end
...@@ -20,6 +20,8 @@ RSpec.describe TemplateFinder do ...@@ -20,6 +20,8 @@ RSpec.describe TemplateFinder do
:dockerfiles | ::Gitlab::Template::CustomDockerfileTemplate :dockerfiles | ::Gitlab::Template::CustomDockerfileTemplate
:gitignores | ::Gitlab::Template::CustomGitignoreTemplate :gitignores | ::Gitlab::Template::CustomGitignoreTemplate
:gitlab_ci_ymls | ::Gitlab::Template::CustomGitlabCiYmlTemplate :gitlab_ci_ymls | ::Gitlab::Template::CustomGitlabCiYmlTemplate
:issues | ::Gitlab::Template::IssueTemplate
:merge_requests | ::Gitlab::Template::MergeRequestTemplate
end end
with_them do with_them do
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe IssuablesDescriptionTemplatesHelper do
include_context 'project issuable templates context'
describe '#issuable_templates' do
context 'when project parent group has a file template project' do
let_it_be(:user) { create(:user) }
let_it_be_with_reload(:parent_group) { create(:group) }
let_it_be_with_reload(:group) { create(:group, parent: parent_group) }
let_it_be_with_reload(:project) { create(:project, :custom_repo, group: group, files: issuable_template_files) }
let_it_be(:file_template_project) { create(:project, :custom_repo, group: parent_group, files: issuable_template_files) }
let_it_be(:group_member) { create(:group_member, :developer, group: parent_group, user: user) }
let_it_be(:inherited_from) { file_template_project }
before do
stub_licensed_features(custom_file_templates_for_namespace: true)
parent_group.update_columns(file_template_project_id: file_template_project.id)
end
it_behaves_like 'project issuable templates'
end
end
end
...@@ -27,20 +27,34 @@ RSpec.describe "Custom file template classes" do ...@@ -27,20 +27,34 @@ RSpec.describe "Custom file template classes" do
'Dockerfile/category/baz.txt' => 'CustomDockerfileTemplate category baz', 'Dockerfile/category/baz.txt' => 'CustomDockerfileTemplate category baz',
'gitignore/category/baz.txt' => 'CustomGitignoreTemplate category baz', 'gitignore/category/baz.txt' => 'CustomGitignoreTemplate category baz',
'gitlab-ci/category/baz.yml' => 'CustomGitlabCiYmlTemplate category baz', 'gitlab-ci/category/baz.yml' => 'CustomGitlabCiYmlTemplate category baz',
'LICENSE/category/baz.txt' => 'CustomLicenseTemplate category baz' 'LICENSE/category/baz.txt' => 'CustomLicenseTemplate category baz',
'.gitlab/issue_templates/bar.md' => 'IssueTemplate Bar',
'.gitlab/issue_templates/foo.md' => 'IssueTemplate Foo',
'.gitlab/issue_templates/bad.txt' => 'IssueTemplate Bad',
'.gitlab/issue_templates/baz.xyz' => 'IssueTemplate Baz',
'.gitlab/merge_request_templates/bar.md' => 'MergeRequestTemplate Bar',
'.gitlab/merge_request_templates/foo.md' => 'MergeRequestTemplate Foo',
'.gitlab/merge_request_templates/bad.txt' => 'MergeRequestTemplate Bad',
'.gitlab/merge_request_templates/baz.xyz' => 'MergeRequestTemplate Baz'
} }
let_it_be(:project) { create(:project, :custom_repo, files: files) } let_it_be(:project) { create(:project, :custom_repo, files: files) }
[ custom_templates = [
::Gitlab::Template::CustomDockerfileTemplate, { class_name: ::Gitlab::Template::CustomDockerfileTemplate, category: 'Custom' },
::Gitlab::Template::CustomGitignoreTemplate, { class_name: ::Gitlab::Template::CustomGitignoreTemplate, category: 'Custom' },
::Gitlab::Template::CustomGitlabCiYmlTemplate, { class_name: ::Gitlab::Template::CustomGitlabCiYmlTemplate, category: 'Custom' },
::Gitlab::Template::CustomLicenseTemplate, { class_name: ::Gitlab::Template::CustomLicenseTemplate, category: 'Custom' },
::Gitlab::Template::CustomMetricsDashboardYmlTemplate { class_name: ::Gitlab::Template::CustomMetricsDashboardYmlTemplate, category: 'Custom' },
].each do |template_class| { class_name: ::Gitlab::Template::IssueTemplate, category: 'Project Templates' },
describe template_class do { class_name: ::Gitlab::Template::MergeRequestTemplate, category: 'Project Templates' }
let(:name) { template_class.name.demodulize } ].freeze
custom_templates.each do |template_class|
describe template_class[:class_name] do
let(:name) { template_class[:class_name].name.demodulize }
describe '.all' do describe '.all' do
it 'returns all valid templates' do it 'returns all valid templates' do
...@@ -48,7 +62,7 @@ RSpec.describe "Custom file template classes" do ...@@ -48,7 +62,7 @@ RSpec.describe "Custom file template classes" do
aggregate_failures do aggregate_failures do
expect(found.map(&:name)).to contain_exactly('foo', 'bar') expect(found.map(&:name)).to contain_exactly('foo', 'bar')
expect(found.map(&:category).uniq).to contain_exactly('Custom') expect(found.map(&:category).uniq).to contain_exactly(template_class[:category])
end end
end end
end end
......
...@@ -87,11 +87,11 @@ module Gitlab ...@@ -87,11 +87,11 @@ module Gitlab
raise NotImplementedError raise NotImplementedError
end end
def by_category(category, project = nil) def by_category(category, project = nil, empty_category_title: nil)
directory = category_directory(category) directory = category_directory(category)
files = finder(project).list_files_for(directory) files = finder(project).list_files_for(directory)
files.map { |f| new(f, project, category: category) }.sort files.map { |f| new(f, project, category: category.presence || empty_category_title) }.sort
end end
def category_directory(category) def category_directory(category)
......
...@@ -25,6 +25,10 @@ module Gitlab ...@@ -25,6 +25,10 @@ module Gitlab
# follow-up issue: https://gitlab.com/gitlab-org/gitlab/-/issues/300279 # follow-up issue: https://gitlab.com/gitlab-org/gitlab/-/issues/300279
project.repository.issue_template_names_by_category project.repository.issue_template_names_by_category
end end
def by_category(category, project = nil, empty_category_title: nil)
super(category, project, empty_category_title: _('Project Templates'))
end
end end
end end
end end
......
...@@ -25,6 +25,10 @@ module Gitlab ...@@ -25,6 +25,10 @@ module Gitlab
# follow-up issue: https://gitlab.com/gitlab-org/gitlab/-/issues/300279 # follow-up issue: https://gitlab.com/gitlab-org/gitlab/-/issues/300279
project.repository.merge_request_template_names_by_category project.repository.merge_request_template_names_by_category
end end
def by_category(category, project = nil, empty_category_title: nil)
super(category, project, empty_category_title: _('Project Templates'))
end
end end
end end
end end
......
...@@ -22961,6 +22961,9 @@ msgstr "" ...@@ -22961,6 +22961,9 @@ msgstr ""
msgid "Project ID" msgid "Project ID"
msgstr "" msgstr ""
msgid "Project Templates"
msgstr ""
msgid "Project URL" msgid "Project URL"
msgstr "" msgstr ""
......
...@@ -160,14 +160,29 @@ RSpec.describe Projects::TemplatesController do ...@@ -160,14 +160,29 @@ RSpec.describe Projects::TemplatesController do
end end
shared_examples 'template names request' do shared_examples 'template names request' do
it 'returns the template names' do context 'when feature flag enabled' do
it 'returns the template names', :aggregate_failures do
get(:names, params: { namespace_id: project.namespace, template_type: template_type, project_id: project }, format: :json)
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['Project Templates'].size).to eq(2)
expect(json_response['Project Templates'].map { |x| x.slice('name') }).to match(expected_template_names)
end
end
context 'when feature flag disabled' do
before do
stub_feature_flags(inherited_issuable_templates: false)
end
it 'returns the template names', :aggregate_failures do
get(:names, params: { namespace_id: project.namespace, template_type: template_type, project_id: project }, format: :json) get(:names, params: { namespace_id: project.namespace, template_type: template_type, project_id: project }, format: :json)
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
expect(json_response.size).to eq(2)
expect(json_response.size).to eq(2) expect(json_response.size).to eq(2)
expect(json_response.map { |x| x.slice('name') }).to match(expected_template_names) expect(json_response.map { |x| x.slice('name') }).to match(expected_template_names)
end end
end
it 'fails for user with no access' do it 'fails for user with no access' do
other_user = create(:user) other_user = create(:user)
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe 'Service Desk Setting', :js do RSpec.describe 'Service Desk Setting', :js, :clean_gitlab_redis_cache do
let(:project) { create(:project_empty_repo, :private, service_desk_enabled: false) } let(:project) { create(:project_empty_repo, :private, service_desk_enabled: false) }
let(:presenter) { project.present(current_user: user) } let(:presenter) { project.present(current_user: user) }
let(:user) { create(:user) } let(:user) { create(:user) }
...@@ -66,5 +66,48 @@ RSpec.describe 'Service Desk Setting', :js do ...@@ -66,5 +66,48 @@ RSpec.describe 'Service Desk Setting', :js do
expect(find('[data-testid="incoming-email"]').value).to eq('address-suffix@example.com') expect(find('[data-testid="incoming-email"]').value).to eq('address-suffix@example.com')
end end
context 'issue description templates' do
let_it_be(:issuable_project_template_files) do
{
'.gitlab/issue_templates/project-issue-bar.md' => 'Project Issue Template Bar',
'.gitlab/issue_templates/project-issue-foo.md' => 'Project Issue Template Foo'
}
end
let_it_be(:issuable_group_template_files) do
{
'.gitlab/issue_templates/group-issue-bar.md' => 'Group Issue Template Bar',
'.gitlab/issue_templates/group-issue-foo.md' => 'Group Issue Template Foo'
}
end
let_it_be_with_reload(:group) { create(:group)}
let_it_be_with_reload(:project) { create(:project, :custom_repo, group: group, files: issuable_project_template_files) }
let_it_be(:group_template_repo) { create(:project, :custom_repo, group: group, files: issuable_group_template_files) }
before do
stub_licensed_features(custom_file_templates_for_namespace: false, custom_file_templates: false)
group.update_columns(file_template_project_id: group_template_repo.id)
end
context 'when inherited_issuable_templates enabled' do
before do
stub_feature_flags(inherited_issuable_templates: true)
visit edit_project_path(project)
end
it_behaves_like 'issue description templates from current project only'
end
context 'when inherited_issuable_templates disabled' do
before do
stub_feature_flags(inherited_issuable_templates: false)
visit edit_project_path(project)
end
it_behaves_like 'issue description templates from current project only'
end
end
end end
end end
...@@ -35,7 +35,7 @@ exports[`Alert integration settings form default state should match the default ...@@ -35,7 +35,7 @@ exports[`Alert integration settings form default state should match the default
Incident template (optional) Incident template (optional)
<gl-link-stub <gl-link-stub
href="/help/user/project/description_templates#creating-issue-templates" href="/help/user/project/description_templates#create-an-issue-template"
target="_blank" target="_blank"
> >
<gl-icon-stub <gl-icon-stub
......
...@@ -422,7 +422,18 @@ describe('Issuable output', () => { ...@@ -422,7 +422,18 @@ describe('Issuable output', () => {
formSpy = jest.spyOn(wrapper.vm, 'updateAndShowForm'); formSpy = jest.spyOn(wrapper.vm, 'updateAndShowForm');
}); });
it('shows the form if template names request is successful', () => { it('shows the form if template names as hash request is successful', () => {
const mockData = {
test: [{ name: 'test', id: 'test', project_path: '/', namespace_path: '/' }],
};
mock.onGet('/issuable-templates-path').reply(() => Promise.resolve([200, mockData]));
return wrapper.vm.requestTemplatesAndShowForm().then(() => {
expect(formSpy).toHaveBeenCalledWith(mockData);
});
});
it('shows the form if template names as array request is successful', () => {
const mockData = [{ name: 'test', id: 'test', project_path: '/', namespace_path: '/' }]; const mockData = [{ name: 'test', id: 'test', project_path: '/', namespace_path: '/' }];
mock.onGet('/issuable-templates-path').reply(() => Promise.resolve([200, mockData])); mock.onGet('/issuable-templates-path').reply(() => Promise.resolve([200, mockData]));
......
import Vue from 'vue'; import Vue from 'vue';
import descriptionTemplate from '~/issue_show/components/fields/description_template.vue'; import descriptionTemplate from '~/issue_show/components/fields/description_template.vue';
describe('Issue description template component', () => { describe('Issue description template component with templates as hash', () => {
let vm; let vm;
let formState; let formState;
...@@ -14,7 +14,9 @@ describe('Issue description template component', () => { ...@@ -14,7 +14,9 @@ describe('Issue description template component', () => {
vm = new Component({ vm = new Component({
propsData: { propsData: {
formState, formState,
issuableTemplates: [{ name: 'test', id: 'test', project_path: '/', namespace_path: '/' }], issuableTemplates: {
test: [{ name: 'test', id: 'test', project_path: '/', namespace_path: '/' }],
},
projectId: 1, projectId: 1,
projectPath: '/', projectPath: '/',
namespacePath: '/', namespacePath: '/',
...@@ -23,9 +25,9 @@ describe('Issue description template component', () => { ...@@ -23,9 +25,9 @@ describe('Issue description template component', () => {
}).$mount(); }).$mount();
}); });
it('renders templates as JSON array in data attribute', () => { it('renders templates as JSON hash in data attribute', () => {
expect(vm.$el.querySelector('.js-issuable-selector').getAttribute('data-data')).toBe( expect(vm.$el.querySelector('.js-issuable-selector').getAttribute('data-data')).toBe(
'[{"name":"test","id":"test","project_path":"/","namespace_path":"/"}]', '{"test":[{"name":"test","id":"test","project_path":"/","namespace_path":"/"}]}',
); );
}); });
...@@ -41,3 +43,32 @@ describe('Issue description template component', () => { ...@@ -41,3 +43,32 @@ describe('Issue description template component', () => {
expect(vm.issuableTemplate.editor.getValue()).toBe('testing new template'); expect(vm.issuableTemplate.editor.getValue()).toBe('testing new template');
}); });
}); });
describe('Issue description template component with templates as array', () => {
let vm;
let formState;
beforeEach(() => {
const Component = Vue.extend(descriptionTemplate);
formState = {
description: 'test',
};
vm = new Component({
propsData: {
formState,
issuableTemplates: [{ name: 'test', id: 'test', project_path: '/', namespace_path: '/' }],
projectId: 1,
projectPath: '/',
namespacePath: '/',
projectNamespace: '/',
},
}).$mount();
});
it('renders templates as JSON array in data attribute', () => {
expect(vm.$el.querySelector('.js-issuable-selector').getAttribute('data-data')).toBe(
'[{"name":"test","id":"test","project_path":"/","namespace_path":"/"}]',
);
});
});
...@@ -42,7 +42,7 @@ describe('Inline edit form component', () => { ...@@ -42,7 +42,7 @@ describe('Inline edit form component', () => {
expect(vm.$el.querySelector('.js-issuable-selector-wrap')).toBeNull(); expect(vm.$el.querySelector('.js-issuable-selector-wrap')).toBeNull();
}); });
it('renders template selector when templates exists', () => { it('renders template selector when templates as array exists', () => {
createComponent({ createComponent({
issuableTemplates: [ issuableTemplates: [
{ name: 'test', id: 'test', project_path: 'test', namespace_path: 'test' }, { name: 'test', id: 'test', project_path: 'test', namespace_path: 'test' },
...@@ -52,6 +52,16 @@ describe('Inline edit form component', () => { ...@@ -52,6 +52,16 @@ describe('Inline edit form component', () => {
expect(vm.$el.querySelector('.js-issuable-selector-wrap')).not.toBeNull(); expect(vm.$el.querySelector('.js-issuable-selector-wrap')).not.toBeNull();
}); });
it('renders template selector when templates as hash exists', () => {
createComponent({
issuableTemplates: {
test: [{ name: 'test', id: 'test', project_path: 'test', namespace_path: 'test' }],
},
});
expect(vm.$el.querySelector('.js-issuable-selector-wrap')).not.toBeNull();
});
it('hides locked warning by default', () => { it('hides locked warning by default', () => {
createComponent(); createComponent();
......
...@@ -13,22 +13,33 @@ RSpec.describe IssuablesDescriptionTemplatesHelper, :clean_gitlab_redis_cache do ...@@ -13,22 +13,33 @@ RSpec.describe IssuablesDescriptionTemplatesHelper, :clean_gitlab_redis_cache do
let_it_be(:group_member) { create(:group_member, :developer, group: parent_group, user: user) } let_it_be(:group_member) { create(:group_member, :developer, group: parent_group, user: user) }
let_it_be(:project_member) { create(:project_member, :developer, user: user, project: project) } let_it_be(:project_member) { create(:project_member, :developer, user: user, project: project) }
it 'returns empty hash when template type does not exist' do context 'when feature flag disabled' do
expect(helper.issuable_templates(build(:project), 'non-existent-template-type')).to eq([]) before do
stub_feature_flags(inherited_issuable_templates: false)
end end
context 'with cached issuable templates' do it 'returns empty array when template type does not exist' do
expect(helper.issuable_templates(project, 'non-existent-template-type')).to eq([])
end
end
context 'when feature flag enabled' do
before do before do
allow(Gitlab::Template::IssueTemplate).to receive(:template_names).and_return({}) stub_feature_flags(inherited_issuable_templates: true)
allow(Gitlab::Template::MergeRequestTemplate).to receive(:template_names).and_return({}) end
helper.issuable_templates(project, 'issues') it 'returns empty hash when template type does not exist' do
helper.issuable_templates(project, 'merge_request') expect(helper.issuable_templates(build(:project), 'non-existent-template-type')).to eq({})
end
end end
context 'with cached issuable templates' do
it 'does not call TemplateFinder' do it 'does not call TemplateFinder' do
expect(Gitlab::Template::IssueTemplate).not_to receive(:template_names) expect(Gitlab::Template::IssueTemplate).to receive(:template_names).once.and_call_original
expect(Gitlab::Template::MergeRequestTemplate).not_to receive(:template_names) expect(Gitlab::Template::MergeRequestTemplate).to receive(:template_names).once.and_call_original
helper.issuable_templates(project, 'issues')
helper.issuable_templates(project, 'merge_request')
helper.issuable_templates(project, 'issues') helper.issuable_templates(project, 'issues')
helper.issuable_templates(project, 'merge_request') helper.issuable_templates(project, 'merge_request')
end end
...@@ -63,8 +74,14 @@ RSpec.describe IssuablesDescriptionTemplatesHelper, :clean_gitlab_redis_cache do ...@@ -63,8 +74,14 @@ RSpec.describe IssuablesDescriptionTemplatesHelper, :clean_gitlab_redis_cache do
end end
describe '#issuable_templates_names' do describe '#issuable_templates_names' do
let(:project) { double(Project, id: 21) } let_it_be(:project) { build(:project) }
before do
allow(helper).to receive(:ref_project).and_return(project)
allow(helper).to receive(:issuable_templates).and_return(templates)
end
context 'when feature flag disabled' do
let(:templates) do let(:templates) do
[ [
{ name: "another_issue_template", id: "another_issue_template", project_id: project.id }, { name: "another_issue_template", id: "another_issue_template", project_id: project.id },
...@@ -72,20 +89,63 @@ RSpec.describe IssuablesDescriptionTemplatesHelper, :clean_gitlab_redis_cache do ...@@ -72,20 +89,63 @@ RSpec.describe IssuablesDescriptionTemplatesHelper, :clean_gitlab_redis_cache do
] ]
end end
before do
stub_feature_flags(inherited_issuable_templates: false)
end
it 'returns project templates only' do it 'returns project templates only' do
allow(helper).to receive(:ref_project).and_return(project) expect(helper.issuable_templates_names(Issue.new)).to eq(%w[another_issue_template custom_issue_template])
allow(helper).to receive(:issuable_templates).and_return(templates) end
end
context 'when feature flag enabled' do
before do
stub_feature_flags(inherited_issuable_templates: true)
end
context 'with matching project templates' do
let(:templates) do
{
"" => [
{ name: "another_issue_template", id: "another_issue_template", project_id: project.id },
{ name: "custom_issue_template", id: "custom_issue_template", project_id: project.id }
],
"Instance" => [
{ name: "first_issue_issue_template", id: "first_issue_issue_template", project_id: non_existing_record_id },
{ name: "second_instance_issue_template", id: "second_instance_issue_template", project_id: non_existing_record_id }
]
}
end
it 'returns project templates only' do
expect(helper.issuable_templates_names(Issue.new)).to eq(%w[another_issue_template custom_issue_template]) expect(helper.issuable_templates_names(Issue.new)).to eq(%w[another_issue_template custom_issue_template])
end end
end
context 'without matching project templates' do
let(:templates) do
{
"Project Templates" => [
{ name: "another_issue_template", id: "another_issue_template", project_id: non_existing_record_id },
{ name: "custom_issue_template", id: "custom_issue_template", project_id: non_existing_record_id }
],
"Instance" => [
{ name: "first_issue_issue_template", id: "first_issue_issue_template", project_id: non_existing_record_id },
{ name: "second_instance_issue_template", id: "second_instance_issue_template", project_id: non_existing_record_id }
]
}
end
it 'returns empty array' do
expect(helper.issuable_templates_names(Issue.new)).to eq([])
end
end
end
context 'when there are not templates in the project' do context 'when there are not templates in the project' do
let(:templates) { {} } let(:templates) { {} }
it 'returns empty array' do it 'returns empty array' do
allow(helper).to receive(:ref_project).and_return(project)
allow(helper).to receive(:issuable_templates).and_return(templates)
expect(helper.issuable_templates_names(Issue.new)).to eq([]) expect(helper.issuable_templates_names(Issue.new)).to eq([])
end end
end end
......
...@@ -23,11 +23,11 @@ RSpec.shared_examples 'project issuable templates' do ...@@ -23,11 +23,11 @@ RSpec.shared_examples 'project issuable templates' do
end end
it 'returns only md files as issue templates' do it 'returns only md files as issue templates' do
expect(helper.issuable_templates(project, 'issue')).to eq(templates('issue', project)) expect(helper.issuable_templates(project, 'issue')).to eq(expected_templates('issue'))
end end
it 'returns only md files as merge_request templates' do it 'returns only md files as merge_request templates' do
expect(helper.issuable_templates(project, 'merge_request')).to eq(templates('merge_request', project)) expect(helper.issuable_templates(project, 'merge_request')).to eq(expected_templates('merge_request'))
end end
end end
......
# frozen_string_literal: true
RSpec.shared_examples 'issue description templates from current project only' do
it 'loads issue description templates from the project only' do
within('#service-desk-template-select') do
expect(page).to have_content('project-issue-bar')
expect(page).to have_content('project-issue-foo')
expect(page).not_to have_content('group-issue-bar')
expect(page).not_to have_content('group-issue-foo')
end
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment