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
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-07-20 15:26:25 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-07-20 15:26:25 +0300
commita09983ae35713f5a2bbb100981116d31ce99826e (patch)
tree2ee2af7bd104d57086db360a7e6d8c9d5d43667a /app/assets/javascripts/pages
parent18c5ab32b738c0b6ecb4d0df3994000482f34bd8 (diff)
Add latest changes from gitlab-org/gitlab@13-2-stable-ee
Diffstat (limited to 'app/assets/javascripts/pages')
-rw-r--r--app/assets/javascripts/pages/admin/abuse_reports/abuse_reports.js2
-rw-r--r--app/assets/javascripts/pages/admin/clusters/show/index.js2
-rw-r--r--app/assets/javascripts/pages/admin/groups/show/index.js24
-rw-r--r--app/assets/javascripts/pages/admin/projects/index.js22
-rw-r--r--app/assets/javascripts/pages/constants.js1
-rw-r--r--app/assets/javascripts/pages/groups/clusters/show/index.js2
-rw-r--r--app/assets/javascripts/pages/groups/group_members/index.js30
-rw-r--r--app/assets/javascripts/pages/groups/group_members/index/index.js14
-rw-r--r--app/assets/javascripts/pages/groups/new/group_path_validator.js10
-rw-r--r--app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js9
-rw-r--r--app/assets/javascripts/pages/groups/shared/group_details.js3
-rw-r--r--app/assets/javascripts/pages/profiles/show/index.js5
-rw-r--r--app/assets/javascripts/pages/projects/clusters/show/cluster_health.js18
-rw-r--r--app/assets/javascripts/pages/projects/clusters/show/index.js2
-rw-r--r--app/assets/javascripts/pages/projects/commit/pipelines/index.js2
-rw-r--r--app/assets/javascripts/pages/projects/edit/index.js7
-rw-r--r--app/assets/javascripts/pages/projects/forks/new/components/fork_groups_list.vue91
-rw-r--r--app/assets/javascripts/pages/projects/forks/new/components/fork_groups_list_item.vue147
-rw-r--r--app/assets/javascripts/pages/projects/graphs/components/code_coverage.vue20
-rw-r--r--app/assets/javascripts/pages/projects/integrations/jira/issues/index/index.js5
-rw-r--r--app/assets/javascripts/pages/projects/issues/service_desk/filtered_search.js30
-rw-r--r--app/assets/javascripts/pages/projects/issues/service_desk/index.js11
-rw-r--r--app/assets/javascripts/pages/projects/issues/show.js20
-rw-r--r--app/assets/javascripts/pages/projects/metrics_dashboard/index.js3
-rw-r--r--app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue215
-rw-r--r--app/assets/javascripts/pages/projects/pipelines/index/index.js3
-rw-r--r--app/assets/javascripts/pages/projects/project_members/index.js26
-rw-r--r--app/assets/javascripts/pages/projects/releases/new/index.js7
-rw-r--r--app/assets/javascripts/pages/projects/settings/operations/show/index.js4
-rw-r--r--app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue2
-rw-r--r--app/assets/javascripts/pages/projects/show/index.js27
-rw-r--r--app/assets/javascripts/pages/projects/tree/show/index.js45
-rw-r--r--app/assets/javascripts/pages/search/init_filtered_search.js2
-rw-r--r--app/assets/javascripts/pages/sessions/new/length_validator.js23
-rw-r--r--app/assets/javascripts/pages/sessions/new/username_validator.js2
-rw-r--r--app/assets/javascripts/pages/shared/wikis/components/delete_wiki_modal.vue15
-rw-r--r--app/assets/javascripts/pages/shared/wikis/wikis.js19
37 files changed, 605 insertions, 265 deletions
diff --git a/app/assets/javascripts/pages/admin/abuse_reports/abuse_reports.js b/app/assets/javascripts/pages/admin/abuse_reports/abuse_reports.js
index 674b807edbe..da7f81759ea 100644
--- a/app/assets/javascripts/pages/admin/abuse_reports/abuse_reports.js
+++ b/app/assets/javascripts/pages/admin/abuse_reports/abuse_reports.js
@@ -32,7 +32,7 @@ export default class AbuseReports {
$messageCellElement.text(originalMessage);
} else {
$messageCellElement.data('messageTruncated', 'true');
- $messageCellElement.text(`${originalMessage.substr(0, MAX_MESSAGE_LENGTH - 3)}...`);
+ $messageCellElement.text(truncate(originalMessage, MAX_MESSAGE_LENGTH));
}
}
}
diff --git a/app/assets/javascripts/pages/admin/clusters/show/index.js b/app/assets/javascripts/pages/admin/clusters/show/index.js
index 8001d2dd1da..ccf631b2c53 100644
--- a/app/assets/javascripts/pages/admin/clusters/show/index.js
+++ b/app/assets/javascripts/pages/admin/clusters/show/index.js
@@ -1,5 +1,7 @@
import ClustersBundle from '~/clusters/clusters_bundle';
+import initClusterHealth from '~/pages/projects/clusters/show/cluster_health';
document.addEventListener('DOMContentLoaded', () => {
new ClustersBundle(); // eslint-disable-line no-new
+ initClusterHealth();
});
diff --git a/app/assets/javascripts/pages/admin/groups/show/index.js b/app/assets/javascripts/pages/admin/groups/show/index.js
index b0cdad627a6..69d219d29f7 100644
--- a/app/assets/javascripts/pages/admin/groups/show/index.js
+++ b/app/assets/javascripts/pages/admin/groups/show/index.js
@@ -1,3 +1,23 @@
-import UsersSelect from '../../../../users_select';
+import Vue from 'vue';
+import UsersSelect from '~/users_select';
+import RemoveMemberModal from '~/vue_shared/components/remove_member_modal.vue';
-document.addEventListener('DOMContentLoaded', () => new UsersSelect());
+function mountRemoveMemberModal() {
+ const el = document.querySelector('.js-remove-member-modal');
+ if (!el) {
+ return false;
+ }
+
+ return new Vue({
+ el,
+ render(createComponent) {
+ return createComponent(RemoveMemberModal);
+ },
+ });
+}
+
+document.addEventListener('DOMContentLoaded', () => {
+ mountRemoveMemberModal();
+
+ new UsersSelect(); // eslint-disable-line no-new
+});
diff --git a/app/assets/javascripts/pages/admin/projects/index.js b/app/assets/javascripts/pages/admin/projects/index.js
index d6b1e747aec..d86c5e2ddb8 100644
--- a/app/assets/javascripts/pages/admin/projects/index.js
+++ b/app/assets/javascripts/pages/admin/projects/index.js
@@ -1,7 +1,25 @@
-import ProjectsList from '../../../projects_list';
-import NamespaceSelect from '../../../namespace_select';
+import Vue from 'vue';
+import ProjectsList from '~/projects_list';
+import NamespaceSelect from '~/namespace_select';
+import RemoveMemberModal from '~/vue_shared/components/remove_member_modal.vue';
+
+function mountRemoveMemberModal() {
+ const el = document.querySelector('.js-remove-member-modal');
+ if (!el) {
+ return false;
+ }
+
+ return new Vue({
+ el,
+ render(createComponent) {
+ return createComponent(RemoveMemberModal);
+ },
+ });
+}
document.addEventListener('DOMContentLoaded', () => {
+ mountRemoveMemberModal();
+
new ProjectsList(); // eslint-disable-line no-new
document
diff --git a/app/assets/javascripts/pages/constants.js b/app/assets/javascripts/pages/constants.js
index 5e119454ce1..35c67190b62 100644
--- a/app/assets/javascripts/pages/constants.js
+++ b/app/assets/javascripts/pages/constants.js
@@ -4,4 +4,5 @@ export const FILTERED_SEARCH = {
MERGE_REQUESTS: 'merge_requests',
ISSUES: 'issues',
ADMIN_RUNNERS: 'admin/runners',
+ GROUP_RUNNERS_ANCHOR: 'runners-settings',
};
diff --git a/app/assets/javascripts/pages/groups/clusters/show/index.js b/app/assets/javascripts/pages/groups/clusters/show/index.js
index 8001d2dd1da..ccf631b2c53 100644
--- a/app/assets/javascripts/pages/groups/clusters/show/index.js
+++ b/app/assets/javascripts/pages/groups/clusters/show/index.js
@@ -1,5 +1,7 @@
import ClustersBundle from '~/clusters/clusters_bundle';
+import initClusterHealth from '~/pages/projects/clusters/show/cluster_health';
document.addEventListener('DOMContentLoaded', () => {
new ClustersBundle(); // eslint-disable-line no-new
+ initClusterHealth();
});
diff --git a/app/assets/javascripts/pages/groups/group_members/index.js b/app/assets/javascripts/pages/groups/group_members/index.js
new file mode 100644
index 00000000000..e146592e134
--- /dev/null
+++ b/app/assets/javascripts/pages/groups/group_members/index.js
@@ -0,0 +1,30 @@
+import Vue from 'vue';
+import Members from 'ee_else_ce/members';
+import memberExpirationDate from '~/member_expiration_date';
+import UsersSelect from '~/users_select';
+import groupsSelect from '~/groups_select';
+import RemoveMemberModal from '~/vue_shared/components/remove_member_modal.vue';
+
+function mountRemoveMemberModal() {
+ const el = document.querySelector('.js-remove-member-modal');
+ if (!el) {
+ return false;
+ }
+
+ return new Vue({
+ el,
+ render(createComponent) {
+ return createComponent(RemoveMemberModal);
+ },
+ });
+}
+
+document.addEventListener('DOMContentLoaded', () => {
+ groupsSelect();
+ memberExpirationDate();
+ memberExpirationDate('.js-access-expiration-date-groups');
+ mountRemoveMemberModal();
+
+ new Members(); // eslint-disable-line no-new
+ new UsersSelect(); // eslint-disable-line no-new
+});
diff --git a/app/assets/javascripts/pages/groups/group_members/index/index.js b/app/assets/javascripts/pages/groups/group_members/index/index.js
deleted file mode 100644
index 0c732922e81..00000000000
--- a/app/assets/javascripts/pages/groups/group_members/index/index.js
+++ /dev/null
@@ -1,14 +0,0 @@
-/* eslint-disable no-new */
-
-import Members from 'ee_else_ce/members';
-import memberExpirationDate from '~/member_expiration_date';
-import UsersSelect from '~/users_select';
-import groupsSelect from '~/groups_select';
-
-document.addEventListener('DOMContentLoaded', () => {
- memberExpirationDate();
- memberExpirationDate('.js-access-expiration-date-groups');
- new Members();
- groupsSelect();
- new UsersSelect();
-});
diff --git a/app/assets/javascripts/pages/groups/new/group_path_validator.js b/app/assets/javascripts/pages/groups/new/group_path_validator.js
index eeaa6527431..d2684b6af59 100644
--- a/app/assets/javascripts/pages/groups/new/group_path_validator.js
+++ b/app/assets/javascripts/pages/groups/new/group_path_validator.js
@@ -12,6 +12,7 @@ const successMessageSelector = '.validation-success';
const pendingMessageSelector = '.validation-pending';
const unavailableMessageSelector = '.validation-error';
const suggestionsMessageSelector = '.gl-path-suggestions';
+const inputGroupSelector = '.input-group';
export default class GroupPathValidator extends InputValidator {
constructor(opts = {}) {
@@ -39,7 +40,7 @@ export default class GroupPathValidator extends InputValidator {
static validateGroupPathInput(inputDomElement) {
const groupPath = inputDomElement.value;
- if (inputDomElement.checkValidity() && groupPath.length > 0) {
+ if (inputDomElement.checkValidity() && groupPath.length > 1) {
GroupPathValidator.setMessageVisibility(inputDomElement, pendingMessageSelector);
fetchGroupPathAvailability(groupPath)
@@ -69,9 +70,10 @@ export default class GroupPathValidator extends InputValidator {
}
static setMessageVisibility(inputDomElement, messageSelector, isVisible = true) {
- const messageElement = inputDomElement.parentElement.parentElement.querySelector(
- messageSelector,
- );
+ const messageElement = inputDomElement
+ .closest(inputGroupSelector)
+ .parentElement.querySelector(messageSelector);
+
messageElement.classList.toggle('hide', !isVisible);
}
diff --git a/app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js b/app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js
index 479c82265f2..23283f46a5d 100644
--- a/app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js
+++ b/app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js
@@ -1,11 +1,20 @@
import initSettingsPanels from '~/settings_panels';
import AjaxVariableList from '~/ci_variable_list/ajax_variable_list';
import initVariableList from '~/ci_variable_list';
+import initFilteredSearch from '~/pages/search/init_filtered_search';
+import AdminRunnersFilteredSearchTokenKeys from '~/filtered_search/admin_runners_filtered_search_token_keys';
+import { FILTERED_SEARCH } from '~/pages/constants';
document.addEventListener('DOMContentLoaded', () => {
// Initialize expandable settings panels
initSettingsPanels();
+ initFilteredSearch({
+ page: FILTERED_SEARCH.ADMIN_RUNNERS,
+ filteredSearchTokenKeys: AdminRunnersFilteredSearchTokenKeys,
+ anchor: FILTERED_SEARCH.GROUP_RUNNERS_ANCHOR,
+ });
+
if (gon.features.newVariablesUi) {
initVariableList();
} else {
diff --git a/app/assets/javascripts/pages/groups/shared/group_details.js b/app/assets/javascripts/pages/groups/shared/group_details.js
index 85daff3f60f..37b253d7c48 100644
--- a/app/assets/javascripts/pages/groups/shared/group_details.js
+++ b/app/assets/javascripts/pages/groups/shared/group_details.js
@@ -8,7 +8,6 @@ import NotificationsForm from '~/notifications_form';
import ProjectsList from '~/projects_list';
import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
import GroupTabs from './group_tabs';
-import initNamespaceStorageLimitAlert from '~/namespace_storage_limit_alert';
export default function initGroupDetails(actionName = 'show') {
const newGroupChildWrapper = document.querySelector('.js-new-project-subgroup');
@@ -28,6 +27,4 @@ export default function initGroupDetails(actionName = 'show') {
if (newGroupChildWrapper) {
new NewGroupChild(newGroupChildWrapper);
}
-
- initNamespaceStorageLimitAlert();
}
diff --git a/app/assets/javascripts/pages/profiles/show/index.js b/app/assets/javascripts/pages/profiles/show/index.js
index ad003181728..74ab1bc13a9 100644
--- a/app/assets/javascripts/pages/profiles/show/index.js
+++ b/app/assets/javascripts/pages/profiles/show/index.js
@@ -4,6 +4,7 @@ import emojiRegex from 'emoji-regex';
import createFlash from '~/flash';
import EmojiMenu from './emoji_menu';
import { __ } from '~/locale';
+import * as Emoji from '~/emoji';
const defaultStatusEmoji = 'speech_balloon';
@@ -55,8 +56,8 @@ document.addEventListener('DOMContentLoaded', () => {
}
});
- import(/* webpackChunkName: 'emoji' */ '~/emoji')
- .then(Emoji => {
+ Emoji.initEmojiMap()
+ .then(() => {
const emojiMenu = new EmojiMenu(
Emoji,
toggleEmojiMenuButtonSelector,
diff --git a/app/assets/javascripts/pages/projects/clusters/show/cluster_health.js b/app/assets/javascripts/pages/projects/clusters/show/cluster_health.js
new file mode 100644
index 00000000000..382d39645a9
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/clusters/show/cluster_health.js
@@ -0,0 +1,18 @@
+import monitoringApp from '~/monitoring/monitoring_app';
+
+export default () => {
+ const el = document.getElementById('prometheus-graphs');
+
+ if (el && el.dataset) {
+ monitoringApp({
+ ...el.dataset,
+ showLegend: false,
+ showHeader: false,
+ showPanels: false,
+ forceSmallGraph: true,
+ smallEmptyState: true,
+ currentEnvironmentName: '',
+ hasMetrics: true,
+ });
+ }
+};
diff --git a/app/assets/javascripts/pages/projects/clusters/show/index.js b/app/assets/javascripts/pages/projects/clusters/show/index.js
index 397f9faf6fe..d20e2c19583 100644
--- a/app/assets/javascripts/pages/projects/clusters/show/index.js
+++ b/app/assets/javascripts/pages/projects/clusters/show/index.js
@@ -1,7 +1,9 @@
import ClustersBundle from '~/clusters/clusters_bundle';
import initGkeNamespace from '~/create_cluster/gke_cluster_namespace';
+import initClusterHealth from './cluster_health';
document.addEventListener('DOMContentLoaded', () => {
new ClustersBundle(); // eslint-disable-line no-new
initGkeNamespace();
+ initClusterHealth();
});
diff --git a/app/assets/javascripts/pages/projects/commit/pipelines/index.js b/app/assets/javascripts/pages/projects/commit/pipelines/index.js
index 9f08260c3d6..1415a6f60c8 100644
--- a/app/assets/javascripts/pages/projects/commit/pipelines/index.js
+++ b/app/assets/javascripts/pages/projects/commit/pipelines/index.js
@@ -1,6 +1,7 @@
import $ from 'jquery';
import MiniPipelineGraph from '~/mini_pipeline_graph_dropdown';
import initPipelines from '~/commit/pipelines/pipelines_bundle';
+import { fetchCommitMergeRequests } from '~/commit_merge_requests';
document.addEventListener('DOMContentLoaded', () => {
new MiniPipelineGraph({
@@ -8,5 +9,6 @@ document.addEventListener('DOMContentLoaded', () => {
}).bindEvents();
// eslint-disable-next-line no-jquery/no-load
$('.commit-info.branches').load(document.querySelector('.js-commit-box').dataset.commitPath);
+ fetchCommitMergeRequests();
initPipelines();
});
diff --git a/app/assets/javascripts/pages/projects/edit/index.js b/app/assets/javascripts/pages/projects/edit/index.js
index 9fb07917f9b..e65c18c07a9 100644
--- a/app/assets/javascripts/pages/projects/edit/index.js
+++ b/app/assets/javascripts/pages/projects/edit/index.js
@@ -7,13 +7,20 @@ import dirtySubmitFactory from '~/dirty_submit/dirty_submit_factory';
import initFilePickers from '~/file_pickers';
import initProjectLoadingSpinner from '../shared/save_project_loader';
import initProjectPermissionsSettings from '../shared/permissions';
+import initProjectRemoveModal from '~/projects/project_remove_modal';
+import UserCallout from '~/user_callout';
+import initServiceDesk from '~/projects/settings_service_desk';
document.addEventListener('DOMContentLoaded', () => {
initFilePickers();
initConfirmDangerModal();
initSettingsPanels();
+ initProjectRemoveModal();
mountBadgeSettings(PROJECT_BADGE);
+ new UserCallout({ className: 'js-service-desk-callout' }); // eslint-disable-line no-new
+ initServiceDesk();
+
initProjectLoadingSpinner();
initProjectPermissionsSettings();
setupTransferEdit('.js-project-transfer-form', 'select.select2');
diff --git a/app/assets/javascripts/pages/projects/forks/new/components/fork_groups_list.vue b/app/assets/javascripts/pages/projects/forks/new/components/fork_groups_list.vue
new file mode 100644
index 00000000000..77753521342
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/forks/new/components/fork_groups_list.vue
@@ -0,0 +1,91 @@
+<script>
+import { GlTabs, GlTab, GlLoadingIcon, GlSearchBoxByType } from '@gitlab/ui';
+import axios from '~/lib/utils/axios_utils';
+import { __ } from '~/locale';
+import createFlash from '~/flash';
+import ForkGroupsListItem from './fork_groups_list_item.vue';
+
+export default {
+ components: {
+ GlTabs,
+ GlTab,
+ GlLoadingIcon,
+ GlSearchBoxByType,
+ ForkGroupsListItem,
+ },
+ props: {
+ hasReachedProjectLimit: {
+ type: Boolean,
+ required: true,
+ },
+ endpoint: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ namespaces: null,
+ filter: '',
+ };
+ },
+ computed: {
+ filteredNamespaces() {
+ return this.namespaces.filter(n => n.name.toLowerCase().includes(this.filter.toLowerCase()));
+ },
+ },
+
+ mounted() {
+ this.loadGroups();
+ },
+
+ methods: {
+ loadGroups() {
+ axios
+ .get(this.endpoint)
+ .then(response => {
+ this.namespaces = response.data.namespaces;
+ })
+ .catch(() => createFlash(__('There was a problem fetching groups.')));
+ },
+ },
+
+ i18n: {
+ searchPlaceholder: __('Search by name'),
+ },
+};
+</script>
+<template>
+ <gl-tabs class="fork-groups">
+ <gl-tab :title="__('Groups and subgroups')">
+ <gl-loading-icon v-if="!namespaces" size="md" class="gl-mt-3" />
+ <template v-else-if="namespaces.length === 0">
+ <div class="gl-text-center">
+ <div class="h5">{{ __('No available groups to fork the project.') }}</div>
+ <p class="gl-mt-5">
+ {{ __('You must have permission to create a project in a group before forking.') }}
+ </p>
+ </div>
+ </template>
+ <div v-else-if="filteredNamespaces.length === 0" class="gl-text-center gl-mt-3">
+ {{ s__('GroupsTree|No groups matched your search') }}
+ </div>
+ <ul v-else class="groups-list group-list-tree">
+ <fork-groups-list-item
+ v-for="(namespace, index) in filteredNamespaces"
+ :key="index"
+ :group="namespace"
+ :has-reached-project-limit="hasReachedProjectLimit"
+ />
+ </ul>
+ </gl-tab>
+ <template #tabs-end>
+ <gl-search-box-by-type
+ v-if="namespaces && namespaces.length"
+ v-model="filter"
+ :placeholder="$options.i18n.searchPlaceholder"
+ class="gl-align-self-center gl-ml-auto fork-filtered-search"
+ />
+ </template>
+ </gl-tabs>
+</template>
diff --git a/app/assets/javascripts/pages/projects/forks/new/components/fork_groups_list_item.vue b/app/assets/javascripts/pages/projects/forks/new/components/fork_groups_list_item.vue
new file mode 100644
index 00000000000..792c2f3db34
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/forks/new/components/fork_groups_list_item.vue
@@ -0,0 +1,147 @@
+<script>
+import {
+ GlLink,
+ GlButton,
+ GlIcon,
+ GlAvatar,
+ GlTooltipDirective,
+ GlTooltip,
+ GlBadge,
+} from '@gitlab/ui';
+import { VISIBILITY_TYPE_ICON, GROUP_VISIBILITY_TYPE } from '~/groups/constants';
+import { __ } from '~/locale';
+import csrf from '~/lib/utils/csrf';
+
+export default {
+ components: {
+ GlIcon,
+ GlAvatar,
+ GlBadge,
+ GlButton,
+ GlTooltip,
+ GlLink,
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+ props: {
+ group: {
+ type: Object,
+ required: true,
+ },
+ hasReachedProjectLimit: {
+ type: Boolean,
+ required: true,
+ },
+ },
+ data() {
+ return { namespaces: null };
+ },
+
+ computed: {
+ rowClass() {
+ return {
+ 'has-description': this.group.description,
+ 'being-removed': this.isGroupPendingRemoval,
+ };
+ },
+ isGroupPendingRemoval() {
+ return this.group.marked_for_deletion;
+ },
+ hasForkedProject() {
+ return Boolean(this.group.forked_project_path);
+ },
+ visibilityIcon() {
+ return VISIBILITY_TYPE_ICON[this.group.visibility];
+ },
+ visibilityTooltip() {
+ return GROUP_VISIBILITY_TYPE[this.group.visibility];
+ },
+ isSelectButtonDisabled() {
+ return this.hasReachedProjectLimit || !this.group.can_create_project;
+ },
+ selectButtonDisabledTooltip() {
+ return this.hasReachedProjectLimit
+ ? this.$options.i18n.hasReachedProjectLimitMessage
+ : this.$options.i18n.insufficientPermissionsMessage;
+ },
+ },
+
+ i18n: {
+ hasReachedProjectLimitMessage: __('You have reached your project limit'),
+ insufficientPermissionsMessage: __(
+ 'You must have permission to create a project in a namespace before forking.',
+ ),
+ },
+
+ csrf,
+};
+</script>
+<template>
+ <li :class="rowClass" class="group-row">
+ <div class="group-row-contents gl-display-flex gl-align-items-center gl-py-3 gl-pr-5">
+ <div class="folder-toggle-wrap gl-mr-2 gl-display-flex gl-align-items-center">
+ <gl-icon name="folder-o" />
+ </div>
+ <gl-link
+ :href="group.relative_path"
+ class="gl-display-none gl-flex-shrink-0 gl-display-sm-flex gl-mr-3"
+ >
+ <gl-avatar :size="32" shape="rect" :entity-name="group.name" :src="group.avatarUrl" />
+ </gl-link>
+ <div class="gl-min-w-0 gl-display-flex gl-flex-grow-1 gl-flex-shrink-1 gl-align-items-center">
+ <div class="gl-min-w-0 gl-flex-grow-1 flex-shrink-1">
+ <div class="title gl-display-flex gl-align-items-center gl-flex-wrap gl-mr-3">
+ <gl-link :href="group.relative_path" class="gl-mt-3 gl-mr-3 gl-text-gray-900!">{{
+ group.full_name
+ }}</gl-link>
+ <gl-icon
+ v-gl-tooltip.hover.bottom
+ class="gl-mr-0 gl-inline-flex gl-mt-3 text-secondary"
+ :name="visibilityIcon"
+ :title="visibilityTooltip"
+ />
+ <gl-badge
+ v-if="isGroupPendingRemoval"
+ variant="warning"
+ class="gl-display-none gl-display-sm-flex gl-mt-3 gl-mr-1"
+ >{{ __('pending removal') }}</gl-badge
+ >
+ <span v-if="group.permission" class="user-access-role gl-mt-3">
+ {{ group.permission }}
+ </span>
+ </div>
+ <div v-if="group.description" class="description">
+ <span v-html="group.markdown_description"> </span>
+ </div>
+ </div>
+ <div class="gl-display-flex gl-flex-shrink-0">
+ <gl-button
+ v-if="hasForkedProject"
+ class="gl-h-7 gl-text-decoration-none!"
+ :href="group.forked_project_path"
+ >{{ __('Go to fork') }}</gl-button
+ >
+ <template v-else>
+ <div ref="selectButtonWrapper">
+ <form method="POST" :action="group.fork_path">
+ <input type="hidden" name="authenticity_token" :value="$options.csrf.token" />
+ <gl-button
+ type="submit"
+ class="gl-h-7 gl-text-decoration-none!"
+ :data-qa-name="group.full_name"
+ variant="success"
+ :disabled="isSelectButtonDisabled"
+ >{{ __('Select') }}</gl-button
+ >
+ </form>
+ </div>
+ <gl-tooltip v-if="isSelectButtonDisabled" :target="() => $refs.selectButtonWrapper">
+ {{ selectButtonDisabledTooltip }}
+ </gl-tooltip>
+ </template>
+ </div>
+ </div>
+ </div>
+ </li>
+</template>
diff --git a/app/assets/javascripts/pages/projects/graphs/components/code_coverage.vue b/app/assets/javascripts/pages/projects/graphs/components/code_coverage.vue
index af8fb032c22..39d6df33a85 100644
--- a/app/assets/javascripts/pages/projects/graphs/components/code_coverage.vue
+++ b/app/assets/javascripts/pages/projects/graphs/components/code_coverage.vue
@@ -63,17 +63,19 @@ export default {
selectedDailyCoverageName() {
return this.selectedDailyCoverage?.group_name;
},
- formattedData() {
- if (this.selectedDailyCoverage?.data) {
- return this.selectedDailyCoverage.data.map(value => [
- dateFormat(value.date, 'mmm dd'),
- value.coverage,
- ]);
- }
-
+ sortedData() {
// If the fetching failed, we return an empty array which
// allow the graph to render while empty
- return [];
+ if (!this.selectedDailyCoverage?.data) {
+ return [];
+ }
+
+ return [...this.selectedDailyCoverage.data].sort(
+ (a, b) => new Date(a.date) - new Date(b.date),
+ );
+ },
+ formattedData() {
+ return this.sortedData.map(value => [dateFormat(value.date, 'mmm dd'), value.coverage]);
},
chartData() {
return [
diff --git a/app/assets/javascripts/pages/projects/integrations/jira/issues/index/index.js b/app/assets/javascripts/pages/projects/integrations/jira/issues/index/index.js
new file mode 100644
index 00000000000..260ba69b4bc
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/integrations/jira/issues/index/index.js
@@ -0,0 +1,5 @@
+import initIssuablesList from '~/issuables_list';
+
+document.addEventListener('DOMContentLoaded', () => {
+ initIssuablesList();
+});
diff --git a/app/assets/javascripts/pages/projects/issues/service_desk/filtered_search.js b/app/assets/javascripts/pages/projects/issues/service_desk/filtered_search.js
new file mode 100644
index 00000000000..72003b61c8a
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/issues/service_desk/filtered_search.js
@@ -0,0 +1,30 @@
+/* eslint-disable class-methods-use-this */
+import IssuableFilteredSearchTokenKeys from 'ee_else_ce/filtered_search/issuable_filtered_search_token_keys';
+import FilteredSearchManager from 'ee_else_ce/filtered_search/filtered_search_manager';
+
+const AUTHOR_PARAM_KEY = 'author_username';
+
+export default class FilteredSearchServiceDesk extends FilteredSearchManager {
+ constructor(supportBotData) {
+ super({
+ page: 'service_desk',
+ filteredSearchTokenKeys: IssuableFilteredSearchTokenKeys,
+ });
+
+ this.supportBotData = supportBotData;
+ }
+
+ canEdit(tokenName) {
+ return tokenName !== 'author';
+ }
+
+ modifyUrlParams(paramsArray) {
+ const supportBotParamPair = `${AUTHOR_PARAM_KEY}=${this.supportBotData.username}`;
+ const onlyValidParams = paramsArray.filter(param => param.indexOf(AUTHOR_PARAM_KEY) === -1);
+
+ // unshift ensures author param is always first token element
+ onlyValidParams.unshift(supportBotParamPair);
+
+ return onlyValidParams;
+ }
+}
diff --git a/app/assets/javascripts/pages/projects/issues/service_desk/index.js b/app/assets/javascripts/pages/projects/issues/service_desk/index.js
new file mode 100644
index 00000000000..56054f5fc80
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/issues/service_desk/index.js
@@ -0,0 +1,11 @@
+import FilteredSearchServiceDesk from './filtered_search';
+
+document.addEventListener('DOMContentLoaded', () => {
+ const supportBotData = JSON.parse(
+ document.querySelector('.js-service-desk-issues').dataset.supportBot,
+ );
+
+ const filteredSearchManager = new FilteredSearchServiceDesk(supportBotData);
+
+ filteredSearchManager.setup();
+});
diff --git a/app/assets/javascripts/pages/projects/issues/show.js b/app/assets/javascripts/pages/projects/issues/show.js
index 46c9b2fe0af..32f77465347 100644
--- a/app/assets/javascripts/pages/projects/issues/show.js
+++ b/app/assets/javascripts/pages/projects/issues/show.js
@@ -3,7 +3,7 @@ import Issue from '~/issue';
import ShortcutsIssuable from '~/behaviors/shortcuts/shortcuts_issuable';
import ZenMode from '~/zen_mode';
import '~/notes/index';
-import initIssueableApp from '~/issue_show';
+import initIssueableApp, { issuableHeaderWarnings } from '~/issue_show';
import initSentryErrorStackTraceApp from '~/sentry_error_stack_trace';
import initRelatedMergeRequestsApp from '~/related_merge_requests';
import initVueIssuableSidebarApp from '~/issuable_sidebar/sidebar_bundle';
@@ -12,15 +12,17 @@ export default function() {
initIssueableApp();
initSentryErrorStackTraceApp();
initRelatedMergeRequestsApp();
+ issuableHeaderWarnings();
- // .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(() => {});
- }
+ import(/* webpackChunkName: 'design_management' */ '~/design_management')
+ .then(module => module.default())
+ .catch(() => {});
+
+ // This will be removed when we remove the `design_management_moved` feature flag
+ // See https://gitlab.com/gitlab-org/gitlab/-/issues/223197
+ import(/* webpackChunkName: 'design_management' */ '~/design_management_new')
+ .then(module => module.default())
+ .catch(() => {});
new Issue(); // eslint-disable-line no-new
new ShortcutsIssuable(); // eslint-disable-line no-new
diff --git a/app/assets/javascripts/pages/projects/metrics_dashboard/index.js b/app/assets/javascripts/pages/projects/metrics_dashboard/index.js
new file mode 100644
index 00000000000..d3028aec313
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/metrics_dashboard/index.js
@@ -0,0 +1,3 @@
+import monitoringApp from '~/monitoring/monitoring_app';
+
+document.addEventListener('DOMContentLoaded', monitoringApp);
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 4efabcb7df3..5ef1f959b2c 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,12 +1,19 @@
<script>
-import { GlSprintf, GlLink } from '@gitlab/ui';
+import { GlFormRadio, GlFormRadioGroup, GlLink, GlSprintf } from '@gitlab/ui';
import { s__, sprintf } from '~/locale';
import { getWeekdayNames } from '~/lib/utils/datetime_utility';
+const KEY_EVERY_DAY = 'everyDay';
+const KEY_EVERY_WEEK = 'everyWeek';
+const KEY_EVERY_MONTH = 'everyMonth';
+const KEY_CUSTOM = 'custom';
+
export default {
components: {
- GlSprintf,
+ GlFormRadio,
+ GlFormRadioGroup,
GlLink,
+ GlSprintf,
},
props: {
initialCronInterval: {
@@ -22,6 +29,7 @@ export default {
randomWeekDayIndex: this.generateRandomWeekDayIndex(),
randomDay: this.generateRandomDay(),
inputNameAttribute: 'schedule[cron]',
+ radioValue: this.initialCronInterval ? KEY_CUSTOM : KEY_EVERY_DAY,
cronInterval: this.initialCronInterval,
cronSyntaxUrl: 'https://en.wikipedia.org/wiki/Cron',
};
@@ -29,14 +37,11 @@ export default {
computed: {
cronIntervalPresets() {
return {
- everyDay: `0 ${this.randomHour} * * *`,
- everyWeek: `0 ${this.randomHour} * * ${this.randomWeekDayIndex}`,
- everyMonth: `0 ${this.randomHour} ${this.randomDay} * *`,
+ [KEY_EVERY_DAY]: `0 ${this.randomHour} * * *`,
+ [KEY_EVERY_WEEK]: `0 ${this.randomHour} * * ${this.randomWeekDayIndex}`,
+ [KEY_EVERY_MONTH]: `0 ${this.randomHour} ${this.randomDay} * *`,
};
},
- intervalIsPreset() {
- return Object.values(this.cronIntervalPresets).includes(this.cronInterval);
- },
formattedTime() {
if (this.randomHour > 12) {
return `${this.randomHour - 12}:00pm`;
@@ -45,24 +50,36 @@ export default {
}
return `${this.randomHour}:00am`;
},
+ radioOptions() {
+ return [
+ {
+ value: KEY_EVERY_DAY,
+ text: sprintf(s__(`Every day (at %{time})`), { time: this.formattedTime }),
+ },
+ {
+ value: KEY_EVERY_WEEK,
+ text: sprintf(s__('Every week (%{weekday} at %{time})'), {
+ weekday: this.weekday,
+ time: this.formattedTime,
+ }),
+ },
+ {
+ value: KEY_EVERY_MONTH,
+ text: sprintf(s__('Every month (Day %{day} at %{time})'), {
+ day: this.randomDay,
+ time: this.formattedTime,
+ }),
+ },
+ {
+ value: KEY_CUSTOM,
+ text: s__('PipelineScheduleIntervalPattern|Custom (%{linkStart}Cron syntax%{linkEnd})'),
+ link: this.cronSyntaxUrl,
+ },
+ ];
+ },
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: {
cronInterval() {
@@ -72,38 +89,18 @@ export default {
gl.pipelineScheduleFieldErrors.updateFormValidityState();
});
},
- },
- // 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;
- }
+ radioValue: {
+ immediate: true,
+ handler(val) {
+ if (val !== KEY_CUSTOM) {
+ this.cronInterval = this.cronIntervalPresets[val];
+ }
+ },
+ },
},
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.isEditingCustom = shouldEnable;
-
- if (shouldEnable) {
- // We need to change the value so other radios don't remain selected
- // because the model (cronInterval) hasn't changed. The server trims it.
- this.cronInterval = `${this.cronInterval} `;
- }
+ onCustomInput() {
+ this.radioValue = KEY_CUSTOM;
},
generateRandomHour() {
return Math.floor(Math.random() * 23);
@@ -119,89 +116,33 @@ export default {
</script>
<template>
- <div class="interval-pattern-form-group">
- <div class="cron-preset-radio-input">
- <input
- id="every-day"
- v-model="cronInterval"
- :name="inputNameAttribute"
- :value="cronIntervalPresets.everyDay"
- class="label-bold"
- type="radio"
- @click="toggleCustomInput(false)"
- />
-
- <label class="label-bold" for="every-day">
- {{ everyDayText }}
- </label>
- </div>
-
- <div class="cron-preset-radio-input">
- <input
- id="every-week"
- v-model="cronInterval"
- :name="inputNameAttribute"
- :value="cronIntervalPresets.everyWeek"
- class="label-bold"
- type="radio"
- @click="toggleCustomInput(false)"
- />
-
- <label class="label-bold" for="every-week">
- {{ everyWeekText }}
- </label>
- </div>
-
- <div class="cron-preset-radio-input">
- <input
- id="every-month"
- v-model="cronInterval"
- :name="inputNameAttribute"
- :value="cronIntervalPresets.everyMonth"
- class="label-bold"
- type="radio"
- @click="toggleCustomInput(false)"
- />
-
- <label class="label-bold" for="every-month">
- {{ 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"
- class="form-control inline cron-interval-input"
- type="text"
- required="true"
- @input="setCustomInput"
- />
- </div>
+ <div>
+ <gl-form-radio-group v-model="radioValue" :name="inputNameAttribute">
+ <gl-form-radio
+ v-for="option in radioOptions"
+ :key="option.value"
+ :value="option.value"
+ :data-testid="option.value"
+ >
+ <gl-sprintf v-if="option.link" :message="option.text">
+ <template #link="{content}">
+ <gl-link :href="option.link" target="_blank" class="gl-font-sm">
+ {{ content }}
+ </gl-link>
+ </template>
+ </gl-sprintf>
+ <template v-else>{{ option.text }}</template>
+ </gl-form-radio>
+ </gl-form-radio-group>
+ <input
+ id="schedule_cron"
+ v-model="cronInterval"
+ :placeholder="__('Define a custom pattern with cron syntax')"
+ :name="inputNameAttribute"
+ class="form-control inline cron-interval-input"
+ type="text"
+ required="true"
+ @input="onCustomInput"
+ />
</div>
</template>
diff --git a/app/assets/javascripts/pages/projects/pipelines/index/index.js b/app/assets/javascripts/pages/projects/pipelines/index/index.js
index 2c37d7da4a7..bed9a751d4c 100644
--- a/app/assets/javascripts/pages/projects/pipelines/index/index.js
+++ b/app/assets/javascripts/pages/projects/pipelines/index/index.js
@@ -8,7 +8,7 @@ import {
} from '~/lib/utils/common_utils';
import { __ } from '~/locale';
import PipelinesStore from '../../../../pipelines/stores/pipelines_store';
-import pipelinesComponent from '../../../../pipelines/components/pipelines.vue';
+import pipelinesComponent from '../../../../pipelines/components/pipelines_list/pipelines.vue';
import Translate from '../../../../vue_shared/translate';
Vue.use(Translate);
@@ -40,6 +40,7 @@ document.addEventListener(
props: {
store: this.store,
endpoint: this.dataset.endpoint,
+ pipelineScheduleUrl: this.dataset.pipelineScheduleUrl,
helpPagePath: this.dataset.helpPagePath,
emptyStateSvgPath: this.dataset.emptyStateSvgPath,
errorStateSvgPath: this.dataset.errorStateSvgPath,
diff --git a/app/assets/javascripts/pages/projects/project_members/index.js b/app/assets/javascripts/pages/projects/project_members/index.js
index f39765818e7..e146592e134 100644
--- a/app/assets/javascripts/pages/projects/project_members/index.js
+++ b/app/assets/javascripts/pages/projects/project_members/index.js
@@ -1,12 +1,30 @@
+import Vue from 'vue';
import Members from 'ee_else_ce/members';
-import memberExpirationDate from '../../../member_expiration_date';
-import UsersSelect from '../../../users_select';
-import groupsSelect from '../../../groups_select';
+import memberExpirationDate from '~/member_expiration_date';
+import UsersSelect from '~/users_select';
+import groupsSelect from '~/groups_select';
+import RemoveMemberModal from '~/vue_shared/components/remove_member_modal.vue';
+
+function mountRemoveMemberModal() {
+ const el = document.querySelector('.js-remove-member-modal');
+ if (!el) {
+ return false;
+ }
+
+ return new Vue({
+ el,
+ render(createComponent) {
+ return createComponent(RemoveMemberModal);
+ },
+ });
+}
document.addEventListener('DOMContentLoaded', () => {
- memberExpirationDate('.js-access-expiration-date-groups');
groupsSelect();
memberExpirationDate();
+ memberExpirationDate('.js-access-expiration-date-groups');
+ mountRemoveMemberModal();
+
new Members(); // eslint-disable-line no-new
new UsersSelect(); // eslint-disable-line no-new
});
diff --git a/app/assets/javascripts/pages/projects/releases/new/index.js b/app/assets/javascripts/pages/projects/releases/new/index.js
new file mode 100644
index 00000000000..0e314aacf8a
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/releases/new/index.js
@@ -0,0 +1,7 @@
+import ZenMode from '~/zen_mode';
+import initNewRelease from '~/releases/mount_new';
+
+document.addEventListener('DOMContentLoaded', () => {
+ new ZenMode(); // eslint-disable-line no-new
+ initNewRelease();
+});
diff --git a/app/assets/javascripts/pages/projects/settings/operations/show/index.js b/app/assets/javascripts/pages/projects/settings/operations/show/index.js
index 721d4a31fe4..1b9ec44ed4a 100644
--- a/app/assets/javascripts/pages/projects/settings/operations/show/index.js
+++ b/app/assets/javascripts/pages/projects/settings/operations/show/index.js
@@ -1,13 +1,17 @@
import mountErrorTrackingForm from '~/error_tracking_settings';
+import mountAlertsSettings from '~/alerts_settings';
import mountOperationSettings from '~/operation_settings';
import mountGrafanaIntegration from '~/grafana_integration';
import initSettingsPanels from '~/settings_panels';
+import initIncidentsSettings from '~/incidents_settings';
document.addEventListener('DOMContentLoaded', () => {
+ initIncidentsSettings();
mountErrorTrackingForm();
mountOperationSettings();
mountGrafanaIntegration();
if (!IS_EE) {
initSettingsPanels();
}
+ mountAlertsSettings(document.querySelector('.js-alerts-settings'));
});
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 7181332a1d6..a95f0af46cd 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
@@ -426,7 +426,7 @@ export default {
v-if="lfsAvailable"
ref="git-lfs-settings"
:help-path="lfsHelpPath"
- :label="s__('ProjectSettings|Git Large File Storage')"
+ :label="s__('ProjectSettings|Git Large File Storage (LFS)')"
:help-text="
s__('ProjectSettings|Manages large files such as audio, video, and graphics files')
"
diff --git a/app/assets/javascripts/pages/projects/show/index.js b/app/assets/javascripts/pages/projects/show/index.js
index 3c44053e2b2..c65cc3e4c57 100644
--- a/app/assets/javascripts/pages/projects/show/index.js
+++ b/app/assets/javascripts/pages/projects/show/index.js
@@ -1,25 +1,18 @@
-import $ from 'jquery';
-import 'jquery.waitforimages';
-
import initBlob from '~/blob_edit/blob_bundle';
import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
import NotificationsForm from '~/notifications_form';
import UserCallout from '~/user_callout';
-import TreeView from '~/tree';
import BlobViewer from '~/blob/viewer/index';
import Activities from '~/activities';
-import { ajaxGet } from '~/lib/utils/common_utils';
-import GpgBadges from '~/gpg_badges';
import initReadMore from '~/read_more';
import leaveByUrl from '~/namespaces/leave_by_url';
import Star from '../../../star';
import notificationsDropdown from '../../../notifications_dropdown';
-import initNamespaceStorageLimitAlert from '~/namespace_storage_limit_alert';
import { showLearnGitLabProjectPopover } from '~/onboarding_issues';
+import initTree from 'ee_else_ce/repository';
document.addEventListener('DOMContentLoaded', () => {
initReadMore();
- initNamespaceStorageLimitAlert();
new Star(); // eslint-disable-line no-new
notificationsDropdown();
new ShortcutsNavigation(); // eslint-disable-line no-new
@@ -31,10 +24,10 @@ document.addEventListener('DOMContentLoaded', () => {
});
// Project show page loads different overview content based on user preferences
- const treeSlider = document.querySelector('#tree-slider');
+ const treeSlider = document.getElementById('js-tree-list');
if (treeSlider) {
- new TreeView(); // eslint-disable-line no-new
initBlob();
+ initTree();
}
if (document.querySelector('.blob-viewer')) {
@@ -45,21 +38,7 @@ document.addEventListener('DOMContentLoaded', () => {
new Activities(); // eslint-disable-line no-new
}
- $(treeSlider).waitForImages(() => {
- ajaxGet(document.querySelector('.js-tree-content').dataset.logsPath);
- });
-
- GpgBadges.fetch();
leaveByUrl('project');
- if (document.getElementById('js-tree-list')) {
- initBlob();
- import('ee_else_ce/repository')
- .then(m => m.default())
- .catch(e => {
- throw e;
- });
- }
-
showLearnGitLabProjectPopover();
});
diff --git a/app/assets/javascripts/pages/projects/tree/show/index.js b/app/assets/javascripts/pages/projects/tree/show/index.js
index 0d1d32317fe..78a4ea23f1a 100644
--- a/app/assets/javascripts/pages/projects/tree/show/index.js
+++ b/app/assets/javascripts/pages/projects/tree/show/index.js
@@ -1,53 +1,12 @@
import $ from 'jquery';
-import 'jquery.waitforimages';
-
-import Vue from 'vue';
import initBlob from '~/blob_edit/blob_bundle';
-import commitPipelineStatus from '~/projects/tree/components/commit_pipeline_status_component.vue';
-import GpgBadges from '~/gpg_badges';
-import TreeView from '../../../../tree';
import ShortcutsNavigation from '../../../../behaviors/shortcuts/shortcuts_navigation';
-import BlobViewer from '../../../../blob/viewer';
import NewCommitForm from '../../../../new_commit_form';
-import { ajaxGet } from '../../../../lib/utils/common_utils';
+import initTree from 'ee_else_ce/repository';
document.addEventListener('DOMContentLoaded', () => {
new ShortcutsNavigation(); // eslint-disable-line no-new
- new TreeView(); // eslint-disable-line no-new
- new BlobViewer(); // eslint-disable-line no-new
new NewCommitForm($('.js-create-dir-form')); // eslint-disable-line no-new
- $('#tree-slider').waitForImages(() =>
- ajaxGet(document.querySelector('.js-tree-content').dataset.logsPath),
- );
-
initBlob();
- const commitPipelineStatusEl = document.querySelector('.js-commit-pipeline-status');
- const statusLink = document.querySelector('.commit-actions .ci-status-link');
- if (statusLink != null) {
- statusLink.remove();
- // eslint-disable-next-line no-new
- new Vue({
- el: commitPipelineStatusEl,
- components: {
- commitPipelineStatus,
- },
- render(createElement) {
- return createElement('commit-pipeline-status', {
- props: {
- endpoint: commitPipelineStatusEl.dataset.endpoint,
- },
- });
- },
- });
- }
-
- GpgBadges.fetch();
-
- if (document.getElementById('js-tree-list')) {
- import('ee_else_ce/repository')
- .then(m => m.default())
- .catch(e => {
- throw e;
- });
- }
+ initTree();
});
diff --git a/app/assets/javascripts/pages/search/init_filtered_search.js b/app/assets/javascripts/pages/search/init_filtered_search.js
index e54e32199f0..b331a2bee6a 100644
--- a/app/assets/javascripts/pages/search/init_filtered_search.js
+++ b/app/assets/javascripts/pages/search/init_filtered_search.js
@@ -7,6 +7,7 @@ export default ({
isGroupAncestor,
isGroupDecendent,
stateFiltersSelector,
+ anchor,
}) => {
const filteredSearchEnabled = FilteredSearchManager && document.querySelector('.filtered-search');
if (filteredSearchEnabled) {
@@ -17,6 +18,7 @@ export default ({
isGroupDecendent,
filteredSearchTokenKeys,
stateFiltersSelector,
+ anchor,
});
filteredSearchManager.setup();
}
diff --git a/app/assets/javascripts/pages/sessions/new/length_validator.js b/app/assets/javascripts/pages/sessions/new/length_validator.js
index 3d687ca08cc..92482c81f3c 100644
--- a/app/assets/javascripts/pages/sessions/new/length_validator.js
+++ b/app/assets/javascripts/pages/sessions/new/length_validator.js
@@ -21,11 +21,24 @@ export default class LengthValidator extends InputValidator {
);
const { value } = this.inputDomElement;
- const { maxLengthMessage, maxLength } = this.inputDomElement.dataset;
-
- this.errorMessage = maxLengthMessage;
-
- this.invalidInput = value.length > parseInt(maxLength, 10);
+ const {
+ minLength,
+ minLengthMessage,
+ maxLengthMessage,
+ maxLength,
+ } = this.inputDomElement.dataset;
+
+ this.invalidInput = false;
+
+ if (value.length > parseInt(maxLength, 10)) {
+ this.invalidInput = true;
+ this.errorMessage = maxLengthMessage;
+ }
+
+ if (value.length < parseInt(minLength, 10)) {
+ this.invalidInput = true;
+ this.errorMessage = minLengthMessage;
+ }
this.setValidationStateAndMessage();
}
diff --git a/app/assets/javascripts/pages/sessions/new/username_validator.js b/app/assets/javascripts/pages/sessions/new/username_validator.js
index 1048e3b4548..ecb5e677290 100644
--- a/app/assets/javascripts/pages/sessions/new/username_validator.js
+++ b/app/assets/javascripts/pages/sessions/new/username_validator.js
@@ -39,7 +39,7 @@ export default class UsernameValidator extends InputValidator {
static validateUsernameInput(inputDomElement) {
const username = inputDomElement.value;
- if (inputDomElement.checkValidity() && username.length > 0) {
+ if (inputDomElement.checkValidity() && username.length > 1) {
UsernameValidator.setMessageVisibility(inputDomElement, pendingMessageSelector);
UsernameValidator.fetchUsernameAvailability(username)
.then(usernameTaken => {
diff --git a/app/assets/javascripts/pages/shared/wikis/components/delete_wiki_modal.vue b/app/assets/javascripts/pages/shared/wikis/components/delete_wiki_modal.vue
index 580cca49b5e..a7b7d597fb7 100644
--- a/app/assets/javascripts/pages/shared/wikis/components/delete_wiki_modal.vue
+++ b/app/assets/javascripts/pages/shared/wikis/components/delete_wiki_modal.vue
@@ -55,13 +55,22 @@ export default {
<template>
<div class="d-inline-block">
- <button v-gl-modal="modalId" type="button" class="btn btn-danger">{{ __('Delete') }}</button>
+ <button
+ v-gl-modal="modalId"
+ type="button"
+ class="btn btn-danger"
+ data-qa-selector="delete_button"
+ >
+ {{ __('Delete') }}
+ </button>
<gl-modal
:title="title"
- :ok-title="s__('WikiPageConfirmDelete|Delete page')"
+ :action-primary="{
+ text: s__('WikiPageConfirmDelete|Delete page'),
+ attributes: { variant: 'danger', 'data-qa-selector': 'confirm_deletion_button' },
+ }"
:modal-id="modalId"
title-tag="h4"
- ok-variant="danger"
@ok="onSubmit"
>
{{ message }}
diff --git a/app/assets/javascripts/pages/shared/wikis/wikis.js b/app/assets/javascripts/pages/shared/wikis/wikis.js
index ed67219383b..41d43812b5d 100644
--- a/app/assets/javascripts/pages/shared/wikis/wikis.js
+++ b/app/assets/javascripts/pages/shared/wikis/wikis.js
@@ -1,5 +1,6 @@
import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
import { s__, sprintf } from '~/locale';
+import Tracking from '~/tracking';
const MARKDOWN_LINK_TEXT = {
markdown: '[Link Title](page-slug)',
@@ -8,6 +9,9 @@ const MARKDOWN_LINK_TEXT = {
org: '[[page-slug]]',
};
+const TRACKING_EVENT_NAME = 'view_wiki_page';
+const TRACKING_CONTEXT_SCHEMA = 'iglu:com.gitlab/wiki_page_context/jsonschema/1-0-0';
+
export default class Wikis {
constructor() {
this.sidebarEl = document.querySelector('.js-wiki-sidebar');
@@ -57,6 +61,8 @@ export default class Wikis {
window.onbeforeunload = null;
});
}
+
+ Wikis.trackPageView();
}
handleWikiTitleChange(e) {
@@ -97,4 +103,17 @@ export default class Wikis {
classList.remove('right-sidebar-expanded');
}
}
+
+ static trackPageView() {
+ const wikiPageContent = document.querySelector('.js-wiki-page-content[data-tracking-context]');
+ if (!wikiPageContent) return;
+
+ Tracking.event(document.body.dataset.page, TRACKING_EVENT_NAME, {
+ label: TRACKING_EVENT_NAME,
+ context: {
+ schema: TRACKING_CONTEXT_SCHEMA,
+ data: JSON.parse(wikiPageContent.dataset.trackingContext),
+ },
+ });
+ }
}