Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-08-20 21:42:06 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-08-20 21:42:06 +0300
commit6e4e1050d9dba2b7b2523fdd1768823ab85feef4 (patch)
tree78be5963ec075d80116a932011d695dd33910b4e /app/assets/javascripts/add_context_commits_modal
parent1ce776de4ae122aba3f349c02c17cebeaa8ecf07 (diff)
Add latest changes from gitlab-org/gitlab@13-3-stable-ee
Diffstat (limited to 'app/assets/javascripts/add_context_commits_modal')
-rw-r--r--app/assets/javascripts/add_context_commits_modal/components/add_context_commits_modal_trigger.vue49
-rw-r--r--app/assets/javascripts/add_context_commits_modal/components/add_context_commits_modal_wrapper.vue279
-rw-r--r--app/assets/javascripts/add_context_commits_modal/components/review_tab_container.vue57
-rw-r--r--app/assets/javascripts/add_context_commits_modal/event_hub.js3
-rw-r--r--app/assets/javascripts/add_context_commits_modal/index.js64
-rw-r--r--app/assets/javascripts/add_context_commits_modal/store/actions.js134
-rw-r--r--app/assets/javascripts/add_context_commits_modal/store/index.js15
-rw-r--r--app/assets/javascripts/add_context_commits_modal/store/mutation_types.js20
-rw-r--r--app/assets/javascripts/add_context_commits_modal/store/mutations.js56
-rw-r--r--app/assets/javascripts/add_context_commits_modal/store/state.js13
-rw-r--r--app/assets/javascripts/add_context_commits_modal/utils.js32
11 files changed, 722 insertions, 0 deletions
diff --git a/app/assets/javascripts/add_context_commits_modal/components/add_context_commits_modal_trigger.vue b/app/assets/javascripts/add_context_commits_modal/components/add_context_commits_modal_trigger.vue
new file mode 100644
index 00000000000..78a575ffe96
--- /dev/null
+++ b/app/assets/javascripts/add_context_commits_modal/components/add_context_commits_modal_trigger.vue
@@ -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>
diff --git a/app/assets/javascripts/add_context_commits_modal/components/add_context_commits_modal_wrapper.vue b/app/assets/javascripts/add_context_commits_modal/components/add_context_commits_modal_wrapper.vue
new file mode 100644
index 00000000000..cb9aa50fa68
--- /dev/null
+++ b/app/assets/javascripts/add_context_commits_modal/components/add_context_commits_modal_wrapper.vue
@@ -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 { deprecatedCreateFlash as 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>
diff --git a/app/assets/javascripts/add_context_commits_modal/components/review_tab_container.vue b/app/assets/javascripts/add_context_commits_modal/components/review_tab_container.vue
new file mode 100644
index 00000000000..36e3449ff27
--- /dev/null
+++ b/app/assets/javascripts/add_context_commits_modal/components/review_tab_container.vue
@@ -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>
diff --git a/app/assets/javascripts/add_context_commits_modal/event_hub.js b/app/assets/javascripts/add_context_commits_modal/event_hub.js
new file mode 100644
index 00000000000..e31806ad199
--- /dev/null
+++ b/app/assets/javascripts/add_context_commits_modal/event_hub.js
@@ -0,0 +1,3 @@
+import createEventHub from '~/helpers/event_hub_factory';
+
+export default createEventHub();
diff --git a/app/assets/javascripts/add_context_commits_modal/index.js b/app/assets/javascripts/add_context_commits_modal/index.js
new file mode 100644
index 00000000000..b5cd111fabc
--- /dev/null
+++ b/app/assets/javascripts/add_context_commits_modal/index.js
@@ -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,
+ },
+ });
+ },
+ });
+ }
+}
diff --git a/app/assets/javascripts/add_context_commits_modal/store/actions.js b/app/assets/javascripts/add_context_commits_modal/store/actions.js
new file mode 100644
index 00000000000..d23955182b2
--- /dev/null
+++ b/app/assets/javascripts/add_context_commits_modal/store/actions.js
@@ -0,0 +1,134 @@
+import _ from 'lodash';
+import axios from '~/lib/utils/axios_utils';
+import { deprecatedCreateFlash as 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);
diff --git a/app/assets/javascripts/add_context_commits_modal/store/index.js b/app/assets/javascripts/add_context_commits_modal/store/index.js
new file mode 100644
index 00000000000..0bf3441379b
--- /dev/null
+++ b/app/assets/javascripts/add_context_commits_modal/store/index.js
@@ -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,
+ });
diff --git a/app/assets/javascripts/add_context_commits_modal/store/mutation_types.js b/app/assets/javascripts/add_context_commits_modal/store/mutation_types.js
new file mode 100644
index 00000000000..eda82f3984d
--- /dev/null
+++ b/app/assets/javascripts/add_context_commits_modal/store/mutation_types.js
@@ -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';
diff --git a/app/assets/javascripts/add_context_commits_modal/store/mutations.js b/app/assets/javascripts/add_context_commits_modal/store/mutations.js
new file mode 100644
index 00000000000..8a3da0ca248
--- /dev/null
+++ b/app/assets/javascripts/add_context_commits_modal/store/mutations.js
@@ -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 = '';
+ },
+};
diff --git a/app/assets/javascripts/add_context_commits_modal/store/state.js b/app/assets/javascripts/add_context_commits_modal/store/state.js
new file mode 100644
index 00000000000..37239adccbb
--- /dev/null
+++ b/app/assets/javascripts/add_context_commits_modal/store/state.js
@@ -0,0 +1,13 @@
+export default () => ({
+ contextCommitsPath: '',
+ tabIndex: 0,
+ isLoadingCommits: false,
+ commits: [],
+ commitsLoadingError: false,
+ selectedCommits: [],
+ isLoadingContextCommits: false,
+ contextCommits: [],
+ contextCommitsLoadingError: false,
+ searchText: '',
+ toRemoveCommits: [],
+});
diff --git a/app/assets/javascripts/add_context_commits_modal/utils.js b/app/assets/javascripts/add_context_commits_modal/utils.js
new file mode 100644
index 00000000000..3495ee17cd3
--- /dev/null
+++ b/app/assets/javascripts/add_context_commits_modal/utils.js
@@ -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;
+};