Commit a3d80768 authored by Dmitriy Zaporozhets's avatar Dmitriy Zaporozhets

Merge remote-tracking branch 'ee-com/master' into ce-to-ee

Signed-off-by: default avatarDmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>
parents 598e33d9 13647d24
...@@ -297,9 +297,9 @@ might be edited to make them small and simple. ...@@ -297,9 +297,9 @@ might be edited to make them small and simple.
Please submit Feature Proposals using the ['Feature Proposal' issue template](.gitlab/issue_templates/Feature Proposal.md) provided on the issue tracker. Please submit Feature Proposals using the ['Feature Proposal' issue template](.gitlab/issue_templates/Feature Proposal.md) provided on the issue tracker.
For changes in the interface, it is helpful to include a mockup. Issues that add to, or change, the interface should For changes in the interface, it is helpful to include a mockup. Issues that add to, or change, the interface should
be given the ~"UX" label. This will allow the UX team to provide input and guidance. You may be given the ~"UX" label. This will allow the UX team to provide input and guidance. You may
need to ask one of the [core team] members to add the label, if you do not have permissions to do it by yourself. need to ask one of the [core team] members to add the label, if you do not have permissions to do it by yourself.
If you want to create something yourself, consider opening an issue first to If you want to create something yourself, consider opening an issue first to
discuss whether it is interesting to include this in GitLab. discuss whether it is interesting to include this in GitLab.
...@@ -677,7 +677,7 @@ When your code contains more than 500 changes, any major breaking changes, or an ...@@ -677,7 +677,7 @@ When your code contains more than 500 changes, any major breaking changes, or an
[license-finder-doc]: doc/development/licensing.md [license-finder-doc]: doc/development/licensing.md
[GitLab Inc engineering workflow]: https://about.gitlab.com/handbook/engineering/workflow/#labelling-issues [GitLab Inc engineering workflow]: https://about.gitlab.com/handbook/engineering/workflow/#labelling-issues
[polling-etag]: https://docs.gitlab.com/ce/development/polling.html [polling-etag]: https://docs.gitlab.com/ce/development/polling.html
[testing]: doc/development/testing.md [testing]: doc/development/testing_guide/index.md
[^1]: Please note that specs other than JavaScript specs are considered backend [^1]: Please note that specs other than JavaScript specs are considered backend
code. code.
## GitLab Core Team & GitLab Inc. Contribution Process ## GitLab core team & GitLab Inc. contribution process
--- ---
......
/* eslint-disable class-methods-use-this */ /* eslint-disable class-methods-use-this */
/* global Flash */
import _ from 'underscore'; import _ from 'underscore';
import Cookies from 'js-cookie'; import Cookies from 'js-cookie';
import { isInIssuePage, updateTooltipTitle } from './lib/utils/common_utils'; import { isInIssuePage, updateTooltipTitle } from './lib/utils/common_utils';
import Flash from './flash';
const animationEndEventString = 'animationend webkitAnimationEnd MSAnimationEnd oAnimationEnd'; const animationEndEventString = 'animationend webkitAnimationEnd MSAnimationEnd oAnimationEnd';
const transitionEndEventString = 'transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd'; const transitionEndEventString = 'transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd';
......
/* global Flash */ import Flash from '../flash';
import BalsamiqViewer from './balsamiq/balsamiq_viewer'; import BalsamiqViewer from './balsamiq/balsamiq_viewer';
function onError() { function onError() {
const flash = new window.Flash('Balsamiq file could not be loaded.'); const flash = new Flash('Balsamiq file could not be loaded.');
return flash; return flash;
} }
......
/* eslint-disable class-methods-use-this */ /* eslint-disable class-methods-use-this */
/* global Flash */ import Flash from '../flash';
import FileTemplateTypeSelector from './template_selectors/type_selector'; import FileTemplateTypeSelector from './template_selectors/type_selector';
import BlobCiYamlSelector from './template_selectors/ci_yaml_selector'; import BlobCiYamlSelector from './template_selectors/ci_yaml_selector';
import DockerfileSelector from './template_selectors/dockerfile_selector'; import DockerfileSelector from './template_selectors/dockerfile_selector';
......
/* global Flash */ import Flash from '../../flash';
import { handleLocationHash } from '../../lib/utils/common_utils'; import { handleLocationHash } from '../../lib/utils/common_utils';
export default class BlobViewer { export default class BlobViewer {
......
/* eslint-disable one-var, quote-props, comma-dangle, space-before-function-paren */ /* eslint-disable one-var, quote-props, comma-dangle, space-before-function-paren */
/* global BoardService */ /* global BoardService */
/* global Flash */
import _ from 'underscore'; import _ from 'underscore';
import Vue from 'vue'; import Vue from 'vue';
import VueResource from 'vue-resource'; import VueResource from 'vue-resource';
import Flash from '../flash';
import FilteredSearchBoards from './filtered_search_boards'; import FilteredSearchBoards from './filtered_search_boards';
import eventHub from './eventhub'; import eventHub from './eventhub';
import './models/issue'; import './models/issue';
......
...@@ -3,9 +3,9 @@ ...@@ -3,9 +3,9 @@
/* global MilestoneSelect */ /* global MilestoneSelect */
/* global LabelsSelect */ /* global LabelsSelect */
/* global Sidebar */ /* global Sidebar */
/* global Flash */
import Vue from 'vue'; import Vue from 'vue';
import Flash from '../../flash';
import eventHub from '../../sidebar/event_hub'; import eventHub from '../../sidebar/event_hub';
import AssigneeTitle from '../../sidebar/components/assignees/assignee_title'; import AssigneeTitle from '../../sidebar/components/assignees/assignee_title';
import Assignees from '../../sidebar/components/assignees/assignees'; import Assignees from '../../sidebar/components/assignees/assignees';
......
/* eslint-disable no-new */ /* eslint-disable no-new */
/* global Flash */
import Vue from 'vue'; import Vue from 'vue';
import Flash from '../../../flash';
import './lists_dropdown'; import './lists_dropdown';
const ModalStore = gl.issueBoards.ModalStore; const ModalStore = gl.issueBoards.ModalStore;
......
/* eslint-disable no-new */ /* eslint-disable no-new */
/* global Flash */
import Vue from 'vue'; import Vue from 'vue';
import Flash from '../../../flash';
const Store = gl.issueBoards.BoardsStore; const Store = gl.issueBoards.BoardsStore;
......
/* eslint-disable no-new */ /* eslint-disable no-new */
/* global Flash */ import Flash from './flash';
import DropLab from './droplab/drop_lab'; import DropLab from './droplab/drop_lab';
import ISetter from './droplab/plugins/input_setter'; import ISetter from './droplab/plugins/input_setter';
......
/* global Flash */
import Vue from 'vue'; import Vue from 'vue';
import Cookies from 'js-cookie'; import Cookies from 'js-cookie';
import Flash from '../flash';
import Translate from '../vue_shared/translate'; import Translate from '../vue_shared/translate';
import banner from './components/banner.vue'; import banner from './components/banner.vue';
import stageCodeComponent from './components/stage_code_component.vue'; import stageCodeComponent from './components/stage_code_component.vue';
......
<script> <script>
/* global Flash */ import Flash from '../../flash';
import eventHub from '../eventhub'; import eventHub from '../eventhub';
import DeployKeysService from '../service'; import DeployKeysService from '../service';
import DeployKeysStore from '../store'; import DeployKeysStore from '../store';
......
/* eslint-disable comma-dangle, object-shorthand, func-names, quote-props, no-else-return, camelcase, max-len */ /* eslint-disable comma-dangle, object-shorthand, func-names, quote-props, no-else-return, camelcase, max-len */
/* global CommentsStore */ /* global CommentsStore */
/* global ResolveService */ /* global ResolveService */
/* global Flash */
import Vue from 'vue'; import Vue from 'vue';
import Flash from '../../flash';
const ResolveBtn = Vue.extend({ const ResolveBtn = Vue.extend({
props: { props: {
......
/* global Flash */
/* global CommentsStore */ /* global CommentsStore */
import Vue from 'vue'; import Vue from 'vue';
import Flash from '../../flash';
import '../../vue_shared/vue_resource_interceptor'; import '../../vue_shared/vue_resource_interceptor';
window.gl = window.gl || {}; window.gl = window.gl || {};
......
<script> <script>
/* global Flash */
import Visibility from 'visibilityjs'; import Visibility from 'visibilityjs';
import Flash from '../../flash';
import EnvironmentsService from '../services/environments_service'; import EnvironmentsService from '../services/environments_service';
import environmentTable from './environments_table.vue'; import environmentTable from './environments_table.vue';
import EnvironmentsStore from '../stores/environments_store'; import EnvironmentsStore from '../stores/environments_store';
......
<script> <script>
/* global Flash */
import Visibility from 'visibilityjs'; import Visibility from 'visibilityjs';
import Flash from '../../flash';
import EnvironmentsService from '../services/environments_service'; import EnvironmentsService from '../services/environments_service';
import environmentTable from '../components/environments_table.vue'; import environmentTable from '../components/environments_table.vue';
import EnvironmentsStore from '../stores/environments_store'; import EnvironmentsStore from '../stores/environments_store';
......
/* global Flash */ import Flash from '../flash';
import Ajax from '../droplab/plugins/ajax';
import Ajax from '~/droplab/plugins/ajax'; import Filter from '../droplab/plugins/filter';
import Filter from '~/droplab/plugins/filter';
import './filtered_search_dropdown'; import './filtered_search_dropdown';
class DropdownEmoji extends gl.FilteredSearchDropdown { class DropdownEmoji extends gl.FilteredSearchDropdown {
......
/* global Flash */ import Flash from '../flash';
import Ajax from '../droplab/plugins/ajax';
import Ajax from '~/droplab/plugins/ajax'; import Filter from '../droplab/plugins/filter';
import Filter from '~/droplab/plugins/filter';
import './filtered_search_dropdown'; import './filtered_search_dropdown';
class DropdownNonUser extends gl.FilteredSearchDropdown { class DropdownNonUser extends gl.FilteredSearchDropdown {
......
/* global Flash */ import Flash from '../flash';
import AjaxFilter from '../droplab/plugins/ajax_filter';
import AjaxFilter from '~/droplab/plugins/ajax_filter';
import './filtered_search_dropdown'; import './filtered_search_dropdown';
import { addClassIfElementExists } from '../lib/utils/dom_utils'; import { addClassIfElementExists } from '../lib/utils/dom_utils';
......
import Flash from '../flash';
import FilteredSearchContainer from './container'; import FilteredSearchContainer from './container';
import RecentSearchesRoot from './recent_searches_root'; import RecentSearchesRoot from './recent_searches_root';
import RecentSearchesStore from './stores/recent_searches_store'; import RecentSearchesStore from './stores/recent_searches_store';
...@@ -44,7 +45,7 @@ class FilteredSearchManager { ...@@ -44,7 +45,7 @@ class FilteredSearchManager {
.catch((error) => { .catch((error) => {
if (error.name === 'RecentSearchesServiceError') return undefined; if (error.name === 'RecentSearchesServiceError') return undefined;
// eslint-disable-next-line no-new // eslint-disable-next-line no-new
new window.Flash('An error occurred while parsing recent searches'); new Flash('An error occurred while parsing recent searches');
// Gracefully fail to empty array // Gracefully fail to empty array
return []; return [];
}) })
......
import AjaxCache from '../lib/utils/ajax_cache'; import AjaxCache from '../lib/utils/ajax_cache';
import '../flash'; /* global Flash */ import Flash from '../flash';
import FilteredSearchContainer from './container'; import FilteredSearchContainer from './container';
import UsersCache from '../lib/utils/users_cache'; import UsersCache from '../lib/utils/users_cache';
......
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, one-var, one-var-declaration-per-line, no-param-reassign, quotes, quote-props, prefer-template, comma-dangle, max-len */ import _ from 'underscore';
window.Flash = (function() { const hideFlash = (flashEl, fadeTransition = true) => {
var hideFlash; if (fadeTransition) {
Object.assign(flashEl.style, {
transition: 'opacity .3s',
opacity: '0',
});
}
hideFlash = function() { flashEl.addEventListener('transitionend', () => {
return $(this).fadeOut(); flashEl.remove();
}; }, {
once: true,
passive: true,
});
/** if (!fadeTransition) flashEl.dispatchEvent(new Event('transitionend'));
* Flash banner supports different types of Flash configurations };
* along with ability to provide actionConfig which can be used to show
* additional action or link on banner next to message
*
* @param {String} message Flash message
* @param {String} type Type of Flash, it can be `notice` or `alert` (default)
* @param {Object} parent Reference to Parent element under which Flash needs to appear
* @param {Object} actionConfig Map of config to show action on banner
* @param {String} href URL to which action link should point (default '#')
* @param {String} title Title of action
* @param {Function} clickHandler Method to call when action is clicked on
*/
function Flash(message, type, parent, actionConfig) {
var flash, textDiv, actionLink;
if (type == null) {
type = 'alert';
}
if (parent == null) {
parent = null;
}
if (parent) {
const $parent = $(parent);
this.flashContainer = $parent.find('.flash-container');
} else {
this.flashContainer = $('.flash-container-page');
}
this.flashContainer.html('');
flash = $('<div/>', {
"class": "flash-" + type
});
flash.on('click', hideFlash);
textDiv = $('<div/>', {
"class": 'flash-text',
text: message
});
textDiv.appendTo(flash);
if (actionConfig) { const createAction = config => `
const actionLinkConfig = { <a
class: 'flash-action', href="${config.href || '#'}"
href: actionConfig.href || '#', class="flash-action"
text: actionConfig.title ${config.href ? '' : 'role="button"'}
}; >
${_.escape(config.title)}
</a>
`;
if (!actionConfig.href) { const createFlashEl = (message, type, isInContentWrapper = false) => `
actionLinkConfig.role = 'button'; <div
} class="flash-${type}"
>
<div
class="flash-text ${isInContentWrapper ? 'container-fluid container-limited' : ''}"
>
${_.escape(message)}
</div>
</div>
`;
actionLink = $('<a/>', actionLinkConfig); /*
* Flash banner supports different types of Flash configurations
* along with ability to provide actionConfig which can be used to show
* additional action or link on banner next to message
*
* @param {String} message Flash message text
* @param {String} type Type of Flash, it can be `notice` or `alert` (default)
* @param {Object} parent Reference to parent element under which Flash needs to appear
* @param {Object} actonConfig Map of config to show action on banner
* @param {String} href URL to which action config should point to (default: '#')
* @param {String} title Title of action
* @param {Function} clickHandler Method to call when action is clicked on
* @param {Boolean} fadeTransition Boolean to determine whether to fade the alert out
*/
const createFlash = function createFlash(
message,
type = 'alert',
parent = document,
actionConfig = null,
fadeTransition = true,
) {
const flashContainer = parent.querySelector('.flash-container');
actionLink.appendTo(flash); if (!flashContainer) return null;
this.flashContainer.on('click', '.flash-action', actionConfig.clickHandler);
} const isInContentWrapper = flashContainer.parentNode.classList.contains('content-wrapper');
if (this.flashContainer.parent().hasClass('content-wrapper')) {
textDiv.addClass('container-fluid container-limited'); flashContainer.innerHTML = createFlashEl(message, type, isInContentWrapper);
const flashEl = flashContainer.querySelector(`.flash-${type}`);
flashEl.addEventListener('click', () => hideFlash(flashEl, fadeTransition));
if (actionConfig) {
flashEl.innerHTML += createAction(actionConfig);
if (actionConfig.clickHandler) {
flashEl.querySelector('.flash-action').addEventListener('click', e => actionConfig.clickHandler(e));
} }
flash.appendTo(this.flashContainer);
this.flashContainer.show();
} }
Flash.prototype.destroy = function() { flashContainer.style.display = 'block';
this.flashContainer.html('');
}; return flashContainer;
};
return Flash; export {
})(); createFlash as default,
createFlashEl,
createAction,
hideFlash,
};
window.Flash = createFlash;
...@@ -46,6 +46,10 @@ class GeoNodeStatus { ...@@ -46,6 +46,10 @@ class GeoNodeStatus {
$closestStatus.toggleClass('hidden'); $closestStatus.toggleClass('hidden');
} }
static formatCountAndPercentage(count, total, percentage) {
return `${gl.text.addDelimiter(count)}/${gl.text.addDelimiter(total)} (${percentage})`;
}
getStatus() { getStatus() {
$.getJSON(this.endpoint, (status) => { $.getJSON(this.endpoint, (status) => {
this.setStatusIcon(status.healthy); this.setStatusIcon(status.healthy);
...@@ -62,10 +66,28 @@ class GeoNodeStatus { ...@@ -62,10 +66,28 @@ class GeoNodeStatus {
this.$dbReplicationLag.text('UNKNOWN'); this.$dbReplicationLag.text('UNKNOWN');
} }
this.$repositoriesSynced.text(`${status.repositories_synced_count}/${status.repositories_count} (${status.repositories_synced_in_percentage})`); const repoText = GeoNodeStatus.formatCountAndPercentage(
this.$repositoriesFailed.text(status.repositories_failed_count); status.repositories_synced_count,
this.$lfsObjectsSynced.text(`${status.lfs_objects_synced_count}/${status.lfs_objects_count} (${status.lfs_objects_synced_in_percentage})`); status.repositories_count,
this.$attachmentsSynced.text(`${status.attachments_synced_count}/${status.attachments_count} (${status.attachments_synced_in_percentage})`); status.repositories_synced_in_percentage);
const repoFailedText = gl.text.addDelimiter(status.repositories_failed_count);
const lfsText = GeoNodeStatus.formatCountAndPercentage(
status.lfs_objects_synced_count,
status.lfs_objects_count,
status.lfs_objects_synced_in_percentage);
const attachmentText = GeoNodeStatus.formatCountAndPercentage(
status.attachments_synced_count,
status.attachments_count,
status.attachments_synced_in_percentage);
this.$repositoriesSynced.text(repoText);
this.$repositoriesFailed.text(repoFailedText);
this.$lfsObjectsSynced.text(lfsText);
this.$attachmentsSynced.text(attachmentText);
const eventDate = gl.utils.formatDate(new Date(status.last_event_date)); const eventDate = gl.utils.formatDate(new Date(status.last_event_date));
const cursorDate = gl.utils.formatDate(new Date(status.cursor_last_event_date)); const cursorDate = gl.utils.formatDate(new Date(status.cursor_last_event_date));
this.$lastEventSeen.text(`${status.last_event_id} (${eventDate})`); this.$lastEventSeen.text(`${status.last_event_id} (${eventDate})`);
......
/* global Flash */
import Vue from 'vue'; import Vue from 'vue';
import Flash from '../flash';
import GroupFilterableList from './groups_filterable_list'; import GroupFilterableList from './groups_filterable_list';
import GroupsComponent from './components/groups.vue'; import GroupsComponent from './components/groups.vue';
import GroupFolder from './components/group_folder.vue'; import GroupFolder from './components/group_folder.vue';
......
...@@ -2,6 +2,8 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -2,6 +2,8 @@ document.addEventListener('DOMContentLoaded', () => {
const showGroupLink = () => { const showGroupLink = () => {
const $cnLink = $('.cn-link'); const $cnLink = $('.cn-link');
const $filterLink = $('.filter-link'); const $filterLink = $('.filter-link');
if (!$cnLink.length || !$filterLink.length) return;
const $checkedSync = $('input[name="sync_method"]:checked').val() === 'group'; const $checkedSync = $('input[name="sync_method"]:checked').val() === 'group';
$cnLink.toggle($checkedSync); $cnLink.toggle($checkedSync);
......
/* global Flash */ import Flash from '../flash';
export default class IntegrationSettingsForm { export default class IntegrationSettingsForm {
constructor(formSelector) { constructor(formSelector) {
...@@ -102,7 +102,7 @@ export default class IntegrationSettingsForm { ...@@ -102,7 +102,7 @@ export default class IntegrationSettingsForm {
}) })
.done((res) => { .done((res) => {
if (res.error) { if (res.error) {
new Flash(`${res.message} ${res.service_response}`, null, null, { new Flash(`${res.message} ${res.service_response}`, 'alert', document, {
title: 'Save anyway', title: 'Save anyway',
clickHandler: (e) => { clickHandler: (e) => {
e.preventDefault(); e.preventDefault();
......
<script> <script>
/* global Flash */
/* /*
`rawReferences` are separated by spaces. `rawReferences` are separated by spaces.
Given `abc 123 zxc`, `rawReferences = ['abc', '123', 'zxc']` Given `abc 123 zxc`, `rawReferences = ['abc', '123', 'zxc']`
...@@ -26,6 +24,7 @@ Your caret can stop touching a `rawReference` can happen in a variety of ways: ...@@ -26,6 +24,7 @@ Your caret can stop touching a `rawReference` can happen in a variety of ways:
*/ */
import Flash from '../../../flash';
import eventHub from '../event_hub'; import eventHub from '../event_hub';
import RelatedIssuesBlock from './related_issues_block.vue'; import RelatedIssuesBlock from './related_issues_block.vue';
import RelatedIssuesStore from '../stores/related_issues_store'; import RelatedIssuesStore from '../stores/related_issues_store';
......
/* eslint-disable comma-dangle, quotes, consistent-return, func-names, array-callback-return, space-before-function-paren, prefer-arrow-callback, max-len, no-unused-expressions, no-sequences, no-underscore-dangle, no-unused-vars, no-param-reassign */ /* eslint-disable comma-dangle, quotes, consistent-return, func-names, array-callback-return, space-before-function-paren, prefer-arrow-callback, max-len, no-unused-expressions, no-sequences, no-underscore-dangle, no-unused-vars, no-param-reassign */
/* global IssuableIndex */ /* global IssuableIndex */
/* global Flash */
import _ from 'underscore'; import _ from 'underscore';
import Flash from './flash';
export default { export default {
init({ container, form, issues, prefixId } = {}) { init({ container, form, issues, prefixId } = {}) {
......
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, one-var, no-underscore-dangle, one-var-declaration-per-line, object-shorthand, no-unused-vars, no-new, comma-dangle, consistent-return, quotes, dot-notation, quote-props, prefer-arrow-callback, max-len */ /* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, one-var, no-underscore-dangle, one-var-declaration-per-line, object-shorthand, no-unused-vars, no-new, comma-dangle, consistent-return, quotes, dot-notation, quote-props, prefer-arrow-callback, max-len */
/* global Flash */
import 'vendor/jquery.waitforimages'; import 'vendor/jquery.waitforimages';
import '~/lib/utils/text_utility'; import '~/lib/utils/text_utility';
import './flash'; import Flash from './flash';
import TaskList from './task_list'; import TaskList from './task_list';
import CreateMergeRequestDropdown from './create_merge_request_dropdown'; import CreateMergeRequestDropdown from './create_merge_request_dropdown';
import IssuablesHelper from './helpers/issuables_helper'; import IssuablesHelper from './helpers/issuables_helper';
......
<script> <script>
/* global Flash */
import Visibility from 'visibilityjs'; import Visibility from 'visibilityjs';
import Poll from '../../lib/utils/poll'; import Poll from '../../lib/utils/poll';
import eventHub from '../event_hub'; import eventHub from '../event_hub';
...@@ -153,7 +152,7 @@ export default { ...@@ -153,7 +152,7 @@ export default {
}) })
.catch(() => { .catch(() => {
eventHub.$emit('close.form'); eventHub.$emit('close.form');
return new Flash('Error updating issue'); window.Flash('Error updating issue');
}); });
}, },
deleteIssuable() { deleteIssuable() {
...@@ -167,7 +166,7 @@ export default { ...@@ -167,7 +166,7 @@ export default {
}) })
.catch(() => { .catch(() => {
eventHub.$emit('close.form'); eventHub.$emit('close.form');
return new Flash('Error deleting issue'); window.Flash('Error deleting issue');
}); });
}, },
}, },
......
<script> <script>
/* global Flash */
import updateMixin from '../../mixins/update'; import updateMixin from '../../mixins/update';
import markdownField from '../../../vue_shared/components/markdown/field.vue'; import markdownField from '../../../vue_shared/components/markdown/field.vue';
......
/* global Flash */
import Vue from 'vue'; import Vue from 'vue';
import JobMediator from './job_details_mediator'; import JobMediator from './job_details_mediator';
import jobHeader from './components/header.vue'; import jobHeader from './components/header.vue';
......
/* global Flash */
/* global Build */ /* global Build */
import Visibility from 'visibilityjs'; import Visibility from 'visibilityjs';
import Flash from '../flash';
import Poll from '../lib/utils/poll'; import Poll from '../lib/utils/poll';
import JobStore from './stores/job_store'; import JobStore from './stores/job_store';
import JobService from './services/job_service'; import JobService from './services/job_service';
......
/* eslint-disable comma-dangle, class-methods-use-this, no-underscore-dangle, no-param-reassign, no-unused-vars, consistent-return, func-names, space-before-function-paren, max-len */ /* eslint-disable comma-dangle, class-methods-use-this, no-underscore-dangle, no-param-reassign, no-unused-vars, consistent-return, func-names, space-before-function-paren, max-len */
/* global Flash */
/* global Sortable */ /* global Sortable */
import Flash from './flash';
((global) => { ((global) => {
class LabelManager { class LabelManager {
constructor({ togglePriorityButton, prioritizedLabels, otherLabels } = {}) { constructor({ togglePriorityButton, prioritizedLabels, otherLabels } = {}) {
......
/* eslint-disable func-names, space-before-function-paren, no-var, quotes, consistent-return, prefer-arrow-callback, comma-dangle, object-shorthand, no-new, max-len, no-multi-spaces, import/newline-after-import, import/first */ /* eslint-disable func-names, space-before-function-paren, no-var, quotes, consistent-return, prefer-arrow-callback, comma-dangle, object-shorthand, no-new, max-len, no-multi-spaces, import/newline-after-import, import/first */
/* global Flash */
/* global ConfirmDangerModal */ /* global ConfirmDangerModal */
/* global Aside */ /* global Aside */
...@@ -57,6 +56,7 @@ import './u2f/util'; ...@@ -57,6 +56,7 @@ import './u2f/util';
import './activities'; import './activities';
import './admin'; import './admin';
import './api'; import './api';
import './ajax_loading_spinner';
import './aside'; import './aside';
import './autosave'; import './autosave';
import loadAwardsHandler from './awards_handler'; import loadAwardsHandler from './awards_handler';
...@@ -76,7 +76,7 @@ import './diff'; ...@@ -76,7 +76,7 @@ import './diff';
import './dropzone_input'; import './dropzone_input';
import './due_date_select'; import './due_date_select';
import './files_comment_button'; import './files_comment_button';
import './flash'; import Flash from './flash';
import './gl_dropdown'; import './gl_dropdown';
import './gl_field_error'; import './gl_field_error';
import './gl_field_errors'; import './gl_field_errors';
...@@ -178,7 +178,6 @@ $(function () { ...@@ -178,7 +178,6 @@ $(function () {
var $document = $(document); var $document = $(document);
var $window = $(window); var $window = $(window);
var $sidebarGutterToggle = $('.js-sidebar-toggle'); var $sidebarGutterToggle = $('.js-sidebar-toggle');
var $flash = $('.flash-container');
var bootstrapBreakpoint = bp.getBreakpointSize(); var bootstrapBreakpoint = bp.getBreakpointSize();
var fitSidebarForSize; var fitSidebarForSize;
...@@ -263,13 +262,6 @@ $(function () { ...@@ -263,13 +262,6 @@ $(function () {
// Form submitter // Form submitter
}); });
gl.utils.localTimeAgo($('abbr.timeago, .js-timeago'), true); gl.utils.localTimeAgo($('abbr.timeago, .js-timeago'), true);
// Flash
if ($flash.length > 0) {
$flash.click(function () {
return $(this).fadeOut();
});
$flash.show();
}
// Disable form buttons while a form is submitting // Disable form buttons while a form is submitting
$body.on('ajax:complete, ajax:beforeSend, submit', 'form', function (e) { $body.on('ajax:complete, ajax:beforeSend, submit', 'form', function (e) {
var buttons; var buttons;
......
/* eslint-disable class-methods-use-this, promise/catch-or-return */ /* eslint-disable class-methods-use-this, promise/catch-or-return */
/* eslint-disable no-new */ /* eslint-disable no-new */
/* global Flash */ import Flash from './flash';
(() => { (() => {
window.gl = window.gl || {}; window.gl = window.gl || {};
......
/* eslint-disable comma-dangle, quote-props, no-useless-computed-key, object-shorthand, no-new, no-param-reassign, max-len */ /* eslint-disable comma-dangle, quote-props, no-useless-computed-key, object-shorthand, no-new, no-param-reassign, max-len */
/* global ace */ /* global ace */
/* global Flash */
import Vue from 'vue'; import Vue from 'vue';
import Flash from '../../flash';
((global) => { ((global) => {
global.mergeConflicts = global.mergeConflicts || {}; global.mergeConflicts = global.mergeConflicts || {};
......
/* eslint-disable new-cap, comma-dangle, no-new */ /* eslint-disable new-cap, comma-dangle, no-new */
/* global Flash */
import Vue from 'vue'; import Vue from 'vue';
import Flash from '../flash';
import initIssuableSidebar from '../init_issuable_sidebar'; import initIssuableSidebar from '../init_issuable_sidebar';
import './merge_conflict_store'; import './merge_conflict_store';
import './merge_conflict_service'; import './merge_conflict_service';
......
/* eslint-disable no-new, class-methods-use-this */ /* eslint-disable no-new, class-methods-use-this */
/* global Flash */
/* global notes */ /* global notes */
import Cookies from 'js-cookie'; import Cookies from 'js-cookie';
import './flash'; import Flash from './flash';
import BlobForkSuggestion from './blob/blob_fork_suggestion'; import BlobForkSuggestion from './blob/blob_fork_suggestion';
import initChangesDropdown from './init_changes_dropdown'; import initChangesDropdown from './init_changes_dropdown';
import bp from './breakpoints'; import bp from './breakpoints';
......
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-use-before-define, camelcase, quotes, object-shorthand, no-shadow, no-unused-vars, comma-dangle, no-var, prefer-template, no-underscore-dangle, consistent-return, one-var, one-var-declaration-per-line, default-case, prefer-arrow-callback, max-len */ /* eslint-disable func-names, space-before-function-paren, wrap-iife, no-use-before-define, camelcase, quotes, object-shorthand, no-shadow, no-unused-vars, comma-dangle, no-var, prefer-template, no-underscore-dangle, consistent-return, one-var, one-var-declaration-per-line, default-case, prefer-arrow-callback, max-len */
/* global Flash */
/* global Sortable */ /* global Sortable */
import Flash from './flash';
(function() { (function() {
this.Milestone = (function() { this.Milestone = (function() {
function Milestone() { function Milestone() {
......
/* eslint-disable no-new */ /* eslint-disable no-new */
/* global Flash */ import Flash from './flash';
/** /**
* In each pipelines table we have a mini pipeline graph for each pipeline. * In each pipelines table we have a mini pipeline graph for each pipeline.
......
/* global Flash */ import Flash from '../flash';
import AUTH_METHOD from './constants'; import AUTH_METHOD from './constants';
import { backOff } from '../lib/utils/common_utils'; import { backOff } from '../lib/utils/common_utils';
......
<script> <script>
/* global Flash */
import _ from 'underscore'; import _ from 'underscore';
import Flash from '../../flash';
import MonitoringService from '../services/monitoring_service'; import MonitoringService from '../services/monitoring_service';
import GraphGroup from './graph_group.vue'; import GraphGroup from './graph_group.vue';
import Graph from './graph.vue'; import Graph from './graph.vue';
......
...@@ -5,7 +5,6 @@ default-case, prefer-template, consistent-return, no-alert, no-return-assign, ...@@ -5,7 +5,6 @@ default-case, prefer-template, consistent-return, no-alert, no-return-assign,
no-param-reassign, prefer-arrow-callback, no-else-return, comma-dangle, no-new, no-param-reassign, prefer-arrow-callback, no-else-return, comma-dangle, no-new,
brace-style, no-lonely-if, vars-on-top, no-unused-vars, no-sequences, no-shadow, brace-style, no-lonely-if, vars-on-top, no-unused-vars, no-sequences, no-shadow,
newline-per-chained-call, no-useless-escape, class-methods-use-this */ newline-per-chained-call, no-useless-escape, class-methods-use-this */
/* global Flash */
/* global Autosave */ /* global Autosave */
/* global ResolveService */ /* global ResolveService */
/* global mrRefreshWidgetUrl */ /* global mrRefreshWidgetUrl */
...@@ -18,6 +17,7 @@ import Dropzone from 'dropzone'; ...@@ -18,6 +17,7 @@ import Dropzone from 'dropzone';
import 'vendor/jquery.caret'; // required by jquery.atwho import 'vendor/jquery.caret'; // required by jquery.atwho
import 'vendor/jquery.atwho'; import 'vendor/jquery.atwho';
import AjaxCache from '~/lib/utils/ajax_cache'; import AjaxCache from '~/lib/utils/ajax_cache';
import Flash from './flash';
import CommentTypeToggle from './comment_type_toggle'; import CommentTypeToggle from './comment_type_toggle';
import loadAwardsHandler from './awards_handler'; import loadAwardsHandler from './awards_handler';
import './autosave'; import './autosave';
...@@ -354,7 +354,7 @@ export default class Notes { ...@@ -354,7 +354,7 @@ export default class Notes {
Object.keys(noteEntity.commands_changes).length > 0) { Object.keys(noteEntity.commands_changes).length > 0) {
$notesList.find('.system-note.being-posted').remove(); $notesList.find('.system-note.being-posted').remove();
} }
this.addFlash(noteEntity.errors.commands_only, 'notice', this.parentTimeline); this.addFlash(noteEntity.errors.commands_only, 'notice', this.parentTimeline.get(0));
this.refresh(); this.refresh();
} }
return; return;
...@@ -593,7 +593,7 @@ export default class Notes { ...@@ -593,7 +593,7 @@ export default class Notes {
} else if ($form.hasClass('js-discussion-note-form')) { } else if ($form.hasClass('js-discussion-note-form')) {
formParentTimeline = $form.closest('.discussion-notes').find('.notes'); formParentTimeline = $form.closest('.discussion-notes').find('.notes');
} }
return this.addFlash('Your comment could not be submitted! Please check your network connection and try again.', 'alert', formParentTimeline); return this.addFlash('Your comment could not be submitted! Please check your network connection and try again.', 'alert', formParentTimeline.get(0));
} }
updateNoteError($parentTimeline) { updateNoteError($parentTimeline) {
...@@ -1213,13 +1213,13 @@ export default class Notes { ...@@ -1213,13 +1213,13 @@ export default class Notes {
} }
addFlash(...flashParams) { addFlash(...flashParams) {
this.flashInstance = new Flash(...flashParams); this.flashContainer = new Flash(...flashParams);
} }
clearFlash() { clearFlash() {
if (this.flashInstance && this.flashInstance.flashContainer) { if (this.flashContainer) {
this.flashInstance.flashContainer.hide(); this.flashContainer.style.display = 'none';
this.flashInstance = null; this.flashContainer = null;
} }
} }
......
<script> <script>
/* global Flash, Autosave */ /* global Autosave */
import { mapActions, mapGetters } from 'vuex'; import { mapActions, mapGetters } from 'vuex';
import _ from 'underscore'; import _ from 'underscore';
import autosize from 'vendor/autosize'; import autosize from 'vendor/autosize';
import Flash from '../../flash';
import '../../autosave'; import '../../autosave';
import TaskList from '../../task_list'; import TaskList from '../../task_list';
import * as constants from '../constants'; import * as constants from '../constants';
...@@ -145,7 +146,7 @@ ...@@ -145,7 +146,7 @@
Flash( Flash(
'Something went wrong while adding your comment. Please try again.', 'Something went wrong while adding your comment. Please try again.',
'alert', 'alert',
$(this.$refs.commentForm), this.$refs.commentForm,
); );
} }
} else { } else {
...@@ -160,7 +161,7 @@ ...@@ -160,7 +161,7 @@
this.isSubmitting = false; this.isSubmitting = false;
this.discard(false); this.discard(false);
const msg = 'Your comment could not be submitted! Please check your network connection and try again.'; const msg = 'Your comment could not be submitted! Please check your network connection and try again.';
Flash(msg, 'alert', $(this.$el)); Flash(msg, 'alert', this.$el);
this.note = noteData.data.note.note; // Restore textarea content. this.note = noteData.data.note.note; // Restore textarea content.
this.removePlaceholderNotes(); this.removePlaceholderNotes();
}); });
......
<script> <script>
/* global Flash */
import { mapActions, mapGetters } from 'vuex'; import { mapActions, mapGetters } from 'vuex';
import Flash from '../../flash';
import { SYSTEM_NOTE } from '../constants'; import { SYSTEM_NOTE } from '../constants';
import issueNote from './issue_note.vue'; import issueNote from './issue_note.vue';
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue'; import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
...@@ -133,7 +133,7 @@ ...@@ -133,7 +133,7 @@
this.isReplying = true; this.isReplying = true;
this.$nextTick(() => { this.$nextTick(() => {
const msg = 'Your comment could not be submitted! Please check your network connection and try again.'; const msg = 'Your comment could not be submitted! Please check your network connection and try again.';
Flash(msg, 'alert', $(this.$el)); Flash(msg, 'alert', this.$el);
this.$refs.noteForm.note = noteText; this.$refs.noteForm.note = noteText;
callback(err); callback(err);
}); });
......
<script> <script>
/* global Flash */
import { mapGetters, mapActions } from 'vuex'; import { mapGetters, mapActions } from 'vuex';
import Flash from '../../flash';
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue'; import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
import issueNoteHeader from './issue_note_header.vue'; import issueNoteHeader from './issue_note_header.vue';
import issueNoteActions from './issue_note_actions.vue'; import issueNoteActions from './issue_note_actions.vue';
...@@ -101,7 +100,7 @@ ...@@ -101,7 +100,7 @@
this.isEditing = true; this.isEditing = true;
this.$nextTick(() => { this.$nextTick(() => {
const msg = 'Something went wrong while editing your comment. Please try again.'; const msg = 'Something went wrong while editing your comment. Please try again.';
Flash(msg, 'alert', $(this.$el)); Flash(msg, 'alert', this.$el);
this.recoverNoteContent(noteText); this.recoverNoteContent(noteText);
callback(); callback();
}); });
......
<script> <script>
/* global Flash */
import { mapActions, mapGetters } from 'vuex'; import { mapActions, mapGetters } from 'vuex';
import emojiSmiling from 'icons/_emoji_slightly_smiling_face.svg'; import emojiSmiling from 'icons/_emoji_slightly_smiling_face.svg';
import emojiSmile from 'icons/_emoji_smile.svg'; import emojiSmile from 'icons/_emoji_smile.svg';
import emojiSmiley from 'icons/_emoji_smiley.svg'; import emojiSmiley from 'icons/_emoji_smiley.svg';
import Flash from '../../flash';
import { glEmojiTag } from '../../emoji'; import { glEmojiTag } from '../../emoji';
import tooltip from '../../vue_shared/directives/tooltip'; import tooltip from '../../vue_shared/directives/tooltip';
......
<script> <script>
/* global Flash */
import { mapGetters, mapActions } from 'vuex'; import { mapGetters, mapActions } from 'vuex';
import Flash from '../../flash';
import store from '../stores/'; import store from '../stores/';
import * as constants from '../constants'; import * as constants from '../constants';
import issueNote from './issue_note.vue'; import issueNote from './issue_note.vue';
......
/* global Flash */
import Visibility from 'visibilityjs'; import Visibility from 'visibilityjs';
import Flash from '../../flash';
import Poll from '../../lib/utils/poll'; import Poll from '../../lib/utils/poll';
import * as types from './mutation_types'; import * as types from './mutation_types';
import * as utils from './utils'; import * as utils from './utils';
...@@ -99,7 +99,7 @@ export const saveNote = ({ commit, dispatch }, noteData) => { ...@@ -99,7 +99,7 @@ export const saveNote = ({ commit, dispatch }, noteData) => {
eTagPoll.makeRequest(); eTagPoll.makeRequest();
$('.js-gfm-input').trigger('clear-commands-cache.atwho'); $('.js-gfm-input').trigger('clear-commands-cache.atwho');
Flash('Commands applied', 'notice', $(noteData.flashContainer)); Flash('Commands applied', 'notice', noteData.flashContainer);
} }
if (commandsChanges) { if (commandsChanges) {
...@@ -114,8 +114,8 @@ export const saveNote = ({ commit, dispatch }, noteData) => { ...@@ -114,8 +114,8 @@ export const saveNote = ({ commit, dispatch }, noteData) => {
.catch(() => { .catch(() => {
Flash( Flash(
'Something went wrong while adding your award. Please try again.', 'Something went wrong while adding your award. Please try again.',
null, 'alert',
$(noteData.flashContainer), noteData.flashContainer,
); );
}); });
} }
...@@ -126,7 +126,7 @@ export const saveNote = ({ commit, dispatch }, noteData) => { ...@@ -126,7 +126,7 @@ export const saveNote = ({ commit, dispatch }, noteData) => {
} }
if (errors && errors.commands_only) { if (errors && errors.commands_only) {
Flash(errors.commands_only, 'notice', $(noteData.flashContainer)); Flash(errors.commands_only, 'notice', noteData.flashContainer);
} }
commit(types.REMOVE_PLACEHOLDER_NOTES); commit(types.REMOVE_PLACEHOLDER_NOTES);
......
/* eslint-disable func-names, space-before-function-paren, wrap-iife, one-var, no-var, one-var-declaration-per-line, no-unused-vars, consistent-return, prefer-arrow-callback, no-else-return, max-len */ /* eslint-disable func-names, space-before-function-paren, wrap-iife, one-var, no-var, one-var-declaration-per-line, no-unused-vars, consistent-return, prefer-arrow-callback, no-else-return, max-len */
/* global Flash */ import Flash from './flash';
(function() { (function() {
this.NotificationsDropdown = (function() { this.NotificationsDropdown = (function() {
......
<script> <script>
/* global Flash */
import '~/flash';
import playIconSvg from 'icons/_icon_play.svg'; import playIconSvg from 'icons/_icon_play.svg';
import eventHub from '../event_hub'; import eventHub from '../event_hub';
import loadingIcon from '../../vue_shared/components/loading_icon.vue'; import loadingIcon from '../../vue_shared/components/loading_icon.vue';
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
* 4. Commit widget * 4. Commit widget
*/ */
/* global Flash */ import Flash from '../../flash';
import { borderlessStatusIconEntityMap } from '../../vue_shared/ci_status_icons'; import { borderlessStatusIconEntityMap } from '../../vue_shared/ci_status_icons';
import loadingIcon from '../../vue_shared/components/loading_icon.vue'; import loadingIcon from '../../vue_shared/components/loading_icon.vue';
import tooltip from '../../vue_shared/directives/tooltip'; import tooltip from '../../vue_shared/directives/tooltip';
......
/* global Flash */
import '~/flash';
import Visibility from 'visibilityjs'; import Visibility from 'visibilityjs';
import Flash from '../../flash';
import Poll from '../../lib/utils/poll'; import Poll from '../../lib/utils/poll';
import emptyState from '../components/empty_state.vue'; import emptyState from '../components/empty_state.vue';
import errorState from '../components/error_state.vue'; import errorState from '../components/error_state.vue';
......
/* global Flash */
import Vue from 'vue'; import Vue from 'vue';
import Flash from '../flash';
import PipelinesMediator from './pipeline_details_mediatior'; import PipelinesMediator from './pipeline_details_mediatior';
import pipelineGraph from './components/graph/graph_component.vue'; import pipelineGraph from './components/graph/graph_component.vue';
import pipelineHeader from './components/header_component.vue'; import pipelineHeader from './components/header_component.vue';
......
/* global Flash */
import Visibility from 'visibilityjs'; import Visibility from 'visibilityjs';
import Flash from '../flash';
import Poll from '../lib/utils/poll'; import Poll from '../lib/utils/poll';
import PipelineStore from './stores/pipeline_store'; import PipelineStore from './stores/pipeline_store';
import PipelineService from './services/pipeline_service'; import PipelineService from './services/pipeline_service';
......
/* eslint-disable comma-dangle, no-unused-vars, class-methods-use-this, quotes, consistent-return, func-names, prefer-arrow-callback, space-before-function-paren, max-len */ /* eslint-disable comma-dangle, no-unused-vars, class-methods-use-this, quotes, consistent-return, func-names, prefer-arrow-callback, space-before-function-paren, max-len */
/* global Flash */ import Flash from '../flash';
import { getPagePath } from '../lib/utils/common_utils'; import { getPagePath } from '../lib/utils/common_utils';
((global) => { ((global) => {
......
<script> <script>
/* global Flash */ import Flash from '../../../flash';
import serviceDeskSetting from './service_desk_setting.vue'; import serviceDeskSetting from './service_desk_setting.vue';
import ServiceDeskStore from '../stores/service_desk_store'; import ServiceDeskStore from '../stores/service_desk_store';
import ServiceDeskService from '../services/service_desk_service'; import ServiceDeskService from '../services/service_desk_service';
...@@ -44,7 +43,7 @@ export default { ...@@ -44,7 +43,7 @@ export default {
methods: { methods: {
fetchIncomingEmail() { fetchIncomingEmail() {
if (this.flash) { if (this.flash) {
this.flash.destroy(); this.flash.innerHTML = '';
} }
this.service.fetchIncomingEmail() this.service.fetchIncomingEmail()
......
/* eslint-disable no-new */ /* eslint-disable no-new */
/* global Flash */ import Flash from '../flash';
import ProtectedBranchAccessDropdown from './protected_branch_access_dropdown'; import ProtectedBranchAccessDropdown from './protected_branch_access_dropdown';
export default class ProtectedBranchEdit { export default class ProtectedBranchEdit {
...@@ -57,7 +56,7 @@ export default class ProtectedBranchEdit { ...@@ -57,7 +56,7 @@ export default class ProtectedBranchEdit {
}, },
}, },
error() { error() {
new Flash('Failed to update branch!', null, $('.js-protected-branches-list')); new Flash('Failed to update branch!', 'alert', document.querySelector('.js-protected-branches-list'));
}, },
}).always(() => { }).always(() => {
this.$allowedToMergeDropdown.enable(); this.$allowedToMergeDropdown.enable();
......
/* eslint-disable no-new */ /* eslint-disable no-new */
/* global Flash */ import Flash from '../flash';
import ProtectedTagAccessDropdown from './protected_tag_access_dropdown'; import ProtectedTagAccessDropdown from './protected_tag_access_dropdown';
export default class ProtectedTagEdit { export default class ProtectedTagEdit {
...@@ -43,7 +42,7 @@ export default class ProtectedTagEdit { ...@@ -43,7 +42,7 @@ export default class ProtectedTagEdit {
}, },
}, },
error() { error() {
new Flash('Failed to update tag!', null, $('.js-protected-tags-list')); new Flash('Failed to update tag!', 'alert', document.querySelector('.js-protected-tags-list'));
}, },
}).always(() => { }).always(() => {
this.$allowedToCreateDropdownButton.enable(); this.$allowedToCreateDropdownButton.enable();
......
<script> <script>
/* global Flash */ import Flash from '../../flash';
import Store from '../stores/repo_store'; import Store from '../stores/repo_store';
import RepoMixin from '../mixins/repo_mixin'; import RepoMixin from '../mixins/repo_mixin';
import Service from '../services/repo_service'; import Service from '../services/repo_service';
......
/* global Flash */
import Service from '../services/repo_service'; import Service from '../services/repo_service';
import Store from '../stores/repo_store'; import Store from '../stores/repo_store';
import '../../flash'; import Flash from '../../flash';
const RepoHelper = { const RepoHelper = {
monacoInstance: null, monacoInstance: null,
......
/* global Flash */
import axios from 'axios'; import axios from 'axios';
import Store from '../stores/repo_store'; import Store from '../stores/repo_store';
import Api from '../../api'; import Api from '../../api';
......
/* global Flash */
import Helper from '../helpers/repo_helper'; import Helper from '../helpers/repo_helper';
import Service from '../services/repo_service'; import Service from '../services/repo_service';
......
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, one-var, one-var-declaration-per-line, object-shorthand, prefer-arrow-callback, comma-dangle, prefer-template, quotes, no-else-return, max-len */ /* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, one-var, one-var-declaration-per-line, object-shorthand, prefer-arrow-callback, comma-dangle, prefer-template, quotes, no-else-return, max-len */
/* global Flash */ import Flash from './flash';
import Api from './api'; import Api from './api';
(function() { (function() {
......
/* global Flash */ import Flash from '../../../flash';
import AssigneeTitle from './assignee_title'; import AssigneeTitle from './assignee_title';
import Assignees from './assignees'; import Assignees from './assignees';
......
<script> <script>
/* global Flash */ import Flash from '../../../flash';
import editForm from './edit_form.vue'; import editForm from './edit_form.vue';
export default { export default {
......
/* global Flash */
function isValidProjectId(id) { function isValidProjectId(id) {
return id > 0; return id > 0;
} }
...@@ -38,7 +36,7 @@ class SidebarMoveIssue { ...@@ -38,7 +36,7 @@ class SidebarMoveIssue {
data: (searchTerm, callback) => { data: (searchTerm, callback) => {
this.mediator.fetchAutocompleteProjects(searchTerm) this.mediator.fetchAutocompleteProjects(searchTerm)
.then(callback) .then(callback)
.catch(() => new Flash('An error occurred while fetching projects autocomplete.')); .catch(() => new window.Flash('An error occurred while fetching projects autocomplete.'));
}, },
renderRow: project => ` renderRow: project => `
<li> <li>
...@@ -73,7 +71,7 @@ class SidebarMoveIssue { ...@@ -73,7 +71,7 @@ class SidebarMoveIssue {
this.mediator.moveIssue() this.mediator.moveIssue()
.catch(() => { .catch(() => {
Flash('An error occurred while moving the issue.'); window.Flash('An error occurred while moving the issue.');
this.$confirmButton this.$confirmButton
.enable() .enable()
.removeClass('is-loading'); .removeClass('is-loading');
......
/* global Flash */ import Flash from '../flash';
import Service from './services/sidebar_service'; import Service from './services/sidebar_service';
import Store from './stores/sidebar_store'; import Store from './stores/sidebar_store';
......
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-unused-vars, one-var, no-var, one-var-declaration-per-line, prefer-arrow-callback, no-new, max-len */ /* eslint-disable func-names, space-before-function-paren, wrap-iife, no-unused-vars, one-var, no-var, one-var-declaration-per-line, prefer-arrow-callback, no-new, max-len */
/* global Flash */ import Flash from './flash';
import { __, s__ } from './locale'; import { __, s__ } from './locale';
export default class Star { export default class Star {
......
/* global Flash */
import 'deckar01-task_list'; import 'deckar01-task_list';
import Flash from './flash';
export default class TaskList { export default class TaskList {
constructor(options = {}) { constructor(options = {}) {
......
/* global Flash */
import '~/lib/utils/datetime_utility'; import '~/lib/utils/datetime_utility';
import Flash from '../../flash';
import MemoryUsage from './mr_widget_memory_usage'; import MemoryUsage from './mr_widget_memory_usage';
import StatusIcon from './mr_widget_status_icon'; import StatusIcon from './mr_widget_status_icon';
import MRWidgetService from '../services/mr_widget_service'; import MRWidgetService from '../services/mr_widget_service';
......
/* global Flash */ import Flash from '../../../flash';
import statusIcon from '../mr_widget_status_icon'; import statusIcon from '../mr_widget_status_icon';
import MRWidgetAuthor from '../../components/mr_widget_author'; import MRWidgetAuthor from '../../components/mr_widget_author';
import eventHub from '../../event_hub'; import eventHub from '../../event_hub';
......
/* global Flash */ import Flash from '../../../flash';
import mrWidgetAuthorTime from '../../components/mr_widget_author_time'; import mrWidgetAuthorTime from '../../components/mr_widget_author_time';
import tooltip from '../../../vue_shared/directives/tooltip'; import tooltip from '../../../vue_shared/directives/tooltip';
import loadingIcon from '../../../vue_shared/components/loading_icon.vue'; import loadingIcon from '../../../vue_shared/components/loading_icon.vue';
......
/* global Flash */
import successSvg from 'icons/_icon_status_success.svg'; import successSvg from 'icons/_icon_status_success.svg';
import warningSvg from 'icons/_icon_status_warning.svg'; import warningSvg from 'icons/_icon_status_warning.svg';
import simplePoll from '~/lib/utils/simple_poll'; import simplePoll from '~/lib/utils/simple_poll';
import Flash from '../../../flash';
import statusIcon from '../mr_widget_status_icon'; import statusIcon from '../mr_widget_status_icon';
import eventHub from '../../event_hub'; import eventHub from '../../event_hub';
......
/* global Flash */
import statusIcon from '../mr_widget_status_icon'; import statusIcon from '../mr_widget_status_icon';
import tooltip from '../../../vue_shared/directives/tooltip'; import tooltip from '../../../vue_shared/directives/tooltip';
import eventHub from '../../event_hub'; import eventHub from '../../event_hub';
...@@ -27,12 +26,12 @@ export default { ...@@ -27,12 +26,12 @@ export default {
.then(res => res.json()) .then(res => res.json())
.then((res) => { .then((res) => {
eventHub.$emit('UpdateWidgetData', res); eventHub.$emit('UpdateWidgetData', res);
new Flash('The merge request can now be merged.', 'notice'); // eslint-disable-line new window.Flash('The merge request can now be merged.', 'notice'); // eslint-disable-line
$('.merge-request .detail-page-description .title').text(this.mr.title); $('.merge-request .detail-page-description .title').text(this.mr.title);
}) })
.catch(() => { .catch(() => {
this.isMakingRequest = false; this.isMakingRequest = false;
new Flash('Something went wrong. Please try again.'); // eslint-disable-line new window.Flash('Something went wrong. Please try again.'); // eslint-disable-line
}); });
}, },
}, },
......
/* global Flash */ import Flash from '../flash';
import { import {
WidgetHeader, WidgetHeader,
WidgetMergeHelp, WidgetMergeHelp,
......
<script> <script>
/* global Flash */ import Flash from '../../../flash';
import markdownHeader from './header.vue'; import markdownHeader from './header.vue';
import markdownToolbar from './toolbar.vue'; import markdownToolbar from './toolbar.vue';
......
...@@ -17,17 +17,11 @@ ...@@ -17,17 +17,11 @@
/* Trial Banner */ /* Trial Banner */
.gitlab-ee-license-banner.alert { .gitlab-ee-license-banner.alert {
display: flex;
align-content: center;
justify-content: center;
padding: 15px 35px; padding: 15px 35px;
position: fixed; position: relative;
top: 0;
width: 100%;
background: $red-400; background: $red-400;
color: $white-light; color: $white-light;
text-align: center; text-align: center;
z-index: 400;
@media (max-width: $screen-md-min) { @media (max-width: $screen-md-min) {
padding: 10px 35px; padding: 10px 35px;
...@@ -54,8 +48,8 @@ ...@@ -54,8 +48,8 @@
.close { .close {
position: absolute; position: absolute;
top: 15%; top: 12px;
right: 10px; right: $gl-padding;
} }
} }
......
...@@ -43,6 +43,9 @@ class Groups::LdapGroupLinksController < Groups::ApplicationController ...@@ -43,6 +43,9 @@ class Groups::LdapGroupLinksController < Groups::ApplicationController
end end
def ldap_group_link_params def ldap_group_link_params
params.require(:ldap_group_link).permit(:cn, :filter, :group_access, :provider) attrs = %i[cn group_access provider]
attrs << :filter if ::License.feature_available?(:ldap_group_sync_filter)
params.require(:ldap_group_link).permit(attrs)
end end
end end
...@@ -17,6 +17,6 @@ class Groups::LdapsController < Groups::ApplicationController ...@@ -17,6 +17,6 @@ class Groups::LdapsController < Groups::ApplicationController
private private
def check_enabled_extras! def check_enabled_extras!
render_404 unless Gitlab::LDAP::Config.enabled_extras? render_404 unless Gitlab::LDAP::Config.group_sync_enabled?
end end
end end
...@@ -230,20 +230,12 @@ class Group < Namespace ...@@ -230,20 +230,12 @@ class Group < Namespace
ldap_group_links.first.try(:cn) ldap_group_links.first.try(:cn)
end end
def ldap_filter
ldap_group_links.first.try(:filter)
end
def ldap_access def ldap_access
ldap_group_links.first.try(:group_access) ldap_group_links.first.try(:group_access)
end end
def ldap_cn_or_filter_present?
ldap_cn.present? || ldap_filter.present?
end
def ldap_synced? def ldap_synced?
Gitlab.config.ldap.enabled && ldap_cn_or_filter_present? Gitlab.config.ldap.enabled && ldap_group_links.any?(&:active?)
end end
def post_create_hook def post_create_hook
......
...@@ -43,6 +43,14 @@ class LdapGroupLink < ActiveRecord::Base ...@@ -43,6 +43,14 @@ class LdapGroupLink < ActiveRecord::Base
config.label config.label
end end
def active?
if filter.present?
::License.feature_available?(:ldap_group_sync_filter)
elsif cn.present?
::License.feature_available?(:ldap_group_sync)
end
end
private private
def nullify_blank_attributes def nullify_blank_attributes
......
...@@ -18,10 +18,11 @@ class License < ActiveRecord::Base ...@@ -18,10 +18,11 @@ class License < ActiveRecord::Base
issue_board_milestone issue_board_milestone
issue_weights issue_weights
jenkins_integration jenkins_integration
ldap_extras ldap_group_sync
merge_request_approvers merge_request_approvers
merge_request_rebase merge_request_rebase
merge_request_squash merge_request_squash
multiple_ldap_servers
multiple_issue_assignees multiple_issue_assignees
multiple_issue_boards multiple_issue_boards
push_rules push_rules
...@@ -42,6 +43,7 @@ class License < ActiveRecord::Base ...@@ -42,6 +43,7 @@ class License < ActiveRecord::Base
geo geo
group_issue_boards group_issue_boards
jira_dev_panel_integration jira_dev_panel_integration
ldap_group_sync_filter
object_storage object_storage
service_desk service_desk
variable_environment_scope variable_environment_scope
...@@ -108,7 +110,9 @@ class License < ActiveRecord::Base ...@@ -108,7 +110,9 @@ class License < ActiveRecord::Base
elastic_search elastic_search
extended_audit_events extended_audit_events
geo geo
ldap_extras ldap_group_sync
ldap_group_sync_filter
multiple_ldap_servers
object_storage object_storage
repository_size_limit repository_size_limit
].freeze ].freeze
......
...@@ -36,7 +36,7 @@ ...@@ -36,7 +36,7 @@
= f.submit 'Save changes', class: "btn btn-save" = f.submit 'Save changes', class: "btn btn-save"
= link_to 'Cancel', admin_group_path(@group), class: "btn btn-cancel" = link_to 'Cancel', admin_group_path(@group), class: "btn btn-cancel"
- if ldap_enabled? && @group.persisted? - if @group.persisted? && Gitlab::LDAP::Config.group_sync_enabled?
%h3.page-title LDAP synchronizations %h3.page-title LDAP synchronizations
= render 'ldap_group_links/form', group: @group = render 'ldap_group_links/form', group: @group
= render 'ldap_group_links/ldap_group_links', group: @group = render 'ldap_group_links/ldap_group_links', group: @group
...@@ -62,11 +62,11 @@ ...@@ -62,11 +62,11 @@
= render partial: "namespaces/shared_runner_status", locals: { namespace: @group } = render partial: "namespaces/shared_runner_status", locals: { namespace: @group }
.panel.panel-default - if Gitlab::LDAP::Config.group_sync_enabled? && @group.ldap_synced?
.panel-heading Active synchronizations .panel.panel-default
%ul.well-list .panel-heading Active synchronizations
- if @group.ldap_group_links.any? %ul.well-list
- @group.ldap_group_links.each do |ldap_group_link| - @group.ldap_group_links.select(&:active?).each do |ldap_group_link|
%li %li
%strong= ldap_group_link.cn ? "Group: #{ldap_group_link.cn}" : "Filter: #{truncate(ldap_group_link.filter, length: 40)}" %strong= ldap_group_link.cn ? "Group: #{ldap_group_link.cn}" : "Filter: #{truncate(ldap_group_link.filter, length: 40)}"
as as
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
The members of this group are managed using LDAP and cannot be added or removed here. The members of this group are managed using LDAP and cannot be added or removed here.
Because LDAP permissions in GitLab get updated one user at a time and because GitLab caches LDAP check results, changes on your LDAP server or in this group's LDAP sync settings may take up to #{Gitlab.config.ldap['sync_time']}s to show in the list below. Because LDAP permissions in GitLab get updated one user at a time and because GitLab caches LDAP check results, changes on your LDAP server or in this group's LDAP sync settings may take up to #{Gitlab.config.ldap['sync_time']}s to show in the list below.
%ul %ul
- @group.ldap_group_links.each do |ldap_group_link| - @group.ldap_group_links.select(&:active?).each do |ldap_group_link|
%li %li
People in People in
%code= ldap_group_link.cn ? "cn: #{ldap_group_link.cn}" : "filter: #{truncate(ldap_group_link.filter, length: 70)}" %code= ldap_group_link.cn ? "cn: #{ldap_group_link.cn}" : "filter: #{truncate(ldap_group_link.filter, length: 70)}"
......
- page_title 'LDAP Syncrhonizations' - page_title 'LDAP Synchronization'
%h3.page-title LDAP synchronizations %h3.page-title LDAP synchronizations
= render 'ldap_group_links/form', group: @group = render 'ldap_group_links/form', group: @group
= render 'ldap_group_links/ldap_group_links', group: @group = render 'ldap_group_links/ldap_group_links', group: @group
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
.content-wrapper.page-with-new-nav .content-wrapper.page-with-new-nav
.mobile-overlay .mobile-overlay
.alert-wrapper .alert-wrapper
= render "layouts/header/ee_license_banner"
= render "layouts/broadcast" = render "layouts/broadcast"
= yield :flash_message = yield :flash_message
- unless @hide_breadcrumbs - unless @hide_breadcrumbs
......
= render "layouts/header/ee_license_banner" = render "layouts/header/ee_license_banner"
%header.navbar.navbar-gitlab %header.navbar.navbar-gitlab.navbar-gitlab-new
%a.sr-only.gl-accessibility{ href: "#content-body", tabindex: "1" } Skip to content %a.sr-only.gl-accessibility{ href: "#content-body", tabindex: "1" } Skip to content
.container-fluid .container-fluid
.header-content .header-content
......
...@@ -9,18 +9,19 @@ ...@@ -9,18 +9,19 @@
.col-sm-10 .col-sm-10
= f.select :provider, ldap_server_select_options, {}, class: 'form-control' = f.select :provider, ldap_server_select_options, {}, class: 'form-control'
.form-group.row - if ::License.feature_available?(:ldap_group_sync_filter)
= f.label :cn, class: 'control-label col-sm-2' do .form-group.row
Sync method = f.label :cn, class: 'control-label col-sm-2' do
.col-sm-10 Sync method
.radio .col-sm-10
= label_tag :sync_method_group do .radio
= radio_button_tag :sync_method, :group, true = label_tag :sync_method_group do
LDAP Group cn = radio_button_tag :sync_method, :group, true
.radio LDAP Group cn
= label_tag :sync_method_filter do .radio
= radio_button_tag :sync_method, :filter = label_tag :sync_method_filter do
LDAP user filter = radio_button_tag :sync_method, :filter
LDAP user filter
.form-group.row.cn-link .form-group.row.cn-link
= f.label :cn, class: 'control-label col-sm-2' do = f.label :cn, class: 'control-label col-sm-2' do
...@@ -32,16 +33,17 @@ ...@@ -32,16 +33,17 @@
%br %br
If you select an LDAP group you do not belong to you will lose ownership of #{group.name}. If you select an LDAP group you do not belong to you will lose ownership of #{group.name}.
.form-group.row.filter-link - if ::License.feature_available?(:ldap_group_sync_filter)
= f.label :filter, class: 'control-label col-sm-2' do .form-group.row.filter-link
LDAP User filter = f.label :filter, class: 'control-label col-sm-2' do
.col-sm-10 LDAP User filter
= f.text_field :filter, placeholder: 'Ex. (&(objectCategory=person)(objectClass=developer))', class: 'form-control xxlarge input-mn-300' .col-sm-10
.help-block = f.text_field :filter, placeholder: 'Ex. (&(objectCategory=person)(objectClass=developer))', class: 'form-control xxlarge input-mn-300'
- ldap_link = link_to 'LDAP Search Filter Syntax', 'https://msdn.microsoft.com/en-us/library/aa746475(v=vs.85).aspx' .help-block
This query must use valid #{ldap_link}. Synchronize #{group.name}'s members with this LDAP user filter. - ldap_link = link_to 'LDAP Search Filter Syntax', 'https://msdn.microsoft.com/en-us/library/aa746475(v=vs.85).aspx'
%br This query must use valid #{ldap_link}. Synchronize #{group.name}'s members with this LDAP user filter.
If you do not belong to this LDAP user filter you will lose ownership of #{group.name}. %br
If you do not belong to this LDAP user filter you will lose ownership of #{group.name}.
.form-group.row .form-group.row
= f.label :group_access, class: 'control-label col-sm-2' do = f.label :group_access, class: 'control-label col-sm-2' do
......
...@@ -13,3 +13,6 @@ ...@@ -13,3 +13,6 @@
Config for LDAP server Config for LDAP server
%code= ldap_group_link.provider %code= ldap_group_link.provider
is not present in GitLab is not present in GitLab
- unless ldap_group_link.active?
(Inactive because syncing with an LDAP user filter is not included in the current license)
...@@ -3,7 +3,7 @@ class LdapAllGroupsSyncWorker ...@@ -3,7 +3,7 @@ class LdapAllGroupsSyncWorker
include CronjobQueue include CronjobQueue
def perform def perform
return unless Gitlab::LDAP::Config.enabled_extras? return unless Gitlab::LDAP::Config.group_sync_enabled?
logger.info 'Started LDAP group sync' logger.info 'Started LDAP group sync'
EE::Gitlab::LDAP::Sync::Groups.execute EE::Gitlab::LDAP::Sync::Groups.execute
......
...@@ -3,7 +3,7 @@ class LdapGroupSyncWorker ...@@ -3,7 +3,7 @@ class LdapGroupSyncWorker
include DedicatedSidekiqQueue include DedicatedSidekiqQueue
def perform(group_ids, provider = nil) def perform(group_ids, provider = nil)
return unless Gitlab::LDAP::Config.enabled_extras? return unless Gitlab::LDAP::Config.group_sync_enabled?
groups = Group.where(id: Array(group_ids)) groups = Group.where(id: Array(group_ids))
......
...@@ -3,7 +3,7 @@ class LdapSyncWorker ...@@ -3,7 +3,7 @@ class LdapSyncWorker
include CronjobQueue include CronjobQueue
def perform def perform
return unless Gitlab::LDAP::Config.enabled_extras? return unless Gitlab::LDAP::Config.group_sync_enabled?
Rails.logger.info "Performing daily LDAP sync task." Rails.logger.info "Performing daily LDAP sync task."
User.ldap.find_each(batch_size: 100).each do |ldap_user| User.ldap.find_each(batch_size: 100).each do |ldap_user|
......
---
title: Fix base link for issues on group boards
merge_request:
author:
type: fixed
...@@ -20,7 +20,7 @@ membership syncing. ...@@ -20,7 +20,7 @@ membership syncing.
## Use-cases ## Use-cases
- User Sync: Once a day, GitLab will update users against LDAP - User Sync: Once a day, GitLab will update users against LDAP
- Group Sync: Once an hour, GitLab will update group membership - Group Sync: Once an hour, GitLab will update group membership
based on LDAP group members based on LDAP group members
## User Sync ## User Sync
...@@ -54,8 +54,8 @@ new groups they might be added to when the user logs in. That way they don't nee ...@@ -54,8 +54,8 @@ new groups they might be added to when the user logs in. That way they don't nee
to wait for the hourly sync to be granted access to the groups that they are in to wait for the hourly sync to be granted access to the groups that they are in
in LDAP. in LDAP.
We can also add a GitLab group to sync with one or multiple LDAP groups or we can In GitLab Enterprise Edition Premium, we can also add a GitLab group to sync with one or multiple LDAP groups or we can
also add a filter. The filter must comply with the syntax defined in [RFC 2254](https://tools.ietf.org/search/rfc2254). also add a filter. The filter must comply with the syntax defined in [RFC 2254](https://tools.ietf.org/search/rfc2254).
A group sync process will run every hour on the hour, and `group_base` must be set A group sync process will run every hour on the hour, and `group_base` must be set
in LDAP configuration for LDAP synchronizations based on group CN to work. This allows in LDAP configuration for LDAP synchronizations based on group CN to work. This allows
......
# Development # GitLab development guides
## Outside of docs ## Get started!
- [CONTRIBUTING.md](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md) main contributing guide - Setup GitLab's development environment with [GitLab Development Kit (GDK)](https://gitlab.com/gitlab-org/gitlab-development-kit/blob/master/doc/howto/README.md)
- [PROCESS.md](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/PROCESS.md) contributing process - [GitLab contributing guide](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md)
- [GitLab Development Kit (GDK)](https://gitlab.com/gitlab-org/gitlab-development-kit/blob/master/doc/howto/README.md) to install a development version - [Architecture](architecture.md) of GitLab
- [Rake tasks](rake_tasks.md) for development
## Styleguides
- [API styleguide](api_styleguide.md) Use this styleguide if you are
contributing to the API.
- [Documentation styleguide](doc_styleguide.md) Use this styleguide if you are
contributing to documentation.
- [Writing documentation](writing_documentation.md)
- [Distinction between general documentation and technical articles](writing_documentation.md#distinction-between-general-documentation-and-technical-articles)
- [SQL Migration Style Guide](migration_style_guide.md) for creating safe SQL migrations
- [Testing standards and style guidelines](testing.md)
- [UX guide](ux_guide/index.md) for building GitLab with existing CSS styles and elements
- [Frontend guidelines](fe_guide/index.md)
- [SQL guidelines](sql.md) for working with SQL queries
- [Sidekiq guidelines](sidekiq_style_guide.md) for working with Sidekiq workers
- [`Gemfile` guidelines](gemfile.md)
## Process ## Processes
- [GitLab core team & GitLab Inc. contribution process](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/PROCESS.md)
- [Generate a changelog entry with `bin/changelog`](changelog.md) - [Generate a changelog entry with `bin/changelog`](changelog.md)
- [Code review guidelines](code_review.md) for reviewing code and having code reviewed.
- [Limit conflicts with EE when developing on CE](limit_ee_conflicts.md) - [Limit conflicts with EE when developing on CE](limit_ee_conflicts.md)
- [Guidelines for implementing Enterprise Edition feature](ee_features.md) - [Guidelines for implementing Enterprise Edition feature](ee_features.md)
- [Code review guidelines](code_review.md) for reviewing code and having code reviewed.
- [Merge request performance guidelines](merge_request_performance_guidelines.md)
for ensuring merge requests do not negatively impact GitLab performance
## Backend howtos ## UX and frontend guides
- [Architecture](architecture.md) of GitLab - [UX guide](ux_guide/index.md) for building GitLab with existing CSS styles and elements
- [Frontend guidelines](fe_guide/index.md)
## Backend guides
- [Testing standards and style guidelines](testing_guide/index.md)
- [API styleguide](api_styleguide.md) Use this styleguide if you are
contributing to the API.
- [Sidekiq guidelines](sidekiq_style_guide.md) for working with Sidekiq workers
- [Working with Gitaly](gitaly.md)
- [Manage feature flags](feature_flags.md)
- [View sent emails or preview mailers](emails.md)
- [Shell commands](shell_commands.md) in the GitLab codebase
- [`Gemfile` guidelines](gemfile.md)
- [Sidekiq debugging](sidekiq_debugging.md)
- [Gotchas](gotchas.md) to avoid - [Gotchas](gotchas.md) to avoid
- [Issue and merge requests state models](object_state_models.md)
- [How to dump production data to staging](db_dump.md) - [How to dump production data to staging](db_dump.md)
## Performance guides
- [Instrumentation](instrumentation.md) - [Instrumentation](instrumentation.md)
- [Performance guidelines](performance.md) - [Performance guidelines](performance.md)
- [Rake tasks](rake_tasks.md) for development - [Merge request performance guidelines](merge_request_performance_guidelines.md)
- [Shell commands](shell_commands.md) in the GitLab codebase for ensuring merge requests do not negatively impact GitLab performance
- [Sidekiq debugging](sidekiq_debugging.md)
- [Object state models](object_state_models.md)
- [Building a package for testing purposes](build_test_package.md)
- [Manage feature flags](feature_flags.md)
- [View sent emails or preview mailers](emails.md)
- [Working with Gitaly](gitaly.md)
## Databases ## Databases guides
### Migrations
- [Merge Request Checklist](database_merge_request_checklist.md)
- [What requires downtime?](what_requires_downtime.md) - [What requires downtime?](what_requires_downtime.md)
- [SQL guidelines](sql.md) for working with SQL queries
- [Migrations style guide](migration_style_guide.md) for creating safe SQL migrations
- [Post deployment migrations](post_deployment_migrations.md)
- [Background migrations](background_migrations.md)
- [Swapping tables](swapping_tables.md)
### Best practices
- [Merge Request checklist](database_merge_request_checklist.md)
- [Adding database indexes](adding_database_indexes.md) - [Adding database indexes](adding_database_indexes.md)
- [Post Deployment Migrations](post_deployment_migrations.md) - [Post Deployment Migrations](post_deployment_migrations.md)
- [Foreign Keys & Associations](foreign_keys.md) - [Foreign keys & associations](foreign_keys.md)
- [Serializing Data](serializing_data.md) - [Serializing data](serializing_data.md)
- [Polymorphic Associations](polymorphic_associations.md) - [Polymorphic associations](polymorphic_associations.md)
- [Single Table Inheritance](single_table_inheritance.md) - [Single table inheritance](single_table_inheritance.md)
- [Background Migrations](background_migrations.md) - [Background Migrations](background_migrations.md)
- [Storing SHA1 Hashes As Binary](sha1_as_binary.md) - [Storing SHA1 hashes as binary](sha1_as_binary.md)
- [Iterating Tables In Batches](iterating_tables_in_batches.md) - [Iterating tables in batches](iterating_tables_in_batches.md)
- [Ordering Table Columns](ordering_table_columns.md) - [Ordering table columns](ordering_table_columns.md)
- [Verifying Database Capabilities](verifying_database_capabilities.md) - [Verifying database capabilities](verifying_database_capabilities.md)
- [Hash Indexes](hash_indexes.md) - [Hash indexes](hash_indexes.md)
- [Swapping Tables](swapping_tables.md) - [Swapping Tables](swapping_tables.md)
## Internationalization (i18n) ## Documentation guides
- [Documentation styleguide](doc_styleguide.md): Use this styleguide if you are
contributing to the documentation.
- [Writing documentation](writing_documentation.md)
- [Distinction between general documentation and technical articles](writing_documentation.md#distinction-between-general-documentation-and-technical-articles)
## Internationalization (i18n) guides
- [Introduction](i18n/index.md) - [Introduction](i18n/index.md)
- [Externalization](i18n/externalization.md) - [Externalization](i18n/externalization.md)
- [Translation](i18n/translation.md) - [Translation](i18n/translation.md)
## Build guides
- [Building a package for testing purposes](build_test_package.md)
## Compliance ## Compliance
- [Licensing](licensing.md) for ensuring license compliance - [Licensing](licensing.md) for ensuring license compliance
...@@ -54,7 +54,8 @@ or make changes to our frontend development guidelines. ...@@ -54,7 +54,8 @@ or make changes to our frontend development guidelines.
--- ---
## [Testing](testing.md) ## [Testing](../testing_guide/frontend_testing.md)
How we write frontend tests, run the GitLab test suite, and debug test related How we write frontend tests, run the GitLab test suite, and debug test related
issues. issues.
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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