Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
aca89cb7e9
commit
2ecc6e22e3
|
@ -1025,7 +1025,6 @@ Style/NumericLiteralPrefix:
|
|||
Style/NumericPredicate:
|
||||
EnforcedStyle: comparison
|
||||
Exclude:
|
||||
- 'spec/**/*'
|
||||
- 'app/controllers/concerns/issuable_collections.rb'
|
||||
- 'app/controllers/concerns/paginated_collection.rb'
|
||||
- 'app/helpers/graph_helper.rb'
|
||||
|
@ -1126,8 +1125,6 @@ Style/NumericPredicate:
|
|||
- 'lib/tasks/gitlab/gitaly.rake'
|
||||
- 'lib/tasks/gitlab/snippets.rake'
|
||||
- 'lib/tasks/gitlab/workhorse.rake'
|
||||
- 'qa/qa/git/repository.rb'
|
||||
- 'qa/qa/support/wait_for_requests.rb'
|
||||
- 'ee/app/models/ee/project.rb'
|
||||
- 'lib/gitlab/usage_data/topology.rb'
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
d02d473234dc18649e5a359ee203d6a3aa5c4031
|
||||
c6fdcae2d1c5d4914a010dfe7ea5dbfcfb8bdabf
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
<script>
|
||||
import { GlButton } from '@gitlab/ui';
|
||||
import { s__ } from '~/locale';
|
||||
import eventHub from '../event_hub';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlButton,
|
||||
},
|
||||
props: {
|
||||
commitsEmpty: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
contextCommitsEmpty: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
buttonText() {
|
||||
return this.contextCommitsEmpty || this.commitsEmpty
|
||||
? s__('AddContextCommits|Add previously merged commits')
|
||||
: s__('AddContextCommits|Add/remove');
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
openModal() {
|
||||
eventHub.$emit('openModal');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<gl-button
|
||||
:class="[
|
||||
{
|
||||
'ml-3': !contextCommitsEmpty,
|
||||
'mt-3': !commitsEmpty && contextCommitsEmpty,
|
||||
},
|
||||
]"
|
||||
:variant="commitsEmpty ? 'info' : 'default'"
|
||||
@click="openModal"
|
||||
>
|
||||
{{ buttonText }}
|
||||
</gl-button>
|
||||
</template>
|
|
@ -0,0 +1,279 @@
|
|||
<script>
|
||||
import { mapState, mapActions } from 'vuex';
|
||||
import { GlModal, GlTabs, GlTab, GlSearchBoxByType, GlSprintf } from '@gitlab/ui';
|
||||
import ReviewTabContainer from '~/add_context_commits_modal/components/review_tab_container.vue';
|
||||
import { s__ } from '~/locale';
|
||||
import eventHub from '../event_hub';
|
||||
import createFlash from '~/flash';
|
||||
import {
|
||||
findCommitIndex,
|
||||
setCommitStatus,
|
||||
removeIfReadyToBeRemoved,
|
||||
removeIfPresent,
|
||||
} from '../utils';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlModal,
|
||||
GlTabs,
|
||||
GlTab,
|
||||
ReviewTabContainer,
|
||||
GlSearchBoxByType,
|
||||
GlSprintf,
|
||||
},
|
||||
props: {
|
||||
contextCommitsPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
targetBranch: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
mergeRequestIid: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
projectId: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapState([
|
||||
'tabIndex',
|
||||
'isLoadingCommits',
|
||||
'commits',
|
||||
'commitsLoadingError',
|
||||
'isLoadingContextCommits',
|
||||
'contextCommits',
|
||||
'contextCommitsLoadingError',
|
||||
'selectedCommits',
|
||||
'searchText',
|
||||
'toRemoveCommits',
|
||||
]),
|
||||
currentTabIndex: {
|
||||
get() {
|
||||
return this.tabIndex;
|
||||
},
|
||||
set(newTabIndex) {
|
||||
this.setTabIndex(newTabIndex);
|
||||
},
|
||||
},
|
||||
selectedCommitsCount() {
|
||||
return this.selectedCommits.filter(selectedCommit => selectedCommit.isSelected).length;
|
||||
},
|
||||
shouldPurge() {
|
||||
return this.selectedCommitsCount !== this.selectedCommits.length;
|
||||
},
|
||||
uniqueCommits() {
|
||||
return this.selectedCommits.filter(
|
||||
selectedCommit =>
|
||||
selectedCommit.isSelected &&
|
||||
findCommitIndex(this.contextCommits, selectedCommit.short_id) === -1,
|
||||
);
|
||||
},
|
||||
disableSaveButton() {
|
||||
// We should have a minimum of one commit selected and that should not be in the context commits list or we should have a context commit to delete
|
||||
return (
|
||||
(this.selectedCommitsCount.length === 0 || this.uniqueCommits.length === 0) &&
|
||||
this.toRemoveCommits.length === 0
|
||||
);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
tabIndex(newTabIndex) {
|
||||
this.handleTabChange(newTabIndex);
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
eventHub.$on('openModal', this.openModal);
|
||||
this.setBaseConfig({
|
||||
contextCommitsPath: this.contextCommitsPath,
|
||||
mergeRequestIid: this.mergeRequestIid,
|
||||
projectId: this.projectId,
|
||||
});
|
||||
},
|
||||
beforeDestroy() {
|
||||
eventHub.$off('openModal', this.openModal);
|
||||
clearTimeout(this.timeout);
|
||||
this.timeout = null;
|
||||
},
|
||||
methods: {
|
||||
...mapActions([
|
||||
'setBaseConfig',
|
||||
'setTabIndex',
|
||||
'searchCommits',
|
||||
'setCommits',
|
||||
'createContextCommits',
|
||||
'fetchContextCommits',
|
||||
'removeContextCommits',
|
||||
'setSelectedCommits',
|
||||
'setSearchText',
|
||||
'setToRemoveCommits',
|
||||
'resetModalState',
|
||||
]),
|
||||
focusSearch() {
|
||||
this.$refs.searchInput.focusInput();
|
||||
},
|
||||
openModal() {
|
||||
this.searchCommits();
|
||||
this.fetchContextCommits();
|
||||
this.$root.$emit('bv::show::modal', 'add-review-item');
|
||||
},
|
||||
handleTabChange(tabIndex) {
|
||||
if (tabIndex === 0) {
|
||||
this.focusSearch();
|
||||
if (this.shouldPurge) {
|
||||
this.setSelectedCommits(
|
||||
[...this.commits, ...this.selectedCommits].filter(commit => commit.isSelected),
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
handleSearchCommits(value) {
|
||||
// We only call the service, if we have 3 characters or we don't have any characters
|
||||
if (value.length >= 3) {
|
||||
clearTimeout(this.timeout);
|
||||
this.timeout = setTimeout(() => {
|
||||
this.searchCommits(value);
|
||||
}, 500);
|
||||
} else if (value.length === 0) {
|
||||
this.searchCommits();
|
||||
}
|
||||
this.setSearchText(value);
|
||||
},
|
||||
handleCommitRowSelect(event) {
|
||||
const index = event[0];
|
||||
const selected = event[1];
|
||||
const tempCommit = this.tabIndex === 0 ? this.commits[index] : this.selectedCommits[index];
|
||||
const commitIndex = findCommitIndex(this.commits, tempCommit.short_id);
|
||||
const tempCommits = setCommitStatus(this.commits, commitIndex, selected);
|
||||
const selectedCommitIndex = findCommitIndex(this.selectedCommits, tempCommit.short_id);
|
||||
let tempSelectedCommits = setCommitStatus(
|
||||
this.selectedCommits,
|
||||
selectedCommitIndex,
|
||||
selected,
|
||||
);
|
||||
|
||||
if (selected) {
|
||||
// If user deselects a commit which is already present in previously merged commits, then user adds it again.
|
||||
// Then the state is neutral, so we remove it from the list
|
||||
this.setToRemoveCommits(
|
||||
removeIfReadyToBeRemoved(this.toRemoveCommits, tempCommit.short_id),
|
||||
);
|
||||
} else {
|
||||
// If user is present in first tab and deselects a commit, remove it directly
|
||||
if (this.tabIndex === 0) {
|
||||
tempSelectedCommits = removeIfPresent(tempSelectedCommits, tempCommit.short_id);
|
||||
}
|
||||
|
||||
// If user deselects a commit which is already present in previously merged commits, we keep track of it in a list to remove
|
||||
const contextCommitsIndex = findCommitIndex(this.contextCommits, tempCommit.short_id);
|
||||
if (contextCommitsIndex !== -1) {
|
||||
this.setToRemoveCommits([...this.toRemoveCommits, tempCommit.short_id]);
|
||||
}
|
||||
}
|
||||
|
||||
this.setCommits({ commits: tempCommits });
|
||||
this.setSelectedCommits([
|
||||
...tempSelectedCommits,
|
||||
...tempCommits.filter(commit => commit.isSelected),
|
||||
]);
|
||||
},
|
||||
handleCreateContextCommits() {
|
||||
if (this.uniqueCommits.length > 0 && this.toRemoveCommits.length > 0) {
|
||||
return Promise.all([
|
||||
this.createContextCommits({ commits: this.uniqueCommits }),
|
||||
this.removeContextCommits(),
|
||||
]).then(values => {
|
||||
if (values[0] || values[1]) {
|
||||
window.location.reload();
|
||||
}
|
||||
if (!values[0] && !values[1]) {
|
||||
createFlash(
|
||||
s__('ContextCommits|Failed to create/remove context commits. Please try again.'),
|
||||
);
|
||||
}
|
||||
});
|
||||
} else if (this.uniqueCommits.length > 0) {
|
||||
return this.createContextCommits({ commits: this.uniqueCommits, forceReload: true });
|
||||
}
|
||||
|
||||
return this.removeContextCommits(true);
|
||||
},
|
||||
handleModalClose() {
|
||||
this.resetModalState();
|
||||
clearTimeout(this.timeout);
|
||||
},
|
||||
handleModalHide() {
|
||||
this.resetModalState();
|
||||
clearTimeout(this.timeout);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<gl-modal
|
||||
ref="modal"
|
||||
cancel-variant="light"
|
||||
size="md"
|
||||
body-class="add-review-item pt-0"
|
||||
:scrollable="true"
|
||||
:ok-title="__('Save changes')"
|
||||
modal-id="add-review-item"
|
||||
:title="__('Add or remove previously merged commits')"
|
||||
:ok-disabled="disableSaveButton"
|
||||
@shown="focusSearch"
|
||||
@ok="handleCreateContextCommits"
|
||||
@cancel="handleModalClose"
|
||||
@close="handleModalClose"
|
||||
@hide="handleModalHide"
|
||||
>
|
||||
<gl-tabs v-model="currentTabIndex" content-class="pt-0">
|
||||
<gl-tab>
|
||||
<template #title>
|
||||
<gl-sprintf :message="__(`Commits in %{codeStart}${targetBranch}%{codeEnd}`)">
|
||||
<template #code="{ content }">
|
||||
<code>{{ content }}</code>
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
</template>
|
||||
<div class="mt-2">
|
||||
<gl-search-box-by-type
|
||||
ref="searchInput"
|
||||
:placeholder="__(`Search by commit title or SHA`)"
|
||||
@input="handleSearchCommits"
|
||||
/>
|
||||
<review-tab-container
|
||||
:is-loading="isLoadingCommits"
|
||||
:loading-error="commitsLoadingError"
|
||||
:loading-failed-text="__('Unable to load commits. Try again later.')"
|
||||
:commits="commits"
|
||||
:empty-list-text="__('Your search didn\'t match any commits. Try a different query.')"
|
||||
@handleCommitSelect="handleCommitRowSelect"
|
||||
/>
|
||||
</div>
|
||||
</gl-tab>
|
||||
<gl-tab>
|
||||
<template #title>
|
||||
{{ __('Selected commits') }}
|
||||
<span class="badge badge-pill">{{ selectedCommitsCount }}</span>
|
||||
</template>
|
||||
<review-tab-container
|
||||
:is-loading="isLoadingContextCommits"
|
||||
:loading-error="contextCommitsLoadingError"
|
||||
:loading-failed-text="__('Unable to load commits. Try again later.')"
|
||||
:commits="selectedCommits"
|
||||
:empty-list-text="
|
||||
__(
|
||||
'Commits you select appear here. Go to the first tab and select commits to add to this merge request.',
|
||||
)
|
||||
"
|
||||
@handleCommitSelect="handleCommitRowSelect"
|
||||
/>
|
||||
</gl-tab>
|
||||
</gl-tabs>
|
||||
</gl-modal>
|
||||
</template>
|
|
@ -0,0 +1,57 @@
|
|||
<script>
|
||||
import { GlLoadingIcon, GlAlert } from '@gitlab/ui';
|
||||
import CommitItem from '~/diffs/components/commit_item.vue';
|
||||
import { __ } from '~/locale';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlLoadingIcon,
|
||||
GlAlert,
|
||||
CommitItem,
|
||||
},
|
||||
props: {
|
||||
isLoading: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
loadingError: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
loadingFailedText: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
commits: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
emptyListText: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: __('No commits present here'),
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<gl-loading-icon v-if="isLoading" size="lg" class="mt-3" />
|
||||
<gl-alert v-else-if="loadingError" variant="danger" :dismissible="false" class="mt-3">
|
||||
{{ loadingFailedText }}
|
||||
</gl-alert>
|
||||
<div v-else-if="commits.length === 0" class="text-center mt-4">
|
||||
<span>{{ emptyListText }}</span>
|
||||
</div>
|
||||
<div v-else>
|
||||
<ul class="content-list commit-list flex-list">
|
||||
<commit-item
|
||||
v-for="(commit, index) in commits"
|
||||
:key="commit.id"
|
||||
:is-selectable="true"
|
||||
:commit="commit"
|
||||
:checked="commit.isSelected"
|
||||
@handleCheckboxChange="$emit('handleCommitSelect', [index, $event])"
|
||||
/>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,3 @@
|
|||
import createEventHub from '~/helpers/event_hub_factory';
|
||||
|
||||
export default createEventHub();
|
|
@ -0,0 +1,64 @@
|
|||
import Vue from 'vue';
|
||||
import { parseBoolean } from '~/lib/utils/common_utils';
|
||||
import createStore from './store';
|
||||
import AddContextCommitsModalTrigger from './components/add_context_commits_modal_trigger.vue';
|
||||
import AddContextCommitsModalWrapper from './components/add_context_commits_modal_wrapper.vue';
|
||||
|
||||
export default function initAddContextCommitsTriggers() {
|
||||
const addContextCommitsModalTriggerEl = document.querySelector('.add-review-item-modal-trigger');
|
||||
const addContextCommitsModalWrapperEl = document.querySelector('.add-review-item-modal-wrapper');
|
||||
|
||||
if (addContextCommitsModalTriggerEl || addContextCommitsModalWrapperEl) {
|
||||
// eslint-disable-next-line no-new
|
||||
new Vue({
|
||||
el: addContextCommitsModalTriggerEl,
|
||||
data() {
|
||||
const { commitsEmpty, contextCommitsEmpty } = this.$options.el.dataset;
|
||||
return {
|
||||
commitsEmpty: parseBoolean(commitsEmpty),
|
||||
contextCommitsEmpty: parseBoolean(contextCommitsEmpty),
|
||||
};
|
||||
},
|
||||
render(createElement) {
|
||||
return createElement(AddContextCommitsModalTrigger, {
|
||||
props: {
|
||||
commitsEmpty: this.commitsEmpty,
|
||||
contextCommitsEmpty: this.contextCommitsEmpty,
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const store = createStore();
|
||||
|
||||
// eslint-disable-next-line no-new
|
||||
new Vue({
|
||||
el: addContextCommitsModalWrapperEl,
|
||||
store,
|
||||
data() {
|
||||
const {
|
||||
contextCommitsPath,
|
||||
targetBranch,
|
||||
mergeRequestIid,
|
||||
projectId,
|
||||
} = this.$options.el.dataset;
|
||||
return {
|
||||
contextCommitsPath,
|
||||
targetBranch,
|
||||
mergeRequestIid: Number(mergeRequestIid),
|
||||
projectId: Number(projectId),
|
||||
};
|
||||
},
|
||||
render(createElement) {
|
||||
return createElement(AddContextCommitsModalWrapper, {
|
||||
props: {
|
||||
contextCommitsPath: this.contextCommitsPath,
|
||||
targetBranch: this.targetBranch,
|
||||
mergeRequestIid: this.mergeRequestIid,
|
||||
projectId: this.projectId,
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,134 @@
|
|||
import _ from 'lodash';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import createFlash from '~/flash';
|
||||
import { s__ } from '~/locale';
|
||||
import Api from '~/api';
|
||||
import * as types from './mutation_types';
|
||||
|
||||
export const setBaseConfig = ({ commit }, options) => {
|
||||
commit(types.SET_BASE_CONFIG, options);
|
||||
};
|
||||
|
||||
export const setTabIndex = ({ commit }, tabIndex) => commit(types.SET_TABINDEX, tabIndex);
|
||||
|
||||
export const searchCommits = ({ dispatch, commit, state }, searchText) => {
|
||||
commit(types.FETCH_COMMITS);
|
||||
|
||||
let params = {};
|
||||
if (searchText) {
|
||||
params = {
|
||||
params: {
|
||||
search: searchText,
|
||||
per_page: 40,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return axios
|
||||
.get(state.contextCommitsPath, params)
|
||||
.then(({ data }) => {
|
||||
let commits = data.map(o => ({ ...o, isSelected: false }));
|
||||
commits = commits.map(c => {
|
||||
const isPresent = state.selectedCommits.find(
|
||||
selectedCommit => selectedCommit.short_id === c.short_id && selectedCommit.isSelected,
|
||||
);
|
||||
if (isPresent) {
|
||||
return { ...c, isSelected: true };
|
||||
}
|
||||
return c;
|
||||
});
|
||||
if (!searchText) {
|
||||
dispatch('setCommits', { commits: [...commits, ...state.contextCommits] });
|
||||
} else {
|
||||
dispatch('setCommits', { commits });
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
commit(types.FETCH_COMMITS_ERROR);
|
||||
});
|
||||
};
|
||||
|
||||
export const setCommits = ({ commit }, { commits: data, silentAddition = false }) => {
|
||||
let commits = _.uniqBy(data, 'short_id');
|
||||
commits = _.orderBy(data, c => new Date(c.committed_date), ['desc']);
|
||||
if (silentAddition) {
|
||||
commit(types.SET_COMMITS_SILENT, commits);
|
||||
} else {
|
||||
commit(types.SET_COMMITS, commits);
|
||||
}
|
||||
};
|
||||
|
||||
export const createContextCommits = ({ state }, { commits, forceReload = false }) =>
|
||||
Api.createContextCommits(state.projectId, state.mergeRequestIid, {
|
||||
commits: commits.map(commit => commit.short_id),
|
||||
})
|
||||
.then(() => {
|
||||
if (forceReload) {
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
return true;
|
||||
})
|
||||
.catch(() => {
|
||||
if (forceReload) {
|
||||
createFlash(s__('ContextCommits|Failed to create context commits. Please try again.'));
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
export const fetchContextCommits = ({ dispatch, commit, state }) => {
|
||||
commit(types.FETCH_CONTEXT_COMMITS);
|
||||
return Api.allContextCommits(state.projectId, state.mergeRequestIid)
|
||||
.then(({ data }) => {
|
||||
const contextCommits = data.map(o => ({ ...o, isSelected: true }));
|
||||
dispatch('setContextCommits', contextCommits);
|
||||
dispatch('setCommits', {
|
||||
commits: [...state.commits, ...contextCommits],
|
||||
silentAddition: true,
|
||||
});
|
||||
dispatch('setSelectedCommits', contextCommits);
|
||||
})
|
||||
.catch(() => {
|
||||
commit(types.FETCH_CONTEXT_COMMITS_ERROR);
|
||||
});
|
||||
};
|
||||
|
||||
export const setContextCommits = ({ commit }, data) => {
|
||||
commit(types.SET_CONTEXT_COMMITS, data);
|
||||
};
|
||||
|
||||
export const removeContextCommits = ({ state }, forceReload = false) =>
|
||||
Api.removeContextCommits(state.projectId, state.mergeRequestIid, {
|
||||
commits: state.toRemoveCommits,
|
||||
})
|
||||
.then(() => {
|
||||
if (forceReload) {
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
return true;
|
||||
})
|
||||
.catch(() => {
|
||||
if (forceReload) {
|
||||
createFlash(s__('ContextCommits|Failed to delete context commits. Please try again.'));
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
export const setSelectedCommits = ({ commit }, selected) => {
|
||||
let selectedCommits = _.uniqBy(selected, 'short_id');
|
||||
selectedCommits = _.orderBy(
|
||||
selectedCommits,
|
||||
selectedCommit => new Date(selectedCommit.committed_date),
|
||||
['desc'],
|
||||
);
|
||||
commit(types.SET_SELECTED_COMMITS, selectedCommits);
|
||||
};
|
||||
|
||||
export const setSearchText = ({ commit }, searchText) => commit(types.SET_SEARCH_TEXT, searchText);
|
||||
|
||||
export const setToRemoveCommits = ({ commit }, data) => commit(types.SET_TO_REMOVE_COMMITS, data);
|
||||
|
||||
export const resetModalState = ({ commit }) => commit(types.RESET_MODAL_STATE);
|
|
@ -0,0 +1,15 @@
|
|||
import Vue from 'vue';
|
||||
import Vuex from 'vuex';
|
||||
import * as actions from './actions';
|
||||
import mutations from './mutations';
|
||||
import state from './state';
|
||||
|
||||
Vue.use(Vuex);
|
||||
|
||||
export default () =>
|
||||
new Vuex.Store({
|
||||
namespaced: true,
|
||||
state: state(),
|
||||
actions,
|
||||
mutations,
|
||||
});
|
|
@ -0,0 +1,20 @@
|
|||
export const SET_BASE_CONFIG = 'SET_BASE_CONFIG';
|
||||
|
||||
export const SET_TABINDEX = 'SET_TABINDEX';
|
||||
|
||||
export const FETCH_COMMITS = 'FETCH_COMMITS';
|
||||
export const SET_COMMITS = 'SET_COMMITS';
|
||||
export const SET_COMMITS_SILENT = 'SET_COMMITS_SILENT';
|
||||
export const FETCH_COMMITS_ERROR = 'FETCH_COMMITS_ERROR';
|
||||
|
||||
export const FETCH_CONTEXT_COMMITS = 'FETCH_CONTEXT_COMMITS';
|
||||
export const SET_CONTEXT_COMMITS = 'SET_CONTEXT_COMMITS';
|
||||
export const FETCH_CONTEXT_COMMITS_ERROR = 'FETCH_CONTEXT_COMMITS_ERROR';
|
||||
|
||||
export const SET_SELECTED_COMMITS = 'SET_SELECTED_COMMITS';
|
||||
|
||||
export const SET_SEARCH_TEXT = 'SET_SEARCH_TEXT';
|
||||
|
||||
export const SET_TO_REMOVE_COMMITS = 'SET_TO_REMOVE_COMMITS';
|
||||
|
||||
export const RESET_MODAL_STATE = 'RESET_MODAL_STATE';
|
|
@ -0,0 +1,56 @@
|
|||
import * as types from './mutation_types';
|
||||
|
||||
export default {
|
||||
[types.SET_BASE_CONFIG](state, options) {
|
||||
Object.assign(state, { ...options });
|
||||
},
|
||||
[types.SET_TABINDEX](state, tabIndex) {
|
||||
state.tabIndex = tabIndex;
|
||||
},
|
||||
[types.FETCH_COMMITS](state) {
|
||||
state.isLoadingCommits = true;
|
||||
state.commitsLoadingError = false;
|
||||
},
|
||||
[types.SET_COMMITS](state, commits) {
|
||||
state.commits = commits;
|
||||
state.isLoadingCommits = false;
|
||||
state.commitsLoadingError = false;
|
||||
},
|
||||
[types.SET_COMMITS_SILENT](state, commits) {
|
||||
state.commits = commits;
|
||||
},
|
||||
[types.FETCH_COMMITS_ERROR](state) {
|
||||
state.commitsLoadingError = true;
|
||||
state.isLoadingCommits = false;
|
||||
},
|
||||
[types.FETCH_CONTEXT_COMMITS](state) {
|
||||
state.isLoadingContextCommits = true;
|
||||
state.contextCommitsLoadingError = false;
|
||||
},
|
||||
[types.SET_CONTEXT_COMMITS](state, contextCommits) {
|
||||
state.contextCommits = contextCommits;
|
||||
state.isLoadingContextCommits = false;
|
||||
state.contextCommitsLoadingError = false;
|
||||
},
|
||||
[types.FETCH_CONTEXT_COMMITS_ERROR](state) {
|
||||
state.contextCommitsLoadingError = true;
|
||||
state.isLoadingContextCommits = false;
|
||||
},
|
||||
[types.SET_SELECTED_COMMITS](state, commits) {
|
||||
state.selectedCommits = commits;
|
||||
},
|
||||
[types.SET_SEARCH_TEXT](state, searchText) {
|
||||
state.searchText = searchText;
|
||||
},
|
||||
[types.SET_TO_REMOVE_COMMITS](state, commits) {
|
||||
state.toRemoveCommits = commits;
|
||||
},
|
||||
[types.RESET_MODAL_STATE](state) {
|
||||
state.tabIndex = 0;
|
||||
state.commits = [];
|
||||
state.contextCommits = [];
|
||||
state.selectedCommits = [];
|
||||
state.toRemoveCommits = [];
|
||||
state.searchText = '';
|
||||
},
|
||||
};
|
|
@ -0,0 +1,13 @@
|
|||
export default () => ({
|
||||
contextCommitsPath: '',
|
||||
tabIndex: 0,
|
||||
isLoadingCommits: false,
|
||||
commits: [],
|
||||
commitsLoadingError: false,
|
||||
selectedCommits: [],
|
||||
isLoadingContextCommits: false,
|
||||
contextCommits: [],
|
||||
contextCommitsLoadingError: false,
|
||||
searchText: '',
|
||||
toRemoveCommits: [],
|
||||
});
|
|
@ -0,0 +1,32 @@
|
|||
export const findCommitIndex = (commits, commitShortId) => {
|
||||
return commits.findIndex(commit => commit.short_id === commitShortId);
|
||||
};
|
||||
|
||||
export const setCommitStatus = (commits, commitIndex, selected) => {
|
||||
const tempCommits = [...commits];
|
||||
tempCommits[commitIndex] = {
|
||||
...tempCommits[commitIndex],
|
||||
isSelected: selected,
|
||||
};
|
||||
return tempCommits;
|
||||
};
|
||||
|
||||
export const removeIfReadyToBeRemoved = (toRemoveCommits, commitShortId) => {
|
||||
const tempToRemoveCommits = [...toRemoveCommits];
|
||||
const isPresentInToRemove = tempToRemoveCommits.indexOf(commitShortId);
|
||||
if (isPresentInToRemove !== -1) {
|
||||
tempToRemoveCommits.splice(isPresentInToRemove, 1);
|
||||
}
|
||||
|
||||
return tempToRemoveCommits;
|
||||
};
|
||||
|
||||
export const removeIfPresent = (selectedCommits, commitShortId) => {
|
||||
const tempSelectedCommits = [...selectedCommits];
|
||||
const selectedCommitsIndex = findCommitIndex(tempSelectedCommits, commitShortId);
|
||||
if (selectedCommitsIndex !== -1) {
|
||||
tempSelectedCommits.splice(selectedCommitsIndex, 1);
|
||||
}
|
||||
|
||||
return tempSelectedCommits;
|
||||
};
|
|
@ -57,6 +57,8 @@ const Api = {
|
|||
pipelinesPath: '/api/:version/projects/:id/pipelines/',
|
||||
createPipelinePath: '/api/:version/projects/:id/pipeline',
|
||||
environmentsPath: '/api/:version/projects/:id/environments',
|
||||
contextCommitsPath:
|
||||
'/api/:version/projects/:id/merge_requests/:merge_request_iid/context_commits',
|
||||
rawFilePath: '/api/:version/projects/:id/repository/files/:path/raw',
|
||||
issuePath: '/api/:version/projects/:id/issues/:issue_iid',
|
||||
tagsPath: '/api/:version/projects/:id/repository/tags',
|
||||
|
@ -598,6 +600,30 @@ const Api = {
|
|||
return axios.get(url);
|
||||
},
|
||||
|
||||
createContextCommits(id, mergeRequestIid, data) {
|
||||
const url = Api.buildUrl(this.contextCommitsPath)
|
||||
.replace(':id', encodeURIComponent(id))
|
||||
.replace(':merge_request_iid', mergeRequestIid);
|
||||
|
||||
return axios.post(url, data);
|
||||
},
|
||||
|
||||
allContextCommits(id, mergeRequestIid) {
|
||||
const url = Api.buildUrl(this.contextCommitsPath)
|
||||
.replace(':id', encodeURIComponent(id))
|
||||
.replace(':merge_request_iid', mergeRequestIid);
|
||||
|
||||
return axios.get(url);
|
||||
},
|
||||
|
||||
removeContextCommits(id, mergeRequestIid, data) {
|
||||
const url = Api.buildUrl(this.contextCommitsPath)
|
||||
.replace(':id', id)
|
||||
.replace(':merge_request_iid', mergeRequestIid);
|
||||
|
||||
return axios.delete(url, { data });
|
||||
},
|
||||
|
||||
getRawFile(id, path, params = { ref: 'master' }) {
|
||||
const url = Api.buildUrl(this.rawFilePath)
|
||||
.replace(':id', encodeURIComponent(id))
|
||||
|
|
|
@ -52,10 +52,20 @@ export default {
|
|||
},
|
||||
mixins: [glFeatureFlagsMixin()],
|
||||
props: {
|
||||
isSelectable: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
commit: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
checked: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
collapsible: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
|
@ -83,6 +93,10 @@ export default {
|
|||
authorAvatar() {
|
||||
return this.author.avatar_url || this.commit.author_gravatar_url;
|
||||
},
|
||||
commitDescription() {
|
||||
// Strip the newline at the beginning
|
||||
return this.commit.description_html.replace(/^
/, '');
|
||||
},
|
||||
nextCommitUrl() {
|
||||
return this.commit.next_commit_id
|
||||
? setUrlParams({ commit_id: this.commit.next_commit_id })
|
||||
|
@ -110,13 +124,22 @@ export default {
|
|||
|
||||
<template>
|
||||
<li :class="{ 'js-toggle-container': collapsible }" class="commit flex-row">
|
||||
<user-avatar-link
|
||||
:link-href="authorUrl"
|
||||
:img-src="authorAvatar"
|
||||
:img-alt="authorName"
|
||||
:img-size="40"
|
||||
class="avatar-cell d-none d-sm-block"
|
||||
/>
|
||||
<div class="d-flex align-items-center align-self-start">
|
||||
<input
|
||||
v-if="isSelectable"
|
||||
class="mr-2"
|
||||
type="checkbox"
|
||||
:checked="checked"
|
||||
@change="$emit('handleCheckboxChange', $event.target.checked)"
|
||||
/>
|
||||
<user-avatar-link
|
||||
:link-href="authorUrl"
|
||||
:img-src="authorAvatar"
|
||||
:img-alt="authorName"
|
||||
:img-size="40"
|
||||
class="avatar-cell d-none d-sm-block"
|
||||
/>
|
||||
</div>
|
||||
<div class="commit-detail flex-list">
|
||||
<div class="commit-content qa-commit-content">
|
||||
<a
|
||||
|
@ -151,7 +174,7 @@ export default {
|
|||
v-if="commit.description_html"
|
||||
:class="{ 'js-toggle-content': collapsible, 'd-block': !collapsible }"
|
||||
class="commit-row-description gl-mb-3 text-dark"
|
||||
v-html="commit.description_html"
|
||||
v-html="commitDescription"
|
||||
></pre>
|
||||
</div>
|
||||
<div class="commit-actions flex-row d-none d-sm-flex">
|
||||
|
|
|
@ -21,6 +21,7 @@ import { localTimeAgo } from './lib/utils/datetime_utility';
|
|||
import syntaxHighlight from './syntax_highlight';
|
||||
import Notes from './notes';
|
||||
import { polyfillSticky } from './lib/utils/sticky';
|
||||
import initAddContextCommitsTriggers from './add_context_commits_modal';
|
||||
import { __ } from './locale';
|
||||
|
||||
// MergeRequestTabs
|
||||
|
@ -340,6 +341,7 @@ export default class MergeRequestTabs {
|
|||
this.scrollToElement('#commits');
|
||||
|
||||
this.toggleLoading(false);
|
||||
initAddContextCommitsTriggers();
|
||||
})
|
||||
.catch(() => {
|
||||
this.toggleLoading(false);
|
||||
|
|
|
@ -7,6 +7,7 @@ export default () => ({
|
|||
deploymentsEndpoint: null,
|
||||
dashboardEndpoint: invalidUrl,
|
||||
dashboardsEndpoint: invalidUrl,
|
||||
panelPreviewEndpoint: invalidUrl,
|
||||
|
||||
// Dashboard request parameters
|
||||
timeRange: null,
|
||||
|
|
|
@ -24,6 +24,7 @@ export const stateAndPropsFromDataset = (dataset = {}) => {
|
|||
deploymentsEndpoint,
|
||||
dashboardEndpoint,
|
||||
dashboardsEndpoint,
|
||||
panelPreviewEndpoint,
|
||||
dashboardTimezone,
|
||||
canAccessOperationsSettings,
|
||||
operationsSettingsPath,
|
||||
|
@ -45,6 +46,7 @@ export const stateAndPropsFromDataset = (dataset = {}) => {
|
|||
deploymentsEndpoint,
|
||||
dashboardEndpoint,
|
||||
dashboardsEndpoint,
|
||||
panelPreviewEndpoint,
|
||||
dashboardTimezone,
|
||||
canAccessOperationsSettings,
|
||||
operationsSettingsPath,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { GlLoadingIcon, GlTooltipDirective, GlDeprecatedButton } from '@gitlab/ui';
|
||||
import { GlTooltipDirective, GlButton } from '@gitlab/ui';
|
||||
import CiStatus from '~/vue_shared/components/ci_icon.vue';
|
||||
import { __, sprintf } from '~/locale';
|
||||
|
||||
|
@ -9,8 +9,7 @@ export default {
|
|||
},
|
||||
components: {
|
||||
CiStatus,
|
||||
GlLoadingIcon,
|
||||
GlDeprecatedButton,
|
||||
GlButton,
|
||||
},
|
||||
props: {
|
||||
pipeline: {
|
||||
|
@ -95,26 +94,21 @@ export default {
|
|||
@mouseover="onDownstreamHovered"
|
||||
@mouseleave="onDownstreamHoverLeave"
|
||||
>
|
||||
<gl-deprecated-button
|
||||
<gl-button
|
||||
:id="buttonId"
|
||||
v-gl-tooltip
|
||||
:title="tooltipText"
|
||||
class="js-linked-pipeline-content linked-pipeline-content"
|
||||
class="linked-pipeline-content"
|
||||
data-qa-selector="linked_pipeline_button"
|
||||
:class="`js-pipeline-expand-${pipeline.id}`"
|
||||
:loading="pipeline.isLoading"
|
||||
@click="onClickLinkedPipeline"
|
||||
>
|
||||
<gl-loading-icon v-if="pipeline.isLoading" class="js-linked-pipeline-loading d-inline" />
|
||||
<ci-status
|
||||
v-else
|
||||
:status="pipelineStatus"
|
||||
css-classes="position-top-0"
|
||||
class="js-linked-pipeline-status"
|
||||
/>
|
||||
<ci-status v-if="!pipeline.isLoading" :status="pipelineStatus" css-classes="gl-top-0" />
|
||||
<span class="str-truncated"> {{ downstreamTitle }} • #{{ pipeline.id }} </span>
|
||||
<div class="gl-pt-2">
|
||||
<span class="badge badge-primary" data-testid="downstream-pipeline-label">{{ label }}</span>
|
||||
</div>
|
||||
</gl-deprecated-button>
|
||||
</gl-button>
|
||||
</li>
|
||||
</template>
|
||||
|
|
|
@ -388,3 +388,9 @@
|
|||
display: block;
|
||||
color: $link-color;
|
||||
}
|
||||
|
||||
.add-review-item {
|
||||
.gl-tab-nav-item {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -225,7 +225,6 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
|
|||
:lets_encrypt_terms_of_service_accepted,
|
||||
:domain_blacklist_file,
|
||||
:raw_blob_request_limit,
|
||||
:namespace_storage_size_limit,
|
||||
:issues_create_limit,
|
||||
:default_branch_name,
|
||||
disabled_oauth_sign_in_sources: [],
|
||||
|
|
|
@ -82,7 +82,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
|
|||
@note = @project.notes.new(noteable: @merge_request)
|
||||
|
||||
@noteable = @merge_request
|
||||
@commits_count = @merge_request.commits_count
|
||||
@commits_count = @merge_request.commits_count + @merge_request.context_commits_count
|
||||
@issuable_sidebar = serializer.represent(@merge_request, serializer: 'sidebar')
|
||||
@current_user_data = UserSerializer.new(project: @project).represent(current_user, {}, MergeRequestUserEntity).to_json
|
||||
@show_whitespace_default = current_user.nil? || current_user.show_whitespace_in_diffs
|
||||
|
@ -116,6 +116,12 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
|
|||
end
|
||||
|
||||
def commits
|
||||
# Get context commits from repository
|
||||
@context_commits =
|
||||
set_commits_for_rendering(
|
||||
@merge_request.recent_context_commits
|
||||
)
|
||||
|
||||
# Get commits from repository
|
||||
# or from cache if already merged
|
||||
@commits =
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Projects
|
||||
module Metrics
|
||||
module Dashboards
|
||||
class BuilderController < Projects::ApplicationController
|
||||
before_action :ensure_feature_flags
|
||||
before_action :authorize_metrics_dashboard!
|
||||
|
||||
def panel_preview
|
||||
respond_to do |format|
|
||||
format.json { render json: render_panel }
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def ensure_feature_flags
|
||||
render_404 unless Feature.enabled?(:metrics_dashboard_new_panel_page, project)
|
||||
end
|
||||
|
||||
def render_panel
|
||||
{
|
||||
"title": "Memory Usage (Total)",
|
||||
"type": "area-chart",
|
||||
"y_label": "Total Memory Used (GB)",
|
||||
"weight": 4,
|
||||
"metrics": [
|
||||
{
|
||||
"id": "system_metrics_kubernetes_container_memory_total",
|
||||
"query_range": "avg(sum(container_memory_usage_bytes{container_name!=\"POD\",pod_name=~\"^{{ci_environment_slug}}-(.*)\",namespace=\"{{kube_namespace}}\"}) by (job)) without (job) /1024/1024/1024",
|
||||
"label": "Total (GB)",
|
||||
"unit": "GB",
|
||||
"metric_id": 15,
|
||||
"edit_path": nil,
|
||||
"prometheus_endpoint_path": "/root/autodevops-deploy/-/environments/29/prometheus/api/v1/query_range?query=avg%28sum%28container_memory_usage_bytes%7Bcontainer_name%21%3D%22POD%22%2Cpod_name%3D~%22%5E%7B%7Bci_environment_slug%7D%7D-%28.%2A%29%22%2Cnamespace%3D%22%7B%7Bkube_namespace%7D%7D%22%7D%29+by+%28job%29%29+without+%28job%29++%2F1024%2F1024%2F1024"
|
||||
}
|
||||
],
|
||||
"id": "4570deed516d0bf93fb42879004117009ab456ced27393ec8dce5b6960438132"
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -25,7 +25,7 @@ class ContextCommitsFinder
|
|||
if search.present?
|
||||
search_commits
|
||||
else
|
||||
project.repository.commits(merge_request.source_branch, { limit: limit, offset: offset })
|
||||
project.repository.commits(merge_request.target_branch, { limit: limit, offset: offset })
|
||||
end
|
||||
|
||||
commits
|
||||
|
@ -47,7 +47,7 @@ class ContextCommitsFinder
|
|||
commits = [commit_by_sha] if commit_by_sha
|
||||
end
|
||||
else
|
||||
commits = project.repository.find_commits_by_message(search, nil, nil, 20)
|
||||
commits = project.repository.find_commits_by_message(search, merge_request.target_branch, nil, 20)
|
||||
end
|
||||
|
||||
commits
|
||||
|
|
|
@ -98,7 +98,8 @@ module EnvironmentsHelper
|
|||
'deployments-endpoint' => project_environment_deployments_path(project, environment, format: :json),
|
||||
'alerts-endpoint' => project_prometheus_alerts_path(project, environment_id: environment.id, format: :json),
|
||||
'operations-settings-path' => project_settings_operations_path(project),
|
||||
'can-access-operations-settings' => can?(current_user, :admin_operations, project).to_s
|
||||
'can-access-operations-settings' => can?(current_user, :admin_operations, project).to_s,
|
||||
'panel-preview-endpoint' => project_metrics_dashboards_builder_path(project, format: :json)
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
@ -5,6 +5,9 @@ class ApplicationSetting < ApplicationRecord
|
|||
include CacheMarkdownField
|
||||
include TokenAuthenticatable
|
||||
include ChronicDurationAttribute
|
||||
include IgnorableColumns
|
||||
|
||||
ignore_column :namespace_storage_size_limit, remove_with: '13.5', remove_after: '2020-09-22'
|
||||
|
||||
GRAFANA_URL_ERROR_MESSAGE = 'Please check your Grafana URL setting in ' \
|
||||
'Admin Area > Settings > Metrics and profiling > Metrics - Grafana'
|
||||
|
@ -363,10 +366,6 @@ class ApplicationSetting < ApplicationRecord
|
|||
length: { maximum: 255 },
|
||||
allow_blank: true
|
||||
|
||||
validates :namespace_storage_size_limit,
|
||||
presence: true,
|
||||
numericality: { only_integer: true, greater_than_or_equal_to: 0 }
|
||||
|
||||
validates :issues_create_limit,
|
||||
numericality: { only_integer: true, greater_than_or_equal_to: 0 }
|
||||
|
||||
|
|
|
@ -96,7 +96,6 @@ module ApplicationSettingImplementation
|
|||
max_import_size: 50,
|
||||
minimum_password_length: DEFAULT_MINIMUM_PASSWORD_LENGTH,
|
||||
mirror_available: true,
|
||||
namespace_storage_size_limit: 0,
|
||||
notify_on_unknown_sign_in: true,
|
||||
outbound_local_requests_whitelist: [],
|
||||
password_authentication_enabled_for_git: true,
|
||||
|
|
|
@ -40,7 +40,7 @@ class MergeRequest < ApplicationRecord
|
|||
has_internal_id :iid, scope: :target_project, track_if: -> { !importing? }, init: ->(s) { s&.target_project&.merge_requests&.maximum(:iid) }
|
||||
|
||||
has_many :merge_request_diffs
|
||||
has_many :merge_request_context_commits
|
||||
has_many :merge_request_context_commits, inverse_of: :merge_request
|
||||
has_many :merge_request_context_commit_diff_files, through: :merge_request_context_commits, source: :diff_files
|
||||
|
||||
has_one :merge_request_diff,
|
||||
|
@ -427,7 +427,7 @@ class MergeRequest < ApplicationRecord
|
|||
end
|
||||
|
||||
def context_commits(limit: nil)
|
||||
@context_commits ||= merge_request_context_commits.limit(limit).map(&:to_commit)
|
||||
@context_commits ||= merge_request_context_commits.order_by_committed_date_desc.limit(limit).map(&:to_commit)
|
||||
end
|
||||
|
||||
def recent_context_commits
|
||||
|
|
|
@ -12,6 +12,9 @@ class MergeRequestContextCommit < ApplicationRecord
|
|||
validates :sha, presence: true
|
||||
validates :sha, uniqueness: { message: 'has already been added' }
|
||||
|
||||
# Sort by committed date in descending order to ensure latest commits comes on the top
|
||||
scope :order_by_committed_date_desc, -> { order('committed_date DESC') }
|
||||
|
||||
# delete all MergeRequestContextCommit & MergeRequestContextCommitDiffFile for given merge_request & commit SHAs
|
||||
def self.delete_bulk(merge_request, commits)
|
||||
commit_ids = commits.map(&:sha)
|
||||
|
|
|
@ -8,14 +8,6 @@
|
|||
= f.label :gravatar_enabled, class: 'form-check-label' do
|
||||
= _('Gravatar enabled')
|
||||
|
||||
.form-group
|
||||
= f.label :namespace_storage_size_limit, class: 'label-bold' do
|
||||
= _('Maximum namespace storage (MB)')
|
||||
= f.number_field :namespace_storage_size_limit, class: 'form-control', min: 0
|
||||
%span.form-text.text-muted
|
||||
= _('Includes repository storage, wiki storage, LFS objects, build artifacts and packages. 0 for unlimited.')
|
||||
= link_to _('More information'), help_page_path('user/admin_area/settings/account_and_limit_settings', anchor: 'maximum-namespace-storage-size'), target: '_blank'
|
||||
|
||||
.form-group
|
||||
= f.label :default_projects_limit, _('Default projects limit'), class: 'label-bold'
|
||||
= f.number_field :default_projects_limit, class: 'form-control', title: _('Maximum number of projects.'), data: { toggle: 'tooltip', container: 'body' }
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
- merge_request = local_assigns.fetch(:merge_request, nil)
|
||||
- project = local_assigns.fetch(:project) { merge_request&.project }
|
||||
- ref = local_assigns.fetch(:ref) { merge_request&.source_branch }
|
||||
- can_update_merge_request = can?(current_user, :update_merge_request, @merge_request)
|
||||
|
||||
- commits = @commits
|
||||
- context_commits = @context_commits
|
||||
- hidden = @hidden_commit_count
|
||||
|
||||
- commits.chunk { |c| c.committed_date.in_time_zone.to_date }.each do |day, daily_commits|
|
||||
|
@ -14,11 +16,26 @@
|
|||
%ul.content-list.commit-list.flex-list
|
||||
= render partial: 'projects/commits/commit', collection: daily_commits, locals: { project: project, ref: ref, merge_request: merge_request }
|
||||
|
||||
- if context_commits.present?
|
||||
%li.commit-header.js-commit-header
|
||||
%span.font-weight-bold= n_("%d previously merged commit", "%d previously merged commits", context_commits.count) % context_commits.count
|
||||
- if project.context_commits_enabled? && can_update_merge_request
|
||||
%button.btn.btn-default.ml-3.add-review-item-modal-trigger{ type: "button", data: { context_commits_empty: 'false' } }
|
||||
= _('Add/remove')
|
||||
|
||||
%li.commits-row
|
||||
%ul.content-list.commit-list.flex-list
|
||||
= render partial: 'projects/commits/commit', collection: context_commits, locals: { project: project, ref: ref, merge_request: merge_request }
|
||||
|
||||
- if hidden > 0
|
||||
%li.alert.alert-warning
|
||||
= n_('%s additional commit has been omitted to prevent performance issues.', '%s additional commits have been omitted to prevent performance issues.', hidden) % number_with_delimiter(hidden)
|
||||
|
||||
- if commits.size == 0
|
||||
- if project.context_commits_enabled? && can_update_merge_request && context_commits&.empty?
|
||||
%button.btn.btn-default.mt-3.add-review-item-modal-trigger{ type: "button", data: { context_commits_empty: 'true' } }
|
||||
= _('Add previously merged commits')
|
||||
|
||||
- if commits.size == 0 && context_commits.nil?
|
||||
.mt-4.text-center
|
||||
.bold
|
||||
= _('Your search didn\'t match any commits.')
|
||||
|
|
|
@ -1,8 +1,18 @@
|
|||
- if @commits.empty?
|
||||
.commits-empty
|
||||
%h4
|
||||
There are no commits yet.
|
||||
- can_update_merge_request = can?(current_user, :update_merge_request, @merge_request)
|
||||
|
||||
- if @commits.empty? && @context_commits.empty?
|
||||
.commits-empty.mt-5
|
||||
= custom_icon ('illustration_no_commits')
|
||||
%h4
|
||||
= _('There are no commits yet.')
|
||||
- if @project&.context_commits_enabled? && can_update_merge_request
|
||||
%p
|
||||
= _('Push commits to the source branch or add previously merged commits to review them.')
|
||||
%button.btn.btn-primary.add-review-item-modal-trigger{ type: "button", data: { commits_empty: 'true', context_commits_empty: 'true' } }
|
||||
= _('Add previously merged commits')
|
||||
- else
|
||||
%ol#commits-list.list-unstyled
|
||||
= render "projects/commits/commits", merge_request: @merge_request
|
||||
|
||||
- if @project&.context_commits_enabled? && can_update_merge_request && @merge_request.iid
|
||||
.add-review-item-modal-wrapper{ data: { context_commits_path: context_commits_project_json_merge_request_url(@merge_request&.project, @merge_request, :json), target_branch: @merge_request.target_branch, merge_request_iid: @merge_request.iid, project_id: @merge_request.project.id } }
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Improve performance of Banzai reference filters
|
||||
merge_request: 38290
|
||||
author:
|
||||
type: performance
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Replace <gl-deprecated-button> with <gl-button> in app/assets/javascripts/pipelines/components/graph/linked_pipeline.vue
|
||||
merge_request: 36968
|
||||
author:
|
||||
type: other
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Remove namespace storage limit setting
|
||||
merge_request: 38108
|
||||
author:
|
||||
type: removed
|
|
@ -28,6 +28,12 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
|
|||
get 'metrics(/:dashboard_path)(/:page)', constraints: { dashboard_path: /.+\.yml/, page: 'panel/new' },
|
||||
to: 'metrics_dashboard#show', as: :metrics_dashboard, format: false
|
||||
|
||||
namespace :metrics, module: :metrics do
|
||||
namespace :dashboards do
|
||||
post :builder, to: 'builder#panel_preview'
|
||||
end
|
||||
end
|
||||
|
||||
resources :artifacts, only: [:index, :destroy]
|
||||
|
||||
resources :packages, only: [:index, :show, :destroy], module: :packages
|
||||
|
|
|
@ -141,7 +141,7 @@ sudo gitlab-rake geo:verification:wiki:reset
|
|||
|
||||
If the **primary** and **secondary** nodes have a checksum verification mismatch, the cause may not be apparent. To find the cause of a checksum mismatch:
|
||||
|
||||
1. Navigate to the **Admin Area >** **{overview}** **Overview > Projects** dashboard on the **primary** node, find the
|
||||
1. Navigate to the **Admin Area > Overview > Projects** dashboard on the **primary** node, find the
|
||||
project that you want to check the checksum differences and click on the
|
||||
**Edit** button:
|
||||
![Projects dashboard](img/checksum-differences-admin-projects.png)
|
||||
|
|
|
@ -135,7 +135,7 @@ This [content was moved to another location](background_verification.md).
|
|||
|
||||
### Notify users of scheduled maintenance
|
||||
|
||||
On the **primary** node, navigate to **Admin Area >** **{bullhorn}** **Messages**, add a broadcast
|
||||
On the **primary** node, navigate to **Admin Area > Messages**, add a broadcast
|
||||
message. You can check under **Admin Area > Geo** to estimate how long it
|
||||
will take to finish syncing. An example message would be:
|
||||
|
||||
|
@ -181,7 +181,7 @@ access to the **primary** node during the maintenance window.
|
|||
connection.
|
||||
|
||||
1. Disable non-Geo periodic background jobs on the **primary** node by navigating
|
||||
to **Admin Area >** **{monitor}** **Monitoring > Background Jobs > Cron**, pressing `Disable All`,
|
||||
to **Admin Area > Monitoring > Background Jobs > Cron**, pressing `Disable All`,
|
||||
and then pressing `Enable` for the `geo_sidekiq_cron_config_worker` cron job.
|
||||
This job will re-enable several other cron jobs that are essential for planned
|
||||
failover to complete successfully.
|
||||
|
@ -190,7 +190,7 @@ access to the **primary** node during the maintenance window.
|
|||
|
||||
1. If you are manually replicating any data not managed by Geo, trigger the
|
||||
final replication process now.
|
||||
1. On the **primary** node, navigate to **Admin Area >** **{monitor}** **Monitoring > Background Jobs > Queues**
|
||||
1. On the **primary** node, navigate to **Admin Area > Monitoring > Background Jobs > Queues**
|
||||
and wait for all queues except those with `geo` in the name to drop to 0.
|
||||
These queues contain work that has been submitted by your users; failing over
|
||||
before it is completed will cause the work to be lost.
|
||||
|
@ -202,7 +202,7 @@ access to the **primary** node during the maintenance window.
|
|||
- Database replication lag is 0ms.
|
||||
- The Geo log cursor is up to date (0 events behind).
|
||||
|
||||
1. On the **secondary** node, navigate to **Admin Area >** **{monitor}** **Monitoring > Background Jobs > Queues**
|
||||
1. On the **secondary** node, navigate to **Admin Area > Monitoring > Background Jobs > Queues**
|
||||
and wait for all the `geo` queues to drop to 0 queued and 0 running jobs.
|
||||
1. On the **secondary** node, use [these instructions](../../raketasks/check.md)
|
||||
to verify the integrity of CI artifacts, LFS objects, and uploads in file
|
||||
|
|
|
@ -10,7 +10,7 @@ To profile a request:
|
|||
|
||||
1. Sign in to GitLab as a user with Administrator or Maintainer [permissions](../../../user/permissions.md).
|
||||
1. In the navigation bar, click **Admin area**.
|
||||
1. Navigate to **{monitor}** **Monitoring > Requests Profiles**.
|
||||
1. Navigate to **Monitoring > Requests Profiles**.
|
||||
1. In the **Requests Profiles** section, copy the token.
|
||||
1. Pass the headers `X-Profile-Token: <token>` and `X-Profile-Mode: <mode>`(where
|
||||
`<mode>` can be `execution` or `memory`) to the request you want to profile. When
|
||||
|
@ -29,7 +29,7 @@ To profile a request:
|
|||
Profiled requests can take longer than usual.
|
||||
|
||||
After the request completes, you can view the profiling output from the
|
||||
**{monitor}** **Monitoring > Requests Profiles** administration page:
|
||||
**Monitoring > Requests Profiles** administration page:
|
||||
|
||||
![Profiling output](img/request_profile_result.png)
|
||||
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 52 KiB |
Binary file not shown.
After Width: | Height: | Size: 18 KiB |
|
@ -15,10 +15,10 @@ of incident management are only available in
|
|||
[GitLab Ultimate and GitLab.com Gold](https://about.gitlab.com/pricing/).
|
||||
|
||||
For users with at least Developer [permissions](../../user/permissions.md), the
|
||||
Incident Management list is available at **{cloud-gear}** **Operations > Incidents**
|
||||
Incident Management list is available at **Operations > Incidents**
|
||||
in your project's sidebar. The list contains the following metrics:
|
||||
|
||||
![Incident Management List](img/incident_list_13_3.png)
|
||||
![Incident Management List](img/incident_list_v13_3.png)
|
||||
|
||||
- **Incident** - The description of the incident, which attempts to capture the
|
||||
most meaningful data.
|
||||
|
@ -34,9 +34,9 @@ Incidents share the [Issues API](../../user/project/issues/index.md).
|
|||
|
||||
> [Moved](https://gitlab.com/gitlab-org/monitor/health/-/issues/24) to GitLab core in 13.3.
|
||||
|
||||
To create a Incident you can take any of the following actions:
|
||||
For users with at least Developer [permissions](../../user/permissions.md), to create a Incident you can take any of the following actions:
|
||||
|
||||
- Navigate to **{cloud-gear}** **Operations > Incidents** and click **Create Incident**.
|
||||
- Navigate to **Operations > Incidents** and click **Create Incident**.
|
||||
- Create a new issue using the `incident` template available when creating it.
|
||||
- Create a new issue and assign the `incident` label to it.
|
||||
|
||||
|
@ -44,7 +44,7 @@ To create a Incident you can take any of the following actions:
|
|||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/4925) in GitLab Ultimate 11.11.
|
||||
|
||||
You can enable or disable Incident Management features in the GitLab user interface
|
||||
With Maintainer or higher [permissions](../../user/permissions.md), you can enable or disable Incident Management features in the GitLab user interface
|
||||
to create issues when alerts are triggered:
|
||||
|
||||
1. Navigate to **Settings > Operations > Incidents** and expand
|
||||
|
|
|
@ -19,7 +19,7 @@ For managed Prometheus instances using auto configuration, you can
|
|||
[configure alerts for metrics](index.md#adding-custom-metrics) directly in the
|
||||
[metrics dashboard](index.md). To set an alert:
|
||||
|
||||
1. In your project, navigate to **{cloud-gear}** **Operations > Metrics**,
|
||||
1. In your project, navigate to **Operations > Metrics**,
|
||||
1. Identify the metric you want to create the alert for, and click the
|
||||
**ellipsis** **{ellipsis_v}** icon in the top right corner of the metric.
|
||||
1. Choose **Alerts**.
|
||||
|
|
|
@ -23,15 +23,14 @@ The metrics as defined below do not support alerts, unlike
|
|||
> UI option [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/223204) in GitLab 13.2.
|
||||
|
||||
You can configure a custom dashboard by adding a new YAML file into your project's
|
||||
`.gitlab/dashboards/` directory. For the dashboard to display on your project's
|
||||
**{cloud-gear}** **Operations > Metrics** page, the files must have a `.yml`
|
||||
`.gitlab/dashboards/` directory. For the dashboard to display on your project's **Operations > Metrics** page, the files must have a `.yml`
|
||||
extension and be present in your project's **default** branch.
|
||||
|
||||
To create a new dashboard from the GitLab user interface:
|
||||
|
||||
1. Sign in to GitLab as a user with Maintainer or Owner
|
||||
[permissions](../../../user/permissions.md#project-members-permissions).
|
||||
1. Navigate to your dashboard at **{cloud-gear}** **Operations > Metrics**.
|
||||
1. Navigate to your dashboard at **Operations > Metrics**.
|
||||
1. In the top-right corner of your dashboard, click the **{file-addition-solid}** **Actions** menu,
|
||||
and select **Create new**:
|
||||
![Monitoring Dashboard actions menu with create new item](img/actions_menu_create_new_dashboard_v13_2.png)
|
||||
|
@ -103,7 +102,7 @@ To manage the settings for your metrics dashboard:
|
|||
|
||||
1. Sign in as a user with project Maintainer or Admin
|
||||
[permissions](../../../user/permissions.md#project-members-permissions).
|
||||
1. Navigate to your dashboard at **{cloud-gear}** **Operations > Metrics**.
|
||||
1. Navigate to your dashboard at **Operations > Metrics**.
|
||||
1. In the top-right corner of your dashboard, click **Metrics Settings**:
|
||||
|
||||
![Monitoring Dashboard actions menu with create new item](img/metrics_settings_button_v13_2.png)
|
||||
|
|
|
@ -20,7 +20,7 @@ To view the metrics dashboard for an environment that has
|
|||
|
||||
1. *If the metrics dashboard is only visible to project members,* sign in to
|
||||
GitLab as a member of a project. Learn more about [metrics dashboard visibility](#metrics-dashboard-visibility).
|
||||
1. In your project, navigate to **{cloud-gear}** **Operations > Metrics**.
|
||||
1. In your project, navigate to **Operations > Metrics**.
|
||||
|
||||
GitLab displays the default metrics dashboard for the environment, like the
|
||||
following example:
|
||||
|
@ -52,11 +52,11 @@ navigation bar contains:
|
|||
## Populate your metrics dashboard
|
||||
|
||||
After [configuring Prometheus for a cluster](../../user/project/integrations/prometheus.md),
|
||||
you must also deploy code for the **{cloud-gear}** **Operations > Metrics** page
|
||||
you must also deploy code for the **Operations > Metrics** page
|
||||
to contain data. Setting up [Auto DevOps](../../topics/autodevops/index.md)
|
||||
helps quickly create a deployment:
|
||||
|
||||
1. Navigate to your project's **{cloud-gear}** **Operations > Kubernetes** page.
|
||||
1. Navigate to your project's **Operations > Kubernetes** page.
|
||||
1. Ensure that, in addition to Prometheus, you also have Runner and Ingress
|
||||
installed.
|
||||
1. After installing Ingress, copy its endpoint.
|
||||
|
@ -68,7 +68,7 @@ helps quickly create a deployment:
|
|||
1. Navigate to your project's **{rocket}** **CI/CD > Pipelines** page, and run a
|
||||
pipeline on any branch.
|
||||
1. When the pipeline has run successfully, graphs are available on the
|
||||
**{cloud-gear}** **Operations > Metrics** page.
|
||||
**Operations > Metrics** page.
|
||||
|
||||
![Monitoring Dashboard](img/prometheus_monitoring_dashboard_v13_1.png)
|
||||
|
||||
|
|
|
@ -52,7 +52,7 @@ user interface:
|
|||
|
||||
1. Sign in to GitLab as a user with Reporter or greater
|
||||
[permissions](../user/permissions.md).
|
||||
1. Navigate to **{cloud-gear}** **Operations > Product Analytics**
|
||||
1. Navigate to **Operations > Product Analytics**
|
||||
|
||||
The user interface contains:
|
||||
|
||||
|
|
|
@ -276,14 +276,14 @@ The following table is an example of how to configure the three different cluste
|
|||
|
||||
To add a different cluster for each environment:
|
||||
|
||||
1. Navigate to your project's **{cloud-gear}** **Operations > Kubernetes**.
|
||||
1. Navigate to your project's **Operations > Kubernetes**.
|
||||
1. Create the Kubernetes clusters with their respective environment scope, as
|
||||
described from the table above.
|
||||
1. After creating the clusters, navigate to each cluster and install
|
||||
Ingress. Wait for the Ingress IP address to be assigned.
|
||||
1. Make sure you've [configured your DNS](#auto-devops-base-domain) with the
|
||||
specified Auto DevOps domains.
|
||||
1. Navigate to each cluster's page, through **{cloud-gear}** **Operations > Kubernetes**,
|
||||
1. Navigate to each cluster's page, through **Operations > Kubernetes**,
|
||||
and add the domain based on its Ingress IP address.
|
||||
|
||||
After completing configuration, you can test your setup by creating a merge request
|
||||
|
|
|
@ -57,7 +57,7 @@ to deploy this project to.
|
|||
## Create a Kubernetes cluster from within GitLab
|
||||
|
||||
1. On your project's landing page, click **Add Kubernetes cluster**
|
||||
(note that this option is also available when you navigate to **{cloud-gear}** **Operations > Kubernetes**).
|
||||
(note that this option is also available when you navigate to **Operations > Kubernetes**).
|
||||
|
||||
![Project landing page](img/guide_project_landing_page_v12_10.png)
|
||||
|
||||
|
@ -194,7 +194,7 @@ to monitor it.
|
|||
|
||||
After successfully deploying your application, you can view its website and check
|
||||
on its health on the **Environments** page by navigating to
|
||||
**{cloud-gear}** **Operations > Environments**. This page displays details about
|
||||
**Operations > Environments**. This page displays details about
|
||||
the deployed applications, and the right-hand column displays icons that link
|
||||
you to common environment tasks:
|
||||
|
||||
|
|
|
@ -650,6 +650,6 @@ To use Auto Monitoring:
|
|||
1. After the pipeline finishes successfully, open the
|
||||
[monitoring dashboard for a deployed environment](../../ci/environments/index.md#monitoring-environments)
|
||||
to view the metrics of your deployed application. To view the metrics of the
|
||||
whole Kubernetes cluster, navigate to **{cloud-gear}** **Operations > Metrics**.
|
||||
whole Kubernetes cluster, navigate to **Operations > Metrics**.
|
||||
|
||||
![Auto Metrics](img/auto_monitoring.png)
|
||||
|
|
|
@ -59,13 +59,13 @@ The Dashboard is the default view of the Admin Area, and is made up of the follo
|
|||
|
||||
## Overview section
|
||||
|
||||
The following topics document the **{overview}** **Overview** section of the Admin Area.
|
||||
The following topics document the **Overview** section of the Admin Area.
|
||||
|
||||
### Administering Projects
|
||||
|
||||
You can administer all projects in the GitLab instance from the Admin Area's Projects page.
|
||||
|
||||
To access the Projects page, go to **Admin Area >** **{overview}** **Overview > Projects**.
|
||||
To access the Projects page, go to **Admin Area > Overview > Projects**.
|
||||
|
||||
Click the **All**, **Private**, **Internal**, or **Public** tab to list only projects of that
|
||||
criteria.
|
||||
|
@ -105,7 +105,7 @@ You can combine the filter options. For example, to list only public projects wi
|
|||
|
||||
You can administer all users in the GitLab instance from the Admin Area's Users page.
|
||||
|
||||
To access the Users page, go to **Admin Area >** **{overview}** **Overview > Users**.
|
||||
To access the Users page, go to **Admin Area > Overview > Users**.
|
||||
|
||||
To list users matching a specific criteria, click on one of the following tabs on the **Users** page:
|
||||
|
||||
|
@ -157,7 +157,7 @@ reflected in the statistics.
|
|||
|
||||
You can administer all groups in the GitLab instance from the Admin Area's Groups page.
|
||||
|
||||
To access the Groups page, go to **Admin Area >** **{overview}** **Overview > Groups**.
|
||||
To access the Groups page, go to **Admin Area > Overview > Groups**.
|
||||
|
||||
For each group, the page displays their name, description, size, number of projects in the group,
|
||||
number of members, and whether the group is private, internal, or public. To edit a group, click
|
||||
|
@ -176,7 +176,7 @@ To [Create a new group](../group/index.md#create-a-new-group) click **New group*
|
|||
|
||||
You can administer all jobs in the GitLab instance from the Admin Area's Jobs page.
|
||||
|
||||
To access the Jobs page, go to **Admin Area >** **{overview}** **Overview > Jobs**.
|
||||
To access the Jobs page, go to **Admin Area > Overview > Jobs**.
|
||||
|
||||
All jobs are listed, in descending order of job ID.
|
||||
|
||||
|
@ -201,7 +201,7 @@ For each job, the following details are listed:
|
|||
You can administer all Runners in the GitLab instance from the Admin Area's **Runners** page. See
|
||||
[GitLab Runner](https://docs.gitlab.com/runner/) for more information on Runner itself.
|
||||
|
||||
To access the **Runners** page, go to **Admin Area >** **{overview}** **Overview > Runners**.
|
||||
To access the **Runners** page, go to **Admin Area > Overview > Runners**.
|
||||
|
||||
The **Runners** page features:
|
||||
|
||||
|
@ -247,7 +247,7 @@ You can also edit, pause, or remove each Runner.
|
|||
You can list all Gitaly servers in the GitLab instance from the Admin Area's **Gitaly Servers**
|
||||
page. For more details, see [Gitaly](../../administration/gitaly/index.md).
|
||||
|
||||
To access the **Gitaly Servers** page, go to **Admin Area >** **{overview}** **Overview > Gitaly Servers**.
|
||||
To access the **Gitaly Servers** page, go to **Admin Area > Overview > Gitaly Servers**.
|
||||
|
||||
For each Gitaly server, the following details are listed:
|
||||
|
||||
|
@ -261,7 +261,7 @@ For each Gitaly server, the following details are listed:
|
|||
|
||||
## Monitoring section
|
||||
|
||||
The following topics document the **{monitor}** **Monitoring** section of the Admin Area.
|
||||
The following topics document the **Monitoring** section of the Admin Area.
|
||||
|
||||
### System Info
|
||||
|
||||
|
|
|
@ -29,20 +29,6 @@ If you choose a size larger than what is currently configured for the web server
|
|||
you will likely get errors. See the [troubleshooting section](#troubleshooting) for more
|
||||
details.
|
||||
|
||||
## Maximum namespace storage size
|
||||
|
||||
This sets a maximum size limit on each namespace. The following are included in the namespace size:
|
||||
|
||||
- Repository
|
||||
- Wiki
|
||||
- LFS objects
|
||||
- Build artifacts
|
||||
- Packages
|
||||
- Snippets
|
||||
|
||||
NOTE: **Note:**
|
||||
This limit is not currently enforced but will be in a future release.
|
||||
|
||||
## Repository size limit **(STARTER ONLY)**
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/740) in [GitLab Enterprise Edition 8.12](https://about.gitlab.com/releases/2016/09/22/gitlab-8-12-released/#limit-project-size-ee).
|
||||
|
|
|
@ -507,6 +507,7 @@ To use SAST in an offline environment, you need:
|
|||
- To keep Docker-In-Docker disabled (default).
|
||||
- A GitLab Runner with the [`docker` or `kubernetes` executor](#requirements).
|
||||
- A Docker Container Registry with locally available copies of SAST [analyzer](https://gitlab.com/gitlab-org/security-products/analyzers) images.
|
||||
- Configure certificate checking of packages (optional).
|
||||
|
||||
NOTE: **Note:**
|
||||
GitLab Runner has a [default `pull policy` of `always`](https://docs.gitlab.com/runner/executors/docker.html#using-the-always-pull-policy),
|
||||
|
@ -563,6 +564,13 @@ variables:
|
|||
The SAST job should now use local copies of the SAST analyzers to scan your code and generate
|
||||
security reports without requiring internet access.
|
||||
|
||||
### Configure certificate checking of packages
|
||||
|
||||
If a SAST job invokes a package manager, you must configure its certificate verification. In an
|
||||
offline environment, certificate verification with an external source isn't possible. Either use a
|
||||
self-signed certificate or disable certificate verification. Refer to the package manager's
|
||||
documentation for instructions.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### `Error response from daemon: error processing tar file: docker-tar: relocation error`
|
||||
|
|
|
@ -28,9 +28,9 @@ This namespace:
|
|||
To see a list of available applications to install. For a:
|
||||
|
||||
- [Project-level cluster](../project/clusters/index.md), navigate to your project's
|
||||
**{cloud-gear}** **Operations > Kubernetes**.
|
||||
**Operations > Kubernetes**.
|
||||
- [Group-level cluster](../group/clusters/index.md), navigate to your group's
|
||||
**{cloud-gear}** **Kubernetes** page.
|
||||
**Kubernetes** page.
|
||||
|
||||
NOTE: **Note:**
|
||||
As of GitLab 11.6, Helm will be upgraded to the latest version supported
|
||||
|
@ -343,7 +343,7 @@ To help you tune your WAF rules, you can globally set your WAF to either
|
|||
To change your WAF's mode:
|
||||
|
||||
1. [Install ModSecurity](../../topics/web_application_firewall/quick_start_guide.md) if you have not already done so.
|
||||
1. Navigate to **{cloud-gear}** **Operations > Kubernetes**.
|
||||
1. Navigate to **Operations > Kubernetes**.
|
||||
1. In **Applications**, scroll to **Ingress**.
|
||||
1. Under **Global default**, select your desired mode.
|
||||
1. Click **Save changes**.
|
||||
|
@ -535,7 +535,7 @@ To enable log shipping:
|
|||
|
||||
1. Ensure your cluster contains at least 3 nodes of instance types larger than
|
||||
`f1-micro`, `g1-small`, or `n1-standard-1`.
|
||||
1. Navigate to **{cloud-gear}** **Operations > Kubernetes**.
|
||||
1. Navigate to **Operations > Kubernetes**.
|
||||
1. In **Kubernetes Cluster**, select a cluster.
|
||||
1. In the **Applications** section, find **Elastic Stack** and click **Install**.
|
||||
|
||||
|
@ -601,7 +601,7 @@ your data. Fluentd sends logs in syslog format.
|
|||
|
||||
To enable Fluentd:
|
||||
|
||||
1. Navigate to **{cloud-gear}** **Operations > Kubernetes** and click
|
||||
1. Navigate to **Operations > Kubernetes** and click
|
||||
**Applications**. You will be prompted to enter a host, port and protocol
|
||||
where the WAF logs will be sent to via syslog.
|
||||
1. Provide the host domain name or URL in **SIEM Hostname**.
|
||||
|
|
|
@ -19,6 +19,9 @@ To access the Compliance Dashboard for a group, navigate to **{shield}** **Secur
|
|||
|
||||
![Compliance Dashboard](img/compliance_dashboard_v13_3.png)
|
||||
|
||||
NOTE: **Note:**
|
||||
The Compliance Dashboard shows only the latest MR on each project.
|
||||
|
||||
## Use cases
|
||||
|
||||
This feature is for people who care about the compliance status of projects within their group.
|
||||
|
|
|
@ -84,7 +84,7 @@ your cluster, which can cause deployment jobs to fail.
|
|||
|
||||
To clear the cache:
|
||||
|
||||
1. Navigate to your group’s **{cloud-gear}** **Kubernetes** page,
|
||||
1. Navigate to your group’s **Kubernetes** page,
|
||||
and select your cluster.
|
||||
1. Expand the **Advanced settings** section.
|
||||
1. Click **Clear cluster cache**.
|
||||
|
|
|
@ -7,13 +7,12 @@ type: reference, howto, concepts
|
|||
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/2772) in GitLab 9.0.
|
||||
|
||||
GitLab supports up to 20 levels of subgroups, also known as nested groups or hierarchical groups.
|
||||
levels of groups.
|
||||
|
||||
By using subgroups you can do the following:
|
||||
|
||||
- **Separate internal / external organizations.** Since every group
|
||||
can have its own visibility level, you are able to host groups for different
|
||||
purposes under the same umbrella.
|
||||
can have its own visibility level ([public, internal, or private](../../../development/permissions.md#general-permissions)),
|
||||
you're able to host groups for different purposes under the same umbrella.
|
||||
- **Organize large projects.** For large projects, subgroups makes it
|
||||
potentially easier to separate permissions on parts of the source code.
|
||||
- **Make it easier to manage people and control visibility.** Give people
|
||||
|
|
|
@ -4,9 +4,9 @@ group: Package
|
|||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
|
||||
---
|
||||
|
||||
# Dependency Proxy **(PREMIUM ONLY)**
|
||||
# Dependency Proxy **(PREMIUM)**
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/7934) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 11.11.
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/7934) in [GitLab Premium](https://about.gitlab.com/pricing/) 11.11.
|
||||
|
||||
NOTE: **Note:**
|
||||
This is the user guide. In order to use the dependency proxy, an administrator
|
||||
|
@ -82,6 +82,6 @@ for more details.
|
|||
|
||||
The following limitations apply:
|
||||
|
||||
- Only public groups are supported (authentication is not supported yet).
|
||||
- Only [public groups are supported](https://gitlab.com/gitlab-org/gitlab/-/issues/11582) (authentication is not supported yet).
|
||||
- Only Docker Hub is supported.
|
||||
- This feature requires Docker Hub being available.
|
||||
|
|
|
@ -56,9 +56,9 @@ Generate an access key for the IAM user, and configure GitLab with the credentia
|
|||
To create and add a new Kubernetes cluster to your project, group, or instance:
|
||||
|
||||
1. Navigate to your:
|
||||
- Project's **{cloud-gear}** **Operations > Kubernetes** page, for a project-level cluster.
|
||||
- Group's **{cloud-gear}** **Kubernetes** page, for a group-level cluster.
|
||||
- **Admin Area >** **{cloud-gear}** **Kubernetes**, for an instance-level cluster.
|
||||
- Project's **Operations > Kubernetes** page, for a project-level cluster.
|
||||
- Group's **Kubernetes** page, for a group-level cluster.
|
||||
- **Admin Area > Kubernetes**, for an instance-level cluster.
|
||||
1. Click **Add Kubernetes cluster**.
|
||||
1. Under the **Create new cluster** tab, click **Amazon EKS**. You will be provided with an
|
||||
`Account ID` and `External ID` to use in the next step.
|
||||
|
|
|
@ -25,14 +25,12 @@ module Banzai
|
|||
def initialize(doc, context = nil, result = nil)
|
||||
super
|
||||
|
||||
if update_nodes_enabled?
|
||||
@new_nodes = {}
|
||||
@nodes = self.result[:reference_filter_nodes]
|
||||
end
|
||||
@new_nodes = {}
|
||||
@nodes = self.result[:reference_filter_nodes]
|
||||
end
|
||||
|
||||
def call_and_update_nodes
|
||||
update_nodes_enabled? ? with_update_nodes { call } : call
|
||||
with_update_nodes { call }
|
||||
end
|
||||
|
||||
# Returns a data attribute String to attach to a reference link
|
||||
|
@ -165,11 +163,7 @@ module Banzai
|
|||
end
|
||||
|
||||
def replace_text_with_html(node, index, html)
|
||||
if update_nodes_enabled?
|
||||
replace_and_update_new_nodes(node, index, html)
|
||||
else
|
||||
node.replace(html)
|
||||
end
|
||||
replace_and_update_new_nodes(node, index, html)
|
||||
end
|
||||
|
||||
def replace_and_update_new_nodes(node, index, html)
|
||||
|
@ -209,10 +203,6 @@ module Banzai
|
|||
end
|
||||
result[:reference_filter_nodes] = nodes
|
||||
end
|
||||
|
||||
def update_nodes_enabled?
|
||||
Feature.enabled?(:update_nodes_for_banzai_reference_filter, project)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -239,6 +239,11 @@ msgid_plural "%d personal projects will be removed and cannot be restored."
|
|||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
msgid "%d previously merged commit"
|
||||
msgid_plural "%d previously merged commits"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
msgid "%d project"
|
||||
msgid_plural "%d projects"
|
||||
msgstr[0] ""
|
||||
|
@ -1528,9 +1533,15 @@ msgstr ""
|
|||
msgid "Add new directory"
|
||||
msgstr ""
|
||||
|
||||
msgid "Add or remove previously merged commits"
|
||||
msgstr ""
|
||||
|
||||
msgid "Add or subtract spent time"
|
||||
msgstr ""
|
||||
|
||||
msgid "Add previously merged commits"
|
||||
msgstr ""
|
||||
|
||||
msgid "Add reaction"
|
||||
msgstr ""
|
||||
|
||||
|
@ -1576,6 +1587,15 @@ msgstr ""
|
|||
msgid "Add webhook"
|
||||
msgstr ""
|
||||
|
||||
msgid "Add/remove"
|
||||
msgstr ""
|
||||
|
||||
msgid "AddContextCommits|Add previously merged commits"
|
||||
msgstr ""
|
||||
|
||||
msgid "AddContextCommits|Add/remove"
|
||||
msgstr ""
|
||||
|
||||
msgid "AddMember|No users specified."
|
||||
msgstr ""
|
||||
|
||||
|
@ -6140,6 +6160,9 @@ msgstr ""
|
|||
msgid "Commits to"
|
||||
msgstr ""
|
||||
|
||||
msgid "Commits you select appear here. Go to the first tab and select commits to add to this merge request."
|
||||
msgstr ""
|
||||
|
||||
msgid "Commits|An error occurred while fetching merge requests data."
|
||||
msgstr ""
|
||||
|
||||
|
@ -6641,6 +6664,15 @@ msgstr ""
|
|||
msgid "Contents of .gitlab-ci.yml"
|
||||
msgstr ""
|
||||
|
||||
msgid "ContextCommits|Failed to create context commits. Please try again."
|
||||
msgstr ""
|
||||
|
||||
msgid "ContextCommits|Failed to create/remove context commits. Please try again."
|
||||
msgstr ""
|
||||
|
||||
msgid "ContextCommits|Failed to delete context commits. Please try again."
|
||||
msgstr ""
|
||||
|
||||
msgid "Continue"
|
||||
msgstr ""
|
||||
|
||||
|
@ -12830,9 +12862,6 @@ msgstr ""
|
|||
msgid "Includes an MVC structure, mvnw and pom.xml to help you get started."
|
||||
msgstr ""
|
||||
|
||||
msgid "Includes repository storage, wiki storage, LFS objects, build artifacts and packages. 0 for unlimited."
|
||||
msgstr ""
|
||||
|
||||
msgid "Incoming email"
|
||||
msgstr ""
|
||||
|
||||
|
@ -14648,9 +14677,6 @@ msgstr ""
|
|||
msgid "Maximum lifetime allowable for Personal Access Tokens is active, your expire date must be set before %{maximum_allowable_date}."
|
||||
msgstr ""
|
||||
|
||||
msgid "Maximum namespace storage (MB)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Maximum number of %{name} (%{count}) exceeded"
|
||||
msgstr ""
|
||||
|
||||
|
@ -16046,6 +16072,9 @@ msgstr ""
|
|||
msgid "No child epics match applied filters"
|
||||
msgstr ""
|
||||
|
||||
msgid "No commits present here"
|
||||
msgstr ""
|
||||
|
||||
msgid "No connection could be made to a Gitaly Server, please check your logs!"
|
||||
msgstr ""
|
||||
|
||||
|
@ -19573,6 +19602,9 @@ msgstr ""
|
|||
msgid "Push an existing folder"
|
||||
msgstr ""
|
||||
|
||||
msgid "Push commits to the source branch or add previously merged commits to review them."
|
||||
msgstr ""
|
||||
|
||||
msgid "Push events"
|
||||
msgstr ""
|
||||
|
||||
|
@ -20941,6 +20973,9 @@ msgstr ""
|
|||
msgid "Search by author"
|
||||
msgstr ""
|
||||
|
||||
msgid "Search by commit title or SHA"
|
||||
msgstr ""
|
||||
|
||||
msgid "Search by message"
|
||||
msgstr ""
|
||||
|
||||
|
@ -21591,6 +21626,9 @@ msgstr ""
|
|||
msgid "Select user"
|
||||
msgstr ""
|
||||
|
||||
msgid "Selected commits"
|
||||
msgstr ""
|
||||
|
||||
msgid "Selected levels cannot be used by non-admin users for groups, projects or snippets. If the public level is restricted, user profiles are only visible to logged in users."
|
||||
msgstr ""
|
||||
|
||||
|
@ -24158,6 +24196,9 @@ msgstr ""
|
|||
msgid "There are no closed merge requests"
|
||||
msgstr ""
|
||||
|
||||
msgid "There are no commits yet."
|
||||
msgstr ""
|
||||
|
||||
msgid "There are no custom project templates set up for this GitLab instance. They are enabled from GitLab's Admin Area. Contact your GitLab instance administrator to setup custom project templates."
|
||||
msgstr ""
|
||||
|
||||
|
@ -25704,6 +25745,9 @@ msgstr ""
|
|||
msgid "Unable to generate new instance ID"
|
||||
msgstr ""
|
||||
|
||||
msgid "Unable to load commits. Try again later."
|
||||
msgstr ""
|
||||
|
||||
msgid "Unable to load file contents. Try again later."
|
||||
msgstr ""
|
||||
|
||||
|
@ -28050,6 +28094,9 @@ msgstr ""
|
|||
msgid "Your search didn't match any commits."
|
||||
msgstr ""
|
||||
|
||||
msgid "Your search didn't match any commits. Try a different query."
|
||||
msgstr ""
|
||||
|
||||
msgid "Your subscription expired!"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -204,7 +204,7 @@ module QA
|
|||
alias_method :to_s, :response
|
||||
|
||||
def success?
|
||||
exitstatus.zero?
|
||||
exitstatus == 0
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -14,13 +14,13 @@ module QA
|
|||
end
|
||||
|
||||
def finished_all_axios_requests?
|
||||
Capybara.page.evaluate_script('window.pendingRequests || 0').zero?
|
||||
Capybara.page.evaluate_script('window.pendingRequests || 0').zero? # rubocop:disable Style/NumericPredicate
|
||||
end
|
||||
|
||||
def finished_all_ajax_requests?
|
||||
return true if Capybara.page.evaluate_script('typeof jQuery === "undefined"')
|
||||
|
||||
Capybara.page.evaluate_script('jQuery.active').zero?
|
||||
Capybara.page.evaluate_script('jQuery.active').zero? # rubocop:disable Style/NumericPredicate
|
||||
end
|
||||
|
||||
def finished_loading?(wait: DEFAULT_MAX_WAIT_TIME)
|
||||
|
|
|
@ -17,7 +17,7 @@ RSpec.describe 'mail_room.yml' do
|
|||
cmd = "puts ERB.new(File.read(#{absolute_path(mailroom_config_path).inspect})).result"
|
||||
|
||||
output, status = Gitlab::Popen.popen(%W(ruby -rerb -e #{cmd}), absolute_path('config'), vars)
|
||||
raise "Error interpreting #{mailroom_config_path}: #{output}" unless status.zero?
|
||||
raise "Error interpreting #{mailroom_config_path}: #{output}" unless status == 0
|
||||
|
||||
YAML.load(output)
|
||||
end
|
||||
|
|
|
@ -105,22 +105,6 @@ RSpec.describe Admin::ApplicationSettingsController do
|
|||
expect(ApplicationSetting.current.minimum_password_length).to eq(10)
|
||||
end
|
||||
|
||||
it 'updates namespace_storage_size_limit setting' do
|
||||
put :update, params: { application_setting: { namespace_storage_size_limit: '100' } }
|
||||
|
||||
expect(response).to redirect_to(general_admin_application_settings_path)
|
||||
expect(response).to set_flash[:notice].to('Application settings saved successfully')
|
||||
expect(ApplicationSetting.current.namespace_storage_size_limit).to eq(100)
|
||||
end
|
||||
|
||||
it 'does not accept an invalid namespace_storage_size_limit' do
|
||||
put :update, params: { application_setting: { namespace_storage_size_limit: '-100' } }
|
||||
|
||||
expect(response).to render_template(:general)
|
||||
expect(assigns(:application_setting).errors[:namespace_storage_size_limit]).to be_present
|
||||
expect(ApplicationSetting.current.namespace_storage_size_limit).not_to eq(-100)
|
||||
end
|
||||
|
||||
it 'updates repository_storages_weighted setting' do
|
||||
put :update, params: { application_setting: { repository_storages_weighted_default: 75 } }
|
||||
|
||||
|
|
|
@ -60,7 +60,7 @@ RSpec.describe Groups::Settings::RepositoryController do
|
|||
'token' => be_a(String),
|
||||
'scopes' => deploy_token_params.inject([]) do |scopes, kv|
|
||||
key, value = kv
|
||||
key.to_s.start_with?('read_') && !value.to_i.zero? ? scopes << key.to_s : scopes
|
||||
key.to_s.start_with?('read_') && value.to_i != 0 ? scopes << key.to_s : scopes
|
||||
end
|
||||
}
|
||||
end
|
||||
|
|
|
@ -77,7 +77,7 @@ RSpec.describe Projects::Settings::RepositoryController do
|
|||
'token' => be_a(String),
|
||||
'scopes' => deploy_token_params.inject([]) do |scopes, kv|
|
||||
key, value = kv
|
||||
key.to_s.start_with?('read_') && !value.to_i.zero? ? scopes << key.to_s : scopes
|
||||
key.to_s.start_with?('read_') && value.to_i != 0 ? scopes << key.to_s : scopes
|
||||
end
|
||||
}
|
||||
end
|
||||
|
|
|
@ -23,7 +23,7 @@ FactoryBot.define do
|
|||
end
|
||||
|
||||
create_versions = ->(design, evaluator, commit_version) do
|
||||
unless evaluator.versions_count.zero?
|
||||
unless evaluator.versions_count == 0
|
||||
project = design.project
|
||||
issue = design.issue
|
||||
repository = project.design_repository
|
||||
|
|
|
@ -40,7 +40,7 @@ FactoryBot.define do
|
|||
)
|
||||
version.designs += specific_designs
|
||||
|
||||
unless evaluator.designs_count.zero? || version.designs.present?
|
||||
unless evaluator.designs_count == 0 || version.designs.present?
|
||||
version.designs << create(:design, issue: version.issue)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -36,7 +36,7 @@ RSpec.describe 'Contributions Calendar', :js do
|
|||
|
||||
def get_cell_date_selector(contributions, date)
|
||||
contribution_text =
|
||||
if contributions.zero?
|
||||
if contributions == 0
|
||||
'No contributions'
|
||||
else
|
||||
"#{contributions} #{'contribution'.pluralize(contributions)}"
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`AddContextCommitsModal renders modal with 2 tabs 1`] = `
|
||||
<gl-modal-stub
|
||||
body-class="add-review-item pt-0"
|
||||
cancel-variant="light"
|
||||
modalclass=""
|
||||
modalid="add-review-item"
|
||||
ok-disabled="true"
|
||||
ok-title="Save changes"
|
||||
scrollable="true"
|
||||
size="md"
|
||||
title="Add or remove previously merged commits"
|
||||
titletag="h4"
|
||||
>
|
||||
<gl-tabs-stub
|
||||
contentclass="pt-0"
|
||||
theme="indigo"
|
||||
value="0"
|
||||
>
|
||||
<gl-tab-stub>
|
||||
|
||||
<div
|
||||
class="mt-2"
|
||||
>
|
||||
<gl-search-box-by-type-stub
|
||||
clearbuttontitle="Clear"
|
||||
placeholder="Search by commit title or SHA"
|
||||
value=""
|
||||
/>
|
||||
|
||||
<review-tab-container-stub
|
||||
commits=""
|
||||
emptylisttext="Your search didn't match any commits. Try a different query."
|
||||
loadingfailedtext="Unable to load commits. Try again later."
|
||||
/>
|
||||
</div>
|
||||
</gl-tab-stub>
|
||||
|
||||
<gl-tab-stub>
|
||||
|
||||
<review-tab-container-stub
|
||||
commits=""
|
||||
emptylisttext="Commits you select appear here. Go to the first tab and select commits to add to this merge request."
|
||||
loadingfailedtext="Unable to load commits. Try again later."
|
||||
/>
|
||||
</gl-tab-stub>
|
||||
</gl-tabs-stub>
|
||||
</gl-modal-stub>
|
||||
`;
|
|
@ -0,0 +1,174 @@
|
|||
import { shallowMount, createLocalVue } from '@vue/test-utils';
|
||||
import Vuex from 'vuex';
|
||||
import { GlModal, GlSearchBoxByType } from '@gitlab/ui';
|
||||
import AddReviewItemsModal from '~/add_context_commits_modal/components/add_context_commits_modal_wrapper.vue';
|
||||
import getDiffWithCommit from '../../diffs/mock_data/diff_with_commit';
|
||||
|
||||
import defaultState from '~/add_context_commits_modal/store/state';
|
||||
import mutations from '~/add_context_commits_modal/store/mutations';
|
||||
import * as actions from '~/add_context_commits_modal/store/actions';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(Vuex);
|
||||
|
||||
describe('AddContextCommitsModal', () => {
|
||||
let wrapper;
|
||||
let store;
|
||||
const createContextCommits = jest.fn();
|
||||
const removeContextCommits = jest.fn();
|
||||
const resetModalState = jest.fn();
|
||||
const searchCommits = jest.fn();
|
||||
const { commit } = getDiffWithCommit();
|
||||
|
||||
const createWrapper = (props = {}) => {
|
||||
store = new Vuex.Store({
|
||||
mutations,
|
||||
state: {
|
||||
...defaultState(),
|
||||
},
|
||||
actions: {
|
||||
...actions,
|
||||
searchCommits,
|
||||
createContextCommits,
|
||||
removeContextCommits,
|
||||
resetModalState,
|
||||
},
|
||||
});
|
||||
|
||||
wrapper = shallowMount(AddReviewItemsModal, {
|
||||
localVue,
|
||||
store,
|
||||
propsData: {
|
||||
contextCommitsPath: '',
|
||||
targetBranch: 'master',
|
||||
mergeRequestIid: 1,
|
||||
projectId: 1,
|
||||
...props,
|
||||
},
|
||||
});
|
||||
return wrapper;
|
||||
};
|
||||
|
||||
const findModal = () => wrapper.find(GlModal);
|
||||
const findSearch = () => wrapper.find(GlSearchBoxByType);
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = createWrapper();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
||||
it('renders modal with 2 tabs', () => {
|
||||
expect(wrapper.element).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('an ok button labeled "Save changes"', () => {
|
||||
expect(findModal().attributes('ok-title')).toEqual('Save changes');
|
||||
});
|
||||
|
||||
describe('when in first tab, renders a modal with', () => {
|
||||
it('renders the search box component', () => {
|
||||
expect(findSearch().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('when user starts entering text in search box, it calls action "searchCommits" after waiting for 500s', () => {
|
||||
const searchText = 'abcd';
|
||||
findSearch().vm.$emit('input', searchText);
|
||||
expect(searchCommits).not.toBeCalled();
|
||||
jest.advanceTimersByTime(500);
|
||||
expect(searchCommits).toHaveBeenCalledWith(expect.anything(), searchText, undefined);
|
||||
});
|
||||
|
||||
it('disabled ok button when no row is selected', () => {
|
||||
expect(findModal().attributes('ok-disabled')).toBe('true');
|
||||
});
|
||||
|
||||
it('enabled ok button when atleast one row is selected', () => {
|
||||
wrapper.vm.$store.state.selectedCommits = [{ ...commit, isSelected: true }];
|
||||
return wrapper.vm.$nextTick().then(() => {
|
||||
expect(findModal().attributes('ok-disabled')).toBeFalsy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when in second tab, renders a modal with', () => {
|
||||
beforeEach(() => {
|
||||
wrapper.vm.$store.state.tabIndex = 1;
|
||||
});
|
||||
it('a disabled ok button when no row is selected', () => {
|
||||
expect(findModal().attributes('ok-disabled')).toBe('true');
|
||||
});
|
||||
|
||||
it('an enabled ok button when atleast one row is selected', () => {
|
||||
wrapper.vm.$store.state.selectedCommits = [{ ...commit, isSelected: true }];
|
||||
return wrapper.vm.$nextTick().then(() => {
|
||||
expect(findModal().attributes('ok-disabled')).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
it('a disabled ok button in first tab, when row is selected in second tab', () => {
|
||||
createWrapper({ selectedContextCommits: [commit] });
|
||||
expect(wrapper.find(GlModal).attributes('ok-disabled')).toBe('true');
|
||||
});
|
||||
});
|
||||
|
||||
describe('has an ok button when clicked calls action', () => {
|
||||
it('"createContextCommits" when only new commits to be added ', () => {
|
||||
wrapper.vm.$store.state.selectedCommits = [{ ...commit, isSelected: true }];
|
||||
findModal().vm.$emit('ok');
|
||||
return wrapper.vm.$nextTick().then(() => {
|
||||
expect(createContextCommits).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
{ commits: [{ ...commit, isSelected: true }], forceReload: true },
|
||||
undefined,
|
||||
);
|
||||
});
|
||||
});
|
||||
it('"removeContextCommits" when only added commits are to be removed ', () => {
|
||||
wrapper.vm.$store.state.toRemoveCommits = [commit.short_id];
|
||||
findModal().vm.$emit('ok');
|
||||
return wrapper.vm.$nextTick().then(() => {
|
||||
expect(removeContextCommits).toHaveBeenCalledWith(expect.anything(), true, undefined);
|
||||
});
|
||||
});
|
||||
it('"createContextCommits" and "removeContextCommits" when new commits are to be added and old commits are to be removed', () => {
|
||||
wrapper.vm.$store.state.selectedCommits = [{ ...commit, isSelected: true }];
|
||||
wrapper.vm.$store.state.toRemoveCommits = [commit.short_id];
|
||||
findModal().vm.$emit('ok');
|
||||
return wrapper.vm.$nextTick().then(() => {
|
||||
expect(createContextCommits).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
{ commits: [{ ...commit, isSelected: true }] },
|
||||
undefined,
|
||||
);
|
||||
expect(removeContextCommits).toHaveBeenCalledWith(expect.anything(), undefined, undefined);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('has a cancel button when clicked', () => {
|
||||
it('does not call "createContextCommits" or "removeContextCommits"', () => {
|
||||
findModal().vm.$emit('cancel');
|
||||
expect(createContextCommits).not.toHaveBeenCalled();
|
||||
expect(removeContextCommits).not.toHaveBeenCalled();
|
||||
});
|
||||
it('"resetModalState" to reset all the modal state', () => {
|
||||
findModal().vm.$emit('cancel');
|
||||
expect(resetModalState).toHaveBeenCalledWith(expect.anything(), undefined, undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when model is closed by clicking the "X" button or by pressing "ESC" key', () => {
|
||||
it('does not call "createContextCommits" or "removeContextCommits"', () => {
|
||||
findModal().vm.$emit('close');
|
||||
expect(createContextCommits).not.toHaveBeenCalled();
|
||||
expect(removeContextCommits).not.toHaveBeenCalled();
|
||||
});
|
||||
it('"resetModalState" to reset all the modal state', () => {
|
||||
findModal().vm.$emit('close');
|
||||
expect(resetModalState).toHaveBeenCalledWith(expect.anything(), undefined, undefined);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,51 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import { GlLoadingIcon } from '@gitlab/ui';
|
||||
import ReviewTabContainer from '~/add_context_commits_modal/components/review_tab_container.vue';
|
||||
import CommitItem from '~/diffs/components/commit_item.vue';
|
||||
import getDiffWithCommit from '../../diffs/mock_data/diff_with_commit';
|
||||
|
||||
describe('ReviewTabContainer', () => {
|
||||
let wrapper;
|
||||
const { commit } = getDiffWithCommit();
|
||||
|
||||
const createWrapper = (props = {}) => {
|
||||
wrapper = shallowMount(ReviewTabContainer, {
|
||||
propsData: {
|
||||
tab: 'commits',
|
||||
isLoading: false,
|
||||
loadingError: false,
|
||||
loadingFailedText: 'Failed to load commits',
|
||||
commits: [],
|
||||
selectedRow: [],
|
||||
...props,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
createWrapper();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
||||
it('shows loading icon when commits are being loaded', () => {
|
||||
createWrapper({ isLoading: true });
|
||||
expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('shows loading error text when API call fails', () => {
|
||||
createWrapper({ loadingError: true });
|
||||
expect(wrapper.text()).toContain('Failed to load commits');
|
||||
});
|
||||
|
||||
it('shows "No commits present here" when commits are not present', () => {
|
||||
expect(wrapper.text()).toContain('No commits present here');
|
||||
});
|
||||
|
||||
it('renders all passed commits as list', () => {
|
||||
createWrapper({ commits: [commit] });
|
||||
expect(wrapper.findAll(CommitItem).length).toBe(1);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,239 @@
|
|||
import MockAdapter from 'axios-mock-adapter';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import {
|
||||
setBaseConfig,
|
||||
setTabIndex,
|
||||
setCommits,
|
||||
createContextCommits,
|
||||
fetchContextCommits,
|
||||
setContextCommits,
|
||||
removeContextCommits,
|
||||
setSelectedCommits,
|
||||
setSearchText,
|
||||
setToRemoveCommits,
|
||||
resetModalState,
|
||||
} from '~/add_context_commits_modal/store/actions';
|
||||
import * as types from '~/add_context_commits_modal/store/mutation_types';
|
||||
import { TEST_HOST } from 'helpers/test_constants';
|
||||
import testAction from '../../helpers/vuex_action_helper';
|
||||
|
||||
describe('AddContextCommitsModalStoreActions', () => {
|
||||
const contextCommitEndpoint =
|
||||
'/api/v4/projects/gitlab-org%2fgitlab/merge_requests/1/context_commits';
|
||||
const mergeRequestIid = 1;
|
||||
const projectId = 1;
|
||||
const projectPath = 'gitlab-org/gitlab';
|
||||
const contextCommitsPath = `${TEST_HOST}/gitlab-org/gitlab/-/merge_requests/1/context_commits.json`;
|
||||
const dummyCommit = {
|
||||
id: 1,
|
||||
title: 'dummy commit',
|
||||
short_id: 'abcdef',
|
||||
committed_date: '2020-06-12',
|
||||
};
|
||||
gon.api_version = 'v4';
|
||||
let mock;
|
||||
|
||||
beforeEach(() => {
|
||||
mock = new MockAdapter(axios);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
mock.restore();
|
||||
});
|
||||
|
||||
describe('setBaseConfig', () => {
|
||||
it('commits SET_BASE_CONFIG', done => {
|
||||
const options = { contextCommitsPath, mergeRequestIid, projectId };
|
||||
testAction(
|
||||
setBaseConfig,
|
||||
options,
|
||||
{
|
||||
contextCommitsPath: '',
|
||||
mergeRequestIid,
|
||||
projectId,
|
||||
},
|
||||
[
|
||||
{
|
||||
type: types.SET_BASE_CONFIG,
|
||||
payload: options,
|
||||
},
|
||||
],
|
||||
[],
|
||||
done,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setTabIndex', () => {
|
||||
it('commits SET_TABINDEX', done => {
|
||||
testAction(
|
||||
setTabIndex,
|
||||
{ tabIndex: 1 },
|
||||
{ tabIndex: 0 },
|
||||
[{ type: types.SET_TABINDEX, payload: { tabIndex: 1 } }],
|
||||
[],
|
||||
done,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setCommits', () => {
|
||||
it('commits SET_COMMITS', done => {
|
||||
testAction(
|
||||
setCommits,
|
||||
{ commits: [], silentAddition: false },
|
||||
{ isLoadingCommits: false, commits: [] },
|
||||
[{ type: types.SET_COMMITS, payload: [] }],
|
||||
[],
|
||||
done,
|
||||
);
|
||||
});
|
||||
|
||||
it('commits SET_COMMITS_SILENT', done => {
|
||||
testAction(
|
||||
setCommits,
|
||||
{ commits: [], silentAddition: true },
|
||||
{ isLoadingCommits: true, commits: [] },
|
||||
[{ type: types.SET_COMMITS_SILENT, payload: [] }],
|
||||
[],
|
||||
done,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('createContextCommits', () => {
|
||||
it('calls API to create context commits', done => {
|
||||
mock.onPost(contextCommitEndpoint).reply(200, {});
|
||||
|
||||
testAction(createContextCommits, { commits: [] }, {}, [], [], done);
|
||||
|
||||
createContextCommits(
|
||||
{ state: { projectId, mergeRequestIid }, commit: () => null },
|
||||
{ commits: [] },
|
||||
)
|
||||
.then(() => {
|
||||
done();
|
||||
})
|
||||
.catch(done.fail);
|
||||
});
|
||||
});
|
||||
|
||||
describe('fetchContextCommits', () => {
|
||||
beforeEach(() => {
|
||||
mock
|
||||
.onGet(
|
||||
`/api/${gon.api_version}/projects/gitlab-org%2Fgitlab/merge_requests/1/context_commits`,
|
||||
)
|
||||
.reply(200, [dummyCommit]);
|
||||
});
|
||||
it('commits FETCH_CONTEXT_COMMITS', done => {
|
||||
const contextCommit = { ...dummyCommit, isSelected: true };
|
||||
testAction(
|
||||
fetchContextCommits,
|
||||
null,
|
||||
{
|
||||
mergeRequestIid,
|
||||
projectId: projectPath,
|
||||
isLoadingContextCommits: false,
|
||||
contextCommitsLoadingError: false,
|
||||
commits: [],
|
||||
},
|
||||
[{ type: types.FETCH_CONTEXT_COMMITS }],
|
||||
[
|
||||
{ type: 'setContextCommits', payload: [contextCommit] },
|
||||
{ type: 'setCommits', payload: { commits: [contextCommit], silentAddition: true } },
|
||||
{ type: 'setSelectedCommits', payload: [contextCommit] },
|
||||
],
|
||||
done,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setContextCommits', () => {
|
||||
it('commits SET_CONTEXT_COMMITS', done => {
|
||||
testAction(
|
||||
setContextCommits,
|
||||
{ data: [] },
|
||||
{ contextCommits: [], isLoadingContextCommits: false },
|
||||
[{ type: types.SET_CONTEXT_COMMITS, payload: { data: [] } }],
|
||||
[],
|
||||
done,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('removeContextCommits', () => {
|
||||
beforeEach(() => {
|
||||
mock
|
||||
.onDelete('/api/v4/projects/gitlab-org%2Fgitlab/merge_requests/1/context_commits')
|
||||
.reply(204);
|
||||
});
|
||||
it('calls API to remove context commits', done => {
|
||||
testAction(
|
||||
removeContextCommits,
|
||||
{ forceReload: false },
|
||||
{ mergeRequestIid, projectId, toRemoveCommits: [] },
|
||||
[],
|
||||
[],
|
||||
done,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setSelectedCommits', () => {
|
||||
it('commits SET_SELECTED_COMMITS', done => {
|
||||
testAction(
|
||||
setSelectedCommits,
|
||||
[dummyCommit],
|
||||
{ selectedCommits: [] },
|
||||
[{ type: types.SET_SELECTED_COMMITS, payload: [dummyCommit] }],
|
||||
[],
|
||||
done,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setSearchText', () => {
|
||||
it('commits SET_SEARCH_TEXT', done => {
|
||||
const searchText = 'Dummy Text';
|
||||
testAction(
|
||||
setSearchText,
|
||||
searchText,
|
||||
{ searchText: '' },
|
||||
[{ type: types.SET_SEARCH_TEXT, payload: searchText }],
|
||||
[],
|
||||
done,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setToRemoveCommits', () => {
|
||||
it('commits SET_TO_REMOVE_COMMITS', done => {
|
||||
const commitId = 'abcde';
|
||||
|
||||
testAction(
|
||||
setToRemoveCommits,
|
||||
[commitId],
|
||||
{ toRemoveCommits: [] },
|
||||
[{ type: types.SET_TO_REMOVE_COMMITS, payload: [commitId] }],
|
||||
[],
|
||||
done,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('resetModalState', () => {
|
||||
it('commits RESET_MODAL_STATE', done => {
|
||||
const commitId = 'abcde';
|
||||
|
||||
testAction(
|
||||
resetModalState,
|
||||
null,
|
||||
{ toRemoveCommits: [commitId] },
|
||||
[{ type: types.RESET_MODAL_STATE }],
|
||||
[],
|
||||
done,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,156 @@
|
|||
import mutations from '~/add_context_commits_modal/store/mutations';
|
||||
import * as types from '~/add_context_commits_modal/store/mutation_types';
|
||||
import { TEST_HOST } from 'helpers/test_constants';
|
||||
import getDiffWithCommit from '../../diffs/mock_data/diff_with_commit';
|
||||
|
||||
describe('AddContextCommitsModalStoreMutations', () => {
|
||||
const { commit } = getDiffWithCommit();
|
||||
describe('SET_BASE_CONFIG', () => {
|
||||
it('should set contextCommitsPath, mergeRequestIid and projectId', () => {
|
||||
const state = {};
|
||||
const contextCommitsPath = `${TEST_HOST}/gitlab-org/gitlab/-/merge_requests/1/context_commits.json`;
|
||||
const mergeRequestIid = 1;
|
||||
const projectId = 1;
|
||||
|
||||
mutations[types.SET_BASE_CONFIG](state, { contextCommitsPath, mergeRequestIid, projectId });
|
||||
|
||||
expect(state.contextCommitsPath).toEqual(contextCommitsPath);
|
||||
expect(state.mergeRequestIid).toEqual(mergeRequestIid);
|
||||
expect(state.projectId).toEqual(projectId);
|
||||
});
|
||||
});
|
||||
|
||||
describe('SET_TABINDEX', () => {
|
||||
it('sets tabIndex to specific index', () => {
|
||||
const state = { tabIndex: 0 };
|
||||
|
||||
mutations[types.SET_TABINDEX](state, 1);
|
||||
|
||||
expect(state.tabIndex).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('FETCH_COMMITS', () => {
|
||||
it('sets isLoadingCommits to true', () => {
|
||||
const state = { isLoadingCommits: false };
|
||||
|
||||
mutations[types.FETCH_COMMITS](state);
|
||||
|
||||
expect(state.isLoadingCommits).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('SET_COMMITS', () => {
|
||||
it('sets commits to passed data and stop loading', () => {
|
||||
const state = { commits: [], isLoadingCommits: true };
|
||||
|
||||
mutations[types.SET_COMMITS](state, [commit]);
|
||||
|
||||
expect(state.commits).toStrictEqual([commit]);
|
||||
expect(state.isLoadingCommits).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('SET_COMMITS_SILENT', () => {
|
||||
it('sets commits to passed data and loading continues', () => {
|
||||
const state = { commits: [], isLoadingCommits: true };
|
||||
|
||||
mutations[types.SET_COMMITS_SILENT](state, [commit]);
|
||||
|
||||
expect(state.commits).toStrictEqual([commit]);
|
||||
expect(state.isLoadingCommits).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('FETCH_COMMITS_ERROR', () => {
|
||||
it('sets commitsLoadingError to true', () => {
|
||||
const state = { commitsLoadingError: false };
|
||||
|
||||
mutations[types.FETCH_COMMITS_ERROR](state);
|
||||
|
||||
expect(state.commitsLoadingError).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('FETCH_CONTEXT_COMMITS', () => {
|
||||
it('sets isLoadingContextCommits to true', () => {
|
||||
const state = { isLoadingContextCommits: false };
|
||||
|
||||
mutations[types.FETCH_CONTEXT_COMMITS](state);
|
||||
|
||||
expect(state.isLoadingContextCommits).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('SET_CONTEXT_COMMITS', () => {
|
||||
it('sets contextCommit to passed data and stop loading', () => {
|
||||
const state = { contextCommits: [], isLoadingContextCommits: true };
|
||||
|
||||
mutations[types.SET_CONTEXT_COMMITS](state, [commit]);
|
||||
|
||||
expect(state.contextCommits).toStrictEqual([commit]);
|
||||
expect(state.isLoadingContextCommits).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('FETCH_CONTEXT_COMMITS_ERROR', () => {
|
||||
it('sets contextCommitsLoadingError to true', () => {
|
||||
const state = { contextCommitsLoadingError: false };
|
||||
|
||||
mutations[types.FETCH_CONTEXT_COMMITS_ERROR](state);
|
||||
|
||||
expect(state.contextCommitsLoadingError).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('SET_SELECTED_COMMITS', () => {
|
||||
it('sets selectedCommits to specified value', () => {
|
||||
const state = { selectedCommits: [] };
|
||||
|
||||
mutations[types.SET_SELECTED_COMMITS](state, [commit]);
|
||||
|
||||
expect(state.selectedCommits).toStrictEqual([commit]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('SET_SEARCH_TEXT', () => {
|
||||
it('sets searchText to specified value', () => {
|
||||
const searchText = 'Test';
|
||||
const state = { searchText: '' };
|
||||
|
||||
mutations[types.SET_SEARCH_TEXT](state, searchText);
|
||||
|
||||
expect(state.searchText).toBe(searchText);
|
||||
});
|
||||
});
|
||||
|
||||
describe('SET_TO_REMOVE_COMMITS', () => {
|
||||
it('sets searchText to specified value', () => {
|
||||
const state = { toRemoveCommits: [] };
|
||||
|
||||
mutations[types.SET_TO_REMOVE_COMMITS](state, [commit.short_id]);
|
||||
|
||||
expect(state.toRemoveCommits).toStrictEqual([commit.short_id]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('RESET_MODAL_STATE', () => {
|
||||
it('sets searchText to specified value', () => {
|
||||
const state = {
|
||||
commits: [commit],
|
||||
contextCommits: [commit],
|
||||
selectedCommits: [commit],
|
||||
toRemoveCommits: [commit.short_id],
|
||||
searchText: 'Test',
|
||||
};
|
||||
|
||||
mutations[types.RESET_MODAL_STATE](state);
|
||||
|
||||
expect(state.commits).toStrictEqual([]);
|
||||
expect(state.contextCommits).toStrictEqual([]);
|
||||
expect(state.selectedCommits).toStrictEqual([]);
|
||||
expect(state.toRemoveCommits).toStrictEqual([]);
|
||||
expect(state.searchText).toBe('');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -667,6 +667,79 @@ describe('Api', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('createContextCommits', () => {
|
||||
it('creates a new context commit', done => {
|
||||
const projectPath = 'abc';
|
||||
const mergeRequestId = '123456';
|
||||
const commitsData = ['abcdefg'];
|
||||
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${projectPath}/merge_requests/${mergeRequestId}/context_commits`;
|
||||
const expectedData = {
|
||||
commits: commitsData,
|
||||
};
|
||||
|
||||
jest.spyOn(axios, 'post');
|
||||
|
||||
mock.onPost(expectedUrl).replyOnce(200, [
|
||||
{
|
||||
id: 'abcdefghijklmnop',
|
||||
short_id: 'abcdefg',
|
||||
title: 'Dummy commit',
|
||||
},
|
||||
]);
|
||||
|
||||
Api.createContextCommits(projectPath, mergeRequestId, expectedData)
|
||||
.then(({ data }) => {
|
||||
expect(data[0].title).toBe('Dummy commit');
|
||||
})
|
||||
.then(done)
|
||||
.catch(done.fail);
|
||||
});
|
||||
});
|
||||
|
||||
describe('allContextCommits', () => {
|
||||
it('gets all context commits', done => {
|
||||
const projectPath = 'abc';
|
||||
const mergeRequestId = '123456';
|
||||
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${projectPath}/merge_requests/${mergeRequestId}/context_commits`;
|
||||
|
||||
jest.spyOn(axios, 'get');
|
||||
|
||||
mock
|
||||
.onGet(expectedUrl)
|
||||
.replyOnce(200, [{ id: 'abcdef', short_id: 'abcdefghi', title: 'Dummy commit title' }]);
|
||||
|
||||
Api.allContextCommits(projectPath, mergeRequestId)
|
||||
.then(({ data }) => {
|
||||
expect(data[0].title).toBe('Dummy commit title');
|
||||
})
|
||||
.then(done)
|
||||
.catch(done.fail);
|
||||
});
|
||||
});
|
||||
|
||||
describe('removeContextCommits', () => {
|
||||
it('removes context commits', done => {
|
||||
const projectPath = 'abc';
|
||||
const mergeRequestId = '123456';
|
||||
const commitsData = ['abcdefg'];
|
||||
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${projectPath}/merge_requests/${mergeRequestId}/context_commits`;
|
||||
const expectedData = {
|
||||
commits: commitsData,
|
||||
};
|
||||
|
||||
jest.spyOn(axios, 'delete');
|
||||
|
||||
mock.onDelete(expectedUrl).replyOnce(204);
|
||||
|
||||
Api.removeContextCommits(projectPath, mergeRequestId, expectedData)
|
||||
.then(() => {
|
||||
expect(axios.delete).toHaveBeenCalledWith(expectedUrl, { data: expectedData });
|
||||
})
|
||||
.then(done)
|
||||
.catch(done.fail);
|
||||
});
|
||||
});
|
||||
|
||||
describe('release-related methods', () => {
|
||||
const dummyProjectPath = 'gitlab-org/gitlab';
|
||||
const dummyTagName = 'v1.3';
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { mount } from '@vue/test-utils';
|
||||
import { GlButton } from '@gitlab/ui';
|
||||
import LinkedPipelineComponent from '~/pipelines/components/graph/linked_pipeline.vue';
|
||||
import CiStatus from '~/vue_shared/components/ci_icon.vue';
|
||||
|
||||
|
@ -12,7 +13,7 @@ const invalidTriggeredPipelineId = mockPipeline.project.id + 5;
|
|||
describe('Linked pipeline', () => {
|
||||
let wrapper;
|
||||
|
||||
const findButton = () => wrapper.find('button');
|
||||
const findButton = () => wrapper.find(GlButton);
|
||||
const findPipelineLabel = () => wrapper.find('[data-testid="downstream-pipeline-label"]');
|
||||
const findLinkedPipeline = () => wrapper.find({ ref: 'linkedPipeline' });
|
||||
|
||||
|
@ -42,9 +43,7 @@ describe('Linked pipeline', () => {
|
|||
});
|
||||
|
||||
it('should render a button', () => {
|
||||
const linkElement = wrapper.find('.js-linked-pipeline-content');
|
||||
|
||||
expect(linkElement.exists()).toBe(true);
|
||||
expect(findButton().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('should render the project name', () => {
|
||||
|
@ -62,7 +61,7 @@ describe('Linked pipeline', () => {
|
|||
});
|
||||
|
||||
it('should have a ci-status child component', () => {
|
||||
expect(wrapper.find('.js-linked-pipeline-status').exists()).toBe(true);
|
||||
expect(wrapper.find(CiStatus).exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('should render the pipeline id', () => {
|
||||
|
@ -77,15 +76,14 @@ describe('Linked pipeline', () => {
|
|||
});
|
||||
|
||||
it('should render the tooltip text as the title attribute', () => {
|
||||
const tooltipRef = wrapper.find('.js-linked-pipeline-content');
|
||||
const titleAttr = tooltipRef.attributes('title');
|
||||
const titleAttr = findButton().attributes('title');
|
||||
|
||||
expect(titleAttr).toContain(mockPipeline.project.name);
|
||||
expect(titleAttr).toContain(mockPipeline.details.status.label);
|
||||
});
|
||||
|
||||
it('does not render the loading icon when isLoading is false', () => {
|
||||
expect(wrapper.find('.js-linked-pipeline-loading').exists()).toBe(false);
|
||||
it('sets the loading prop to false', () => {
|
||||
expect(findButton().props('loading')).toBe(false);
|
||||
});
|
||||
|
||||
it('should display multi-project label when pipeline project id is not the same as triggered pipeline project id', () => {
|
||||
|
@ -132,8 +130,8 @@ describe('Linked pipeline', () => {
|
|||
createWrapper(props);
|
||||
});
|
||||
|
||||
it('renders a loading icon', () => {
|
||||
expect(wrapper.find('.js-linked-pipeline-loading').exists()).toBe(true);
|
||||
it('sets the loading prop to true', () => {
|
||||
expect(findButton().props('loading')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -44,7 +44,8 @@ RSpec.describe EnvironmentsHelper do
|
|||
'prometheus-alerts-available' => 'true',
|
||||
'custom-dashboard-base-path' => Gitlab::Metrics::Dashboard::RepoDashboardFinder::DASHBOARD_ROOT,
|
||||
'operations-settings-path' => project_settings_operations_path(project),
|
||||
'can-access-operations-settings' => 'true'
|
||||
'can-access-operations-settings' => 'true',
|
||||
'panel-preview-endpoint' => project_metrics_dashboards_builder_path(project, format: :json)
|
||||
)
|
||||
end
|
||||
|
||||
|
|
|
@ -110,20 +110,6 @@ RSpec.describe Banzai::Filter::ReferenceFilter do
|
|||
|
||||
expect(filter.instance_variable_get(:@new_nodes)).to eq({ index => [filter.each_node.to_a[index]] })
|
||||
end
|
||||
|
||||
context "with update_nodes_for_banzai_reference_filter feature flag disabled" do
|
||||
before do
|
||||
stub_feature_flags(update_nodes_for_banzai_reference_filter: false)
|
||||
end
|
||||
|
||||
it 'does not call replace_and_update_new_nodes' do
|
||||
expect(filter).not_to receive(:replace_and_update_new_nodes).with(filter.nodes[index], index, html)
|
||||
|
||||
filter.send(method_name, *args) do
|
||||
html
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -198,49 +184,20 @@ RSpec.describe Banzai::Filter::ReferenceFilter do
|
|||
end
|
||||
|
||||
describe "#call_and_update_nodes" do
|
||||
context "with update_nodes_for_banzai_reference_filter feature flag enabled" do
|
||||
include_context 'new nodes'
|
||||
let(:document) { Nokogiri::HTML.fragment('<a href="foo">foo</a>') }
|
||||
let(:filter) { described_class.new(document, project: project) }
|
||||
include_context 'new nodes'
|
||||
let(:document) { Nokogiri::HTML.fragment('<a href="foo">foo</a>') }
|
||||
let(:filter) { described_class.new(document, project: project) }
|
||||
|
||||
before do
|
||||
stub_feature_flags(update_nodes_for_banzai_reference_filter: true)
|
||||
end
|
||||
it "updates all new nodes", :aggregate_failures do
|
||||
filter.instance_variable_set('@nodes', nodes)
|
||||
|
||||
it "updates all new nodes", :aggregate_failures do
|
||||
filter.instance_variable_set('@nodes', nodes)
|
||||
expect(filter).to receive(:call) { filter.instance_variable_set('@new_nodes', new_nodes) }
|
||||
expect(filter).to receive(:with_update_nodes).and_call_original
|
||||
expect(filter).to receive(:update_nodes!).and_call_original
|
||||
|
||||
expect(filter).to receive(:call) { filter.instance_variable_set('@new_nodes', new_nodes) }
|
||||
expect(filter).to receive(:with_update_nodes).and_call_original
|
||||
expect(filter).to receive(:update_nodes!).and_call_original
|
||||
filter.call_and_update_nodes
|
||||
|
||||
filter.call_and_update_nodes
|
||||
|
||||
expect(filter.result[:reference_filter_nodes]).to eq(expected_nodes)
|
||||
end
|
||||
end
|
||||
|
||||
context "with update_nodes_for_banzai_reference_filter feature flag disabled" do
|
||||
include_context 'new nodes'
|
||||
|
||||
before do
|
||||
stub_feature_flags(update_nodes_for_banzai_reference_filter: false)
|
||||
end
|
||||
|
||||
it "does not change nodes", :aggregate_failures do
|
||||
document = Nokogiri::HTML.fragment('<a href="foo">foo</a>')
|
||||
filter = described_class.new(document, project: project)
|
||||
filter.instance_variable_set('@nodes', nodes)
|
||||
|
||||
expect(filter).to receive(:call) { filter.instance_variable_set('@new_nodes', new_nodes) }
|
||||
expect(filter).not_to receive(:with_update_nodes)
|
||||
expect(filter).not_to receive(:update_nodes!)
|
||||
|
||||
filter.call_and_update_nodes
|
||||
|
||||
expect(filter.nodes).to eq(nodes)
|
||||
expect(filter.result[:reference_filter_nodes]).to be nil
|
||||
end
|
||||
expect(filter.result[:reference_filter_nodes]).to eq(expected_nodes)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -251,10 +208,6 @@ RSpec.describe Banzai::Filter::ReferenceFilter do
|
|||
|
||||
let(:result) { { reference_filter_nodes: nodes } }
|
||||
|
||||
before do
|
||||
stub_feature_flags(update_nodes_for_banzai_reference_filter: true)
|
||||
end
|
||||
|
||||
it "updates all nodes", :aggregate_failures do
|
||||
expect_next_instance_of(described_class) do |filter|
|
||||
expect(filter).to receive(:call_and_update_nodes).and_call_original
|
||||
|
@ -267,26 +220,5 @@ RSpec.describe Banzai::Filter::ReferenceFilter do
|
|||
|
||||
expect(result[:reference_filter_nodes]).to eq(expected_nodes)
|
||||
end
|
||||
|
||||
context "with update_nodes_for_banzai_reference_filter feature flag disabled" do
|
||||
let(:result) { {} }
|
||||
|
||||
before do
|
||||
stub_feature_flags(update_nodes_for_banzai_reference_filter: false)
|
||||
end
|
||||
|
||||
it "updates all nodes", :aggregate_failures do
|
||||
expect_next_instance_of(described_class) do |filter|
|
||||
expect(filter).to receive(:call_and_update_nodes).and_call_original
|
||||
expect(filter).not_to receive(:with_update_nodes)
|
||||
expect(filter).to receive(:call) { filter.instance_variable_set('@new_nodes', new_nodes) }
|
||||
expect(filter).not_to receive(:update_nodes!)
|
||||
end
|
||||
|
||||
described_class.call(document, { project: project }, result)
|
||||
|
||||
expect(result[:reference_filter_nodes]).to be nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -30,34 +30,6 @@ RSpec.describe Banzai::Pipeline::GfmPipeline do
|
|||
described_class.call(markdown, project: project)
|
||||
end
|
||||
|
||||
context "with update_nodes_for_banzai_reference_filter feature flag disabled" do
|
||||
before do
|
||||
stub_feature_flags(update_nodes_for_banzai_reference_filter: false)
|
||||
end
|
||||
|
||||
context 'when shorthand pattern #ISSUE_ID is used' do
|
||||
it 'links an internal issues and doesnt store nodes in result[:reference_filter_nodes]', :aggregate_failures do
|
||||
issue = create(:issue, project: project)
|
||||
markdown = "text #{issue.to_reference(project, full: true)}"
|
||||
result = described_class.call(markdown, project: project)
|
||||
link = result[:output].css('a').first
|
||||
|
||||
expect(link['href']).to eq(Gitlab::Routing.url_helpers.project_issue_path(project, issue))
|
||||
expect(result[:reference_filter_nodes]).to eq nil
|
||||
end
|
||||
end
|
||||
|
||||
it 'execute :each_node for each reference_filter', :aggregate_failures do
|
||||
issue = create(:issue, project: project)
|
||||
markdown = "text #{issue.to_reference(project, full: true)}"
|
||||
described_class.reference_filters do |reference_filter|
|
||||
expect_any_instance_of(reference_filter).to receive(:each_node).once
|
||||
end
|
||||
|
||||
described_class.call(markdown, project: project)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when shorthand pattern #ISSUE_ID is used' do
|
||||
it 'links an internal issue if it exists' do
|
||||
issue = create(:issue, project: project)
|
||||
|
|
|
@ -18,7 +18,7 @@ RSpec.describe Gitlab::Git::DiffCollection, :seed_helper do
|
|||
return enum_for(:each) unless block_given?
|
||||
|
||||
loop do
|
||||
break if @count.zero?
|
||||
break if @count == 0
|
||||
|
||||
# It is critical to decrement before yielding. We may never reach the lines after 'yield'.
|
||||
@count -= 1
|
||||
|
|
|
@ -169,7 +169,7 @@ RSpec.describe Gitlab::GithubImport::Client do
|
|||
expect(client).to receive(:raise_or_wait_for_rate_limit)
|
||||
|
||||
client.with_rate_limit do
|
||||
if retries.zero?
|
||||
if retries == 0
|
||||
retries += 1
|
||||
raise(Octokit::TooManyRequests)
|
||||
end
|
||||
|
|
|
@ -118,7 +118,7 @@ RSpec.describe Gitlab::Popen::Runner do
|
|||
stdout: 'stdout',
|
||||
stderr: '',
|
||||
exitstatus: 0,
|
||||
status: double(exitstatus: exitstatus, success?: exitstatus.zero?),
|
||||
status: double(exitstatus: exitstatus, success?: exitstatus == 0),
|
||||
duration: 0.1)
|
||||
|
||||
result =
|
||||
|
|
|
@ -191,7 +191,7 @@ RSpec.describe Gitlab::TreeSummary do
|
|||
|
||||
with_them do
|
||||
before do
|
||||
create_file('dummy', path: 'other') if num_entries.zero?
|
||||
create_file('dummy', path: 'other') if num_entries == 0
|
||||
1.upto(num_entries) { |n| create_file(n, path: path) }
|
||||
end
|
||||
|
||||
|
@ -218,7 +218,7 @@ RSpec.describe Gitlab::TreeSummary do
|
|||
|
||||
with_them do
|
||||
before do
|
||||
create_file('dummy', path: 'other') if num_entries.zero?
|
||||
create_file('dummy', path: 'other') if num_entries == 0
|
||||
1.upto(num_entries) { |n| create_file(n, path: path) }
|
||||
end
|
||||
|
||||
|
|
|
@ -87,11 +87,6 @@ RSpec.describe ApplicationSetting do
|
|||
it { is_expected.not_to allow_value('abc').for(:minimum_password_length) }
|
||||
it { is_expected.to allow_value(10).for(:minimum_password_length) }
|
||||
|
||||
it { is_expected.to allow_value(0).for(:namespace_storage_size_limit) }
|
||||
it { is_expected.to allow_value(1).for(:namespace_storage_size_limit) }
|
||||
it { is_expected.not_to allow_value(nil).for(:namespace_storage_size_limit) }
|
||||
it { is_expected.not_to allow_value(-1).for(:namespace_storage_size_limit) }
|
||||
|
||||
it { is_expected.to allow_value(300).for(:issues_create_limit) }
|
||||
it { is_expected.not_to allow_value('three').for(:issues_create_limit) }
|
||||
it { is_expected.not_to allow_value(nil).for(:issues_create_limit) }
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe 'Projects::Metrics::Dashboards::BuilderController' do
|
||||
let_it_be(:project) { create(:project) }
|
||||
let_it_be(:environment) { create(:environment, project: project) }
|
||||
let_it_be(:user) { create(:user) }
|
||||
|
||||
def send_request(params = {})
|
||||
post namespace_project_metrics_dashboards_builder_path(namespace_id: project.namespace, project_id: project, format: :json, **params)
|
||||
end
|
||||
|
||||
describe 'POST /:namespace/:project/-/metrics/dashboards/builder' do
|
||||
context 'as anonymous user' do
|
||||
before do
|
||||
stub_feature_flags(metrics_dashboard_new_panel_page: true)
|
||||
end
|
||||
|
||||
it 'redirects to sign in' do
|
||||
send_request
|
||||
|
||||
expect(response).to redirect_to(new_user_session_path)
|
||||
end
|
||||
end
|
||||
|
||||
context 'as user with reporter access' do
|
||||
before do
|
||||
stub_feature_flags(metrics_dashboard_new_panel_page: true)
|
||||
project.add_guest(user)
|
||||
login_as(user)
|
||||
end
|
||||
|
||||
it 'returns not found' do
|
||||
send_request
|
||||
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
end
|
||||
end
|
||||
|
||||
context 'as logged in user' do
|
||||
before do
|
||||
project.add_developer(user)
|
||||
login_as(user)
|
||||
end
|
||||
|
||||
context 'metrics_dashboard_new_panel_page is enabled' do
|
||||
before do
|
||||
stub_feature_flags(metrics_dashboard_new_panel_page: true)
|
||||
end
|
||||
|
||||
it 'returns success' do
|
||||
send_request
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
end
|
||||
end
|
||||
|
||||
context 'metrics_dashboard_new_panel_page is disabled' do
|
||||
before do
|
||||
stub_feature_flags(metrics_dashboard_new_panel_page: false)
|
||||
end
|
||||
|
||||
it 'returns not found' do
|
||||
send_request
|
||||
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -44,7 +44,7 @@ class BareRepoOperations
|
|||
yield stdin if block_given?
|
||||
end
|
||||
|
||||
unless status.zero?
|
||||
unless status == 0
|
||||
if allow_failure
|
||||
return []
|
||||
else
|
||||
|
|
|
@ -21,7 +21,7 @@ module MemoryUsageHelper
|
|||
|
||||
def get_memory_usage
|
||||
output, status = Gitlab::Popen.popen(%w(free -m))
|
||||
abort "`free -m` return code is #{status}: #{output}" unless status.zero?
|
||||
abort "`free -m` return code is #{status}: #{output}" unless status == 0
|
||||
|
||||
result = output.split("\n")[1].split(" ")[1..-1]
|
||||
attrs = %i(m_total m_used m_free m_shared m_buffers_cache m_available).freeze
|
||||
|
|
|
@ -42,7 +42,7 @@ module WaitForRequests
|
|||
private
|
||||
|
||||
def finished_all_rack_requests?
|
||||
Gitlab::Testing::RequestBlockerMiddleware.num_active_requests.zero?
|
||||
Gitlab::Testing::RequestBlockerMiddleware.num_active_requests == 0
|
||||
end
|
||||
|
||||
def finished_all_js_requests?
|
||||
|
@ -53,12 +53,12 @@ module WaitForRequests
|
|||
end
|
||||
|
||||
def finished_all_axios_requests?
|
||||
Capybara.page.evaluate_script('window.pendingRequests || 0').zero?
|
||||
Capybara.page.evaluate_script('window.pendingRequests || 0').zero? # rubocop:disable Style/NumericPredicate
|
||||
end
|
||||
|
||||
def finished_all_ajax_requests?
|
||||
return true if Capybara.page.evaluate_script('typeof jQuery === "undefined"')
|
||||
|
||||
Capybara.page.evaluate_script('jQuery.active').zero?
|
||||
Capybara.page.evaluate_script('jQuery.active').zero? # rubocop:disable Style/NumericPredicate
|
||||
end
|
||||
end
|
||||
|
|
|
@ -44,7 +44,7 @@ module ExceedQueryLimitHelpers
|
|||
def log_message
|
||||
if expected.is_a?(ActiveRecord::QueryRecorder)
|
||||
counts = count_queries(strip_marginalia_annotations(expected.log))
|
||||
extra_queries = strip_marginalia_annotations(@recorder.log).reject { |query| counts[query] -= 1 unless counts[query].zero? }
|
||||
extra_queries = strip_marginalia_annotations(@recorder.log).reject { |query| counts[query] -= 1 unless counts[query] == 0 }
|
||||
extra_queries_display = count_queries(extra_queries).map { |query, count| "[#{count}] #{query}" }
|
||||
|
||||
(['Extra queries:'] + extra_queries_display).join("\n\n")
|
||||
|
@ -188,7 +188,7 @@ RSpec::Matchers.define :issue_same_number_of_queries_as do
|
|||
|
||||
def expected_count_message
|
||||
or_fewer_msg = "or fewer" if @or_fewer
|
||||
threshold_msg = "(+/- #{threshold})" unless threshold.zero?
|
||||
threshold_msg = "(+/- #{threshold})" unless threshold == 0
|
||||
|
||||
["#{expected_count}", or_fewer_msg, threshold_msg].compact.join(' ')
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue