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
path: root/app
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-02-13 15:08:49 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-02-13 15:08:49 +0300
commit1308dc5eb484ab0f8064989fc551ebdb4b1a7976 (patch)
tree614a93d9bf8df34ecfc25c02648329987fb21dde /app
parentf0707f413ce49b5712fca236b950acbec029be1e (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/code_navigation/store/actions.js3
-rw-r--r--app/assets/javascripts/diffs/components/app.vue2
-rw-r--r--app/assets/javascripts/flash.js2
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/form.vue12
-rw-r--r--app/assets/javascripts/ide/components/file_row_extra.vue15
-rw-r--r--app/assets/javascripts/ide/stores/actions.js9
-rw-r--r--app/assets/javascripts/ide/stores/actions/file.js4
-rw-r--r--app/assets/javascripts/registry/explorer/components/group_empty_state.vue39
-rw-r--r--app/assets/javascripts/registry/explorer/components/project_empty_state.vue113
-rw-r--r--app/assets/javascripts/registry/explorer/pages/details.vue329
-rw-r--r--app/assets/javascripts/registry/explorer/pages/list.vue211
-rw-r--r--app/assets/javascripts/registry/explorer/stores/actions.js4
-rw-r--r--app/assets/javascripts/registry/explorer/stores/mutations.js1
-rw-r--r--app/controllers/ide_controller.rb4
-rw-r--r--app/models/ci/bridge.rb3
-rw-r--r--app/models/ci/pipeline.rb24
-rw-r--r--app/models/concerns/ci/pipeline_delegator.rb2
-rw-r--r--app/models/concerns/has_ref.rb2
-rw-r--r--app/models/concerns/reactive_caching.rb2
-rw-r--r--app/models/merge_request.rb2
-rw-r--r--app/models/merge_request/pipelines.rb2
-rw-r--r--app/models/wiki_page.rb18
-rw-r--r--app/presenters/ci/pipeline_presenter.rb2
-rw-r--r--app/serializers/pipeline_entity.rb8
-rw-r--r--app/services/merge_requests/create_pipeline_service.rb2
-rw-r--r--app/views/groups/registry/repositories/index.html.haml1
-rw-r--r--app/views/projects/merge_requests/conflicts/components/_diff_file_editor.html.haml2
-rw-r--r--app/views/projects/merge_requests/conflicts/show.html.haml2
-rw-r--r--app/views/projects/merge_requests/creations/_new_compare.html.haml6
-rw-r--r--app/views/projects/merge_requests/creations/_new_submit.html.haml3
-rw-r--r--app/views/projects/merge_requests/show.html.haml3
-rw-r--r--app/views/projects/wikis/_form.html.haml6
32 files changed, 762 insertions, 76 deletions
diff --git a/app/assets/javascripts/code_navigation/store/actions.js b/app/assets/javascripts/code_navigation/store/actions.js
index cb6d30c775d..2c52074e362 100644
--- a/app/assets/javascripts/code_navigation/store/actions.js
+++ b/app/assets/javascripts/code_navigation/store/actions.js
@@ -1,6 +1,4 @@
import api from '~/api';
-import { __ } from '~/locale';
-import createFlash from '~/flash';
import * as types from './mutation_types';
import { getCurrentHoverElement, setCurrentHoverElement, addInteractionClass } from '../utils';
@@ -10,7 +8,6 @@ export default {
},
requestDataError({ commit }) {
commit(types.REQUEST_DATA_ERROR);
- createFlash(__('An error occurred loading code navigation'));
},
fetchData({ commit, dispatch, state }) {
commit(types.REQUEST_DATA);
diff --git a/app/assets/javascripts/diffs/components/app.vue b/app/assets/javascripts/diffs/components/app.vue
index 878b54f7d53..f9d3d31e152 100644
--- a/app/assets/javascripts/diffs/components/app.vue
+++ b/app/assets/javascripts/diffs/components/app.vue
@@ -354,7 +354,7 @@ export default {
<template>
<div v-show="shouldShow">
- <div v-if="isLoading" class="loading"><gl-loading-icon /></div>
+ <div v-if="isLoading" class="loading"><gl-loading-icon size="lg" /></div>
<div v-else id="diffs" :class="{ active: shouldShow }" class="diffs tab-pane">
<compare-versions
:merge-request-diffs="mergeRequestDiffs"
diff --git a/app/assets/javascripts/flash.js b/app/assets/javascripts/flash.js
index 347f7b450ff..4d62ec6e385 100644
--- a/app/assets/javascripts/flash.js
+++ b/app/assets/javascripts/flash.js
@@ -11,7 +11,7 @@ const FLASH_TYPES = {
const hideFlash = (flashEl, fadeTransition = true) => {
if (fadeTransition) {
Object.assign(flashEl.style, {
- transition: 'opacity .3s',
+ transition: 'opacity 0.15s',
opacity: '0',
});
}
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/form.vue b/app/assets/javascripts/ide/components/commit_sidebar/form.vue
index 491814bb408..9777f365557 100644
--- a/app/assets/javascripts/ide/components/commit_sidebar/form.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/form.vue
@@ -6,7 +6,6 @@ import CommitMessageField from './message_field.vue';
import Actions from './actions.vue';
import SuccessMessage from './success_message.vue';
import { activityBarViews, MAX_WINDOW_HEIGHT_COMPACT } from '../../constants';
-import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
export default {
components: {
@@ -15,7 +14,6 @@ export default {
CommitMessageField,
SuccessMessage,
},
- mixins: [glFeatureFlagsMixin()],
data() {
return {
isCompact: true,
@@ -29,13 +27,9 @@ export default {
...mapGetters('commit', ['discardDraftButtonDisabled', 'preBuiltCommitMessage']),
overviewText() {
return sprintf(
- this.glFeatures.stageAllByDefault
- ? __(
- '<strong>%{stagedFilesLength} staged</strong> and <strong>%{changedFilesLength} unstaged</strong> changes',
- )
- : __(
- '<strong>%{changedFilesLength} unstaged</strong> and <strong>%{stagedFilesLength} staged</strong> changes',
- ),
+ __(
+ '<strong>%{stagedFilesLength} staged</strong> and <strong>%{changedFilesLength} unstaged</strong> changes',
+ ),
{
stagedFilesLength: this.stagedFiles.length,
changedFilesLength: this.changedFiles.length,
diff --git a/app/assets/javascripts/ide/components/file_row_extra.vue b/app/assets/javascripts/ide/components/file_row_extra.vue
index 33098eb1af0..3ef7d863bd5 100644
--- a/app/assets/javascripts/ide/components/file_row_extra.vue
+++ b/app/assets/javascripts/ide/components/file_row_extra.vue
@@ -6,7 +6,6 @@ import Icon from '~/vue_shared/components/icon.vue';
import ChangedFileIcon from '~/vue_shared/components/changed_file_icon.vue';
import NewDropdown from './new_dropdown/index.vue';
import MrFileIcon from './mr_file_icon.vue';
-import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
export default {
name: 'FileRowExtra',
@@ -19,7 +18,6 @@ export default {
ChangedFileIcon,
MrFileIcon,
},
- mixins: [glFeatureFlagsMixin()],
props: {
file: {
type: Object,
@@ -57,15 +55,10 @@ export default {
return n__('%d staged change', '%d staged changes', this.folderStagedCount);
}
- return sprintf(
- this.glFeatures.stageAllByDefault
- ? __('%{staged} staged and %{unstaged} unstaged changes')
- : __('%{unstaged} unstaged and %{staged} staged changes'),
- {
- unstaged: this.folderUnstagedCount,
- staged: this.folderStagedCount,
- },
- );
+ return sprintf(__('%{staged} staged and %{unstaged} unstaged changes'), {
+ unstaged: this.folderUnstagedCount,
+ staged: this.folderStagedCount,
+ });
},
showTreeChangesCount() {
return this.isTree && this.changesCount > 0 && !this.file.opened;
diff --git a/app/assets/javascripts/ide/stores/actions.js b/app/assets/javascripts/ide/stores/actions.js
index 9bc008c0dd5..ddc0925efb9 100644
--- a/app/assets/javascripts/ide/stores/actions.js
+++ b/app/assets/javascripts/ide/stores/actions.js
@@ -79,10 +79,7 @@ export const createTempEntry = (
if (type === 'blob') {
commit(types.TOGGLE_FILE_OPEN, file.path);
-
- if (gon.features?.stageAllByDefault)
- commit(types.STAGE_CHANGE, { path: file.path, diffInfo: getters.getDiffInfo(file.path) });
- else commit(types.ADD_FILE_TO_CHANGED, file.path);
+ commit(types.STAGE_CHANGE, { path: file.path, diffInfo: getters.getDiffInfo(file.path) });
dispatch('setFileActive', file.path);
dispatch('triggerFilesChange');
@@ -250,9 +247,7 @@ export const renameEntry = ({ dispatch, commit, state, getters }, { path, name,
if (isReset) {
commit(types.REMOVE_FILE_FROM_STAGED_AND_CHANGED, newEntry);
} else if (!isInChanges) {
- if (gon.features?.stageAllByDefault)
- commit(types.STAGE_CHANGE, { path: newPath, diffInfo: getters.getDiffInfo(newPath) });
- else commit(types.ADD_FILE_TO_CHANGED, newPath);
+ commit(types.STAGE_CHANGE, { path: newPath, diffInfo: getters.getDiffInfo(newPath) });
}
if (!newEntry.tempFile) {
diff --git a/app/assets/javascripts/ide/stores/actions/file.js b/app/assets/javascripts/ide/stores/actions/file.js
index 052120059c4..da7d4a44bde 100644
--- a/app/assets/javascripts/ide/stores/actions/file.js
+++ b/app/assets/javascripts/ide/stores/actions/file.js
@@ -158,9 +158,7 @@ export const changeFileContent = ({ commit, state, getters }, { path, content })
const indexOfChangedFile = state.changedFiles.findIndex(f => f.path === path);
if (file.changed && indexOfChangedFile === -1) {
- if (gon.features?.stageAllByDefault)
- commit(types.STAGE_CHANGE, { path, diffInfo: getters.getDiffInfo(path) });
- else commit(types.ADD_FILE_TO_CHANGED, path);
+ commit(types.STAGE_CHANGE, { path, diffInfo: getters.getDiffInfo(path) });
} else if (!file.changed && !file.tempFile && indexOfChangedFile !== -1) {
commit(types.REMOVE_FILE_FROM_CHANGED, path);
}
diff --git a/app/assets/javascripts/registry/explorer/components/group_empty_state.vue b/app/assets/javascripts/registry/explorer/components/group_empty_state.vue
new file mode 100644
index 00000000000..a29a9bd23c3
--- /dev/null
+++ b/app/assets/javascripts/registry/explorer/components/group_empty_state.vue
@@ -0,0 +1,39 @@
+<script>
+import { GlEmptyState, GlSprintf, GlLink } from '@gitlab/ui';
+import { mapState } from 'vuex';
+
+export default {
+ name: 'GroupEmptyState',
+ components: {
+ GlEmptyState,
+ GlSprintf,
+ GlLink,
+ },
+ computed: {
+ ...mapState(['config']),
+ },
+};
+</script>
+<template>
+ <gl-empty-state
+ :title="s__('ContainerRegistry|There are no container images available in this group')"
+ :svg-path="config.noContainersImage"
+ class="container-message"
+ >
+ <template #description>
+ <p class="js-no-container-images-text">
+ <gl-sprintf
+ :message="
+ s__(
+ `ContainerRegistry|With the Container Registry, every project can have its own space to store its Docker images. Push at least one Docker image in one of this group's projects in order to show up here. %{docLinkStart}More Information%{docLinkEnd}`,
+ )
+ "
+ >
+ <template #docLink="{content}">
+ <gl-link :href="config.helpPagePath" target="_blank">{{ content }}</gl-link>
+ </template>
+ </gl-sprintf>
+ </p>
+ </template>
+ </gl-empty-state>
+</template>
diff --git a/app/assets/javascripts/registry/explorer/components/project_empty_state.vue b/app/assets/javascripts/registry/explorer/components/project_empty_state.vue
new file mode 100644
index 00000000000..53853b4b9fb
--- /dev/null
+++ b/app/assets/javascripts/registry/explorer/components/project_empty_state.vue
@@ -0,0 +1,113 @@
+<script>
+import { GlEmptyState, GlSprintf, GlLink } from '@gitlab/ui';
+import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
+import { mapState } from 'vuex';
+
+export default {
+ name: 'ProjectEmptyState',
+ components: {
+ ClipboardButton,
+ GlEmptyState,
+ GlSprintf,
+ GlLink,
+ },
+ computed: {
+ ...mapState(['config']),
+ dockerBuildCommand() {
+ // eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings
+ return `docker build -t ${this.config.repositoryUrl} .`;
+ },
+ dockerPushCommand() {
+ // eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings
+ return `docker push ${this.config.repositoryUrl}`;
+ },
+ dockerLoginCommand() {
+ // eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings
+ return `docker login ${this.config.registryHostUrlWithPort}`;
+ },
+ },
+};
+</script>
+<template>
+ <gl-empty-state
+ :title="s__('ContainerRegistry|There are no container images stored for this project')"
+ :svg-path="config.noContainersImage"
+ class="container-message"
+ >
+ <template #description>
+ <p class="js-no-container-images-text">
+ <gl-sprintf
+ :message="
+ s__(`ContainerRegistry|With the Container Registry, every project can have its own space to
+ store its Docker images. %{docLinkStart}More Information%{docLinkEnd}`)
+ "
+ >
+ <template #docLink="{content}">
+ <gl-link :href="config.helpPagePath" target="_blank">{{ content }}</gl-link>
+ </template>
+ </gl-sprintf>
+ </p>
+ <h5>{{ s__('ContainerRegistry|Quick Start') }}</h5>
+ <p class="js-not-logged-in-to-registry-text">
+ <gl-sprintf
+ :message="
+ s__(`ContainerRegistry|If you are not already logged in, you need to authenticate to
+ the Container Registry by using your GitLab username and password. If you have
+ %{twofaDocLinkStart}Two-Factor Authentication%{twofaDocLinkEnd} enabled, use a
+ %{personalAccessTokensDocLinkStart}Personal Access Token%{personalAccessTokensDocLinkEnd}
+ instead of a password.`)
+ "
+ >
+ <template #twofaDocLink="{content}">
+ <gl-link :href="config.twoFactorAuthHelpLink" target="_blank">{{ content }}</gl-link>
+ </template>
+ <template #personalAccessTokensDocLink="{content}">
+ <gl-link :href="config.personalAccessTokensHelpLink" target="_blank">{{
+ content
+ }}</gl-link>
+ </template>
+ </gl-sprintf>
+ </p>
+ <div class="input-group append-bottom-10">
+ <input :value="dockerLoginCommand" type="text" class="form-control monospace" readonly />
+ <span class="input-group-append">
+ <clipboard-button
+ :text="dockerLoginCommand"
+ :title="s__('ContainerRegistry|Copy login command')"
+ class="input-group-text"
+ />
+ </span>
+ </div>
+ <p></p>
+ <p>
+ {{
+ s__(
+ 'ContainerRegistry|You can add an image to this registry with the following commands:',
+ )
+ }}
+ </p>
+
+ <div class="input-group append-bottom-10">
+ <input :value="dockerBuildCommand" type="text" class="form-control monospace" readonly />
+ <span class="input-group-append">
+ <clipboard-button
+ :text="dockerBuildCommand"
+ :title="s__('ContainerRegistry|Copy build command')"
+ class="input-group-text"
+ />
+ </span>
+ </div>
+
+ <div class="input-group">
+ <input :value="dockerPushCommand" type="text" class="form-control monospace" readonly />
+ <span class="input-group-append">
+ <clipboard-button
+ :text="dockerPushCommand"
+ :title="s__('ContainerRegistry|Copy push command')"
+ class="input-group-text"
+ />
+ </span>
+ </div>
+ </template>
+ </gl-empty-state>
+</template>
diff --git a/app/assets/javascripts/registry/explorer/pages/details.vue b/app/assets/javascripts/registry/explorer/pages/details.vue
index 6d32ba41eae..bff67bb8376 100644
--- a/app/assets/javascripts/registry/explorer/pages/details.vue
+++ b/app/assets/javascripts/registry/explorer/pages/details.vue
@@ -1,7 +1,332 @@
<script>
-export default {};
+import { mapState, mapActions } from 'vuex';
+import {
+ GlTable,
+ GlFormCheckbox,
+ GlButton,
+ GlIcon,
+ GlTooltipDirective,
+ GlPagination,
+ GlModal,
+ GlLoadingIcon,
+ GlSprintf,
+ GlEmptyState,
+ GlResizeObserverDirective,
+} from '@gitlab/ui';
+import { GlBreakpointInstance } from '@gitlab/ui/dist/utils';
+import { n__, s__ } from '~/locale';
+import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
+import { numberToHumanSize } from '~/lib/utils/number_utils';
+import timeagoMixin from '~/vue_shared/mixins/timeago';
+import Tracking from '~/tracking';
+import {
+ LIST_KEY_TAG,
+ LIST_KEY_IMAGE_ID,
+ LIST_KEY_SIZE,
+ LIST_KEY_LAST_UPDATED,
+ LIST_KEY_ACTIONS,
+ LIST_KEY_CHECKBOX,
+ LIST_LABEL_TAG,
+ LIST_LABEL_IMAGE_ID,
+ LIST_LABEL_SIZE,
+ LIST_LABEL_LAST_UPDATED,
+} from '../constants';
+
+export default {
+ components: {
+ GlTable,
+ GlFormCheckbox,
+ GlButton,
+ GlIcon,
+ ClipboardButton,
+ GlPagination,
+ GlModal,
+ GlLoadingIcon,
+ GlSprintf,
+ GlEmptyState,
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ GlResizeObserver: GlResizeObserverDirective,
+ },
+ mixins: [timeagoMixin, Tracking.mixin()],
+ data() {
+ return {
+ selectedItems: [],
+ itemsToBeDeleted: [],
+ selectAllChecked: false,
+ modalDescription: null,
+ isDesktop: true,
+ };
+ },
+ computed: {
+ ...mapState(['tags', 'tagsPagination', 'isLoading', 'config']),
+ imageName() {
+ const { name } = JSON.parse(window.atob(this.$route.params.id));
+ return name;
+ },
+ fields() {
+ return [
+ { key: LIST_KEY_CHECKBOX, label: '' },
+ { key: LIST_KEY_TAG, label: LIST_LABEL_TAG },
+ { key: LIST_KEY_IMAGE_ID, label: LIST_LABEL_IMAGE_ID },
+ { key: LIST_KEY_SIZE, label: LIST_LABEL_SIZE },
+ { key: LIST_KEY_LAST_UPDATED, label: LIST_LABEL_LAST_UPDATED },
+ { key: LIST_KEY_ACTIONS, label: '' },
+ ].filter(f => f.key !== LIST_KEY_CHECKBOX || this.isDesktop);
+ },
+ isMultiDelete() {
+ return this.itemsToBeDeleted.length > 1;
+ },
+ tracking() {
+ return {
+ label: this.isMultiDelete ? 'bulk_registry_tag_delete' : 'registry_tag_delete',
+ };
+ },
+ modalAction() {
+ return n__(
+ 'ContainerRegistry|Remove tag',
+ 'ContainerRegistry|Remove tags',
+ this.isMultiDelete ? this.itemsToBeDeleted.length : 1,
+ );
+ },
+ currentPage: {
+ get() {
+ return this.tagsPagination.page;
+ },
+ set(page) {
+ this.requestTagsList({ pagination: { page }, id: this.$route.params.id });
+ },
+ },
+ },
+ methods: {
+ ...mapActions(['requestTagsList', 'requestDeleteTag', 'requestDeleteTags']),
+ setModalDescription(itemIndex = -1) {
+ if (itemIndex === -1) {
+ this.modalDescription = {
+ message: s__(`ContainerRegistry|You are about to remove %{item} tags. Are you sure?`),
+ item: this.itemsToBeDeleted.length,
+ };
+ } else {
+ const { path } = this.tags[itemIndex];
+
+ this.modalDescription = {
+ message: s__(`ContainerRegistry|You are about to remove %{item}. Are you sure?`),
+ item: path,
+ };
+ }
+ },
+ formatSize(size) {
+ return numberToHumanSize(size);
+ },
+ layers(layers) {
+ return layers ? n__('%d layer', '%d layers', layers) : '';
+ },
+ onSelectAllChange() {
+ if (this.selectAllChecked) {
+ this.deselectAll();
+ } else {
+ this.selectAll();
+ }
+ },
+ selectAll() {
+ this.selectedItems = this.tags.map((x, index) => index);
+ this.selectAllChecked = true;
+ },
+ deselectAll() {
+ this.selectedItems = [];
+ this.selectAllChecked = false;
+ },
+ updateSelectedItems(index) {
+ const delIndex = this.selectedItems.findIndex(x => x === index);
+
+ if (delIndex > -1) {
+ this.selectedItems.splice(delIndex, 1);
+ this.selectAllChecked = false;
+ } else {
+ this.selectedItems.push(index);
+
+ if (this.selectedItems.length === this.tags.length) {
+ this.selectAllChecked = true;
+ }
+ }
+ },
+ deleteSingleItem(index) {
+ this.setModalDescription(index);
+ this.itemsToBeDeleted = [index];
+ this.track('click_button');
+ this.$refs.deleteModal.show();
+ },
+ deleteMultipleItems() {
+ this.itemsToBeDeleted = [...this.selectedItems];
+ if (this.selectedItems.length === 1) {
+ this.setModalDescription(this.itemsToBeDeleted[0]);
+ } else if (this.selectedItems.length > 1) {
+ this.setModalDescription();
+ }
+ this.track('click_button');
+ this.$refs.deleteModal.show();
+ },
+ handleSingleDelete(itemToDelete) {
+ this.itemsToBeDeleted = [];
+ this.requestDeleteTag({ tag: itemToDelete, imageId: this.$route.params.id });
+ },
+ handleMultipleDelete() {
+ const { itemsToBeDeleted } = this;
+ this.itemsToBeDeleted = [];
+ this.selectedItems = [];
+
+ this.requestDeleteTags({
+ ids: itemsToBeDeleted.map(x => this.tags[x].name),
+ imageId: this.$route.params.id,
+ });
+ },
+ onDeletionConfirmed() {
+ this.track('confirm_delete');
+ if (this.isMultiDelete) {
+ this.handleMultipleDelete();
+ } else {
+ const index = this.itemsToBeDeleted[0];
+ this.handleSingleDelete(this.tags[index]);
+ }
+ },
+ handleResize() {
+ this.isDesktop = GlBreakpointInstance.isDesktop();
+ },
+ },
+};
</script>
<template>
- <div></div>
+ <div
+ v-gl-resize-observer="handleResize"
+ class="my-3 position-absolute w-100 slide-enter-to-element"
+ >
+ <div class="d-flex my-3 align-items-center">
+ <h4>
+ <gl-sprintf :message="s__('ContainerRegistry|%{imageName} tags')">
+ <template #imageName>
+ {{ imageName }}
+ </template>
+ </gl-sprintf>
+ </h4>
+ </div>
+ <gl-loading-icon v-if="isLoading" />
+ <template v-else-if="tags.length > 0">
+ <gl-table :items="tags" :fields="fields" :stacked="!isDesktop">
+ <template v-if="isDesktop" #head(checkbox)>
+ <gl-form-checkbox
+ ref="mainCheckbox"
+ :checked="selectAllChecked"
+ @change="onSelectAllChange"
+ />
+ </template>
+ <template #head(actions)>
+ <gl-button
+ ref="bulkDeleteButton"
+ v-gl-tooltip
+ :disabled="!selectedItems || selectedItems.length === 0"
+ class="float-right"
+ variant="danger"
+ :title="s__('ContainerRegistry|Remove selected tags')"
+ :aria-label="s__('ContainerRegistry|Remove selected tags')"
+ @click="deleteMultipleItems()"
+ >
+ <gl-icon name="remove" />
+ </gl-button>
+ </template>
+
+ <template #cell(checkbox)="{index}">
+ <gl-form-checkbox
+ ref="rowCheckbox"
+ class="js-row-checkbox"
+ :checked="selectedItems.includes(index)"
+ @change="updateSelectedItems(index)"
+ />
+ </template>
+ <template #cell(name)="{item}">
+ <span ref="rowName">
+ {{ item.name }}
+ </span>
+ <clipboard-button
+ v-if="item.location"
+ ref="rowClipboardButton"
+ :title="item.location"
+ :text="item.location"
+ css-class="btn-default btn-transparent btn-clipboard"
+ />
+ </template>
+ <template #cell(short_revision)="{value}">
+ <span ref="rowShortRevision">
+ {{ value }}
+ </span>
+ </template>
+ <template #cell(total_size)="{item}">
+ <span ref="rowSize">
+ {{ formatSize(item.total_size) }}
+ <template v-if="item.total_size && item.layers">
+ &middot;
+ </template>
+ {{ layers(item.layers) }}
+ </span>
+ </template>
+ <template #cell(created_at)="{value}">
+ <span ref="rowTime">
+ {{ timeFormatted(value) }}
+ </span>
+ </template>
+ <template #cell(actions)="{index, item}">
+ <gl-button
+ ref="singleDeleteButton"
+ :title="s__('ContainerRegistry|Remove tag')"
+ :aria-label="s__('ContainerRegistry|Remove tag')"
+ :disabled="!item.destroy_path"
+ variant="danger"
+ :class="['js-delete-registry float-right btn-inverted btn-border-color btn-icon']"
+ @click="deleteSingleItem(index)"
+ >
+ <gl-icon name="remove" />
+ </gl-button>
+ </template>
+ </gl-table>
+ <gl-pagination
+ ref="pagination"
+ v-model="currentPage"
+ :per-page="tagsPagination.perPage"
+ :total-items="tagsPagination.total"
+ align="center"
+ class="w-100"
+ />
+ <gl-modal
+ ref="deleteModal"
+ modal-id="delete-tag-modal"
+ ok-variant="danger"
+ @ok="onDeletionConfirmed"
+ @cancel="track('cancel_delete')"
+ >
+ <template #modal-title>{{ modalAction }}</template>
+ <template #modal-ok>{{ modalAction }}</template>
+ <p v-if="modalDescription">
+ <gl-sprintf :message="modalDescription.message">
+ <template #item>
+ <b>{{ modalDescription.item }}</b>
+ </template>
+ </gl-sprintf>
+ </p>
+ </gl-modal>
+ </template>
+ <gl-empty-state
+ v-else
+ :title="s__('ContainerRegistry|This image has no active tags')"
+ :svg-path="config.noContainersImage"
+ :description="
+ s__(
+ `ContainerRegistry|The last tag related to this image was recently removed.
+ This empty image and any associated data will be automatically removed as part of the regular Garbage Collection process.
+ If you have any questions, contact your administrator.`,
+ )
+ "
+ class="mx-auto my-0"
+ />
+ </div>
</template>
diff --git a/app/assets/javascripts/registry/explorer/pages/list.vue b/app/assets/javascripts/registry/explorer/pages/list.vue
index 6d32ba41eae..dc730ac2828 100644
--- a/app/assets/javascripts/registry/explorer/pages/list.vue
+++ b/app/assets/javascripts/registry/explorer/pages/list.vue
@@ -1,7 +1,214 @@
<script>
-export default {};
+import { mapState, mapActions } from 'vuex';
+import {
+ GlLoadingIcon,
+ GlEmptyState,
+ GlPagination,
+ GlTooltipDirective,
+ GlButton,
+ GlIcon,
+ GlModal,
+ GlSprintf,
+ GlLink,
+} from '@gitlab/ui';
+import Tracking from '~/tracking';
+import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
+import ProjectEmptyState from '../components/project_empty_state.vue';
+import GroupEmptyState from '../components/group_empty_state.vue';
+
+export default {
+ name: 'RegistryListApp',
+ components: {
+ GlEmptyState,
+ GlLoadingIcon,
+ GlPagination,
+ ProjectEmptyState,
+ GroupEmptyState,
+ ClipboardButton,
+ GlButton,
+ GlIcon,
+ GlModal,
+ GlSprintf,
+ GlLink,
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+ mixins: [Tracking.mixin()],
+ data() {
+ return {
+ itemToDelete: {},
+ };
+ },
+ computed: {
+ ...mapState(['config', 'isLoading', 'images', 'pagination']),
+ tracking() {
+ return {
+ label: 'registry_repository_delete',
+ };
+ },
+ currentPage: {
+ get() {
+ return this.pagination.page;
+ },
+ set(page) {
+ this.requestImagesList({ page });
+ },
+ },
+ },
+ methods: {
+ ...mapActions(['requestImagesList', 'requestDeleteImage']),
+ deleteImage(item) {
+ // This event is already tracked in the system and so the name must be kept to aggregate the data
+ this.track('click_button');
+ this.itemToDelete = item;
+ this.$refs.deleteModal.show();
+ },
+ handleDeleteRepository() {
+ this.track('confirm_delete');
+ this.requestDeleteImage(this.itemToDelete.destroy_path);
+ this.itemToDelete = {};
+ },
+ encodeListItem(item) {
+ const params = JSON.stringify({ name: item.path, tags_path: item.tags_path });
+ return window.btoa(params);
+ },
+ },
+};
</script>
<template>
- <div></div>
+ <div class="position-absolute w-100 slide-enter-from-element">
+ <gl-empty-state
+ v-if="config.characterError"
+ :title="s__('ContainerRegistry|Docker connection error')"
+ :svg-path="config.containersErrorImage"
+ >
+ <template #description>
+ <p>
+ <gl-sprintf
+ :message="
+ s__(`ContainerRegistry|We are having trouble connecting to Docker, which could be due to an
+ issue with your project name or path.
+ %{docLinkStart}More Information%{docLinkEnd}`)
+ "
+ >
+ <template #docLink="{content}">
+ <gl-link :href="`${config.helpPagePath}#docker-connection-error`" target="_blank">
+ {{ content }}
+ </gl-link>
+ </template>
+ </gl-sprintf>
+ </p>
+ </template>
+ </gl-empty-state>
+
+ <template v-else>
+ <gl-loading-icon v-if="isLoading" size="md" class="prepend-top-16" />
+
+ <template v-else>
+ <div v-if="images.length" ref="imagesList">
+ <h4>{{ s__('ContainerRegistry|Container Registry') }}</h4>
+ <p>
+ <gl-sprintf
+ :message="
+ s__(`ContainerRegistry|With the Docker Container Registry integrated into GitLab, every
+ project can have its own space to store its Docker images.
+ %{docLinkStart}More Information%{docLinkEnd}`)
+ "
+ >
+ <template #docLink="{content}">
+ <gl-link :href="config.helpPagePath" target="_blank">
+ {{ content }}
+ </gl-link>
+ </template>
+ </gl-sprintf>
+ </p>
+
+ <div class="d-flex flex-column">
+ <div
+ v-for="(listItem, index) in images"
+ :key="index"
+ ref="rowItem"
+ :class="[
+ 'd-flex justify-content-between align-items-center py-2 border-bottom',
+ { 'border-top': index === 0 },
+ ]"
+ >
+ <div>
+ <router-link
+ ref="detailsLink"
+ :to="{ name: 'details', params: { id: encodeListItem(listItem) } }"
+ >
+ {{ listItem.path }}
+ </router-link>
+ <clipboard-button
+ v-if="listItem.location"
+ ref="clipboardButton"
+ :text="listItem.location"
+ :title="listItem.location"
+ css-class="btn-default btn-transparent btn-clipboard"
+ />
+ </div>
+ <div
+ v-gl-tooltip="{ disabled: listItem.destroy_path }"
+ class="d-none d-sm-block"
+ :title="
+ s__(
+ 'ContainerRegistry|Missing or insufficient permission, delete button disabled',
+ )
+ "
+ >
+ <gl-button
+ ref="deleteImageButton"
+ v-gl-tooltip
+ :disabled="!listItem.destroy_path"
+ :title="s__('ContainerRegistry|Remove repository')"
+ :aria-label="s__('ContainerRegistry|Remove repository')"
+ class="btn-inverted"
+ variant="danger"
+ @click="deleteImage(listItem)"
+ >
+ <gl-icon name="remove" />
+ </gl-button>
+ </div>
+ </div>
+ </div>
+ <gl-pagination
+ v-model="currentPage"
+ :per-page="pagination.perPage"
+ :total-items="pagination.total"
+ align="center"
+ class="w-100 mt-2"
+ />
+ </div>
+ <template v-else>
+ <project-empty-state v-if="!config.isGroupPage" />
+ <group-empty-state v-else />
+ </template>
+ </template>
+
+ <gl-modal
+ ref="deleteModal"
+ modal-id="delete-image-modal"
+ ok-variant="danger"
+ @ok="handleDeleteRepository"
+ @cancel="track('cancel_delete')"
+ >
+ <template #modal-title>{{ s__('ContainerRegistry|Remove repository') }}</template>
+ <p>
+ <gl-sprintf
+ :message=" s__(
+ 'ContainerRegistry|You are about to remove repository %{title}. Once you confirm, this repository will be permanently deleted.',
+ ),"
+ >
+ <template #title>
+ <b>{{ itemToDelete.path }}</b>
+ </template>
+ </gl-sprintf>
+ </p>
+ <template #modal-ok>{{ __('Remove') }}</template>
+ </gl-modal>
+ </template>
+ </div>
</template>
diff --git a/app/assets/javascripts/registry/explorer/stores/actions.js b/app/assets/javascripts/registry/explorer/stores/actions.js
index 7c06a12a5fc..25ff105ac53 100644
--- a/app/assets/javascripts/registry/explorer/stores/actions.js
+++ b/app/assets/javascripts/registry/explorer/stores/actions.js
@@ -45,11 +45,11 @@ export const requestImagesList = ({ commit, dispatch, state }, pagination = {})
export const requestTagsList = ({ commit, dispatch }, { pagination = {}, id }) => {
commit(types.SET_MAIN_LOADING, true);
- const url = window.atob(id);
+ const { tags_path } = JSON.parse(window.atob(id));
const { page = DEFAULT_PAGE, perPage = DEFAULT_PAGE_SIZE } = pagination;
return axios
- .get(url, { params: { page, per_page: perPage } })
+ .get(tags_path, { params: { page, per_page: perPage } })
.then(({ data, headers }) => {
dispatch('receiveTagsListSuccess', { data, headers });
})
diff --git a/app/assets/javascripts/registry/explorer/stores/mutations.js b/app/assets/javascripts/registry/explorer/stores/mutations.js
index 186f36a759a..a2c6a11de20 100644
--- a/app/assets/javascripts/registry/explorer/stores/mutations.js
+++ b/app/assets/javascripts/registry/explorer/stores/mutations.js
@@ -5,6 +5,7 @@ export default {
[types.SET_INITIAL_STATE](state, config) {
state.config = {
...config,
+ isGroupPage: config.isGroupPage !== undefined,
};
},
diff --git a/app/controllers/ide_controller.rb b/app/controllers/ide_controller.rb
index ca35b07111c..4c9aac9a327 100644
--- a/app/controllers/ide_controller.rb
+++ b/app/controllers/ide_controller.rb
@@ -3,10 +3,6 @@
class IdeController < ApplicationController
layout 'fullscreen'
- before_action do
- push_frontend_feature_flag(:stage_all_by_default, default_enabled: true)
- end
-
def index
Gitlab::UsageDataCounters::WebIdeCounter.increment_views_count
end
diff --git a/app/models/ci/bridge.rb b/app/models/ci/bridge.rb
index bb22cc3e039..26997d17816 100644
--- a/app/models/ci/bridge.rb
+++ b/app/models/ci/bridge.rb
@@ -186,7 +186,8 @@ module Ci
},
execute_params: {
ignore_skip_ci: true,
- bridge: self
+ bridge: self,
+ merge_request: parent_pipeline.merge_request
}
}
end
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index bb59fc937b1..a8685da3cd9 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -77,9 +77,7 @@ module Ci
validates :sha, presence: { unless: :importing? }
validates :ref, presence: { unless: :importing? }
- validates :merge_request, presence: { if: :merge_request_event? }
- validates :merge_request, absence: { unless: :merge_request_event? }
- validates :tag, inclusion: { in: [false], if: :merge_request_event? }
+ validates :tag, inclusion: { in: [false], if: :merge_request? }
validates :external_pull_request, presence: { if: :external_pull_request_event? }
validates :external_pull_request, absence: { unless: :external_pull_request_event? }
@@ -662,7 +660,7 @@ module Ci
variables.concat(predefined_commit_variables)
- if merge_request_event? && merge_request
+ if merge_request?
variables.append(key: 'CI_MERGE_REQUEST_EVENT_TYPE', value: merge_request_event_type.to_s)
variables.append(key: 'CI_MERGE_REQUEST_SOURCE_BRANCH_SHA', value: source_sha.to_s)
variables.append(key: 'CI_MERGE_REQUEST_TARGET_BRANCH_SHA', value: target_sha.to_s)
@@ -720,7 +718,7 @@ module Ci
# All the merge requests for which the current pipeline runs/ran against
def all_merge_requests
@all_merge_requests ||=
- if merge_request_event?
+ if merge_request?
MergeRequest.where(id: merge_request_id)
else
MergeRequest.where(source_project_id: project_id, source_branch: ref)
@@ -812,7 +810,7 @@ module Ci
# * nil: Modified path can not be evaluated
def modified_paths
strong_memoize(:modified_paths) do
- if merge_request_event?
+ if merge_request?
merge_request.modified_paths
elsif branch_updated?
push_details.modified_paths
@@ -836,12 +834,12 @@ module Ci
ref == project.default_branch
end
- def triggered_by_merge_request?
- merge_request_event? && merge_request_id.present?
+ def merge_request?
+ merge_request_id.present?
end
def detached_merge_request_pipeline?
- triggered_by_merge_request? && target_sha.nil?
+ merge_request? && target_sha.nil?
end
def legacy_detached_merge_request_pipeline?
@@ -849,7 +847,7 @@ module Ci
end
def merge_request_pipeline?
- triggered_by_merge_request? && target_sha.present?
+ merge_request? && target_sha.present?
end
def merge_request_ref?
@@ -865,7 +863,7 @@ module Ci
end
def source_ref
- if triggered_by_merge_request?
+ if merge_request?
merge_request.source_branch
else
ref
@@ -885,7 +883,7 @@ module Ci
end
def merge_request_event_type
- return unless merge_request_event?
+ return unless merge_request?
strong_memoize(:merge_request_event_type) do
if merge_request_pipeline?
@@ -918,7 +916,7 @@ module Ci
def git_ref
strong_memoize(:git_ref) do
- if merge_request_event?
+ if merge_request?
##
# In the future, we're going to change this ref to
# merge request's merged reference, such as "refs/merge-requests/:iid/merge".
diff --git a/app/models/concerns/ci/pipeline_delegator.rb b/app/models/concerns/ci/pipeline_delegator.rb
index 9f95dc38422..68ad0fcee31 100644
--- a/app/models/concerns/ci/pipeline_delegator.rb
+++ b/app/models/concerns/ci/pipeline_delegator.rb
@@ -11,7 +11,7 @@ module Ci
extend ActiveSupport::Concern
included do
- delegate :merge_request_event?,
+ delegate :merge_request?,
:merge_request_ref?,
:legacy_detached_merge_request_pipeline?,
:merge_train_pipeline?, to: :pipeline
diff --git a/app/models/concerns/has_ref.rb b/app/models/concerns/has_ref.rb
index fa0cf5ddfd2..22e5955984d 100644
--- a/app/models/concerns/has_ref.rb
+++ b/app/models/concerns/has_ref.rb
@@ -7,7 +7,7 @@ module HasRef
extend ActiveSupport::Concern
def branch?
- !tag? && !merge_request_event?
+ !tag? && !merge_request?
end
def git_ref
diff --git a/app/models/concerns/reactive_caching.rb b/app/models/concerns/reactive_caching.rb
index 28d65e0bd45..010e0018414 100644
--- a/app/models/concerns/reactive_caching.rb
+++ b/app/models/concerns/reactive_caching.rb
@@ -9,6 +9,8 @@ module ReactiveCaching
ExceededReactiveCacheLimit = Class.new(StandardError)
included do
+ extend ActiveModel::Naming
+
class_attribute :reactive_cache_key
class_attribute :reactive_cache_lease_timeout
class_attribute :reactive_cache_refresh_interval
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index cf2763180c4..b8f51b58fd3 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -1163,7 +1163,7 @@ class MergeRequest < ApplicationRecord
# Since deployments run on a merge request ref (e.g. `refs/merge-requests/:iid/head`),
# we cannot look up environments with source branch name.
def environments
- return Environment.none unless actual_head_pipeline&.triggered_by_merge_request?
+ return Environment.none unless actual_head_pipeline&.merge_request?
actual_head_pipeline.environments
end
diff --git a/app/models/merge_request/pipelines.rb b/app/models/merge_request/pipelines.rb
index c32f29a9304..72756e8e9d0 100644
--- a/app/models/merge_request/pipelines.rb
+++ b/app/models/merge_request/pipelines.rb
@@ -61,6 +61,8 @@ class MergeRequest::Pipelines
pipelines.joins(shas_table)
end
+ # NOTE: this method returns only parent merge request pipelines.
+ # Child merge request pipelines have a different source.
def triggered_by_merge_request
source_project.ci_pipelines
.where(source: :merge_request_event, merge_request: merge_request)
diff --git a/app/models/wiki_page.rb b/app/models/wiki_page.rb
index c6867e48cbf..26beb77a025 100644
--- a/app/models/wiki_page.rb
+++ b/app/models/wiki_page.rb
@@ -5,6 +5,9 @@ class WikiPage
PageChangedError = Class.new(StandardError)
PageRenameError = Class.new(StandardError)
+ MAX_TITLE_BYTES = 245
+ MAX_DIRECTORY_BYTES = 255
+
include ActiveModel::Validations
include ActiveModel::Conversion
include StaticModel
@@ -51,6 +54,7 @@ class WikiPage
validates :title, presence: true
validates :content, presence: true
+ validate :validate_path_limits, if: :title_changed?
# The GitLab ProjectWiki instance.
attr_reader :wiki
@@ -262,7 +266,7 @@ class WikiPage
end
def title_changed?
- title.present? && self.class.unhyphenize(@page.url_path) != title
+ title.present? && (@page.nil? || self.class.unhyphenize(@page.url_path) != title)
end
# Updates the current @attributes hash by merging a hash of params
@@ -324,4 +328,16 @@ class WikiPage
set_attributes
@persisted = errors.blank?
end
+
+ def validate_path_limits
+ *dirnames, title = @attributes[:title].split('/')
+
+ if title.bytesize > MAX_TITLE_BYTES
+ errors.add(:title, _("exceeds the limit of %{bytes} bytes for page titles") % { bytes: MAX_TITLE_BYTES })
+ end
+
+ if dirnames.any? { |d| d.bytesize > MAX_DIRECTORY_BYTES }
+ errors.add(:title, _("exceeds the limit of %{bytes} bytes for directory names") % { bytes: MAX_DIRECTORY_BYTES })
+ end
+ end
end
diff --git a/app/presenters/ci/pipeline_presenter.rb b/app/presenters/ci/pipeline_presenter.rb
index b113c7043e7..37abefb5664 100644
--- a/app/presenters/ci/pipeline_presenter.rb
+++ b/app/presenters/ci/pipeline_presenter.rb
@@ -116,7 +116,7 @@ module Ci
def merge_request_presenter
strong_memoize(:merge_request_presenter) do
- if pipeline.triggered_by_merge_request?
+ if pipeline.merge_request?
pipeline.merge_request.present(current_user: current_user)
end
end
diff --git a/app/serializers/pipeline_entity.rb b/app/serializers/pipeline_entity.rb
index ba8f4fffe02..c3ddbb88c9c 100644
--- a/app/serializers/pipeline_entity.rb
+++ b/app/serializers/pipeline_entity.rb
@@ -25,7 +25,7 @@ class PipelineEntity < Grape::Entity
expose :flags do
expose :stuck?, as: :stuck
expose :auto_devops_source?, as: :auto_devops
- expose :merge_request_event?, as: :merge_request
+ expose :merge_request?, as: :merge_request
expose :has_yaml_errors?, as: :yaml_errors
expose :can_retry?, as: :retryable
expose :can_cancel?, as: :cancelable
@@ -59,11 +59,11 @@ class PipelineEntity < Grape::Entity
expose :tag?, as: :tag
expose :branch?, as: :branch
- expose :merge_request_event?, as: :merge_request
+ expose :merge_request?, as: :merge_request
end
expose :commit, using: CommitEntity
- expose :merge_request_event_type, if: -> (pipeline, _) { pipeline.merge_request_event? }
+ expose :merge_request_event_type, if: -> (pipeline, _) { pipeline.merge_request? }
expose :source_sha, if: -> (pipeline, _) { pipeline.merge_request_pipeline? }
expose :target_sha, if: -> (pipeline, _) { pipeline.merge_request_pipeline? }
expose :yaml_errors, if: -> (pipeline, _) { pipeline.has_yaml_errors? }
@@ -104,7 +104,7 @@ class PipelineEntity < Grape::Entity
end
def has_presentable_merge_request?
- pipeline.triggered_by_merge_request? &&
+ pipeline.merge_request? &&
can?(request.current_user, :read_merge_request, pipeline.merge_request)
end
diff --git a/app/services/merge_requests/create_pipeline_service.rb b/app/services/merge_requests/create_pipeline_service.rb
index 9eb11820f7a..8258efba6bf 100644
--- a/app/services/merge_requests/create_pipeline_service.rb
+++ b/app/services/merge_requests/create_pipeline_service.rb
@@ -24,7 +24,7 @@ module MergeRequests
##
# UpdateMergeRequestsWorker could be retried by an exception.
# pipelines for merge request should not be recreated in such case.
- return false if !allow_duplicate && merge_request.find_actual_head_pipeline&.triggered_by_merge_request?
+ return false if !allow_duplicate && merge_request.find_actual_head_pipeline&.merge_request?
return false if merge_request.has_no_commits?
true
diff --git a/app/views/groups/registry/repositories/index.html.haml b/app/views/groups/registry/repositories/index.html.haml
index 96edf837875..b82910df5d5 100644
--- a/app/views/groups/registry/repositories/index.html.haml
+++ b/app/views/groups/registry/repositories/index.html.haml
@@ -11,6 +11,7 @@
"no_containers_image" => image_path('illustrations/docker-empty-state.svg'),
"containers_error_image" => image_path('illustrations/docker-error-state.svg'),
"registry_host_url_with_port" => escape_once(registry_config.host_port),
+ is_group_page: true,
character_error: @character_error.to_s } }
- else
#js-vue-registry-images{ data: { endpoint: group_container_registries_path(@group, format: :json),
diff --git a/app/views/projects/merge_requests/conflicts/components/_diff_file_editor.html.haml b/app/views/projects/merge_requests/conflicts/components/_diff_file_editor.html.haml
index aff3fb82fa6..3ca82adccf1 100644
--- a/app/views/projects/merge_requests/conflicts/components/_diff_file_editor.html.haml
+++ b/app/views/projects/merge_requests/conflicts/components/_diff_file_editor.html.haml
@@ -8,6 +8,6 @@
%button.btn.btn-sm{ "@click" => "cancelDiscardConfirmation(file)" } Cancel
.editor-wrap{ ":class" => "classObject" }
.loading
- %i.fa.fa-spinner.fa-spin
+ .spinner.spinner-md
.editor
%pre{ "style" => "height: 350px" }
diff --git a/app/views/projects/merge_requests/conflicts/show.html.haml b/app/views/projects/merge_requests/conflicts/show.html.haml
index f48390aa046..d933675eac5 100644
--- a/app/views/projects/merge_requests/conflicts/show.html.haml
+++ b/app/views/projects/merge_requests/conflicts/show.html.haml
@@ -11,7 +11,7 @@
#conflicts{ "v-cloak" => "true", data: { conflicts_path: conflicts_project_merge_request_path(@merge_request.project, @merge_request, format: :json),
resolve_conflicts_path: resolve_conflicts_project_merge_request_path(@merge_request.project, @merge_request) } }
.loading{ "v-if" => "isLoading" }
- %i.fa.fa-spinner.fa-spin
+ .spinner.spinner-md
.nothing-here-block{ "v-if" => "hasError" }
{{conflictsData.errorMessage}}
diff --git a/app/views/projects/merge_requests/creations/_new_compare.html.haml b/app/views/projects/merge_requests/creations/_new_compare.html.haml
index c6615b26bc0..99537ba8152 100644
--- a/app/views/projects/merge_requests/creations/_new_compare.html.haml
+++ b/app/views/projects/merge_requests/creations/_new_compare.html.haml
@@ -30,7 +30,8 @@
= dropdown_content
= dropdown_loading
.card-footer
- .text-center= icon('spinner spin', class: 'js-source-loading')
+ .text-center
+ .js-source-loading.mt-1.spinner.spinner-sm
%ul.list-unstyled.mr_source_commit
.col-lg-6
@@ -58,7 +59,8 @@
= dropdown_content
= dropdown_loading
.card-footer
- .text-center= icon('spinner spin', class: "js-target-loading")
+ .text-center
+ .js-target-loading.mt-1.spinner.spinner-sm
%ul.list-unstyled.mr_target_commit
- if @merge_request.errors.any?
diff --git a/app/views/projects/merge_requests/creations/_new_submit.html.haml b/app/views/projects/merge_requests/creations/_new_submit.html.haml
index 15c83f92474..0fb4d9ae70f 100644
--- a/app/views/projects/merge_requests/creations/_new_submit.html.haml
+++ b/app/views/projects/merge_requests/creations/_new_submit.html.haml
@@ -47,4 +47,5 @@
= render 'projects/merge_requests/pipelines', endpoint: url_for(safe_params.merge(action: 'pipelines', format: :json)), disable_initialization: true
.mr-loading-status
- = spinner
+ .loading.hide
+ .spinner.spinner-md
diff --git a/app/views/projects/merge_requests/show.html.haml b/app/views/projects/merge_requests/show.html.haml
index 310cd355d22..d65c874f245 100644
--- a/app/views/projects/merge_requests/show.html.haml
+++ b/app/views/projects/merge_requests/show.html.haml
@@ -88,7 +88,8 @@
show_whitespace_default: @show_whitespace_default.to_s }
.mr-loading-status
- = spinner
+ .loading.hide
+ .spinner.spinner-md
= render 'shared/issuable/sidebar', issuable_sidebar: @issuable_sidebar, assignees: @merge_request.assignees
diff --git a/app/views/projects/wikis/_form.html.haml b/app/views/projects/wikis/_form.html.haml
index a153f527ee0..0affd9f0e6f 100644
--- a/app/views/projects/wikis/_form.html.haml
+++ b/app/views/projects/wikis/_form.html.haml
@@ -17,9 +17,13 @@
= icon('lightbulb-o')
- if @page.persisted?
= s_("WikiEditPageTip|Tip: You can move this page by adding the path to the beginning of the title.")
- = link_to icon('question-circle'), help_page_path('user/project/wiki/index', anchor: 'moving-a-wiki-page'), target: '_blank'
+ = link_to icon('question-circle'), help_page_path('user/project/wiki/index', anchor: 'moving-a-wiki-page'),
+ target: '_blank', rel: 'noopener noreferrer'
- else
= s_("WikiNewPageTip|Tip: You can specify the full path for the new file. We will automatically create any missing directories.")
+ = succeed '.' do
+ = link_to _('Learn more'), help_page_path('user/project/wiki/index', anchor: 'creating-a-new-wiki-page'),
+ target: '_blank', rel: 'noopener noreferrer'
.form-group.row
.col-sm-12= f.label :format, class: 'control-label-full-width'
.col-sm-12