diff options
Diffstat (limited to 'app/assets/javascripts/pages/projects')
18 files changed, 197 insertions, 75 deletions
diff --git a/app/assets/javascripts/pages/projects/alert_management/details/index.js b/app/assets/javascripts/pages/projects/alert_management/details/index.js new file mode 100644 index 00000000000..0124795e1af --- /dev/null +++ b/app/assets/javascripts/pages/projects/alert_management/details/index.js @@ -0,0 +1,5 @@ +import AlertDetails from '~/alert_management/details'; + +document.addEventListener('DOMContentLoaded', () => { + AlertDetails('#js-alert_details'); +}); diff --git a/app/assets/javascripts/pages/projects/alert_management/index/index.js b/app/assets/javascripts/pages/projects/alert_management/index/index.js new file mode 100644 index 00000000000..1e98bcfd2eb --- /dev/null +++ b/app/assets/javascripts/pages/projects/alert_management/index/index.js @@ -0,0 +1,5 @@ +import AlertManagementList from '~/alert_management/list'; + +document.addEventListener('DOMContentLoaded', () => { + AlertManagementList(); +}); diff --git a/app/assets/javascripts/pages/projects/blob/new/index.js b/app/assets/javascripts/pages/projects/blob/new/index.js index 720cb249052..189053f3ed7 100644 --- a/app/assets/javascripts/pages/projects/blob/new/index.js +++ b/app/assets/javascripts/pages/projects/blob/new/index.js @@ -1,12 +1,3 @@ import initBlobBundle from '~/blob_edit/blob_bundle'; -import initPopover from '~/blob/suggest_gitlab_ci_yml'; -document.addEventListener('DOMContentLoaded', () => { - initBlobBundle(); - - const suggestEl = document.querySelector('.js-suggest-gitlab-ci-yml'); - - if (suggestEl) { - initPopover(suggestEl); - } -}); +document.addEventListener('DOMContentLoaded', initBlobBundle); diff --git a/app/assets/javascripts/pages/projects/blob/show/index.js b/app/assets/javascripts/pages/projects/blob/show/index.js index 557aea0c5de..e5e4670a5d7 100644 --- a/app/assets/javascripts/pages/projects/blob/show/index.js +++ b/app/assets/javascripts/pages/projects/blob/show/index.js @@ -32,9 +32,10 @@ document.addEventListener('DOMContentLoaded', () => { GpgBadges.fetch(); - if (gon.features?.codeNavigation) { - const el = document.getElementById('js-code-navigation'); - const { codeNavigationPath, blobPath, definitionPathPrefix } = el.dataset; + const codeNavEl = document.getElementById('js-code-navigation'); + + if (gon.features?.codeNavigation && codeNavEl) { + const { codeNavigationPath, blobPath, definitionPathPrefix } = codeNavEl.dataset; // eslint-disable-next-line promise/catch-or-return import('~/code_navigation').then(m => diff --git a/app/assets/javascripts/pages/projects/environments/metrics/index.js b/app/assets/javascripts/pages/projects/environments/metrics/index.js index 0d69a689316..31ec4e29ad2 100644 --- a/app/assets/javascripts/pages/projects/environments/metrics/index.js +++ b/app/assets/javascripts/pages/projects/environments/metrics/index.js @@ -1,3 +1,3 @@ -import monitoringBundle from 'ee_else_ce/monitoring/monitoring_bundle'; +import monitoringBundle from '~/monitoring/monitoring_bundle_with_alerts'; document.addEventListener('DOMContentLoaded', monitoringBundle); diff --git a/app/assets/javascripts/pages/projects/issues/index/index.js b/app/assets/javascripts/pages/projects/issues/index/index.js index bf54ca972b2..e8e0cda2139 100644 --- a/app/assets/javascripts/pages/projects/issues/index/index.js +++ b/app/assets/javascripts/pages/projects/issues/index/index.js @@ -7,6 +7,7 @@ import UsersSelect from '~/users_select'; import initFilteredSearch from '~/pages/search/init_filtered_search'; import { FILTERED_SEARCH } from '~/pages/constants'; import { ISSUABLE_INDEX } from '~/pages/projects/constants'; +import initIssuablesList from '~/issuables_list'; import initManualOrdering from '~/manual_ordering'; document.addEventListener('DOMContentLoaded', () => { @@ -16,9 +17,11 @@ document.addEventListener('DOMContentLoaded', () => { page: FILTERED_SEARCH.ISSUES, filteredSearchTokenKeys: IssuableFilteredSearchTokenKeys, }); - new IssuableIndex(ISSUABLE_INDEX.ISSUE); + new IssuableIndex(ISSUABLE_INDEX.ISSUE); new ShortcutsNavigation(); new UsersSelect(); + initManualOrdering(); + initIssuablesList(); }); diff --git a/app/assets/javascripts/pages/projects/issues/show.js b/app/assets/javascripts/pages/projects/issues/show.js index 75df80a0f6c..46c9b2fe0af 100644 --- a/app/assets/javascripts/pages/projects/issues/show.js +++ b/app/assets/javascripts/pages/projects/issues/show.js @@ -12,6 +12,16 @@ export default function() { initIssueableApp(); initSentryErrorStackTraceApp(); initRelatedMergeRequestsApp(); + + // .js-design-management is currently EE-only. + // This will be moved to CE as part of https://gitlab.com/gitlab-org/gitlab/-/issues/212566#frontend + // at which point this conditional can be removed. + if (document.querySelector('.js-design-management')) { + import(/* webpackChunkName: 'design_management' */ '~/design_management') + .then(module => module.default()) + .catch(() => {}); + } + new Issue(); // eslint-disable-line no-new new ShortcutsIssuable(); // eslint-disable-line no-new new ZenMode(); // eslint-disable-line no-new diff --git a/app/assets/javascripts/pages/projects/labels/components/promote_label_modal.vue b/app/assets/javascripts/pages/projects/labels/components/promote_label_modal.vue index 12e16b79d37..3b26047455d 100644 --- a/app/assets/javascripts/pages/projects/labels/components/promote_label_modal.vue +++ b/app/assets/javascripts/pages/projects/labels/components/promote_label_modal.vue @@ -1,5 +1,5 @@ <script> -import { escape as esc } from 'lodash'; +import { escape } from 'lodash'; import axios from '~/lib/utils/axios_utils'; import createFlash from '~/flash'; import DeprecatedModal2 from '~/vue_shared/components/deprecated_modal_2.vue'; @@ -49,7 +49,7 @@ export default { const label = `<span class="label color-label" style="background-color: ${this.labelColor}; color: ${this.labelTextColor};" - >${esc(this.labelTitle)}</span>`; + >${escape(this.labelTitle)}</span>`; return sprintf( s__('Labels|<span>Promote label</span> %{labelTitle} <span>to Group Label?</span>'), diff --git a/app/assets/javascripts/pages/projects/labels/event_hub.js b/app/assets/javascripts/pages/projects/labels/event_hub.js index 0948c2e5352..e31806ad199 100644 --- a/app/assets/javascripts/pages/projects/labels/event_hub.js +++ b/app/assets/javascripts/pages/projects/labels/event_hub.js @@ -1,3 +1,3 @@ -import Vue from 'vue'; +import createEventHub from '~/helpers/event_hub_factory'; -export default new Vue(); +export default createEventHub(); diff --git a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue index 3a0d9c17228..4efabcb7df3 100644 --- a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue +++ b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue @@ -1,5 +1,13 @@ <script> +import { GlSprintf, GlLink } from '@gitlab/ui'; +import { s__, sprintf } from '~/locale'; +import { getWeekdayNames } from '~/lib/utils/datetime_utility'; + export default { + components: { + GlSprintf, + GlLink, + }, props: { initialCronInterval: { type: String, @@ -9,25 +17,51 @@ export default { }, data() { return { + isEditingCustom: false, + randomHour: this.generateRandomHour(), + randomWeekDayIndex: this.generateRandomWeekDayIndex(), + randomDay: this.generateRandomDay(), inputNameAttribute: 'schedule[cron]', cronInterval: this.initialCronInterval, - cronIntervalPresets: { - everyDay: '0 4 * * *', - everyWeek: '0 4 * * 0', - everyMonth: '0 4 1 * *', - }, cronSyntaxUrl: 'https://en.wikipedia.org/wiki/Cron', - customInputEnabled: false, }; }, computed: { + cronIntervalPresets() { + return { + everyDay: `0 ${this.randomHour} * * *`, + everyWeek: `0 ${this.randomHour} * * ${this.randomWeekDayIndex}`, + everyMonth: `0 ${this.randomHour} ${this.randomDay} * *`, + }; + }, intervalIsPreset() { return Object.values(this.cronIntervalPresets).includes(this.cronInterval); }, - // The text input is editable when there's a custom interval, or when it's - // a preset interval and the user clicks the 'custom' radio button - isEditable() { - return Boolean(this.customInputEnabled || !this.intervalIsPreset); + formattedTime() { + if (this.randomHour > 12) { + return `${this.randomHour - 12}:00pm`; + } else if (this.randomHour === 12) { + return `12:00pm`; + } + return `${this.randomHour}:00am`; + }, + weekday() { + return getWeekdayNames()[this.randomWeekDayIndex]; + }, + everyDayText() { + return sprintf(s__(`Every day (at %{time})`), { time: this.formattedTime }); + }, + everyWeekText() { + return sprintf(s__('Every week (%{weekday} at %{time})'), { + weekday: this.weekday, + time: this.formattedTime, + }); + }, + everyMonthText() { + return sprintf(s__('Every month (Day %{day} at %{time})'), { + day: this.randomDay, + time: this.formattedTime, + }); }, }, watch: { @@ -39,14 +73,31 @@ export default { }); }, }, - created() { - if (this.intervalIsPreset) { - this.enableCustomInput = false; + // If at the mounting stage the default is still an empty string, we + // know we are not editing an existing field so we update it so + // that the default is the first radio option + mounted() { + if (this.cronInterval === '') { + this.cronInterval = this.cronIntervalPresets.everyDay; } }, methods: { + setCustomInput(e) { + if (!this.isEditingCustom) { + this.isEditingCustom = true; + this.$refs.customInput.click(); + // Because we need to manually trigger the click on the radio btn, + // it will add a space to update the v-model. If the user is typing + // and the space is added, it will feel very unituitive so we reset + // the value to the original + this.cronInterval = e.target.value; + } + if (this.intervalIsPreset) { + this.isEditingCustom = false; + } + }, toggleCustomInput(shouldEnable) { - this.customInputEnabled = shouldEnable; + this.isEditingCustom = shouldEnable; if (shouldEnable) { // We need to change the value so other radios don't remain selected @@ -54,6 +105,15 @@ export default { this.cronInterval = `${this.cronInterval} `; } }, + generateRandomHour() { + return Math.floor(Math.random() * 23); + }, + generateRandomWeekDayIndex() { + return Math.floor(Math.random() * 6); + }, + generateRandomDay() { + return Math.floor(Math.random() * 28); + }, }, }; </script> @@ -62,24 +122,6 @@ export default { <div class="interval-pattern-form-group"> <div class="cron-preset-radio-input"> <input - id="custom" - :name="inputNameAttribute" - :value="cronInterval" - :checked="isEditable" - class="label-bold" - type="radio" - @click="toggleCustomInput(true)" - /> - - <label for="custom"> {{ s__('PipelineSheduleIntervalPattern|Custom') }} </label> - - <span class="cron-syntax-link-wrap"> - (<a :href="cronSyntaxUrl" target="_blank"> {{ __('Cron syntax') }} </a>) - </span> - </div> - - <div class="cron-preset-radio-input"> - <input id="every-day" v-model="cronInterval" :name="inputNameAttribute" @@ -89,7 +131,9 @@ export default { @click="toggleCustomInput(false)" /> - <label class="label-bold" for="every-day"> {{ __('Every day (at 4:00am)') }} </label> + <label class="label-bold" for="every-day"> + {{ everyDayText }} + </label> </div> <div class="cron-preset-radio-input"> @@ -104,7 +148,7 @@ export default { /> <label class="label-bold" for="every-week"> - {{ __('Every week (Sundays at 4:00am)') }} + {{ everyWeekText }} </label> </div> @@ -120,20 +164,43 @@ export default { /> <label class="label-bold" for="every-month"> - {{ __('Every month (on the 1st at 4:00am)') }} + {{ everyMonthText }} </label> </div> + <div class="cron-preset-radio-input"> + <input + id="custom" + ref="customInput" + v-model="cronInterval" + :name="inputNameAttribute" + :value="cronInterval" + class="label-bold" + type="radio" + @click="toggleCustomInput(true)" + /> + + <label for="custom"> {{ s__('PipelineSheduleIntervalPattern|Custom') }} </label> + + <gl-sprintf :message="__('(%{linkStart}Cron syntax%{linkEnd})')"> + <template #link="{content}"> + <gl-link :href="cronSyntaxUrl" target="_blank" class="gl-font-sm"> + {{ content }} + </gl-link> + </template> + </gl-sprintf> + </div> + <div class="cron-interval-input-wrapper"> <input id="schedule_cron" v-model="cronInterval" :placeholder="__('Define a custom pattern with cron syntax')" :name="inputNameAttribute" - :disabled="!isEditable" class="form-control inline cron-interval-input" type="text" required="true" + @input="setCustomInput" /> </div> </div> diff --git a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/pipeline_schedules_callout.vue b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/pipeline_schedules_callout.vue index 22512a6f12a..da96e6f36b4 100644 --- a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/pipeline_schedules_callout.vue +++ b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/pipeline_schedules_callout.vue @@ -2,7 +2,8 @@ import Vue from 'vue'; import Cookies from 'js-cookie'; import Translate from '../../../../../vue_shared/translate'; -import illustrationSvg from '../icons/intro_illustration.svg'; +// Full path is needed for Jest to be able to correctly mock this file +import illustrationSvg from '~/pages/projects/pipeline_schedules/shared/icons/intro_illustration.svg'; import { parseBoolean } from '~/lib/utils/common_utils'; Vue.use(Translate); diff --git a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/init_form.js b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/init_form.js index dc6df27f1c7..497e2c9c0ae 100644 --- a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/init_form.js +++ b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/init_form.js @@ -11,9 +11,7 @@ Vue.use(Translate); function initIntervalPatternInput() { const intervalPatternMount = document.getElementById('interval-pattern-input'); - const initialCronInterval = intervalPatternMount - ? intervalPatternMount.dataset.initialInterval - : ''; + const initialCronInterval = intervalPatternMount?.dataset?.initialInterval; return new Vue({ el: intervalPatternMount, diff --git a/app/assets/javascripts/pages/projects/pipelines/dag/index.js b/app/assets/javascripts/pages/projects/pipelines/dag/index.js new file mode 100644 index 00000000000..d19c22ba556 --- /dev/null +++ b/app/assets/javascripts/pages/projects/pipelines/dag/index.js @@ -0,0 +1,2 @@ +// /dag is an alias for show +import '../show/index'; diff --git a/app/assets/javascripts/pages/projects/pipelines/index/index.js b/app/assets/javascripts/pages/projects/pipelines/index/index.js index 4b4a274794d..bbad3238ec4 100644 --- a/app/assets/javascripts/pages/projects/pipelines/index/index.js +++ b/app/assets/javascripts/pages/projects/pipelines/index/index.js @@ -50,6 +50,7 @@ document.addEventListener( hasGitlabCi: parseBoolean(this.dataset.hasGitlabCi), ciLintPath: this.dataset.ciLintPath, resetCachePath: this.dataset.resetCachePath, + projectId: this.dataset.projectId, }, }); }, diff --git a/app/assets/javascripts/pages/projects/settings/access_tokens/index.js b/app/assets/javascripts/pages/projects/settings/access_tokens/index.js new file mode 100644 index 00000000000..ae2209b0292 --- /dev/null +++ b/app/assets/javascripts/pages/projects/settings/access_tokens/index.js @@ -0,0 +1,3 @@ +import initExpiresAtField from '~/access_tokens'; + +document.addEventListener('DOMContentLoaded', initExpiresAtField); diff --git a/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue b/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue index 6efddec1172..ab32fe18972 100644 --- a/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue +++ b/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue @@ -1,9 +1,8 @@ <script> -import { GlSprintf, GlLink } from '@gitlab/ui'; +import { GlSprintf, GlLink, GlFormCheckbox } from '@gitlab/ui'; import settingsMixin from 'ee_else_ce/pages/projects/shared/permissions/mixins/settings_pannel_mixin'; import { s__ } from '~/locale'; -import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import projectFeatureSetting from './project_feature_setting.vue'; import projectFeatureToggle from '~/vue_shared/components/toggle_button.vue'; import projectSettingRow from './project_setting_row.vue'; @@ -12,6 +11,7 @@ import { visibilityLevelDescriptions, featureAccessLevelMembers, featureAccessLevelEveryone, + featureAccessLevel, } from '../constants'; import { toggleHiddenClassBySelector } from '../external'; @@ -24,8 +24,9 @@ export default { projectSettingRow, GlSprintf, GlLink, + GlFormCheckbox, }, - mixins: [settingsMixin, glFeatureFlagsMixin()], + mixins: [settingsMixin], props: { currentSettings: { @@ -127,7 +128,7 @@ export default { wikiAccessLevel: 20, snippetsAccessLevel: 20, pagesAccessLevel: 20, - metricsAccessLevel: visibilityOptions.PRIVATE, + metricsDashboardAccessLevel: featureAccessLevel.PROJECT_MEMBERS, containerRegistryEnabled: true, lfsEnabled: true, requestAccessEnabled: true, @@ -174,6 +175,10 @@ export default { return options; }, + metricsOptionsDropdownEnabled() { + return this.featureAccessLevelOptions.length < 2; + }, + repositoryEnabled() { return this.repositoryAccessLevel > 0; }, @@ -195,10 +200,6 @@ export default { 'ProjectSettings|View and edit files in this project. Non-project members will only have read access', ); }, - - metricsDashboardVisibilitySwitchingAvailable() { - return this.glFeatures.metricsDashboardVisibilitySwitchingAvailable; - }, }, watch: { @@ -211,6 +212,7 @@ export default { this.buildsAccessLevel = Math.min(10, this.buildsAccessLevel); this.wikiAccessLevel = Math.min(10, this.wikiAccessLevel); this.snippetsAccessLevel = Math.min(10, this.snippetsAccessLevel); + this.metricsDashboardAccessLevel = Math.min(10, this.metricsDashboardAccessLevel); if (this.pagesAccessLevel === 20) { // When from Internal->Private narrow access for only members this.pagesAccessLevel = 10; @@ -225,6 +227,7 @@ export default { if (this.wikiAccessLevel > 0) this.wikiAccessLevel = 20; if (this.snippetsAccessLevel > 0) this.snippetsAccessLevel = 20; if (this.pagesAccessLevel === 10) this.pagesAccessLevel = 20; + if (this.metricsDashboardAccessLevel === 10) this.metricsDashboardAccessLevel = 20; this.highlightChanges(); } }, @@ -473,7 +476,6 @@ export default { /> </project-setting-row> <project-setting-row - v-if="metricsDashboardVisibilitySwitchingAvailable" ref="metrics-visibility-settings" :label="__('Metrics Dashboard')" :help-text=" @@ -485,17 +487,18 @@ export default { <div class="project-feature-controls"> <div class="select-wrapper"> <select - v-model="metricsAccessLevel" + v-model="metricsDashboardAccessLevel" + :disabled="metricsOptionsDropdownEnabled" name="project[project_feature_attributes][metrics_dashboard_access_level]" - class="form-control select-control" + class="form-control project-repo-select select-control" > <option - :value="visibilityOptions.PRIVATE" - :disabled="!visibilityAllowed(visibilityOptions.PRIVATE)" + :value="featureAccessLevelMembers[0]" + :disabled="!visibilityAllowed(visibilityOptions.INTERNAL)" >{{ featureAccessLevelMembers[1] }}</option > <option - :value="visibilityOptions.PUBLIC" + :value="featureAccessLevelEveryone[0]" :disabled="!visibilityAllowed(visibilityOptions.PUBLIC)" >{{ featureAccessLevelEveryone[1] }}</option > @@ -517,5 +520,23 @@ export default { ) }}</span> </project-setting-row> + <project-setting-row class="mb-3"> + <input + :value="showDefaultAwardEmojis" + type="hidden" + name="project[project_setting_attributes][show_default_award_emojis]" + /> + <gl-form-checkbox + v-model="showDefaultAwardEmojis" + name="project[project_setting_attributes][show_default_award_emojis]" + > + {{ s__('ProjectSettings|Show default award emojis') }} + <template #help>{{ + s__( + 'ProjectSettings|When enabled, issues, merge requests, and snippets will always show thumbs-up and thumbs-down award emoji buttons.', + ) + }}</template> + </gl-form-checkbox> + </project-setting-row> </div> </template> diff --git a/app/assets/javascripts/pages/projects/wikis/components/delete_wiki_modal.vue b/app/assets/javascripts/pages/projects/wikis/components/delete_wiki_modal.vue index 6af346ace67..580cca49b5e 100644 --- a/app/assets/javascripts/pages/projects/wikis/components/delete_wiki_modal.vue +++ b/app/assets/javascripts/pages/projects/wikis/components/delete_wiki_modal.vue @@ -1,5 +1,5 @@ <script> -import { escape as esc } from 'lodash'; +import { escape } from 'lodash'; import { GlModal, GlModalDirective } from '@gitlab/ui'; import { s__, sprintf } from '~/locale'; @@ -38,7 +38,7 @@ export default { return sprintf( s__('WikiPageConfirmDelete|Delete page %{pageTitle}?'), { - pageTitle: esc(this.pageTitle), + pageTitle: escape(this.pageTitle), }, false, ); @@ -46,6 +46,7 @@ export default { }, methods: { onSubmit() { + window.onbeforeunload = null; this.$refs.form.submit(); }, }, diff --git a/app/assets/javascripts/pages/projects/wikis/wikis.js b/app/assets/javascripts/pages/projects/wikis/wikis.js index 93afdc54ce1..ed67219383b 100644 --- a/app/assets/javascripts/pages/projects/wikis/wikis.js +++ b/app/assets/javascripts/pages/projects/wikis/wikis.js @@ -44,6 +44,19 @@ export default class Wikis { linkExample.innerHTML = MARKDOWN_LINK_TEXT[e.target.value]; }); } + + const wikiTextarea = document.querySelector('form.wiki-form #wiki_content'); + const wikiForm = document.querySelector('form.wiki-form'); + + if (wikiTextarea) { + wikiTextarea.addEventListener('input', () => { + window.onbeforeunload = () => ''; + }); + + wikiForm.addEventListener('submit', () => { + window.onbeforeunload = null; + }); + } } handleWikiTitleChange(e) { |