Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-07-15 18:15:01 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-07-15 18:15:01 +0300
commitb616fd825faac3e7f194e1f942ef30730021e463 (patch)
tree8e187d885200ee5dd7958d7ef32383918ba8e99f /app
parent1a129420d6bd3e5223e8ba4a5b7749764118a885 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/header_search/init.js48
-rw-r--r--app/assets/javascripts/issues/new/components/type_popover.vue5
-rw-r--r--app/assets/javascripts/linked_resources/components/add_issuable_resource_link_form.vue75
-rw-r--r--app/assets/javascripts/linked_resources/components/resource_links_block.vue54
-rw-r--r--app/assets/javascripts/linked_resources/constants.js17
-rw-r--r--app/assets/javascripts/main.js30
-rw-r--r--app/assets/javascripts/pages/projects/init_blob.js2
-rw-r--r--app/assets/javascripts/repository/queries/blob_info.query.graphql1
-rw-r--r--app/assets/javascripts/vue_shared/components/source_viewer/components/chunk.vue5
-rw-r--r--app/assets/javascripts/vue_shared/components/source_viewer/components/chunk_line.vue31
-rw-r--r--app/assets/javascripts/vue_shared/components/source_viewer/source_viewer.vue2
-rw-r--r--app/assets/javascripts/work_items/components/work_item_detail.vue27
-rw-r--r--app/assets/javascripts/work_items/components/work_item_information.vue57
-rw-r--r--app/assets/javascripts/work_items/constants.js1
-rw-r--r--app/assets/stylesheets/framework/files.scss4
-rw-r--r--app/assets/stylesheets/framework/highlight.scss44
-rw-r--r--app/assets/stylesheets/highlight/common.scss48
-rw-r--r--app/assets/stylesheets/highlight/themes/dark.scss14
-rw-r--r--app/assets/stylesheets/highlight/themes/monokai.scss14
-rw-r--r--app/assets/stylesheets/highlight/themes/none.scss14
-rw-r--r--app/assets/stylesheets/highlight/themes/solarized-dark.scss14
-rw-r--r--app/assets/stylesheets/highlight/themes/solarized-light.scss14
-rw-r--r--app/assets/stylesheets/highlight/white_base.scss14
-rw-r--r--app/graphql/types/ci/job_type.rb10
-rw-r--r--app/graphql/types/ci/variable_type.rb9
-rw-r--r--app/models/ci/pipeline.rb4
-rw-r--r--app/policies/work_item_policy.rb4
-rw-r--r--app/services/work_items/create_from_task_service.rb1
-rw-r--r--app/services/work_items/parent_links/create_service.rb8
-rw-r--r--app/services/work_items/parent_links/destroy_service.rb2
-rw-r--r--app/services/work_items/task_list_reference_removal_service.rb15
-rw-r--r--app/services/work_items/task_list_reference_replacement_service.rb9
-rw-r--r--app/views/projects/protected_tags/shared/_create_protected_tag.html.haml2
-rw-r--r--app/views/projects/settings/ci_cd/_autodevops_form.html.haml2
-rw-r--r--app/views/shared/_file_highlight.html.haml9
-rw-r--r--app/views/shared/issuable/form/_type_selector.html.haml60
36 files changed, 371 insertions, 299 deletions
diff --git a/app/assets/javascripts/header_search/init.js b/app/assets/javascripts/header_search/init.js
new file mode 100644
index 00000000000..2b9fac0b395
--- /dev/null
+++ b/app/assets/javascripts/header_search/init.js
@@ -0,0 +1,48 @@
+import * as Sentry from '@sentry/browser';
+
+async function eventHandler(callback = () => {}) {
+ if (this.newHeaderSearchFeatureFlag) {
+ const { initHeaderSearchApp } = await import(
+ /* webpackChunkName: 'globalSearch' */ '~/header_search'
+ ).catch((error) => Sentry.captureException(error));
+
+ // In case the user started searching before we bootstrapped,
+ // let's pass the search along.
+ const initialSearchValue = this.searchInputBox.value;
+ initHeaderSearchApp(initialSearchValue);
+
+ // this is new #search input element. We need to re-find it.
+ // And re-focus in it.
+ document.querySelector('#search').focus();
+ callback();
+ return;
+ }
+
+ const { default: initSearchAutocomplete } = await import(
+ /* webpackChunkName: 'globalSearch' */ '../search_autocomplete'
+ ).catch((error) => Sentry.captureException(error));
+
+ const searchDropdown = initSearchAutocomplete();
+ searchDropdown.onSearchInputFocus();
+ callback();
+}
+
+function cleanEventListeners() {
+ document.querySelector('#search').removeEventListener('focus', eventHandler);
+}
+
+function initHeaderSearch() {
+ const searchInputBox = document.querySelector('#search');
+
+ searchInputBox?.addEventListener(
+ 'focus',
+ eventHandler.bind(
+ { searchInputBox, newHeaderSearchFeatureFlag: gon?.features?.newHeaderSearch },
+ cleanEventListeners,
+ ),
+ { once: true },
+ );
+}
+
+export default initHeaderSearch;
+export { eventHandler };
diff --git a/app/assets/javascripts/issues/new/components/type_popover.vue b/app/assets/javascripts/issues/new/components/type_popover.vue
index a70e79b70f9..9c43e527f8b 100644
--- a/app/assets/javascripts/issues/new/components/type_popover.vue
+++ b/app/assets/javascripts/issues/new/components/type_popover.vue
@@ -18,8 +18,9 @@ export default {
</script>
<template>
- <span id="popovercontainer">
- <gl-icon id="issue-type-info" name="question-o" class="gl-ml-5 gl-text-gray-500" />
+ <span id="popovercontainer" class="gl-ml-2">
+ <gl-icon id="issue-type-info" name="question-o" class="gl-text-blue-600" />
+
<gl-popover
target="issue-type-info"
container="popovercontainer"
diff --git a/app/assets/javascripts/linked_resources/components/add_issuable_resource_link_form.vue b/app/assets/javascripts/linked_resources/components/add_issuable_resource_link_form.vue
new file mode 100644
index 00000000000..6a0deb41fd1
--- /dev/null
+++ b/app/assets/javascripts/linked_resources/components/add_issuable_resource_link_form.vue
@@ -0,0 +1,75 @@
+<script>
+import { GlFormGroup, GlButton, GlFormInput } from '@gitlab/ui';
+import autofocusonshow from '~/vue_shared/directives/autofocusonshow';
+import { resourceLinksFormI18n } from '../constants';
+
+export default {
+ name: 'AddIssuableResourceLinkForm',
+ components: {
+ GlFormGroup,
+ GlButton,
+ GlFormInput,
+ },
+ i18n: resourceLinksFormI18n,
+ directives: {
+ autofocusonshow,
+ },
+ props: {
+ isSubmitting: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ },
+ data() {
+ return {
+ linkTextValue: '',
+ linkValue: '',
+ };
+ },
+ computed: {
+ isSubmitButtonDisabled() {
+ return this.linkValue.length === 0 || this.isSubmitting;
+ },
+ },
+ methods: {
+ onFormCancel() {
+ this.linkValue = '';
+ this.linkTextValue = '';
+ this.$emit('add-issuable-resource-link-form-cancel');
+ },
+ },
+};
+</script>
+
+<template>
+ <form @submit.prevent>
+ <gl-form-group :label="$options.i18n.linkTextLabel">
+ <gl-form-input
+ v-model="linkTextValue"
+ v-autofocusonshow
+ data-testid="link-text-input"
+ type="text"
+ />
+ </gl-form-group>
+ <gl-form-group :label="$options.i18n.linkValueLabel">
+ <gl-form-input v-model="linkValue" data-testid="link-value-input" type="text" />
+ </gl-form-group>
+ <div class="gl-mt-5 gl-clearfix">
+ <gl-button
+ category="primary"
+ variant="confirm"
+ data-testid="add-button"
+ :disabled="isSubmitButtonDisabled"
+ :loading="isSubmitting"
+ type="submit"
+ class="gl-float-left"
+ >
+ {{ $options.i18n.submitButtonText }}
+ </gl-button>
+ <gl-button class="gl-float-right" @click="onFormCancel">
+ {{ $options.i18n.cancelButtonText }}
+ </gl-button>
+ </div>
+ </form>
+</template>
diff --git a/app/assets/javascripts/linked_resources/components/resource_links_block.vue b/app/assets/javascripts/linked_resources/components/resource_links_block.vue
index 3bfee61df15..46c4fc7f632 100644
--- a/app/assets/javascripts/linked_resources/components/resource_links_block.vue
+++ b/app/assets/javascripts/linked_resources/components/resource_links_block.vue
@@ -1,10 +1,7 @@
<script>
import { GlLink, GlIcon, GlButton } from '@gitlab/ui';
-import {
- LINKED_RESOURCES_HEADER_TEXT,
- LINKED_RESOURCES_HELP_TEXT,
- LINKED_RESOURCES_ADD_BUTTON_TEXT,
-} from '../constants';
+import { resourceLinksI18n } from '../constants';
+import AddIssuableResourceLinkForm from './add_issuable_resource_link_form.vue';
export default {
name: 'ResourceLinksBlock',
@@ -12,7 +9,9 @@ export default {
GlLink,
GlButton,
GlIcon,
+ AddIssuableResourceLinkForm,
},
+ i18n: resourceLinksI18n,
props: {
helpPath: {
type: String,
@@ -25,18 +24,26 @@ export default {
default: false,
},
},
+ data() {
+ return {
+ isFormVisible: false,
+ isSubmitting: false,
+ };
+ },
computed: {
- helpLinkText() {
- return LINKED_RESOURCES_HELP_TEXT;
- },
badgeLabel() {
return 0;
},
- resourceLinkAddButtonText() {
- return LINKED_RESOURCES_ADD_BUTTON_TEXT;
+ hasBody() {
+ return this.isFormVisible;
+ },
+ },
+ methods: {
+ async toggleResourceLinkForm() {
+ this.isFormVisible = !this.isFormVisible;
},
- resourceLinkHeaderText() {
- return LINKED_RESOURCES_HEADER_TEXT;
+ hideResourceLinkForm() {
+ this.isFormVisible = false;
},
},
};
@@ -46,7 +53,7 @@ export default {
<div id="resource-links" class="gl-mt-5">
<div class="card card-slim gl-overflow-hidden">
<div
- :class="{ 'panel-empty-heading border-bottom-0': true }"
+ :class="{ 'panel-empty-heading border-bottom-0': !hasBody }"
class="card-header gl-display-flex gl-justify-content-space-between"
>
<h3
@@ -58,13 +65,13 @@ export default {
href="#resource-links"
aria-hidden="true"
/>
- <slot name="header-text">{{ resourceLinkHeaderText }}</slot>
+ <slot name="header-text">{{ $options.i18n.headerText }}</slot>
<gl-link
:href="helpPath"
target="_blank"
class="gl-display-flex gl-align-items-center gl-ml-2 gl-text-gray-500"
data-testid="help-link"
- :aria-label="helpLinkText"
+ :aria-label="$options.i18n.helpText"
>
<gl-icon name="question" :size="12" />
</gl-link>
@@ -79,11 +86,26 @@ export default {
<gl-button
v-if="canAddResourceLinks"
icon="plus"
- :aria-label="resourceLinkAddButtonText"
+ :aria-label="$options.i18n.addButtonText"
+ @click="toggleResourceLinkForm"
/>
</div>
</h3>
</div>
+ <div
+ class="linked-issues-card-body bg-gray-light"
+ :class="{
+ 'gl-p-5': isFormVisible,
+ }"
+ >
+ <div v-show="isFormVisible" class="card-body bordered-box gl-bg-white">
+ <add-issuable-resource-link-form
+ ref="resourceLinkForm"
+ :is-submitting="isSubmitting"
+ @add-issuable-resource-link-form-cancel="hideResourceLinkForm"
+ />
+ </div>
+ </div>
</div>
</div>
</template>
diff --git a/app/assets/javascripts/linked_resources/constants.js b/app/assets/javascripts/linked_resources/constants.js
index 358de326830..1b11cfc5f88 100644
--- a/app/assets/javascripts/linked_resources/constants.js
+++ b/app/assets/javascripts/linked_resources/constants.js
@@ -1,5 +1,14 @@
-import { __ } from '~/locale';
+import { s__ } from '~/locale';
-export const LINKED_RESOURCES_HEADER_TEXT = __('Linked resources');
-export const LINKED_RESOURCES_HELP_TEXT = __('Read more about linked resources');
-export const LINKED_RESOURCES_ADD_BUTTON_TEXT = __('Add a resource link');
+export const resourceLinksI18n = Object.freeze({
+ headerText: s__('LinkedResources|Linked resources'),
+ helpText: s__('LinkedResources|Read more about linked resources'),
+ addButtonText: s__('LinkedResources|Add a resource link'),
+});
+
+export const resourceLinksFormI18n = Object.freeze({
+ linkTextLabel: s__('LinkedResources|Text (Optional)'),
+ linkValueLabel: s__('LinkedResources|Link'),
+ submitButtonText: s__('LinkedResources|Add'),
+ cancelButtonText: s__('LinkedResources|Cancel'),
+});
diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js
index 21d5decb15b..349a28ace52 100644
--- a/app/assets/javascripts/main.js
+++ b/app/assets/javascripts/main.js
@@ -36,6 +36,7 @@ import initUserPopovers from './user_popovers';
import initBroadcastNotifications from './broadcast_notification';
import { initTopNav } from './nav';
import { initCopyCodeButton } from './behaviors/copy_code';
+import initHeaderSearch from './header_search/init';
import 'ee_else_ce/main_ee';
import 'jh_else_ce/main_jh';
@@ -141,35 +142,10 @@ function deferredInitialisation() {
}
}
+// header search vue component bootstrap
// loading this inside requestIdleCallback is causing issues
// see https://gitlab.com/gitlab-org/gitlab/-/issues/365746
-const searchInputBox = document.querySelector('#search');
-if (searchInputBox) {
- searchInputBox.addEventListener(
- 'focus',
- () => {
- if (gon.features?.newHeaderSearch) {
- import(/* webpackChunkName: 'globalSearch' */ '~/header_search')
- .then(async ({ initHeaderSearchApp }) => {
- // In case the user started searching before we bootstrapped, let's pass the search along.
- const initialSearchValue = searchInputBox.value;
- await initHeaderSearchApp(initialSearchValue);
- // this is new #search input element. We need to re-find it.
- document.querySelector('#search').focus();
- })
- .catch(() => {});
- } else {
- import(/* webpackChunkName: 'globalSearch' */ './search_autocomplete')
- .then(({ default: initSearchAutocomplete }) => {
- const searchDropdown = initSearchAutocomplete();
- searchDropdown.onSearchInputFocus();
- })
- .catch(() => {});
- }
- },
- { once: true },
- );
-}
+initHeaderSearch();
const $body = $('body');
const $document = $(document);
diff --git a/app/assets/javascripts/pages/projects/init_blob.js b/app/assets/javascripts/pages/projects/init_blob.js
index f37a2987685..7db34816cfe 100644
--- a/app/assets/javascripts/pages/projects/init_blob.js
+++ b/app/assets/javascripts/pages/projects/init_blob.js
@@ -11,7 +11,7 @@ export default () => {
// eslint-disable-next-line no-new
new BlobLinePermalinkUpdater(
document.querySelector('#blob-content-holder'),
- '.file-line-num[data-line-number], .file-line-num[data-line-number] *',
+ '.diff-line-num[data-line-number], .diff-line-num[data-line-number] *',
document.querySelectorAll('.js-data-file-blob-permalink-url, .js-blob-blame-link'),
);
diff --git a/app/assets/javascripts/repository/queries/blob_info.query.graphql b/app/assets/javascripts/repository/queries/blob_info.query.graphql
index 45a7793e559..8baee80e5d6 100644
--- a/app/assets/javascripts/repository/queries/blob_info.query.graphql
+++ b/app/assets/javascripts/repository/queries/blob_info.query.graphql
@@ -27,7 +27,6 @@ query getBlobInfo(
fileType
language
path
- blamePath
editBlobPath
gitpodBlobUrl
ideEditPath
diff --git a/app/assets/javascripts/vue_shared/components/source_viewer/components/chunk.vue b/app/assets/javascripts/vue_shared/components/source_viewer/components/chunk.vue
index 9683288f937..6babbca58c3 100644
--- a/app/assets/javascripts/vue_shared/components/source_viewer/components/chunk.vue
+++ b/app/assets/javascripts/vue_shared/components/source_viewer/components/chunk.vue
@@ -51,10 +51,6 @@ export default {
required: false,
default: null,
},
- blamePath: {
- type: String,
- required: true,
- },
},
computed: {
lines() {
@@ -80,7 +76,6 @@ export default {
:number="startingFrom + index + 1"
:content="line"
:language="language"
- :blame-path="blamePath"
/>
</div>
<div v-else class="gl-display-flex">
diff --git a/app/assets/javascripts/vue_shared/components/source_viewer/components/chunk_line.vue b/app/assets/javascripts/vue_shared/components/source_viewer/components/chunk_line.vue
index b6854ee0375..7b62f0cdb7d 100644
--- a/app/assets/javascripts/vue_shared/components/source_viewer/components/chunk_line.vue
+++ b/app/assets/javascripts/vue_shared/components/source_viewer/components/chunk_line.vue
@@ -1,5 +1,5 @@
<script>
-import { GlLink, GlSafeHtmlDirective, GlTooltipDirective } from '@gitlab/ui';
+import { GlLink, GlSafeHtmlDirective } from '@gitlab/ui';
import { setAttributes } from '~/lib/utils/dom_utils';
import { BIDI_CHARS, BIDI_CHARS_CLASS_LIST, BIDI_CHAR_TOOLTIP } from '../constants';
@@ -9,7 +9,6 @@ export default {
},
directives: {
SafeHtml: GlSafeHtmlDirective,
- GlTooltip: GlTooltipDirective,
},
props: {
number: {
@@ -24,10 +23,6 @@ export default {
type: String,
required: true,
},
- blamePath: {
- type: String,
- required: true,
- },
},
computed: {
formattedContent() {
@@ -63,35 +58,21 @@ export default {
};
</script>
<template>
- <div class="gl-display-flex line-links-wrapper">
- <div
- class="gl-p-0! gl-absolute gl-z-index-3 diff-line-num gl-border-r gl-display-flex line-links line-numbers"
- :class="firstLineClass"
- >
- <gl-link
- v-gl-tooltip="__('View blame')"
- class="gl-user-select-none gl-ml-3 gl-shadow-none! file-line-blame"
- :href="`${blamePath}#L${number}`"
- data-track-action="click_link"
- data-track-label="file_line_action"
- data-track-property="blame"
- />
-
+ <div class="gl-display-flex">
+ <div class="gl-p-0! gl-absolute gl-z-index-3 gl-border-r diff-line-num line-numbers">
<gl-link
:id="`L${number}`"
- class="gl-user-select-none gl-flex-grow-1 gl-justify-content-end gl-pr-3 gl-shadow-none! file-line-num"
+ class="gl-user-select-none gl-ml-5 gl-pr-3 gl-shadow-none! file-line-num diff-line-num"
+ :class="firstLineClass"
:to="`#L${number}`"
:data-line-number="number"
- data-track-action="click_link"
- data-track-label="file_line_action"
- data-track-property="link"
>
{{ number }}
</gl-link>
</div>
<pre
- class="gl-p-0! gl-w-full gl-overflow-visible! gl-border-none! code highlight gl-line-height-normal"
+ class="gl-p-0! gl-w-full gl-overflow-visible! gl-ml-11! gl-border-none! code highlight gl-line-height-normal"
:class="firstLineClass"
><code><span :id="`LC${number}`" v-safe-html="formattedContent" :lang="language" class="line" data-testid="content"></span></code></pre>
</div>
diff --git a/app/assets/javascripts/vue_shared/components/source_viewer/source_viewer.vue b/app/assets/javascripts/vue_shared/components/source_viewer/source_viewer.vue
index ccc8b44942a..1bdae40332f 100644
--- a/app/assets/javascripts/vue_shared/components/source_viewer/source_viewer.vue
+++ b/app/assets/javascripts/vue_shared/components/source_viewer/source_viewer.vue
@@ -199,7 +199,6 @@ export default {
:starting-from="firstChunk.startingFrom"
:is-highlighted="firstChunk.isHighlighted"
:language="firstChunk.language"
- :blame-path="blob.blamePath"
/>
<gl-loading-icon v-if="isLoading" size="sm" class="gl-my-5" />
@@ -214,7 +213,6 @@ export default {
:is-highlighted="chunk.isHighlighted"
:chunk-index="index"
:language="chunk.language"
- :blame-path="blob.blamePath"
@appear="highlightChunk"
/>
</div>
diff --git a/app/assets/javascripts/work_items/components/work_item_detail.vue b/app/assets/javascripts/work_items/components/work_item_detail.vue
index 40467205d0d..7314b0afc54 100644
--- a/app/assets/javascripts/work_items/components/work_item_detail.vue
+++ b/app/assets/javascripts/work_items/components/work_item_detail.vue
@@ -1,6 +1,7 @@
<script>
import { GlAlert, GlSkeletonLoader, GlIcon, GlButton } from '@gitlab/ui';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
+import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
import {
i18n,
WIDGET_TYPE_ASSIGNEES,
@@ -8,6 +9,7 @@ import {
WIDGET_TYPE_DESCRIPTION,
WIDGET_TYPE_WEIGHT,
WIDGET_TYPE_HIERARCHY,
+ WORK_ITEM_VIEWED_STORAGE_KEY,
} from '../constants';
import workItemQuery from '../graphql/work_item.query.graphql';
import workItemTitleSubscription from '../graphql/work_item_title.subscription.graphql';
@@ -18,6 +20,7 @@ import WorkItemDescription from './work_item_description.vue';
import WorkItemAssignees from './work_item_assignees.vue';
import WorkItemLabels from './work_item_labels.vue';
import WorkItemWeight from './work_item_weight.vue';
+import WorkItemInformation from './work_item_information.vue';
export default {
i18n,
@@ -33,6 +36,8 @@ export default {
WorkItemTitle,
WorkItemState,
WorkItemWeight,
+ WorkItemInformation,
+ LocalStorageSync,
},
mixins: [glFeatureFlagMixin()],
props: {
@@ -56,6 +61,7 @@ export default {
return {
error: undefined,
workItem: {},
+ showInfoBanner: true,
};
},
apollo: {
@@ -120,6 +126,17 @@ export default {
return `../../issues/${this.parentWorkItem?.iid}`;
},
},
+ beforeDestroy() {
+ /** make sure that if the user has not even dismissed the alert ,
+ * should no be able to see the information next time and update the local storage * */
+ this.dismissBanner();
+ },
+ methods: {
+ dismissBanner() {
+ this.showInfoBanner = false;
+ },
+ },
+ WORK_ITEM_VIEWED_STORAGE_KEY,
};
</script>
@@ -174,6 +191,16 @@ export default {
@click="$emit('close')"
/>
</div>
+ <local-storage-sync
+ v-model="showInfoBanner"
+ :storage-key="$options.WORK_ITEM_VIEWED_STORAGE_KEY"
+ >
+ <work-item-information
+ v-if="showInfoBanner"
+ :show-info-banner="showInfoBanner"
+ @work-item-banner-dismissed="dismissBanner"
+ />
+ </local-storage-sync>
<work-item-title
:work-item-id="workItem.id"
:work-item-title="workItem.title"
diff --git a/app/assets/javascripts/work_items/components/work_item_information.vue b/app/assets/javascripts/work_items/components/work_item_information.vue
new file mode 100644
index 00000000000..2ff7ba169ea
--- /dev/null
+++ b/app/assets/javascripts/work_items/components/work_item_information.vue
@@ -0,0 +1,57 @@
+<script>
+import { GlAlert, GlSprintf, GlLink } from '@gitlab/ui';
+import { s__ } from '~/locale';
+import { helpPagePath } from '~/helpers/help_page_helper';
+
+export default {
+ i18n: {
+ learnTasksButtonText: s__('WorkItem|Learn about tasks'),
+ workItemsText: s__('WorkItem|work items'),
+ tasksInformationTitle: s__('WorkItem|Introducing tasks'),
+ tasksInformationBody: s__(
+ 'WorkItem|A task provides the ability to break down your work into smaller pieces tied to an issue. Tasks are the first items using our new %{workItemsLink} objects. Additional work item types will be coming soon.',
+ ),
+ },
+ helpPageLinks: {
+ tasksDocLinkPath: helpPagePath('user/tasks'),
+ workItemsLinkPath: helpPagePath(`development/work_items`),
+ },
+ components: {
+ GlAlert,
+ GlSprintf,
+ GlLink,
+ },
+ props: {
+ showInfoBanner: {
+ type: Boolean,
+ required: false,
+ default: true,
+ },
+ },
+ emits: ['work-item-banner-dismissed'],
+};
+</script>
+
+<template>
+ <section class="gl-display-block gl-mb-2">
+ <gl-alert
+ v-if="showInfoBanner"
+ variant="tip"
+ :title="$options.i18n.tasksInformationTitle"
+ :primary-button-link="$options.helpPageLinks.tasksDocLinkPath"
+ :primary-button-text="$options.i18n.learnTasksButtonText"
+ data-testid="work-item-information"
+ class="gl-mt-3"
+ @dismiss="$emit('work-item-banner-dismissed')"
+ >
+ <gl-sprintf :message="$options.i18n.tasksInformationBody">
+ <template #workItemsLink>
+ <gl-link :href="$options.helpPageLinks.workItemsLinkPath">{{
+ $options.i18n.workItemsText
+ }}</gl-link>
+ </template>
+ ></gl-sprintf
+ >
+ </gl-alert>
+ </section>
+</template>
diff --git a/app/assets/javascripts/work_items/constants.js b/app/assets/javascripts/work_items/constants.js
index c9ccbd48ba1..2140b418e6d 100644
--- a/app/assets/javascripts/work_items/constants.js
+++ b/app/assets/javascripts/work_items/constants.js
@@ -20,6 +20,7 @@ export const WIDGET_TYPE_DESCRIPTION = 'DESCRIPTION';
export const WIDGET_TYPE_LABELS = 'LABELS';
export const WIDGET_TYPE_WEIGHT = 'WEIGHT';
export const WIDGET_TYPE_HIERARCHY = 'HIERARCHY';
+export const WORK_ITEM_VIEWED_STORAGE_KEY = 'gl-show-work-item-banner';
export const WIDGET_TYPE_TASK_ICON = 'task-done';
diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss
index d59ca14ee84..f322c6c8929 100644
--- a/app/assets/stylesheets/framework/files.scss
+++ b/app/assets/stylesheets/framework/files.scss
@@ -202,10 +202,6 @@
float: none;
border-left: 1px solid $gray-100;
- .file-line-num {
- @include gl-min-w-9;
- }
-
i {
float: none;
margin-right: 0;
diff --git a/app/assets/stylesheets/framework/highlight.scss b/app/assets/stylesheets/framework/highlight.scss
index f1f43e55921..1c43212f501 100644
--- a/app/assets/stylesheets/framework/highlight.scss
+++ b/app/assets/stylesheets/framework/highlight.scss
@@ -48,9 +48,8 @@
a {
font-family: $monospace-font;
+ display: block;
white-space: nowrap;
- @include gl-display-flex;
- @include gl-justify-content-end;
i,
svg {
@@ -91,44 +90,3 @@ td.line-numbers {
cursor: pointer;
text-decoration: underline wavy $red-500;
}
-
-.blob-viewer {
- .line-numbers {
- // for server-side-rendering
- .line-links {
- min-width: 6.5rem;
-
- &:first-child {
- margin-top: 10px;
- }
-
- &:last-child {
- margin-bottom: 10px;
- }
- }
-
- // for client
- &.line-links {
- min-width: 6.5rem;
- border-bottom-left-radius: 0;
-
- + pre {
- margin-left: 6.5rem;
- }
- }
- }
-
- .line-links {
- &:hover .file-line-blame::before,
- &:hover .file-line-num::before,
- &:focus-within .file-line-blame::before,
- &:focus-within .file-line-num::before {
- @include gl-visibility-visible;
- }
- }
-
- .file-line-num,
- .file-line-blame {
- @include gl-align-items-center;
- }
-}
diff --git a/app/assets/stylesheets/highlight/common.scss b/app/assets/stylesheets/highlight/common.scss
index 8e4f4600d5c..fcbd05141b9 100644
--- a/app/assets/stylesheets/highlight/common.scss
+++ b/app/assets/stylesheets/highlight/common.scss
@@ -98,50 +98,32 @@
}
}
+@mixin line-number-link($color) {
+ min-width: $gl-spacing-scale-9;
-@mixin line-link($color, $icon) {
&::before {
- @include gl-visibility-hidden;
+ @include gl-display-none;
@include gl-align-self-center;
- @include gl-mr-1;
- @include gl-w-5;
- @include gl-h-5;
- background-color: rgba($color, 0.3);
- mask-image: asset_url('icons-stacked.svg##{$icon}');
+ @include gl-mt-2;
+ @include gl-mr-2;
+ @include gl-w-4;
+ @include gl-h-4;
+ @include gl-absolute;
+ @include gl-left-3;
+ background-color: $color;
+ mask-image: asset_url('icons-stacked.svg#link');
mask-repeat: no-repeat;
mask-size: cover;
mask-position: center;
content: '';
}
- &:hover {
- &::before {
- background-color: rgba($color, 0.6);
- }
- }
-}
-
-@mixin line-hover-bg($color: $white-normal) {
- &:hover,
- &:focus-within {
- background-color: darken($color, 10);
+ &:hover::before {
+ @include gl-display-inline-block;
}
-}
-@mixin first-line-top-space($bg-color: $gray-light, $border-color: $white-normal) {
- &:first-child {
- .line-links {
- &::before {
- @include gl-absolute;
- @include gl-h-3;
- content: '';
- bottom: 100%;
- left: 0;
- width: 6.5rem;
- background-color: $bg-color;
- border-right: 1px solid $border-color;
- }
- }
+ &:focus::before {
+ @include gl-display-inline-block;
}
}
diff --git a/app/assets/stylesheets/highlight/themes/dark.scss b/app/assets/stylesheets/highlight/themes/dark.scss
index f7f3c8964bf..709e7f5ae18 100644
--- a/app/assets/stylesheets/highlight/themes/dark.scss
+++ b/app/assets/stylesheets/highlight/themes/dark.scss
@@ -127,19 +127,7 @@ $dark-il: #de935f;
.code.dark {
// Line numbers
.file-line-num {
- @include line-link($white, 'link');
- }
-
- .file-line-blame {
- @include line-link($white, 'git');
- }
-
- .line-links {
- @include line-hover-bg($dark-main-bg);
- }
-
- .line-links-wrapper {
- @include first-line-top-space($dark-main-bg, $dark-code-border);
+ @include line-number-link($dark-line-num-color);
}
.line-numbers,
diff --git a/app/assets/stylesheets/highlight/themes/monokai.scss b/app/assets/stylesheets/highlight/themes/monokai.scss
index 9dfe48f5609..0ed9c209417 100644
--- a/app/assets/stylesheets/highlight/themes/monokai.scss
+++ b/app/assets/stylesheets/highlight/themes/monokai.scss
@@ -120,19 +120,7 @@ $monokai-gh: #75715e;
// Line numbers
.file-line-num {
- @include line-link($white, 'link');
- }
-
- .file-line-blame {
- @include line-link($white, 'git');
- }
-
- .line-links {
- @include line-hover-bg($monokai-bg);
- }
-
- .line-links-wrapper {
- @include first-line-top-space($monokai-bg, $monokai-border);
+ @include line-number-link($monokai-line-num-color);
}
.line-numbers,
diff --git a/app/assets/stylesheets/highlight/themes/none.scss b/app/assets/stylesheets/highlight/themes/none.scss
index 7fd81353363..868e466b1f8 100644
--- a/app/assets/stylesheets/highlight/themes/none.scss
+++ b/app/assets/stylesheets/highlight/themes/none.scss
@@ -25,19 +25,7 @@
// Line numbers
.file-line-num {
- @include line-link($black, 'link');
- }
-
- .file-line-blame {
- @include line-link($black, 'git');
- }
-
- .line-links {
- @include line-hover-bg;
- }
-
- .line-links-wrapper {
- @include first-line-top-space;
+ @include line-number-link($black-transparent);
}
.line-numbers,
diff --git a/app/assets/stylesheets/highlight/themes/solarized-dark.scss b/app/assets/stylesheets/highlight/themes/solarized-dark.scss
index 95d3c8feb54..6260339a48d 100644
--- a/app/assets/stylesheets/highlight/themes/solarized-dark.scss
+++ b/app/assets/stylesheets/highlight/themes/solarized-dark.scss
@@ -123,19 +123,7 @@ $solarized-dark-il: #2aa198;
// Line numbers
.file-line-num {
- @include line-link($white, 'link');
- }
-
- .file-line-blame {
- @include line-link($white, 'git');
- }
-
- .line-links {
- @include line-hover-bg($solarized-dark-pre-bg);
- }
-
- .line-links-wrapper {
- @include first-line-top-space($solarized-dark-pre-bg, $solarized-dark-pre-border);
+ @include line-number-link($solarized-dark-line-color);
}
.line-numbers,
diff --git a/app/assets/stylesheets/highlight/themes/solarized-light.scss b/app/assets/stylesheets/highlight/themes/solarized-light.scss
index 47f5e0bd3da..e6f098f4cdf 100644
--- a/app/assets/stylesheets/highlight/themes/solarized-light.scss
+++ b/app/assets/stylesheets/highlight/themes/solarized-light.scss
@@ -109,19 +109,7 @@ $solarized-light-il: #2aa198;
@include hljs-override('title.class_.inherited__', $solarized-light-no);
// Line numbers
.file-line-num {
- @include line-link($black, 'link');
- }
-
- .file-line-blame {
- @include line-link($black, 'git');
- }
-
- .line-links {
- @include line-hover-bg($solarized-light-pre-bg);
- }
-
- .line-links-wrapper {
- @include first-line-top-space($solarized-light-pre-bg, $solarized-light-border);
+ @include line-number-link($solarized-light-line-color);
}
.line-numbers,
diff --git a/app/assets/stylesheets/highlight/white_base.scss b/app/assets/stylesheets/highlight/white_base.scss
index 336c2b739f1..770a90bbc57 100644
--- a/app/assets/stylesheets/highlight/white_base.scss
+++ b/app/assets/stylesheets/highlight/white_base.scss
@@ -95,15 +95,7 @@ $white-gc-bg: #eaf2f5;
// Line numbers
.file-line-num {
- @include line-link($black, 'link');
-}
-
-.file-line-blame {
- @include line-link($black, 'git');
-}
-
-.line-links {
- @include line-hover-bg;
+ @include line-number-link($black-transparent);
}
.line-numbers,
@@ -134,10 +126,6 @@ pre.code,
border-color: $white-normal;
}
-.line-links-wrapper {
- @include first-line-top-space;
-}
-
&,
pre.code,
.line_holder .line_content {
diff --git a/app/graphql/types/ci/job_type.rb b/app/graphql/types/ci/job_type.rb
index 802cb09b36a..42b55f47f92 100644
--- a/app/graphql/types/ci/job_type.rb
+++ b/app/graphql/types/ci/job_type.rb
@@ -70,6 +70,8 @@ module Types
description: 'Downstream pipeline for a bridge.'
field :manual_job, GraphQL::Types::Boolean, null: true,
description: 'Whether the job has a manual action.'
+ field :manual_variables, VariableType.connection_type, null: true,
+ description: 'Variables added to a manual job when the job is triggered.'
field :playable, GraphQL::Types::Boolean, null: false, method: :playable?,
description: 'Indicates the job can be played.'
field :previous_stage_jobs_or_needs, Types::Ci::JobNeedUnion.connection_type, null: true,
@@ -190,6 +192,14 @@ module Types
def triggered
object.try(:trigger_request)
end
+
+ def manual_variables
+ if object.manual? && object.respond_to?(:job_variables)
+ object.job_variables
+ else
+ []
+ end
+ end
end
end
end
diff --git a/app/graphql/types/ci/variable_type.rb b/app/graphql/types/ci/variable_type.rb
index 5d2acfb9c9f..63f89b6d207 100644
--- a/app/graphql/types/ci/variable_type.rb
+++ b/app/graphql/types/ci/variable_type.rb
@@ -26,6 +26,15 @@ module Types
field :raw, GraphQL::Types::Boolean, null: true,
description: 'Indicates whether the variable is raw.'
+
+ field :environment_scope, GraphQL::Types::String, null: true,
+ description: 'Scope defining the environments in which the variable can be used.'
+
+ def environment_scope
+ if object.respond_to?(:environment_scope)
+ object.environment_scope
+ end
+ end
end
end
end
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index 795def55306..1c01a81ee41 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -338,8 +338,8 @@ module Ci
scope :for_id, -> (id) { where(id: id) }
scope :for_iid, -> (iid) { where(iid: iid) }
scope :for_project, -> (project_id) { where(project_id: project_id) }
- scope :created_after, -> (time) { where('ci_pipelines.created_at > ?', time) }
- scope :created_before_id, -> (id) { where('ci_pipelines.id < ?', id) }
+ scope :created_after, -> (time) { where(arel_table[:created_at].gt(time)) }
+ scope :created_before_id, -> (id) { where(arel_table[:id].lt(id)) }
scope :before_pipeline, -> (pipeline) { created_before_id(pipeline.id).outside_pipeline_family(pipeline) }
scope :with_pipeline_source, -> (source) { where(source: source) }
diff --git a/app/policies/work_item_policy.rb b/app/policies/work_item_policy.rb
index ea7559592e1..2f3561f1135 100644
--- a/app/policies/work_item_policy.rb
+++ b/app/policies/work_item_policy.rb
@@ -13,4 +13,8 @@ class WorkItemPolicy < IssuePolicy
# need to make sure we also prevent this rule if read_issue
# is prevented
rule { ~can?(:read_issue) }.prevent :read_work_item
+
+ rule { can?(:reporter_access) }.policy do
+ enable :admin_parent_link
+ end
end
diff --git a/app/services/work_items/create_from_task_service.rb b/app/services/work_items/create_from_task_service.rb
index d5fa5fca772..ef1d47c560d 100644
--- a/app/services/work_items/create_from_task_service.rb
+++ b/app/services/work_items/create_from_task_service.rb
@@ -27,6 +27,7 @@ module WorkItems
replacement_result = TaskListReferenceReplacementService.new(
work_item: @work_item,
+ current_user: @current_user,
work_item_reference: create_and_link_result[:work_item].to_reference,
line_number_start: @work_item_params[:line_number_start],
line_number_end: @work_item_params[:line_number_end],
diff --git a/app/services/work_items/parent_links/create_service.rb b/app/services/work_items/parent_links/create_service.rb
index 87995cc6550..78013f081c8 100644
--- a/app/services/work_items/parent_links/create_service.rb
+++ b/app/services/work_items/parent_links/create_service.rb
@@ -20,7 +20,7 @@ module WorkItems
def linkable_issuables(work_items)
@linkable_issuables ||= begin
- return [] unless can?(current_user, :read_work_item, issuable.project)
+ return [] unless can?(current_user, :admin_parent_link, issuable)
work_items.select do |work_item|
linkable?(work_item)
@@ -29,7 +29,7 @@ module WorkItems
end
def linkable?(work_item)
- can?(current_user, :update_work_item, work_item) &&
+ can?(current_user, :admin_parent_link, work_item) &&
!previous_related_issuables.include?(work_item)
end
@@ -42,8 +42,8 @@ module WorkItems
::WorkItem.find(id)
rescue ActiveRecord::RecordNotFound
@errors << _("Task with ID: %{id} could not be found.") % { id: id }
- nil
- end
+ next
+ end.compact
end
# TODO: Create system notes when work item's parent or children are updated
diff --git a/app/services/work_items/parent_links/destroy_service.rb b/app/services/work_items/parent_links/destroy_service.rb
index deca24159d3..55870d44db9 100644
--- a/app/services/work_items/parent_links/destroy_service.rb
+++ b/app/services/work_items/parent_links/destroy_service.rb
@@ -25,7 +25,7 @@ module WorkItems
end
def permission_to_remove_relation?
- can?(current_user, :update_work_item, child) && can?(current_user, :update_work_item, parent)
+ can?(current_user, :admin_parent_link, child) && can?(current_user, :admin_parent_link, parent)
end
end
end
diff --git a/app/services/work_items/task_list_reference_removal_service.rb b/app/services/work_items/task_list_reference_removal_service.rb
index e7ec73a96e0..9152580bef0 100644
--- a/app/services/work_items/task_list_reference_removal_service.rb
+++ b/app/services/work_items/task_list_reference_removal_service.rb
@@ -11,6 +11,7 @@ module WorkItems
@line_number_end = line_number_end
@lock_version = lock_version
@current_user = current_user
+ @task_reference = /#{Regexp.escape(@task.to_reference)}(?!\d)\+/
end
def execute
@@ -26,7 +27,9 @@ module WorkItems
line_matches_reference = (@line_number_start..@line_number_end).any? do |line_number|
markdown_line = source_lines[line_number - 1]
- /#{Regexp.escape(@task.to_reference)}(?!\d)/.match?(markdown_line)
+ if @task_reference.match?(markdown_line)
+ markdown_line.sub!(@task_reference, @task.title)
+ end
end
unless line_matches_reference
@@ -35,8 +38,6 @@ module WorkItems
)
end
- remove_task_lines!(source_lines)
-
::WorkItems::UpdateService.new(
project: @work_item.project,
current_user: @current_user,
@@ -51,13 +52,5 @@ module WorkItems
rescue ActiveRecord::StaleObjectError
::ServiceResponse.error(message: STALE_OBJECT_MESSAGE)
end
-
- private
-
- def remove_task_lines!(source_lines)
- source_lines.delete_if.each_with_index do |_line, index|
- index >= @line_number_start - 1 && index < @line_number_end
- end
- end
end
end
diff --git a/app/services/work_items/task_list_reference_replacement_service.rb b/app/services/work_items/task_list_reference_replacement_service.rb
index 1044a4feb88..b098d67561b 100644
--- a/app/services/work_items/task_list_reference_replacement_service.rb
+++ b/app/services/work_items/task_list_reference_replacement_service.rb
@@ -4,8 +4,9 @@ module WorkItems
class TaskListReferenceReplacementService
STALE_OBJECT_MESSAGE = 'Stale work item. Check lock version'
- def initialize(work_item:, work_item_reference:, line_number_start:, line_number_end:, title:, lock_version:)
+ def initialize(work_item:, current_user:, work_item_reference:, line_number_start:, line_number_end:, title:, lock_version:)
@work_item = work_item
+ @current_user = current_user
@work_item_reference = work_item_reference
@line_number_start = line_number_start
@line_number_end = line_number_end
@@ -32,7 +33,11 @@ module WorkItems
source_lines[@line_number_start - 1] = markdown_task_first_line
remove_additional_lines!(source_lines)
- @work_item.update!(description: source_lines.join("\n"))
+ ::WorkItems::UpdateService.new(
+ project: @work_item.project,
+ current_user: @current_user,
+ params: { description: source_lines.join("\n"), lock_version: @lock_version }
+ ).execute(@work_item)
::ServiceResponse.success
rescue ActiveRecord::StaleObjectError
diff --git a/app/views/projects/protected_tags/shared/_create_protected_tag.html.haml b/app/views/projects/protected_tags/shared/_create_protected_tag.html.haml
index ba0935fff7d..e257117a32e 100644
--- a/app/views/projects/protected_tags/shared/_create_protected_tag.html.haml
+++ b/app/views/projects/protected_tags/shared/_create_protected_tag.html.haml
@@ -4,7 +4,7 @@
.card-header
= _('Protect a tag')
.card-body
- = form_errors(@protected_tag)
+ = form_errors(@protected_tag, pajamas_alert: true)
.form-group.row
= f.label :name, _('Tag:'), class: 'col-md-2 text-left text-md-right'
.col-md-10.protected-tags-dropdown
diff --git a/app/views/projects/settings/ci_cd/_autodevops_form.html.haml b/app/views/projects/settings/ci_cd/_autodevops_form.html.haml
index 96564e44cf2..64f45ec89d1 100644
--- a/app/views/projects/settings/ci_cd/_autodevops_form.html.haml
+++ b/app/views/projects/settings/ci_cd/_autodevops_form.html.haml
@@ -16,7 +16,7 @@
.row
.col-lg-12
= gitlab_ui_form_for @project, url: project_settings_ci_cd_path(@project, anchor: 'autodevops-settings') do |f|
- = form_errors(@project)
+ = form_errors(@project, pajamas_alert: true)
%fieldset.builds-feature.js-auto-devops-settings
.form-group
= f.fields_for :auto_devops_attributes, @auto_devops do |form|
diff --git a/app/views/shared/_file_highlight.html.haml b/app/views/shared/_file_highlight.html.haml
index 453c6438edf..f8ac3832a77 100644
--- a/app/views/shared/_file_highlight.html.haml
+++ b/app/views/shared/_file_highlight.html.haml
@@ -1,16 +1,13 @@
#blob-content.file-content.code.js-syntax-highlight
- offset = defined?(first_line_number) ? first_line_number : 1
- .line-numbers{ class: "gl-p-0\!" }
+ .line-numbers
- if blob.data.present?
- link = blob_link if defined?(blob_link)
- - blame_link = project_blame_path(@project, tree_join(@ref, blob.path))
- blob.data.each_line.each_with_index do |_, index|
- i = index + offset
-# We're not using `link_to` because it is too slow once we get to thousands of lines.
- .gl-display-flex.line-links.diff-line-num
- %a.file-line-blame.gl-display-flex.has-tooltip.gl-ml-3{ href: "#{blame_link}#L#{i}", title: _('View blame'), data: { track_action: "click_link", track_label: "file_line_action", track_property: "blame" } }
- %a.file-line-num.gl-display-flex.gl-justify-content-end.flex-grow-1.gl-pr-3{ href: "#{link}#L#{i}", id: "L#{i}", 'data-line-number' => i, data: { track_action: "click_link", track_label: "file_line_action", track_property: "link" } }
- = i
+ %a.file-line-num.diff-line-num{ href: "#{link}#L#{i}", id: "L#{i}", 'data-line-number' => i }
+ = i
- highlight = defined?(highlight_line) && highlight_line ? highlight_line - offset : nil
.blob-content{ data: { blob_id: blob.id, path: blob.path, highlight_line: highlight, qa_selector: 'file_content' } }
%pre.code.highlight
diff --git a/app/views/shared/issuable/form/_type_selector.html.haml b/app/views/shared/issuable/form/_type_selector.html.haml
index d5c696b1698..a94ef70b2d5 100644
--- a/app/views/shared/issuable/form/_type_selector.html.haml
+++ b/app/views/shared/issuable/form/_type_selector.html.haml
@@ -1,35 +1,35 @@
- return unless issuable.supports_issue_type? && can?(current_user, :create_issue, @project)
.form-group
- = form.label :type, _('Type')
- .gl-display-flex.gl-align-items-center
- .issuable-form-select-holder.selectbox.form-group.gl-mb-0
- .dropdown.js-issuable-type-filter-dropdown-wrap
- %button.dropdown-menu-toggle{ type: 'button', 'data-toggle' => 'dropdown' }
- %span.dropdown-toggle-text.is-default
- = issuable.issue_type.capitalize || _("Select type")
- = sprite_icon('chevron-down', css_class: "dropdown-menu-toggle-icon gl-top-3")
- .dropdown-menu.dropdown-menu-selectable.dropdown-select
- .dropdown-title.gl-display-flex
- %span.gl-ml-auto
- = _("Select type")
- %button.dropdown-title-button.dropdown-menu-close.gl-ml-auto{ type: 'button', "aria-label" => _('Close') }
- = sprite_icon('close', size: 16, css_class: 'dropdown-menu-close-icon')
- .dropdown-content{ data: { testid: 'issue-type-select-dropdown' } }
- %ul
- - if create_issue_type_allowed?(@project, :issue)
- %li.js-filter-issuable-type
- = link_to new_project_issue_path(@project), class: ("is-active" if issuable.issue?) do
- #{sprite_icon(work_item_type_icon(:issue), css_class: 'gl-icon')} #{_('Issue')}
- - if create_issue_type_allowed?(@project, :incident)
- %li.js-filter-issuable-type{ data: { track: { action: "select_issue_type_incident", label: "select_issue_type_incident_dropdown_option" } } }
- = link_to new_project_issue_path(@project, { issuable_template: 'incident', issue: { issue_type: 'incident' } }), class: ("is-active" if issuable.incident?) do
- #{sprite_icon(work_item_type_icon(:incident), css_class: 'gl-icon')} #{_('Incident')}
-
+ = form.label :type do
+ = _('Type')
#js-type-popover
- - if issuable.incident?
- %p.form-text.text-muted
- - incident_docs_url = help_page_path('operations/incident_management/incidents.md')
- - incident_docs_start = format('<a href="%{url}" target="_blank" rel="noopener noreferrer">', url: incident_docs_url)
- = format(_('A %{incident_docs_start}modified issue%{incident_docs_end} to guide the resolution of incidents.'), incident_docs_start: incident_docs_start, incident_docs_end: '</a>').html_safe
+ .issuable-form-select-holder.selectbox.form-group.gl-mb-0.gl-display-block
+ .dropdown.js-issuable-type-filter-dropdown-wrap
+ %button.dropdown-menu-toggle{ type: 'button', 'data-toggle' => 'dropdown' }
+ %span.dropdown-toggle-text.is-default
+ = issuable.issue_type.capitalize || _("Select type")
+ = sprite_icon('chevron-down', css_class: "dropdown-menu-toggle-icon gl-top-3")
+ .dropdown-menu.dropdown-menu-selectable.dropdown-select
+ .dropdown-title.gl-display-flex
+ %span.gl-ml-auto
+ = _("Select type")
+ %button.dropdown-title-button.dropdown-menu-close.gl-ml-auto{ type: 'button', "aria-label" => _('Close') }
+ = sprite_icon('close', size: 16, css_class: 'dropdown-menu-close-icon')
+ .dropdown-content{ data: { testid: 'issue-type-select-dropdown' } }
+ %ul
+ - if create_issue_type_allowed?(@project, :issue)
+ %li.js-filter-issuable-type
+ = link_to new_project_issue_path(@project), class: ("is-active" if issuable.issue?) do
+ #{sprite_icon(work_item_type_icon(:issue), css_class: 'gl-icon')} #{_('Issue')}
+ - if create_issue_type_allowed?(@project, :incident)
+ %li.js-filter-issuable-type{ data: { track: { action: "select_issue_type_incident", label: "select_issue_type_incident_dropdown_option" } } }
+ = link_to new_project_issue_path(@project, { issuable_template: 'incident', issue: { issue_type: 'incident' } }), class: ("is-active" if issuable.incident?) do
+ #{sprite_icon(work_item_type_icon(:incident), css_class: 'gl-icon')} #{_('Incident')}
+
+ - if issuable.incident?
+ %p.form-text.text-muted
+ - incident_docs_url = help_page_path('operations/incident_management/incidents.md')
+ - incident_docs_start = format('<a href="%{url}" target="_blank" rel="noopener noreferrer">', url: incident_docs_url)
+ = format(_('A %{incident_docs_start}modified issue%{incident_docs_end} to guide the resolution of incidents.'), incident_docs_start: incident_docs_start, incident_docs_end: '</a>').html_safe