Commit 565fba25 authored by Jose Ivan Vargas's avatar Jose Ivan Vargas

Merge branch 'ph/optimizeMRJSBundle' into 'master'

Improves merge request JS bundle

See merge request gitlab-org/gitlab!67967
parents 5d90d345 1bdfa789
......@@ -10,7 +10,6 @@ export default {
},
computed: {
...mapGetters(['isNotesFetched']),
...mapGetters('batchComments', ['draftsCount']),
},
watch: {
isNotesFetched() {
......@@ -25,7 +24,7 @@ export default {
};
</script>
<template>
<div v-show="draftsCount > 0">
<div>
<nav class="review-bar-component" data-testid="review_bar_component">
<div
class="review-bar-content d-flex gl-justify-content-end"
......
import Vue from 'vue';
import { mapActions } from 'vuex';
import { mapActions, mapGetters } from 'vuex';
import store from '~/mr_notes/stores';
import ReviewBar from './components/review_bar.vue';
export const initReviewBar = () => {
const el = document.getElementById('js-review-bar');
......@@ -10,6 +9,12 @@ export const initReviewBar = () => {
new Vue({
el,
store,
components: {
ReviewBar: () => import('./components/review_bar.vue'),
},
computed: {
...mapGetters('batchComments', ['draftsCount']),
},
mounted() {
this.fetchDrafts();
},
......@@ -17,7 +22,9 @@ export const initReviewBar = () => {
...mapActions('batchComments', ['fetchDrafts']),
},
render(createElement) {
return createElement(ReviewBar);
if (this.draftsCount === 0) return null;
return createElement('review-bar');
},
});
};
import Vue from 'vue';
import { initPipelineCountListener } from './utils';
/**
* Used in:
......@@ -12,13 +13,7 @@ export default () => {
if (pipelineTableViewEl) {
// Update MR and Commits tabs
pipelineTableViewEl.addEventListener('update-pipelines-count', (event) => {
if (event.detail.pipelineCount) {
const badge = document.querySelector('.js-pipelines-mr-count');
badge.textContent = event.detail.pipelineCount;
}
});
initPipelineCountListener(pipelineTableViewEl);
if (pipelineTableViewEl.dataset.disableInitialization === undefined) {
const table = new Vue({
......
export function initPipelineCountListener(el) {
if (!el) return;
el.addEventListener('update-pipelines-count', (event) => {
if (event.detail.pipelineCount) {
const badge = document.querySelector('.js-pipelines-mr-count');
badge.textContent = event.detail.pipelineCount;
}
});
}
......@@ -17,7 +17,6 @@ export default {
},
mounted() {
this.width = this.$el.parentNode.offsetWidth;
window.test = this;
this.$_itemsWithSizeWatcher = this.$watch('vscrollParent.itemsWithSize', async () => {
await this.$nextTick();
......
......@@ -3,7 +3,6 @@ import Vue from 'vue';
import { mapActions, mapState, mapGetters } from 'vuex';
import { parseBoolean } from '~/lib/utils/common_utils';
import { getParameterValues } from '~/lib/utils/url_utility';
import FindFile from '~/vue_shared/components/file_finder/index.vue';
import eventHub from '../notes/event_hub';
import diffsApp from './components/app.vue';
......@@ -12,51 +11,7 @@ import { getReviewsForMergeRequest } from './utils/file_reviews';
import { getDerivedMergeRequestInformation } from './utils/merge_request';
export default function initDiffsApp(store) {
const fileFinderEl = document.getElementById('js-diff-file-finder');
if (fileFinderEl) {
// eslint-disable-next-line no-new
new Vue({
el: fileFinderEl,
store,
computed: {
...mapState('diffs', ['fileFinderVisible', 'isLoading']),
...mapGetters('diffs', ['flatBlobsList']),
},
watch: {
fileFinderVisible(newVal, oldVal) {
if (newVal && !oldVal && !this.flatBlobsList.length) {
eventHub.$emit('fetchDiffData');
}
},
},
methods: {
...mapActions('diffs', ['toggleFileFinder', 'scrollToFile']),
openFile(file) {
window.mrTabs.tabShown('diffs');
this.scrollToFile(file.path);
},
},
render(createElement) {
return createElement(FindFile, {
props: {
files: this.flatBlobsList,
visible: this.fileFinderVisible,
loading: this.isLoading,
showDiffStats: true,
clearSearchOnClose: false,
},
on: {
toggle: this.toggleFileFinder,
click: this.openFile,
},
class: ['diff-file-finder'],
});
},
});
}
return new Vue({
const vm = new Vue({
el: '#js-diffs-app',
name: 'MergeRequestDiffs',
components: {
......@@ -157,4 +112,53 @@ export default function initDiffsApp(store) {
});
},
});
const fileFinderEl = document.getElementById('js-diff-file-finder');
if (fileFinderEl) {
// eslint-disable-next-line no-new
new Vue({
el: fileFinderEl,
store,
components: {
FindFile: () => import('~/vue_shared/components/file_finder/index.vue'),
},
computed: {
...mapState('diffs', ['fileFinderVisible', 'isLoading']),
...mapGetters('diffs', ['flatBlobsList']),
},
watch: {
fileFinderVisible(newVal, oldVal) {
if (newVal && !oldVal && !this.flatBlobsList.length) {
eventHub.$emit('fetchDiffData');
}
},
},
methods: {
...mapActions('diffs', ['toggleFileFinder', 'scrollToFile']),
openFile(file) {
window.mrTabs.tabShown('diffs');
this.scrollToFile(file.path);
},
},
render(createElement) {
return createElement('find-file', {
props: {
files: this.flatBlobsList,
visible: this.fileFinderVisible,
loading: this.isLoading,
showDiffStats: true,
clearSearchOnClose: false,
},
on: {
toggle: this.toggleFileFinder,
click: this.openFile,
},
class: ['diff-file-finder'],
});
},
});
}
return vm;
}
/* eslint-disable no-new */
import { mountSidebarLabels, getSidebarOptions } from '~/sidebar/mount_sidebar';
import { getSidebarOptions } from '~/sidebar/mount_sidebar';
import IssuableContext from './issuable_context';
import LabelsSelect from './labels_select';
import MilestoneSelect from './milestone_select';
import Sidebar from './right_sidebar';
export default () => {
......@@ -13,12 +11,6 @@ export default () => {
const sidebarOptions = getSidebarOptions(sidebarOptEl);
new MilestoneSelect({
full_path: sidebarOptions.fullPath,
});
new LabelsSelect();
new IssuableContext(sidebarOptions.currentUser);
Sidebar.initialize();
mountSidebarLabels();
};
......@@ -19,11 +19,9 @@ function MergeRequest(opts) {
this.opts = opts != null ? opts : {};
this.submitNoteForm = this.submitNoteForm.bind(this);
this.$el = $('.merge-request');
this.$('.show-all-commits').on('click', () => this.showAllCommits());
this.initTabs();
this.initMRBtnListeners();
this.initCommitMessageListeners();
if ($('.description.js-task-list-container').length) {
this.taskList = new TaskList({
......@@ -59,11 +57,6 @@ MergeRequest.prototype.initTabs = function () {
window.mrTabs = new MergeRequestTabs(this.opts);
};
MergeRequest.prototype.showAllCommits = function () {
this.$('.first-commits').remove();
return this.$('.all-commits').removeClass('hide');
};
MergeRequest.prototype.initMRBtnListeners = function () {
const _this = this;
const draftToggles = document.querySelectorAll('.js-draft-toggle-button');
......@@ -128,26 +121,6 @@ MergeRequest.prototype.submitNoteForm = function (form, $button) {
}
};
MergeRequest.prototype.initCommitMessageListeners = function () {
$(document).on('click', 'a.js-with-description-link', (e) => {
const textarea = $('textarea.js-commit-message');
e.preventDefault();
textarea.val(textarea.data('messageWithDescription'));
$('.js-with-description-hint').hide();
$('.js-without-description-hint').show();
});
$(document).on('click', 'a.js-without-description-link', (e) => {
const textarea = $('textarea.js-commit-message');
e.preventDefault();
textarea.val(textarea.data('messageWithoutDescription'));
$('.js-with-description-hint').show();
$('.js-without-description-hint').hide();
});
};
MergeRequest.decreaseCounter = function (by = 1) {
const $el = $('.js-merge-counter');
const count = Math.max(parseInt($el.text().replace(/[^\d]/, ''), 10) - by, 0);
......
......@@ -19,6 +19,7 @@ export default function initMrNotes() {
action: mrShowNode.dataset.mrAction,
});
initDiffsApp(store);
initNotesApp();
document.addEventListener('merged:UpdateActions', () => {
......@@ -26,20 +27,25 @@ export default function initMrNotes() {
initCherryPickCommitModal();
});
// eslint-disable-next-line no-new
new Vue({
el: '#js-vue-discussion-counter',
name: 'DiscussionCounter',
components: {
discussionCounter,
},
store,
render(createElement) {
return createElement('discussion-counter');
},
});
requestIdleCallback(() => {
const el = document.getElementById('js-vue-discussion-counter');
initDiscussionFilters(store);
initSortDiscussions(store);
initDiffsApp(store);
if (el) {
// eslint-disable-next-line no-new
new Vue({
el,
name: 'DiscussionCounter',
components: {
discussionCounter,
},
store,
render(createElement) {
return createElement('discussion-counter');
},
});
}
initDiscussionFilters(store);
initSortDiscussions(store);
});
}
import { store } from '~/notes/stores';
import initRelatedIssues from '~/related_issues';
import initSidebarBundle from '~/sidebar/sidebar_bundle';
import initShow from '../show';
initShow();
initSidebarBundle();
initSidebarBundle(store);
initRelatedIssues();
......@@ -2,11 +2,10 @@ import Vue from 'vue';
import VueApollo from 'vue-apollo';
import loadAwardsHandler from '~/awards_handler';
import ShortcutsIssuable from '~/behaviors/shortcuts/shortcuts_issuable';
import initPipelines from '~/commit/pipelines/pipelines_bundle';
import { initPipelineCountListener } from '~/commit/pipelines/utils';
import initIssuableSidebar from '~/init_issuable_sidebar';
import StatusBox from '~/issuable/components/status_box.vue';
import createDefaultClient from '~/lib/graphql';
import { handleLocationHash } from '~/lib/utils/common_utils';
import initSourcegraph from '~/sourcegraph';
import ZenMode from '~/zen_mode';
import getStateQuery from './queries/get_state.query.graphql';
......@@ -15,11 +14,10 @@ export default function initMergeRequestShow() {
const awardEmojiEl = document.getElementById('js-vue-awards-block');
new ZenMode(); // eslint-disable-line no-new
initIssuableSidebar();
initPipelines();
initPipelineCountListener(document.querySelector('#commit-pipeline-table-view'));
new ShortcutsIssuable(true); // eslint-disable-line no-new
handleLocationHash();
initSourcegraph();
initIssuableSidebar();
if (awardEmojiEl) {
import('~/emoji/awards_app')
.then((m) => m.default(awardEmojiEl))
......
......@@ -5,8 +5,11 @@ import initSidebarBundle from '~/sidebar/sidebar_bundle';
import initIssuableHeaderWarning from '~/vue_shared/components/issuable/init_issuable_header_warning';
import initShow from '../init_merge_request_show';
initShow();
initSidebarBundle();
initMrNotes();
initReviewBar();
initIssuableHeaderWarning(store);
initShow();
requestIdleCallback(() => {
initSidebarBundle(store);
initReviewBar();
initIssuableHeaderWarning(store);
});
import { memoize } from 'lodash';
import { createNodeDict } from '../utils';
import { createSankey } from './dag/drawing_utils';
/*
The following functions are the main engine in transforming the data as
received from the endpoint into the format the d3 graph expects.
Input is of the form:
[nodes]
nodes: [{category, name, jobs, size}]
category is the stage name
name is a group name; in the case that the group has one job, it is
also the job name
size is the number of parallel jobs
jobs: [{ name, needs}]
job name is either the same as the group name or group x/y
needs: [job-names]
needs is an array of job-name strings
Output is of the form:
{ nodes: [node], links: [link] }
node: { name, category }, + unused info passed through
link: { source, target, value }, with source & target being node names
and value being a constant
We create nodes in the GraphQL update function, and then here we create the node dictionary,
then create links, and then dedupe the links, so that in the case where
job 4 depends on job 1 and job 2, and job 2 depends on job 1, we show only a single link
from job 1 to job 2 then another from job 2 to job 4.
CREATE LINKS
nodes.name -> target
nodes.name.needs.each -> source (source is the name of the group, not the parallel job)
10 -> value (constant)
*/
export const createNodeDict = (nodes) => {
return nodes.reduce((acc, node) => {
const newNode = {
...node,
needs: node.jobs.map((job) => job.needs || []).flat(),
};
if (node.size > 1) {
node.jobs.forEach((job) => {
acc[job.name] = newNode;
});
}
acc[node.name] = newNode;
return acc;
}, {});
};
/*
A peformant alternative to lodash's isEqual. Because findIndex always finds
the first instance of a match, if the found index is not the first, we know
......
import * as Sentry from '@sentry/browser';
import { pickBy } from 'lodash';
import { createNodeDict } from './components/parsing_utils';
import { SUPPORTED_FILTER_PARAMETERS } from './constants';
/*
The following functions are the main engine in transforming the data as
received from the endpoint into the format the d3 graph expects.
Input is of the form:
[nodes]
nodes: [{category, name, jobs, size}]
category is the stage name
name is a group name; in the case that the group has one job, it is
also the job name
size is the number of parallel jobs
jobs: [{ name, needs}]
job name is either the same as the group name or group x/y
needs: [job-names]
needs is an array of job-name strings
Output is of the form:
{ nodes: [node], links: [link] }
node: { name, category }, + unused info passed through
link: { source, target, value }, with source & target being node names
and value being a constant
We create nodes in the GraphQL update function, and then here we create the node dictionary,
then create links, and then dedupe the links, so that in the case where
job 4 depends on job 1 and job 2, and job 2 depends on job 1, we show only a single link
from job 1 to job 2 then another from job 2 to job 4.
CREATE LINKS
nodes.name -> target
nodes.name.needs.each -> source (source is the name of the group, not the parallel job)
10 -> value (constant)
*/
export const createNodeDict = (nodes) => {
return nodes.reduce((acc, node) => {
const newNode = {
...node,
needs: node.jobs.map((job) => job.needs || []).flat(),
};
if (node.size > 1) {
node.jobs.forEach((job) => {
acc[job.name] = newNode;
});
}
acc[node.name] = newNode;
return acc;
}, {});
};
export const validateParams = (params) => {
return pickBy(params, (val, key) => SUPPORTED_FILTER_PARAMETERS.includes(key) && val);
};
......
import IssueStatusIcon from '~/reports/components/issue_status_icon.vue';
import AccessibilityIssueBody from '../accessibility_report/components/accessibility_issue_body.vue';
import CodequalityIssueBody from '../codequality_report/components/codequality_issue_body.vue';
import TestIssueBody from '../grouped_test_report/components/test_issue_body.vue';
export const components = {
AccessibilityIssueBody,
CodequalityIssueBody,
TestIssueBody,
AccessibilityIssueBody: () =>
import('../accessibility_report/components/accessibility_issue_body.vue'),
CodequalityIssueBody: () => import('../codequality_report/components/codequality_issue_body.vue'),
TestIssueBody: () => import('../grouped_test_report/components/test_issue_body.vue'),
};
export const componentNames = {
AccessibilityIssueBody: AccessibilityIssueBody.name,
CodequalityIssueBody: CodequalityIssueBody.name,
TestIssueBody: TestIssueBody.name,
AccessibilityIssueBody: 'AccessibilityIssueBody',
CodequalityIssueBody: 'CodequalityIssueBody',
TestIssueBody: 'TestIssueBody',
};
export const iconComponents = {
......
import $ from 'jquery';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createFlash from '~/flash';
import { TYPE_ISSUE, TYPE_MERGE_REQUEST } from '~/graphql_shared/constants';
import { convertToGraphQLId } from '~/graphql_shared/utils';
import initInviteMembersModal from '~/invite_members/init_invite_members_modal';
......@@ -13,7 +12,6 @@ import {
isInIncidentPage,
parseBoolean,
} from '~/lib/utils/common_utils';
import { __ } from '~/locale';
import CollapsedAssigneeList from '~/sidebar/components/assignees/collapsed_assignee_list.vue';
import SidebarAssigneesWidget from '~/sidebar/components/assignees/sidebar_assignees_widget.vue';
import SidebarConfidentialityWidget from '~/sidebar/components/confidential/sidebar_confidentiality_widget.vue';
......@@ -363,10 +361,10 @@ function mountReferenceComponent() {
});
}
function mountLockComponent() {
function mountLockComponent(store) {
const el = document.getElementById('js-lock-entry-point');
if (!el) {
if (!el || !store) {
return;
}
......@@ -375,37 +373,20 @@ function mountLockComponent() {
const dataNode = document.getElementById('js-lock-issue-data');
const initialData = JSON.parse(dataNode.innerHTML);
let importStore;
if (isInIssuePage() || isInIncidentPage()) {
importStore = import(/* webpackChunkName: 'notesStore' */ '~/notes/stores').then(
({ store }) => store,
);
} else {
importStore = import(/* webpackChunkName: 'mrNotesStore' */ '~/mr_notes/stores').then(
(store) => store.default,
);
}
importStore
.then(
(store) =>
new Vue({
el,
store,
provide: {
fullPath,
},
render: (createElement) =>
createElement(IssuableLockForm, {
props: {
isEditable: initialData.is_editable,
},
}),
}),
)
.catch(() => {
createFlash({ message: __('Failed to load sidebar lock status') });
});
// eslint-disable-next-line no-new
new Vue({
el,
store,
provide: {
fullPath,
},
render: (createElement) =>
createElement(IssuableLockForm, {
props: {
isEditable: initialData.is_editable,
},
}),
});
}
function mountParticipantsComponent() {
......@@ -537,7 +518,7 @@ function mountCopyEmailComponent() {
const isAssigneesWidgetShown =
(isInIssuePage() || isInDesignPage()) && gon.features.issueAssigneesWidget;
export function mountSidebar(mediator) {
export function mountSidebar(mediator, store) {
initInviteMembersModal();
initInviteMembersTrigger();
......@@ -548,11 +529,12 @@ export function mountSidebar(mediator) {
mountAssigneesComponentDeprecated(mediator);
}
mountReviewersComponent(mediator);
mountSidebarLabels();
mountMilestoneSelect();
mountConfidentialComponent(mediator);
mountDueDateComponent(mediator);
mountReferenceComponent(mediator);
mountLockComponent();
mountLockComponent(store);
mountParticipantsComponent();
mountSubscriptionsComponent();
mountCopyEmailComponent();
......
import { mountSidebar, getSidebarOptions } from './mount_sidebar';
import { mountSidebar, getSidebarOptions } from 'ee_else_ce/sidebar/mount_sidebar';
import Mediator from './sidebar_mediator';
export default () => {
export default (store) => {
const mediator = new Mediator(getSidebarOptions());
mediator.fetch();
mountSidebar(mediator);
mountSidebar(mediator, store);
};
......@@ -7,8 +7,6 @@ import VueApollo from 'vue-apollo';
import MrWidgetOptions from 'ee_else_ce/vue_merge_request_widget/mr_widget_options.vue';
import createDefaultClient from '~/lib/graphql';
import Translate from '../vue_shared/translate';
import { registerExtension } from './components/extensions';
import issueExtension from './extensions/issues';
Vue.use(Translate);
Vue.use(VueApollo);
......@@ -28,8 +26,6 @@ export default () => {
gl.mrWidgetData.gitlabLogo = gon.gitlab_logo;
gl.mrWidgetData.defaultAvatarUrl = gon.default_avatar_url;
registerExtension(issueExtension);
const vm = new Vue({
el: '#js-vue-mr-widget',
provide: {
......
......@@ -12,9 +12,6 @@ import { sprintf, s__, __ } from '~/locale';
import Project from '~/pages/projects/project';
import SmartInterval from '~/smart_interval';
import { setFaviconOverlay } from '../lib/utils/favicon';
import GroupedAccessibilityReportsApp from '../reports/accessibility_report/grouped_accessibility_reports_app.vue';
import GroupedCodequalityReportsApp from '../reports/codequality_report/grouped_codequality_reports_app.vue';
import GroupedTestReportsApp from '../reports/grouped_test_report/grouped_test_reports_app.vue';
import Loading from './components/loading.vue';
import MrWidgetAlertMessage from './components/mr_widget_alert_message.vue';
import WidgetHeader from './components/mr_widget_header.vue';
......@@ -42,7 +39,6 @@ import ShaMismatch from './components/states/sha_mismatch.vue';
import UnresolvedDiscussionsState from './components/states/unresolved_discussions.vue';
import WorkInProgressState from './components/states/work_in_progress.vue';
// import ExtensionsContainer from './components/extensions/container';
import TerraformPlan from './components/terraform/mr_widget_terraform_container.vue';
import eventHub from './event_hub';
import mergeRequestQueryVariablesMixin from './mixins/merge_request_query_variables';
import getStateQuery from './queries/get_state.query.graphql';
......@@ -84,10 +80,13 @@ export default {
'mr-widget-auto-merge-failed': AutoMergeFailed,
'mr-widget-rebase': RebaseState,
SourceBranchRemovalStatus,
GroupedCodequalityReportsApp,
GroupedTestReportsApp,
TerraformPlan,
GroupedAccessibilityReportsApp,
GroupedCodequalityReportsApp: () =>
import('../reports/codequality_report/grouped_codequality_reports_app.vue'),
GroupedTestReportsApp: () =>
import('../reports/grouped_test_report/grouped_test_reports_app.vue'),
TerraformPlan: () => import('./components/terraform/mr_widget_terraform_container.vue'),
GroupedAccessibilityReportsApp: () =>
import('../reports/accessibility_report/grouped_accessibility_reports_app.vue'),
MrWidgetApprovals,
SecurityReportsApp: () => import('~/vue_shared/security_reports/security_reports_app.vue'),
},
......
import initRelatedFeatureFlags from 'ee/related_feature_flags';
import initSidebarBundle from 'ee/sidebar/sidebar_bundle';
import { store } from '~/notes/stores';
import initShow from '~/pages/projects/issues/show';
import initRelatedIssues from '~/related_issues';
import initSidebarBundle from '~/sidebar/sidebar_bundle';
import UserCallout from '~/user_callout';
initShow();
initSidebarBundle();
initSidebarBundle(store);
initRelatedIssues();
initRelatedFeatureFlags();
......
import initSidebarBundle from 'ee/sidebar/sidebar_bundle';
import initMergeConflicts from '~/merge_conflicts/merge_conflicts_bundle';
import initSidebarBundle from '~/sidebar/sidebar_bundle';
initSidebarBundle();
initMergeConflicts();
import initSidebarBundle from 'ee/sidebar/sidebar_bundle';
import { initReviewBar } from '~/batch_comments';
import initMrNotes from '~/mr_notes';
import store from '~/mr_notes/stores';
import initShow from '~/pages/projects/merge_requests/init_merge_request_show';
import initIssuableHeaderWarning from '~/vue_shared/components/issuable/init_issuable_header_warning';
initShow();
initSidebarBundle();
initMrNotes();
initReviewBar();
initIssuableHeaderWarning(store);
import BlockingMergeRequestsBody from 'ee/vue_merge_request_widget/components/blocking_merge_requests/blocking_merge_request_body.vue';
import PerformanceIssueBody from 'ee/vue_merge_request_widget/components/performance_issue_body.vue';
import StatusCheckIssueBody from 'ee/vue_merge_request_widget/components/status_check_issue_body.vue';
import LicenseIssueBody from 'ee/vue_shared/license_compliance/components/license_issue_body.vue';
import LicenseStatusIcon from 'ee/vue_shared/license_compliance/components/license_status_icon.vue';
import MetricsReportsIssueBody from 'ee/vue_shared/metrics_reports/components/metrics_reports_issue_body.vue';
import SecurityIssueBody from 'ee/vue_shared/security_reports/components/security_issue_body.vue';
import {
components as componentsCE,
componentNames as componentNamesCE,
......@@ -14,22 +8,30 @@ import {
export const components = {
...componentsCE,
StatusCheckIssueBody,
PerformanceIssueBody,
LicenseIssueBody,
SecurityIssueBody,
MetricsReportsIssueBody,
BlockingMergeRequestsBody,
StatusCheckIssueBody: () =>
import('ee/vue_merge_request_widget/components/status_check_issue_body.vue'),
PerformanceIssueBody: () =>
import('ee/vue_merge_request_widget/components/performance_issue_body.vue'),
LicenseIssueBody: () =>
import('ee/vue_shared/license_compliance/components/license_issue_body.vue'),
SecurityIssueBody: () =>
import('ee/vue_shared/security_reports/components/security_issue_body.vue'),
MetricsReportsIssueBody: () =>
import('ee/vue_shared/metrics_reports/components/metrics_reports_issue_body.vue'),
BlockingMergeRequestsBody: () =>
import(
'ee/vue_merge_request_widget/components/blocking_merge_requests/blocking_merge_request_body.vue'
),
};
export const componentNames = {
...componentNamesCE,
StatusCheckIssueBody: StatusCheckIssueBody.name,
PerformanceIssueBody: PerformanceIssueBody.name,
LicenseIssueBody: LicenseIssueBody.name,
SecurityIssueBody: SecurityIssueBody.name,
MetricsReportsIssueBody: MetricsReportsIssueBody.name,
BlockingMergeRequestsBody: BlockingMergeRequestsBody.name,
StatusCheckIssueBody: 'StatusCheckIssueBody',
PerformanceIssueBody: 'PerformanceIssueBody',
LicenseIssueBody: 'LicenseIssueBody',
SecurityIssueBody: 'SecurityIssueBody',
MetricsReportsIssueBody: 'MetricsReportsIssueBody',
BlockingMergeRequestsBody: 'BlockingMergeRequestsBody',
};
export const iconComponents = {
......
......@@ -2,7 +2,6 @@ import Vue from 'vue';
import VueApollo from 'vue-apollo';
import { IssuableType } from '~/issue_show/constants';
import { parseBoolean } from '~/lib/utils/common_utils';
import { store } from '~/notes/stores';
import { apolloProvider } from '~/sidebar/graphql';
import * as CEMountSidebar from '~/sidebar/mount_sidebar';
import CveIdRequest from './components/cve_id_request/cve_id_request_sidebar.vue';
......@@ -42,7 +41,7 @@ const mountWeightComponent = () => {
});
};
const mountStatusComponent = () => {
const mountStatusComponent = (store) => {
const el = document.querySelector('.js-sidebar-status-entry-point');
if (!el) {
......@@ -70,7 +69,7 @@ const mountStatusComponent = () => {
});
};
function mountCveIdRequestComponent() {
function mountCveIdRequestComponent(store) {
const el = document.getElementById('js-sidebar-cve-id-request-entry-point');
if (!el) {
......@@ -153,14 +152,16 @@ function mountIterationSelect() {
});
}
export default function mountSidebar(mediator) {
CEMountSidebar.mountSidebar(mediator);
export const { getSidebarOptions } = CEMountSidebar;
export function mountSidebar(mediator, store) {
CEMountSidebar.mountSidebar(mediator, store);
mountWeightComponent();
mountStatusComponent(mediator);
mountStatusComponent(store);
mountEpicsSelect();
mountIterationSelect();
if (gon.features.cveIdRequestButton) {
mountCveIdRequestComponent();
mountCveIdRequestComponent(store);
}
}
import { getSidebarOptions } from '~/sidebar/mount_sidebar';
import mountSidebar from './mount_sidebar';
import Mediator from './sidebar_mediator';
export default () => {
const sidebarOptEl = document.querySelector('.js-sidebar-options');
if (!sidebarOptEl) return;
const mediator = new Mediator(getSidebarOptions(sidebarOptEl));
mediator.fetch();
mountSidebar(mediator);
};
<script>
import { GlSafeHtmlDirective } from '@gitlab/ui';
import GroupedBrowserPerformanceReportsApp from 'ee/reports/browser_performance_report/grouped_browser_performance_reports_app.vue';
import { componentNames } from 'ee/reports/components/issue_body';
import GroupedLoadPerformanceReportsApp from 'ee/reports/load_performance_report/grouped_load_performance_reports_app.vue';
import StatusChecksReportsApp from 'ee/reports/status_checks_report/status_checks_reports_app.vue';
import MrWidgetLicenses from 'ee/vue_shared/license_compliance/mr_widget_license_report.vue';
import GroupedMetricsReportsApp from 'ee/vue_shared/metrics_reports/grouped_metrics_reports_app.vue';
import reportsMixin from 'ee/vue_shared/security_reports/mixins/reports_mixin';
import { s__, __, sprintf } from '~/locale';
import ReportSection from '~/reports/components/report_section.vue';
import CEWidgetOptions from '~/vue_merge_request_widget/mr_widget_options.vue';
import BlockingMergeRequestsReport from './components/blocking_merge_requests/blocking_merge_requests_report.vue';
import MrWidgetJiraAssociationMissing from './components/states/mr_widget_jira_association_missing.vue';
import MrWidgetPolicyViolation from './components/states/mr_widget_policy_violation.vue';
import MrWidgetGeoSecondaryNode from './components/states/mr_widget_secondary_geo_node.vue';
......@@ -21,21 +14,25 @@ export default {
MrWidgetGeoSecondaryNode,
MrWidgetPolicyViolation,
MrWidgetJiraAssociationMissing,
StatusChecksReportsApp,
BlockingMergeRequestsReport,
StatusChecksReportsApp: () =>
import('ee/reports/status_checks_report/status_checks_reports_app.vue'),
BlockingMergeRequestsReport: () =>
import('./components/blocking_merge_requests/blocking_merge_requests_report.vue'),
GroupedSecurityReportsApp: () =>
import('ee/vue_shared/security_reports/grouped_security_reports_app.vue'),
GroupedMetricsReportsApp,
GroupedBrowserPerformanceReportsApp,
GroupedLoadPerformanceReportsApp,
ReportSection,
GroupedMetricsReportsApp: () =>
import('ee/vue_shared/metrics_reports/grouped_metrics_reports_app.vue'),
GroupedBrowserPerformanceReportsApp: () =>
import('ee/reports/browser_performance_report/grouped_browser_performance_reports_app.vue'),
GroupedLoadPerformanceReportsApp: () =>
import('ee/reports/load_performance_report/grouped_load_performance_reports_app.vue'),
ReportSection: () => import('~/reports/components/report_section.vue'),
},
directives: {
SafeHtml: GlSafeHtmlDirective,
},
extends: CEWidgetOptions,
mixins: [reportsMixin],
componentNames,
data() {
return {
isLoadingBrowserPerformance: false,
......
......@@ -2,6 +2,13 @@ import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
// Force Jest to transpile and cache
// eslint-disable-next-line no-unused-vars
import _GroupedBrowserPerformanceReportsApp from 'ee/reports/browser_performance_report/grouped_browser_performance_reports_app.vue';
// eslint-disable-next-line no-unused-vars
import _GroupedLoadPerformanceReportsApp from 'ee/reports/load_performance_report/grouped_load_performance_reports_app.vue';
import StatusChecksReportsApp from 'ee/reports/status_checks_report/status_checks_reports_app.vue';
import PerformanceIssueBody from 'ee/vue_merge_request_widget/components/performance_issue_body.vue';
import MrWidgetOptions from 'ee/vue_merge_request_widget/mr_widget_options.vue';
......@@ -96,7 +103,6 @@ describe('ee merge request widget options', () => {
});
const findBrowserPerformanceWidget = () => wrapper.find('.js-browser-performance-widget');
const findLoadPerformanceWidget = () => wrapper.find('.js-load-performance-widget');
const findExtendedSecurityWidget = () => wrapper.find('.js-security-widget');
const findBaseSecurityWidget = () => wrapper.find('[data-testid="security-mr-widget"]');
const findStatusChecksReport = () => wrapper.findComponent(StatusChecksReportsApp);
......@@ -336,29 +342,13 @@ describe('ee merge request widget options', () => {
};
});
describe('when it is loading', () => {
it('should render loading indicator', (done) => {
mock.onGet('head.json').reply(200, headBrowserPerformance);
mock.onGet('base.json').reply(200, baseBrowserPerformance);
createComponent({ propsData: { mrData: gl.mrWidgetData } });
wrapper.vm.mr.browserPerformance = { ...DEFAULT_BROWSER_PERFORMANCE };
nextTick(() => {
expect(trimText(findBrowserPerformanceWidget().text())).toContain(
'Loading browser-performance report',
);
done();
});
});
});
describe('with successful request', () => {
beforeEach(() => {
beforeEach(async () => {
mock.onGet(DEFAULT_BROWSER_PERFORMANCE.head_path).reply(200, headBrowserPerformance);
mock.onGet(DEFAULT_BROWSER_PERFORMANCE.base_path).reply(200, baseBrowserPerformance);
createComponent({ propsData: { mrData: gl.mrWidgetData } });
await waitForPromises();
});
describe('default', () => {
......@@ -500,29 +490,13 @@ describe('ee merge request widget options', () => {
};
});
describe('when it is loading', () => {
it('should render loading indicator', (done) => {
mock.onGet(DEFAULT_LOAD_PERFORMANCE.head_path).reply(200, headLoadPerformance);
mock.onGet(DEFAULT_LOAD_PERFORMANCE.base_path).reply(200, baseLoadPerformance);
createComponent({ propsData: { mrData: gl.mrWidgetData } });
wrapper.vm.mr.loadPerformance = { ...DEFAULT_LOAD_PERFORMANCE };
nextTick(() => {
expect(trimText(findLoadPerformanceWidget().text())).toContain(
'Loading load-performance report',
);
done();
});
});
});
describe('with successful request', () => {
beforeEach(() => {
beforeEach(async () => {
mock.onGet(DEFAULT_LOAD_PERFORMANCE.head_path).reply(200, headLoadPerformance);
mock.onGet(DEFAULT_LOAD_PERFORMANCE.base_path).reply(200, baseLoadPerformance);
createComponent({ propsData: { mrData: gl.mrWidgetData } });
await waitForPromises();
});
describe('default', () => {
......
import { mount } from '@vue/test-utils';
import Vue from 'vue';
import { componentNames } from 'ee/reports/components/issue_body';
import SecurityIssueBody from 'ee/vue_shared/security_reports/components/security_issue_body.vue';
import store from 'ee/vue_shared/security_reports/store';
import { codequalityParsedIssues } from 'ee_jest/vue_mr_widget/mock_data';
import {
......@@ -14,6 +16,7 @@ import { STATUS_FAILED, STATUS_SUCCESS } from '~/reports/constants';
describe('Report issues', () => {
let vm;
let wrapper;
let ReportIssues;
beforeEach(() => {
......@@ -21,7 +24,9 @@ describe('Report issues', () => {
});
afterEach(() => {
vm.$destroy();
if (vm?.$destroy) vm.$destroy();
if (wrapper) wrapper.destroy();
});
describe('for codequality issues', () => {
......@@ -59,14 +64,19 @@ describe('Report issues', () => {
describe('with location', () => {
it('should render location', () => {
vm = mountComponent(ReportIssues, {
issue: sastParsedIssues[0],
component: componentNames.SecurityIssueBody,
status: STATUS_FAILED,
vm = mount(ReportIssues, {
propsData: {
issue: sastParsedIssues[0],
component: componentNames.SecurityIssueBody,
status: STATUS_FAILED,
},
stubs: {
SecurityIssueBody,
},
});
expect(vm.$el.textContent).toContain('in');
expect(vm.$el.querySelector('.report-block-list-issue a').getAttribute('href')).toEqual(
expect(vm.text()).toContain('in');
expect(vm.find('.report-block-list-issue a').attributes('href')).toEqual(
sastParsedIssues[0].urlPath,
);
});
......
import { shallowMount } from '@vue/test-utils';
import { mount } from '@vue/test-utils';
import Vue from 'vue';
import { componentNames, iconComponentNames } from 'ee/reports/components/issue_body';
import LicenseIssueBody from 'ee/vue_shared/license_compliance/components/license_issue_body.vue';
import LicenseStatusIcon from 'ee/vue_shared/license_compliance/components/license_status_icon.vue';
import SecurityIssueBody from 'ee/vue_shared/security_reports/components/security_issue_body.vue';
import store from 'ee/vue_shared/security_reports/store';
import { codequalityParsedIssues } from 'ee_jest/vue_mr_widget/mock_data';
import {
......@@ -26,7 +27,9 @@ describe('Report issue', () => {
});
afterEach(() => {
vm.$destroy();
if (vm?.$destroy) vm.$destroy();
if (wrapper) wrapper.destroy();
});
describe('for codequality issue', () => {
......@@ -64,31 +67,39 @@ describe('Report issue', () => {
describe('with location', () => {
it('should render location', () => {
vm = mountComponent(ReportIssue, {
issue: sastParsedIssues[0],
component: componentNames.SecurityIssueBody,
status: STATUS_FAILED,
vm = mount(ReportIssue, {
propsData: {
issue: sastParsedIssues[0],
component: componentNames.SecurityIssueBody,
status: STATUS_FAILED,
},
stubs: {
SecurityIssueBody,
},
});
expect(vm.$el.textContent).toContain('in');
expect(vm.$el.querySelector('li a').getAttribute('href')).toEqual(
sastParsedIssues[0].urlPath,
);
expect(vm.text()).toContain('in');
expect(vm.find('li a').attributes('href')).toEqual(sastParsedIssues[0].urlPath);
});
});
describe('without location', () => {
it('should not render location', () => {
vm = mountComponent(ReportIssue, {
issue: {
title: 'foo',
vm = mount(ReportIssue, {
propsData: {
issue: {
title: 'foo',
},
component: componentNames.SecurityIssueBody,
status: STATUS_SUCCESS,
},
stubs: {
SecurityIssueBody,
},
component: componentNames.SecurityIssueBody,
status: STATUS_SUCCESS,
});
expect(vm.$el.textContent).not.toContain('in');
expect(vm.$el.querySelector('a')).toEqual(null);
expect(vm.text()).not.toContain('in');
expect(vm.find('a').exists()).toBe(false);
});
});
......@@ -156,13 +167,17 @@ describe('Report issue', () => {
describe('for license compliance issue', () => {
it('renders LicenseIssueBody & LicenseStatusIcon', () => {
wrapper = shallowMount(ReportIssue, {
wrapper = mount(ReportIssue, {
propsData: {
issue: licenseComplianceParsedIssues[0],
component: componentNames.LicenseIssueBody,
iconComponent: iconComponentNames.LicenseStatusIcon,
status: STATUS_NEUTRAL,
},
stubs: {
LicenseIssueBody,
LicenseStatusIcon,
},
});
expect(wrapper.findComponent(LicenseIssueBody).exists()).toBe(true);
......
......@@ -13938,9 +13938,6 @@ msgstr ""
msgid "Failed to load related branches"
msgstr ""
msgid "Failed to load sidebar lock status"
msgstr ""
msgid "Failed to load stacktrace."
msgstr ""
......
......@@ -25,10 +25,6 @@ RSpec.describe 'Merge request > Batch comments', :js do
visit_diffs
end
it 'has review bar' do
expect(page).to have_selector('[data-testid="review_bar_component"]', visible: false)
end
it 'adds draft note' do
write_diff_comment
......
import { createSankey } from '~/pipelines/components/dag/drawing_utils';
import {
createNodeDict,
makeLinksFromNodes,
filterByAncestors,
generateColumnsFromLayersListBare,
......@@ -9,6 +8,7 @@ import {
removeOrphanNodes,
getMaxNodes,
} from '~/pipelines/components/parsing_utils';
import { createNodeDict } from '~/pipelines/utils';
import { mockParsedGraphQLNodes, missingJob } from './components/dag/mock_data';
import { generateResponse, mockPipelineResponse } from './graph/mock_data';
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment