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>2019-10-17 15:07:33 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2019-10-17 15:07:33 +0300
commit6b75320f525f841454f1ab162d141d3610f2e77b (patch)
tree4971c27759e4fbc18b85e71800c3b9c12346317e /app
parent4226aca420920c1844e8eade4798a2dff188a6fc (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/jobs/store/mutations.js6
-rw-r--r--app/assets/javascripts/jobs/store/utils.js18
-rw-r--r--app/assets/javascripts/pages/groups/registry/repositories/index.js3
-rw-r--r--app/assets/javascripts/registry/components/app.vue117
-rw-r--r--app/assets/javascripts/registry/components/collapsible_container.vue10
-rw-r--r--app/assets/javascripts/registry/components/group_empty_state.vue46
-rw-r--r--app/assets/javascripts/registry/components/project_empty_state.vue133
-rw-r--r--app/assets/javascripts/registry/components/table_registry.vue19
-rw-r--r--app/assets/javascripts/registry/index.js31
-rw-r--r--app/assets/javascripts/registry/stores/actions.js2
-rw-r--r--app/assets/javascripts/registry/stores/getters.js1
-rw-r--r--app/assets/javascripts/registry/stores/mutation_types.js1
-rw-r--r--app/assets/javascripts/registry/stores/mutations.js5
-rw-r--r--app/assets/javascripts/registry/stores/state.js1
-rw-r--r--app/controllers/boards/application_controller.rb2
-rw-r--r--app/controllers/boards/lists_controller.rb4
-rw-r--r--app/controllers/concerns/milestone_actions.rb2
-rw-r--r--app/controllers/groups/milestones_controller.rb2
-rw-r--r--app/controllers/groups/registry/repositories_controller.rb34
-rw-r--r--app/helpers/application_settings_helper.rb3
-rw-r--r--app/helpers/gitlab_routing_helper.rb6
-rw-r--r--app/helpers/groups_helper.rb10
-rw-r--r--app/helpers/todos_helper.rb6
-rw-r--r--app/models/application_setting.rb3
-rw-r--r--app/models/application_setting_implementation.rb1
-rw-r--r--app/models/board.rb5
-rw-r--r--app/models/container_repository.rb1
-rw-r--r--app/models/global_milestone.rb2
-rw-r--r--app/models/gpg_signature.rb2
-rw-r--r--app/models/merge_request_diff.rb18
-rw-r--r--app/models/milestone.rb3
-rw-r--r--app/models/note.rb3
-rw-r--r--app/models/push_event.rb2
-rw-r--r--app/models/todo.rb3
-rw-r--r--app/policies/board_policy.rb4
-rw-r--r--app/policies/milestone_policy.rb2
-rw-r--r--app/serializers/container_repository_entity.rb2
-rw-r--r--app/services/bulk_push_event_payload_service.rb19
-rw-r--r--app/services/event_create_service.rb24
-rw-r--r--app/services/git/base_hooks_service.rb2
-rw-r--r--app/services/git/process_ref_changes_service.rb21
-rw-r--r--app/services/notes/quick_actions_service.rb2
-rw-r--r--app/services/search/snippet_service.rb8
-rw-r--r--app/views/admin/application_settings/_performance.html.haml5
-rw-r--r--app/views/events/event/_push.html.haml12
-rw-r--r--app/views/groups/registry/repositories/index.html.haml12
-rw-r--r--app/views/groups/sidebar/_packages.html.haml16
-rw-r--r--app/views/layouts/nav/sidebar/_group.html.haml2
-rw-r--r--app/views/shared/boards/_switcher.html.haml2
-rw-r--r--app/views/shared/issuable/_board_create_list_dropdown.html.haml2
-rw-r--r--app/views/shared/issuable/_search_bar.html.haml2
51 files changed, 452 insertions, 190 deletions
diff --git a/app/assets/javascripts/jobs/store/mutations.js b/app/assets/javascripts/jobs/store/mutations.js
index 702f00888d0..77c68cac4a6 100644
--- a/app/assets/javascripts/jobs/store/mutations.js
+++ b/app/assets/javascripts/jobs/store/mutations.js
@@ -26,7 +26,7 @@ export default {
if (log.append) {
if (isNewJobLogActive()) {
- state.trace = updateIncrementalTrace(log.lines, state.trace);
+ state.trace = log.lines ? updateIncrementalTrace(log.lines, state.trace) : state.trace;
} else {
state.trace += log.html;
}
@@ -35,9 +35,9 @@ export default {
// When the job still does not have a trace
// the trace response will not have a defined
// html or size. We keep the old value otherwise these
- // will be set to `undefined`
+ // will be set to `null`
if (isNewJobLogActive()) {
- state.trace = logLinesParser(log.lines) || state.trace;
+ state.trace = log.lines ? logLinesParser(log.lines) : state.trace;
} else {
state.trace = log.html || state.trace;
}
diff --git a/app/assets/javascripts/jobs/store/utils.js b/app/assets/javascripts/jobs/store/utils.js
index 12069e0c123..58e49f54d96 100644
--- a/app/assets/javascripts/jobs/store/utils.js
+++ b/app/assets/javascripts/jobs/store/utils.js
@@ -147,13 +147,15 @@ export const findOffsetAndRemove = (newLog = [], oldParsed = []) => {
const firstNew = newLog[0];
- if (last.offset === firstNew.offset || (last.line && last.line.offset === firstNew.offset)) {
- cloneOldLog.splice(lastIndex);
- } else if (last.lines && last.lines.length) {
- const lastNestedIndex = last.lines.length - 1;
- const lastNested = last.lines[lastNestedIndex];
- if (lastNested.offset === firstNew.offset) {
- last.lines.splice(lastNestedIndex);
+ if (last && firstNew) {
+ if (last.offset === firstNew.offset || (last.line && last.line.offset === firstNew.offset)) {
+ cloneOldLog.splice(lastIndex);
+ } else if (last.lines && last.lines.length) {
+ const lastNestedIndex = last.lines.length - 1;
+ const lastNested = last.lines[lastNestedIndex];
+ if (lastNested.offset === firstNew.offset) {
+ last.lines.splice(lastNestedIndex);
+ }
}
}
@@ -170,7 +172,7 @@ export const findOffsetAndRemove = (newLog = [], oldParsed = []) => {
* @param array oldLog
* @param array newLog
*/
-export const updateIncrementalTrace = (newLog, oldParsed = []) => {
+export const updateIncrementalTrace = (newLog = [], oldParsed = []) => {
const parsedLog = findOffsetAndRemove(newLog, oldParsed);
return logLinesParser(newLog, parsedLog);
diff --git a/app/assets/javascripts/pages/groups/registry/repositories/index.js b/app/assets/javascripts/pages/groups/registry/repositories/index.js
new file mode 100644
index 00000000000..b663defad0e
--- /dev/null
+++ b/app/assets/javascripts/pages/groups/registry/repositories/index.js
@@ -0,0 +1,3 @@
+import initRegistryImages from '~/registry';
+
+document.addEventListener('DOMContentLoaded', initRegistryImages);
diff --git a/app/assets/javascripts/registry/components/app.vue b/app/assets/javascripts/registry/components/app.vue
index a20bae9e37e..11b2c3b7016 100644
--- a/app/assets/javascripts/registry/components/app.vue
+++ b/app/assets/javascripts/registry/components/app.vue
@@ -2,17 +2,19 @@
import { mapGetters, mapActions } from 'vuex';
import { GlLoadingIcon, GlEmptyState } from '@gitlab/ui';
import store from '../stores';
-import clipboardButton from '../../vue_shared/components/clipboard_button.vue';
import CollapsibleContainer from './collapsible_container.vue';
+import ProjectEmptyState from './project_empty_state.vue';
+import GroupEmptyState from './group_empty_state.vue';
import { s__, sprintf } from '../../locale';
export default {
name: 'RegistryListApp',
components: {
- clipboardButton,
CollapsibleContainer,
GlEmptyState,
GlLoadingIcon,
+ ProjectEmptyState,
+ GroupEmptyState,
},
props: {
characterError: {
@@ -38,19 +40,27 @@ export default {
},
personalAccessTokensHelpLink: {
type: String,
- required: true,
+ required: false,
+ default: null,
},
registryHostUrlWithPort: {
type: String,
- required: true,
+ required: false,
+ default: null,
},
repositoryUrl: {
type: String,
required: true,
},
+ isGroupPage: {
+ type: Boolean,
+ default: false,
+ required: false,
+ },
twoFactorAuthHelpLink: {
type: String,
- required: true,
+ required: false,
+ default: null,
},
},
store,
@@ -91,37 +101,10 @@ export default {
false,
);
},
- notLoggedInToRegistryText() {
- return sprintf(
- 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.`),
- {
- twofaDocLinkStart: `<a href="${this.twoFactorAuthHelpLink}" target="_blank">`,
- twofaDocLinkEnd: '</a>',
- personalAccessTokensDocLinkStart: `<a href="${this.personalAccessTokensHelpLink}" target="_blank">`,
- personalAccessTokensDocLinkEnd: '</a>',
- },
- false,
- );
- },
- dockerLoginCommand() {
- // eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings
- return `docker login ${this.registryHostUrlWithPort}`;
- },
- dockerBuildCommand() {
- // eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings
- return `docker build -t ${this.repositoryUrl} .`;
- },
- dockerPushCommand() {
- // eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings
- return `docker push ${this.repositoryUrl}`;
- },
},
created() {
this.setMainEndpoint(this.endpoint);
+ this.setIsDeleteDisabled(this.isGroupPage);
},
mounted() {
if (!this.characterError) {
@@ -129,7 +112,7 @@ export default {
}
},
methods: {
- ...mapActions(['setMainEndpoint', 'fetchRepos']),
+ ...mapActions(['setMainEndpoint', 'fetchRepos', 'setIsDeleteDisabled']),
},
};
</script>
@@ -152,57 +135,19 @@ export default {
<p v-html="introText"></p>
<collapsible-container v-for="item in repos" :key="item.id" :repo="item" />
</div>
-
- <gl-empty-state
- v-else
- :title="s__('ContainerRegistry|There are no container images stored for this project')"
- :svg-path="noContainersImage"
- class="container-message"
- >
- <template #description>
- <p class="js-no-container-images-text" v-html="noContainerImagesText"></p>
- <h5>{{ s__('ContainerRegistry|Quick Start') }}</h5>
- <p class="js-not-logged-in-to-registry-text" v-html="notLoggedInToRegistryText"></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>
- {{
- 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>
+ <project-empty-state
+ v-else-if="!isGroupPage"
+ :no-containers-image="noContainersImage"
+ :help-page-path="helpPagePath"
+ :repository-url="repositoryUrl"
+ :two-factor-auth-help-link="twoFactorAuthHelpLink"
+ :personal-access-tokens-help-link="personalAccessTokensHelpLink"
+ :registry-host-url-with-port="registryHostUrlWithPort"
+ />
+ <group-empty-state
+ v-else-if="isGroupPage"
+ :no-containers-image="noContainersImage"
+ :help-page-path="helpPagePath"
+ />
</div>
</template>
diff --git a/app/assets/javascripts/registry/components/collapsible_container.vue b/app/assets/javascripts/registry/components/collapsible_container.vue
index 3e31d24088e..ed48331f459 100644
--- a/app/assets/javascripts/registry/components/collapsible_container.vue
+++ b/app/assets/javascripts/registry/components/collapsible_container.vue
@@ -1,5 +1,5 @@
<script>
-import { mapActions } from 'vuex';
+import { mapActions, mapGetters } from 'vuex';
import { GlLoadingIcon, GlButton, GlTooltipDirective, GlModal, GlModalDirective } from '@gitlab/ui';
import createFlash from '../../flash';
import ClipboardButton from '../../vue_shared/components/clipboard_button.vue';
@@ -35,9 +35,13 @@ export default {
};
},
computed: {
+ ...mapGetters(['isDeleteDisabled']),
iconName() {
return this.isOpen ? 'angle-up' : 'angle-right';
},
+ canDeleteRepo() {
+ return this.repo.canDelete && !this.isDeleteDisabled;
+ },
},
methods: {
...mapActions(['fetchRepos', 'fetchList', 'deleteItem']),
@@ -80,7 +84,7 @@ export default {
<div class="controls d-none d-sm-block float-right">
<gl-button
- v-if="repo.canDelete"
+ v-if="canDeleteRepo"
v-gl-tooltip
v-gl-modal="modalId"
:title="s__('ContainerRegistry|Remove repository')"
@@ -98,7 +102,7 @@ export default {
<gl-loading-icon v-if="repo.isLoading" size="md" class="append-bottom-20" />
<div v-else-if="!repo.isLoading && isOpen" class="container-image-tags">
- <table-registry v-if="repo.list.length" :repo="repo" />
+ <table-registry v-if="repo.list.length" :repo="repo" :can-delete-repo="canDeleteRepo" />
<div v-else class="nothing-here-block">
{{ s__('ContainerRegistry|No tags in Container Registry for this container image.') }}
diff --git a/app/assets/javascripts/registry/components/group_empty_state.vue b/app/assets/javascripts/registry/components/group_empty_state.vue
new file mode 100644
index 00000000000..7885fd2146d
--- /dev/null
+++ b/app/assets/javascripts/registry/components/group_empty_state.vue
@@ -0,0 +1,46 @@
+<script>
+import { GlEmptyState } from '@gitlab/ui';
+import { s__, sprintf } from '~/locale';
+
+export default {
+ name: 'GroupEmptyState',
+ components: {
+ GlEmptyState,
+ },
+ props: {
+ noContainersImage: {
+ type: String,
+ required: true,
+ },
+ helpPagePath: {
+ type: String,
+ required: true,
+ },
+ },
+ computed: {
+ noContainerImagesText() {
+ return sprintf(
+ 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}`,
+ ),
+ {
+ docLinkStart: `<a href="${this.helpPagePath}" target="_blank">`,
+ docLinkEnd: '</a>',
+ },
+ false,
+ );
+ },
+ },
+};
+</script>
+<template>
+ <gl-empty-state
+ :title="s__('ContainerRegistry|There are no container images available in this group')"
+ :svg-path="noContainersImage"
+ class="container-message"
+ >
+ <template #description>
+ <p class="js-no-container-images-text" v-html="noContainerImagesText"></p>
+ </template>
+ </gl-empty-state>
+</template>
diff --git a/app/assets/javascripts/registry/components/project_empty_state.vue b/app/assets/javascripts/registry/components/project_empty_state.vue
new file mode 100644
index 00000000000..80ef31004c8
--- /dev/null
+++ b/app/assets/javascripts/registry/components/project_empty_state.vue
@@ -0,0 +1,133 @@
+<script>
+import { GlEmptyState } from '@gitlab/ui';
+import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
+import { s__, sprintf } from '~/locale';
+
+export default {
+ name: 'ProjectEmptyState',
+ components: {
+ ClipboardButton,
+ GlEmptyState,
+ },
+ props: {
+ noContainersImage: {
+ type: String,
+ required: true,
+ },
+ repositoryUrl: {
+ type: String,
+ required: true,
+ },
+ helpPagePath: {
+ type: String,
+ required: true,
+ },
+ twoFactorAuthHelpLink: {
+ type: String,
+ required: true,
+ },
+ personalAccessTokensHelpLink: {
+ type: String,
+ required: true,
+ },
+ registryHostUrlWithPort: {
+ type: String,
+ required: true,
+ },
+ },
+ computed: {
+ dockerBuildCommand() {
+ // eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings
+ return `docker build -t ${this.repositoryUrl} .`;
+ },
+ dockerPushCommand() {
+ // eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings
+ return `docker push ${this.repositoryUrl}`;
+ },
+ dockerLoginCommand() {
+ // eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings
+ return `docker login ${this.registryHostUrlWithPort}`;
+ },
+ noContainerImagesText() {
+ return sprintf(
+ s__(`ContainerRegistry|With the Container Registry, every project can have its own space to
+ store its Docker images. %{docLinkStart}More Information%{docLinkEnd}`),
+ {
+ docLinkStart: `<a href="${this.helpPagePath}" target="_blank">`,
+ docLinkEnd: '</a>',
+ },
+ false,
+ );
+ },
+ notLoggedInToRegistryText() {
+ return sprintf(
+ 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.`),
+ {
+ twofaDocLinkStart: `<a href="${this.twoFactorAuthHelpLink}" target="_blank">`,
+ twofaDocLinkEnd: '</a>',
+ personalAccessTokensDocLinkStart: `<a href="${this.personalAccessTokensHelpLink}" target="_blank">`,
+ personalAccessTokensDocLinkEnd: '</a>',
+ },
+ false,
+ );
+ },
+ },
+};
+</script>
+<template>
+ <gl-empty-state
+ :title="s__('ContainerRegistry|There are no container images stored for this project')"
+ :svg-path="noContainersImage"
+ class="container-message"
+ >
+ <template #description>
+ <p class="js-no-container-images-text" v-html="noContainerImagesText"></p>
+ <h5>{{ s__('ContainerRegistry|Quick Start') }}</h5>
+ <p class="js-not-logged-in-to-registry-text" v-html="notLoggedInToRegistryText"></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/components/table_registry.vue b/app/assets/javascripts/registry/components/table_registry.vue
index 00acc0eb04a..ac7272c4d29 100644
--- a/app/assets/javascripts/registry/components/table_registry.vue
+++ b/app/assets/javascripts/registry/components/table_registry.vue
@@ -1,5 +1,5 @@
<script>
-import { mapActions } from 'vuex';
+import { mapActions, mapGetters } from 'vuex';
import {
GlButton,
GlFormCheckbox,
@@ -35,6 +35,11 @@ export default {
type: Object,
required: true,
},
+ canDeleteRepo: {
+ type: Boolean,
+ default: false,
+ required: false,
+ },
},
data() {
return {
@@ -45,6 +50,7 @@ export default {
};
},
computed: {
+ ...mapGetters(['isDeleteDisabled']),
bulkDeletePath() {
return this.repo.tagsPath ? this.repo.tagsPath.replace('?format=json', '/bulk_destroy') : '';
},
@@ -165,6 +171,9 @@ export default {
}
}
},
+ canDeleteRow(item) {
+ return item && item.canDelete && !this.isDeleteDisabled;
+ },
},
};
</script>
@@ -175,7 +184,7 @@ export default {
<tr>
<th>
<gl-form-checkbox
- v-if="repo.canDelete"
+ v-if="canDeleteRepo"
class="js-select-all-checkbox"
:checked="selectAllChecked"
@change="onSelectAllChange"
@@ -187,7 +196,7 @@ export default {
<th>{{ s__('ContainerRegistry|Last Updated') }}</th>
<th>
<gl-button
- v-if="repo.canDelete"
+ v-if="canDeleteRepo"
v-gl-tooltip
v-gl-modal="modalId"
:disabled="!itemsToBeDeleted || itemsToBeDeleted.length === 0"
@@ -208,7 +217,7 @@ export default {
<tr v-for="(item, index) in repo.list" :key="item.tag" class="registry-image-row">
<td class="check">
<gl-form-checkbox
- v-if="item.canDelete"
+ v-if="canDeleteRow(item)"
class="js-select-checkbox"
:checked="itemsToBeDeleted && itemsToBeDeleted.includes(index)"
@change="updateItemsToBeDeleted(index)"
@@ -244,7 +253,7 @@ export default {
<td class="content action-buttons">
<gl-button
- v-if="item.canDelete"
+ v-if="canDeleteRow(item)"
v-gl-modal="modalId"
:title="s__('ContainerRegistry|Remove tag')"
:aria-label="s__('ContainerRegistry|Remove tag')"
diff --git a/app/assets/javascripts/registry/index.js b/app/assets/javascripts/registry/index.js
index 38c3d67042c..18fd360f586 100644
--- a/app/assets/javascripts/registry/index.js
+++ b/app/assets/javascripts/registry/index.js
@@ -13,29 +13,24 @@ export default () =>
data() {
const { dataset } = document.querySelector(this.$options.el);
return {
- characterError: Boolean(dataset.characterError),
- containersErrorImage: dataset.containersErrorImage,
- endpoint: dataset.endpoint,
- helpPagePath: dataset.helpPagePath,
- noContainersImage: dataset.noContainersImage,
- personalAccessTokensHelpLink: dataset.personalAccessTokensHelpLink,
- registryHostUrlWithPort: dataset.registryHostUrlWithPort,
- repositoryUrl: dataset.repositoryUrl,
- twoFactorAuthHelpLink: dataset.twoFactorAuthHelpLink,
+ registryData: {
+ endpoint: dataset.endpoint,
+ characterError: Boolean(dataset.characterError),
+ helpPagePath: dataset.helpPagePath,
+ noContainersImage: dataset.noContainersImage,
+ containersErrorImage: dataset.containersErrorImage,
+ repositoryUrl: dataset.repositoryUrl,
+ isGroupPage: dataset.isGroupPage,
+ personalAccessTokensHelpLink: dataset.personalAccessTokensHelpLink,
+ registryHostUrlWithPort: dataset.registryHostUrlWithPort,
+ twoFactorAuthHelpLink: dataset.twoFactorAuthHelpLink,
+ },
};
},
render(createElement) {
return createElement('registry-app', {
props: {
- characterError: this.characterError,
- containersErrorImage: this.containersErrorImage,
- endpoint: this.endpoint,
- helpPagePath: this.helpPagePath,
- noContainersImage: this.noContainersImage,
- personalAccessTokensHelpLink: this.personalAccessTokensHelpLink,
- registryHostUrlWithPort: this.registryHostUrlWithPort,
- repositoryUrl: this.repositoryUrl,
- twoFactorAuthHelpLink: this.twoFactorAuthHelpLink,
+ ...this.registryData,
},
});
},
diff --git a/app/assets/javascripts/registry/stores/actions.js b/app/assets/javascripts/registry/stores/actions.js
index a2e0130e79e..2121f518a7a 100644
--- a/app/assets/javascripts/registry/stores/actions.js
+++ b/app/assets/javascripts/registry/stores/actions.js
@@ -20,7 +20,6 @@ export const fetchRepos = ({ commit, state }) => {
export const fetchList = ({ commit }, { repo, page }) => {
commit(types.TOGGLE_REGISTRY_LIST_LOADING, repo);
-
return axios
.get(repo.tagsPath, { params: { page } })
.then(response => {
@@ -40,6 +39,7 @@ export const multiDeleteItems = (_, { path, items }) =>
axios.delete(path, { params: { ids: items } });
export const setMainEndpoint = ({ commit }, data) => commit(types.SET_MAIN_ENDPOINT, data);
+export const setIsDeleteDisabled = ({ commit }, data) => commit(types.SET_IS_DELETE_DISABLED, data);
export const toggleLoading = ({ commit }) => commit(types.TOGGLE_MAIN_LOADING);
// prevent babel-plugin-rewire from generating an invalid default during karma tests
diff --git a/app/assets/javascripts/registry/stores/getters.js b/app/assets/javascripts/registry/stores/getters.js
index f4923512578..ac90bde1b2a 100644
--- a/app/assets/javascripts/registry/stores/getters.js
+++ b/app/assets/javascripts/registry/stores/getters.js
@@ -1,5 +1,6 @@
export const isLoading = state => state.isLoading;
export const repos = state => state.repos;
+export const isDeleteDisabled = state => state.isDeleteDisabled;
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
diff --git a/app/assets/javascripts/registry/stores/mutation_types.js b/app/assets/javascripts/registry/stores/mutation_types.js
index 2c69bf11807..6740bfede1a 100644
--- a/app/assets/javascripts/registry/stores/mutation_types.js
+++ b/app/assets/javascripts/registry/stores/mutation_types.js
@@ -1,4 +1,5 @@
export const SET_MAIN_ENDPOINT = 'SET_MAIN_ENDPOINT';
+export const SET_IS_DELETE_DISABLED = 'SET_IS_DELETE_DISABLED';
export const SET_REPOS_LIST = 'SET_REPOS_LIST';
export const TOGGLE_MAIN_LOADING = 'TOGGLE_MAIN_LOADING';
diff --git a/app/assets/javascripts/registry/stores/mutations.js b/app/assets/javascripts/registry/stores/mutations.js
index 8ace6657ad1..ea5925247d1 100644
--- a/app/assets/javascripts/registry/stores/mutations.js
+++ b/app/assets/javascripts/registry/stores/mutations.js
@@ -6,6 +6,10 @@ export default {
Object.assign(state, { endpoint });
},
+ [types.SET_IS_DELETE_DISABLED](state, isDeleteDisabled) {
+ Object.assign(state, { isDeleteDisabled });
+ },
+
[types.SET_REPOS_LIST](state, list) {
Object.assign(state, {
repos: list.map(el => ({
@@ -17,6 +21,7 @@ export default {
location: el.location,
name: el.path,
tagsPath: el.tags_path,
+ projectId: el.project_id,
})),
});
},
diff --git a/app/assets/javascripts/registry/stores/state.js b/app/assets/javascripts/registry/stores/state.js
index feeac10cbe1..724c64b4994 100644
--- a/app/assets/javascripts/registry/stores/state.js
+++ b/app/assets/javascripts/registry/stores/state.js
@@ -1,6 +1,7 @@
export default () => ({
isLoading: false,
endpoint: '', // initial endpoint to fetch the repos list
+ isDeleteDisabled: false, // controls the delete buttons in the registry
/**
* Each object in `repos` has the following strucure:
* {
diff --git a/app/controllers/boards/application_controller.rb b/app/controllers/boards/application_controller.rb
index eab908ba5ed..15ef6698472 100644
--- a/app/controllers/boards/application_controller.rb
+++ b/app/controllers/boards/application_controller.rb
@@ -13,7 +13,7 @@ module Boards
end
def board_parent
- @board_parent ||= board.parent
+ @board_parent ||= board.resource_parent
end
def record_not_found(exception)
diff --git a/app/controllers/boards/lists_controller.rb b/app/controllers/boards/lists_controller.rb
index 90e04414d8d..880f7500708 100644
--- a/app/controllers/boards/lists_controller.rb
+++ b/app/controllers/boards/lists_controller.rb
@@ -9,7 +9,7 @@ module Boards
skip_before_action :authenticate_user!, only: [:index]
def index
- lists = Boards::Lists::ListService.new(board.parent, current_user).execute(board)
+ lists = Boards::Lists::ListService.new(board.resource_parent, current_user).execute(board)
List.preload_preferences_for_user(lists, current_user)
@@ -17,7 +17,7 @@ module Boards
end
def create
- list = Boards::Lists::CreateService.new(board.parent, current_user, create_list_params).execute(board)
+ list = Boards::Lists::CreateService.new(board.resource_parent, current_user, create_list_params).execute(board)
if list.valid?
render json: serialize_as_json(list)
diff --git a/app/controllers/concerns/milestone_actions.rb b/app/controllers/concerns/milestone_actions.rb
index 1ead631663e..672d31ec779 100644
--- a/app/controllers/concerns/milestone_actions.rb
+++ b/app/controllers/concerns/milestone_actions.rb
@@ -35,7 +35,7 @@ module MilestoneActions
render json: tabs_json("shared/milestones/_labels_tab", {
labels: milestone_labels.map do |label|
- label.present(issuable_subject: @milestone.parent)
+ label.present(issuable_subject: @milestone.resource_parent)
end
})
end
diff --git a/app/controllers/groups/milestones_controller.rb b/app/controllers/groups/milestones_controller.rb
index 1eacae06457..1e9d51cf970 100644
--- a/app/controllers/groups/milestones_controller.rb
+++ b/app/controllers/groups/milestones_controller.rb
@@ -44,7 +44,7 @@ class Groups::MilestonesController < Groups::ApplicationController
# all projects milestones states at once.
milestones, update_params = get_milestones_for_update
milestones.each do |milestone|
- Milestones::UpdateService.new(milestone.parent, current_user, update_params).execute(milestone)
+ Milestones::UpdateService.new(milestone.resource_parent, current_user, update_params).execute(milestone)
end
redirect_to milestone_path
diff --git a/app/controllers/groups/registry/repositories_controller.rb b/app/controllers/groups/registry/repositories_controller.rb
new file mode 100644
index 00000000000..39f6963ee0a
--- /dev/null
+++ b/app/controllers/groups/registry/repositories_controller.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+module Groups
+ module Registry
+ class RepositoriesController < Groups::ApplicationController
+ before_action :verify_container_registry_enabled!
+ before_action :authorize_read_container_image!
+
+ def index
+ track_event(:list_repositories)
+
+ respond_to do |format|
+ format.html
+ format.json do
+ @images = group.container_repositories.with_api_entity_associations
+
+ render json: ContainerRepositoriesSerializer
+ .new(current_user: current_user)
+ .represent(@images)
+ end
+ end
+ end
+
+ private
+
+ def verify_container_registry_enabled!
+ render_404 unless Gitlab.config.registry.enabled
+ end
+
+ def authorize_read_container_image!
+ return render_404 unless can?(current_user, :read_container_image, group)
+ end
+ end
+ end
+end
diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb
index 3c70ff3b59f..115d1031a8a 100644
--- a/app/helpers/application_settings_helper.rb
+++ b/app/helpers/application_settings_helper.rb
@@ -290,7 +290,8 @@ module ApplicationSettingsHelper
:snowplow_cookie_domain,
:snowplow_enabled,
:snowplow_site_id,
- :push_event_hooks_limit
+ :push_event_hooks_limit,
+ :push_event_activities_limit
]
end
diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb
index f05218efe0c..4f31cc67ccc 100644
--- a/app/helpers/gitlab_routing_helper.rb
+++ b/app/helpers/gitlab_routing_helper.rb
@@ -76,10 +76,10 @@ module GitlabRoutingHelper
end
def edit_milestone_path(entity, *args)
- if entity.parent.is_a?(Group)
- edit_group_milestone_path(entity.parent, entity, *args)
+ if entity.resource_parent.is_a?(Group)
+ edit_group_milestone_path(entity.resource_parent, entity, *args)
else
- edit_project_milestone_path(entity.parent, entity, *args)
+ edit_project_milestone_path(entity.resource_parent, entity, *args)
end
end
diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb
index 9cba87ac444..811467ca03a 100644
--- a/app/helpers/groups_helper.rb
+++ b/app/helpers/groups_helper.rb
@@ -15,6 +15,16 @@ module GroupsHelper
%w[groups#projects groups#edit badges#index ci_cd#show ldap_group_links#index hooks#index audit_events#index pipeline_quota#index]
end
+ def group_packages_nav_link_paths
+ %w[
+ groups/container_registries#index
+ ]
+ end
+
+ def group_container_registry_nav?
+ Gitlab.config.registry.enabled && can?(current_user, :read_container_image, @group)
+ end
+
def group_sidebar_links
@group_sidebar_links ||= get_group_sidebar_links
end
diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb
index a919c068c42..dce0842060d 100644
--- a/app/helpers/todos_helper.rb
+++ b/app/helpers/todos_helper.rb
@@ -45,8 +45,8 @@ module TodosHelper
end
def todo_parent_path(todo)
- if todo.parent.is_a?(Group)
- link_to todo.parent.name, group_path(todo.parent)
+ if todo.resource_parent.is_a?(Group)
+ link_to todo.resource_parent.name, group_path(todo.resource_parent)
else
link_to_project(todo.project)
end
@@ -64,7 +64,7 @@ module TodosHelper
if todo.for_commit?
project_commit_path(todo.project, todo.target, path_options)
else
- path = [todo.parent, todo.target]
+ path = [todo.resource_parent, todo.target]
path.unshift(:pipelines) if todo.build_failed?
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index 0724ee8f39d..a07933d4975 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -217,6 +217,9 @@ class ApplicationSetting < ApplicationRecord
validates :push_event_hooks_limit,
numericality: { greater_than_or_equal_to: 0 }
+ validates :push_event_activities_limit,
+ numericality: { greater_than_or_equal_to: 0 }
+
SUPPORTED_KEY_TYPES.each do |type|
validates :"#{type}_key_restriction", presence: true, key_restriction: { type: type }
end
diff --git a/app/models/application_setting_implementation.rb b/app/models/application_setting_implementation.rb
index e9aab4a3d05..b341cf04403 100644
--- a/app/models/application_setting_implementation.rb
+++ b/app/models/application_setting_implementation.rb
@@ -83,6 +83,7 @@ module ApplicationSettingImplementation
project_export_enabled: true,
protected_ci_variables: false,
push_event_hooks_limit: 3,
+ push_event_activities_limit: 3,
raw_blob_request_limit: 300,
recaptcha_enabled: false,
login_recaptcha_protection_enabled: false,
diff --git a/app/models/board.rb b/app/models/board.rb
index 31011dc4742..f3f938224a4 100644
--- a/app/models/board.rb
+++ b/app/models/board.rb
@@ -16,10 +16,9 @@ class Board < ApplicationRecord
!group
end
- def parent
- @parent ||= group || project
+ def resource_parent
+ @resource_parent ||= group || project
end
- alias_method :resource_parent, :parent
def group_board?
group_id.present?
diff --git a/app/models/container_repository.rb b/app/models/container_repository.rb
index e055b66989b..27bb76835c7 100644
--- a/app/models/container_repository.rb
+++ b/app/models/container_repository.rb
@@ -11,6 +11,7 @@ class ContainerRepository < ApplicationRecord
delegate :client, to: :registry
scope :ordered, -> { order(:name) }
+ scope :with_api_entity_associations, -> { preload(:project) }
# rubocop: disable CodeReuse/ServiceClass
def registry
diff --git a/app/models/global_milestone.rb b/app/models/global_milestone.rb
index 1d553fc8312..7d766e1f25c 100644
--- a/app/models/global_milestone.rb
+++ b/app/models/global_milestone.rb
@@ -11,7 +11,7 @@ class GlobalMilestone
delegate :title, :state, :due_date, :start_date, :participants, :project,
:group, :expires_at, :closed?, :iid, :group_milestone?, :safe_title,
- :milestoneish_id, :parent, to: :milestone
+ :milestoneish_id, :resource_parent, to: :milestone
def to_hash
{
diff --git a/app/models/gpg_signature.rb b/app/models/gpg_signature.rb
index 46cac1d41bb..0c36e51120f 100644
--- a/app/models/gpg_signature.rb
+++ b/app/models/gpg_signature.rb
@@ -23,6 +23,8 @@ class GpgSignature < ApplicationRecord
validates :project_id, presence: true
validates :gpg_key_primary_keyid, presence: true
+ scope :by_commit_sha, ->(shas) { where(commit_sha: shas) }
+
def self.with_key_and_subkeys(gpg_key)
subkey_ids = gpg_key.subkeys.pluck(:id)
diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb
index ca50820a879..fe8ba9765b7 100644
--- a/app/models/merge_request_diff.rb
+++ b/app/models/merge_request_diff.rb
@@ -136,6 +136,7 @@ class MergeRequestDiff < ApplicationRecord
# All diff information is collected from repository after object is created.
# It allows you to override variables like head_commit_sha before getting diff.
after_create :save_git_content, unless: :importing?
+ after_create_commit :set_as_latest_diff
after_save :update_external_diff_store, if: -> { !importing? && saved_change_to_external_diff? }
@@ -150,10 +151,6 @@ class MergeRequestDiff < ApplicationRecord
# Collect information about commits and diff from repository
# and save it to the database as serialized data
def save_git_content
- MergeRequest
- .where('id = ? AND COALESCE(latest_merge_request_diff_id, 0) < ?', self.merge_request_id, self.id)
- .update_all(latest_merge_request_diff_id: self.id)
-
ensure_commit_shas
save_commits
save_diffs
@@ -168,6 +165,12 @@ class MergeRequestDiff < ApplicationRecord
keep_around_commits
end
+ def set_as_latest_diff
+ MergeRequest
+ .where('id = ? AND COALESCE(latest_merge_request_diff_id, 0) < ?', self.merge_request_id, self.id)
+ .update_all(latest_merge_request_diff_id: self.id)
+ end
+
def ensure_commit_shas
self.start_commit_sha ||= merge_request.target_branch_sha
self.head_commit_sha ||= merge_request.source_branch_sha
@@ -502,11 +505,6 @@ class MergeRequestDiff < ApplicationRecord
merge_request.closed? && merge_request.metrics.latest_closed_at < EXTERNAL_DIFF_CUTOFF.ago
end
- # We can't rely on `merge_request.latest_merge_request_diff_id` because that
- # may have been changed in `save_git_content` without being reflected in
- # the association's instance. This query is always subject to races, but
- # the worst case is that we *don't* make a diff external when we could. The
- # background worker will make it external at a later date.
def old_version?
latest_id = MergeRequest
.where(id: merge_request_id)
@@ -514,7 +512,7 @@ class MergeRequestDiff < ApplicationRecord
.pluck(:latest_merge_request_diff_id)
.first
- self.id != latest_id
+ latest_id && self.id < latest_id
end
def load_diffs(options)
diff --git a/app/models/milestone.rb b/app/models/milestone.rb
index 916c11a8d03..2fa0cfc9b93 100644
--- a/app/models/milestone.rb
+++ b/app/models/milestone.rb
@@ -257,10 +257,9 @@ class Milestone < ApplicationRecord
title.to_slug.normalize.to_s
end
- def parent
+ def resource_parent
group || project
end
- alias_method :resource_parent, :parent
def group_milestone?
group_id.present?
diff --git a/app/models/note.rb b/app/models/note.rb
index 3e645d79e15..43f349c6fa2 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -483,10 +483,9 @@ class Note < ApplicationRecord
Upload.find_by(model: self, path: paths)
end
- def parent
+ def resource_parent
project
end
- alias_method :resource_parent, :parent
private
diff --git a/app/models/push_event.rb b/app/models/push_event.rb
index 4698df39730..6f7365a2763 100644
--- a/app/models/push_event.rb
+++ b/app/models/push_event.rb
@@ -26,6 +26,8 @@ class PushEvent < Event
delegate :commit_count, to: :push_event_payload
alias_method :commits_count, :commit_count
+ delegate :ref_count, to: :push_event_payload
+
# Returns events of pushes that either pushed to an existing ref or created a
# new one.
def self.created_or_pushed
diff --git a/app/models/todo.rb b/app/models/todo.rb
index 6b71845856a..456115872d1 100644
--- a/app/models/todo.rb
+++ b/app/models/todo.rb
@@ -144,10 +144,9 @@ class Todo < ApplicationRecord
end
end
- def parent
+ def resource_parent
project
end
- alias_method :resource_parent, :parent
def unmergeable?
action == UNMERGEABLE
diff --git a/app/policies/board_policy.rb b/app/policies/board_policy.rb
index b8435dad3f1..e2b16249c85 100644
--- a/app/policies/board_policy.rb
+++ b/app/policies/board_policy.rb
@@ -3,7 +3,7 @@
class BoardPolicy < BasePolicy
include FindGroupProjects
- delegate { @subject.parent }
+ delegate { @subject.resource_parent }
condition(:is_group_board) { @subject.group_board? }
condition(:is_project_board) { @subject.project_board? }
@@ -19,7 +19,7 @@ class BoardPolicy < BasePolicy
condition(:reporter_of_group_projects) do
next unless @user
- group_projects_for(user: @user, group: @subject.parent)
+ group_projects_for(user: @user, group: @subject.resource_parent)
.visible_to_user_and_access_level(@user, ::Gitlab::Access::REPORTER)
.exists?
end
diff --git a/app/policies/milestone_policy.rb b/app/policies/milestone_policy.rb
index 2d56eea6a78..9cea8ddd7b3 100644
--- a/app/policies/milestone_policy.rb
+++ b/app/policies/milestone_policy.rb
@@ -1,5 +1,5 @@
# frozen_string_literal: true
class MilestonePolicy < BasePolicy
- delegate { @subject.parent }
+ delegate { @subject.resource_parent }
end
diff --git a/app/serializers/container_repository_entity.rb b/app/serializers/container_repository_entity.rb
index cc746698a05..db9cf1c7835 100644
--- a/app/serializers/container_repository_entity.rb
+++ b/app/serializers/container_repository_entity.rb
@@ -18,7 +18,7 @@ class ContainerRepositoryEntity < Grape::Entity
alias_method :repository, :object
def project
- request.project
+ request.respond_to?(:project) ? request.project : object.project
end
def can_destroy?
diff --git a/app/services/bulk_push_event_payload_service.rb b/app/services/bulk_push_event_payload_service.rb
new file mode 100644
index 00000000000..54157bc23f9
--- /dev/null
+++ b/app/services/bulk_push_event_payload_service.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class BulkPushEventPayloadService
+ def initialize(event, push_data)
+ @event = event
+ @push_data = push_data
+ end
+
+ def execute
+ @event.build_push_event_payload(
+ action: @push_data[:action],
+ commit_count: 0,
+ ref_count: @push_data[:ref_count],
+ ref_type: @push_data[:ref_type]
+ )
+
+ @event.push_event_payload.tap(&:save!)
+ end
+end
diff --git a/app/services/event_create_service.rb b/app/services/event_create_service.rb
index 395c5fe09ac..f7282c22a52 100644
--- a/app/services/event_create_service.rb
+++ b/app/services/event_create_service.rb
@@ -73,15 +73,27 @@ class EventCreateService
end
def push(project, current_user, push_data)
+ create_push_event(PushEventPayloadService, project, current_user, push_data)
+ end
+
+ def bulk_push(project, current_user, push_data)
+ create_push_event(BulkPushEventPayloadService, project, current_user, push_data)
+ end
+
+ private
+
+ def create_record_event(record, current_user, status)
+ create_event(record.resource_parent, current_user, status, target_id: record.id, target_type: record.class.name)
+ end
+
+ def create_push_event(service_class, project, current_user, push_data)
# We're using an explicit transaction here so that any errors that may occur
# when creating push payload data will result in the event creation being
# rolled back as well.
event = Event.transaction do
new_event = create_event(project, current_user, Event::PUSHED)
- PushEventPayloadService
- .new(new_event, push_data)
- .execute
+ service_class.new(new_event, push_data).execute
new_event
end
@@ -92,12 +104,6 @@ class EventCreateService
Users::ActivityService.new(current_user, 'push').execute
end
- private
-
- def create_record_event(record, current_user, status)
- create_event(record.resource_parent, current_user, status, target_id: record.id, target_type: record.class.name)
- end
-
def create_event(resource_parent, current_user, status, attributes = {})
attributes.reverse_merge!(
action: status,
diff --git a/app/services/git/base_hooks_service.rb b/app/services/git/base_hooks_service.rb
index b1faef58e33..0801fd4d03f 100644
--- a/app/services/git/base_hooks_service.rb
+++ b/app/services/git/base_hooks_service.rb
@@ -48,6 +48,8 @@ module Git
# Push events in the activity feed only show information for the
# last commit.
def create_events
+ return unless params.fetch(:create_push_event, true)
+
EventCreateService.new.push(project, current_user, event_push_data)
end
diff --git a/app/services/git/process_ref_changes_service.rb b/app/services/git/process_ref_changes_service.rb
index 62159d4e7e5..3052bed51bc 100644
--- a/app/services/git/process_ref_changes_service.rb
+++ b/app/services/git/process_ref_changes_service.rb
@@ -16,8 +16,8 @@ module Git
def process_changes_by_action(ref_type, changes)
changes_by_action = group_changes_by_action(changes)
- changes_by_action.each do |_, changes|
- process_changes(ref_type, changes, execute_project_hooks: execute_project_hooks?(changes)) if changes.any?
+ changes_by_action.each do |action, changes|
+ process_changes(ref_type, action, changes, execute_project_hooks: execute_project_hooks?(changes)) if changes.any?
end
end
@@ -38,9 +38,11 @@ module Git
(changes.size <= Gitlab::CurrentSettings.push_event_hooks_limit) || Feature.enabled?(:git_push_execute_all_project_hooks, project)
end
- def process_changes(ref_type, changes, execute_project_hooks:)
+ def process_changes(ref_type, action, changes, execute_project_hooks:)
push_service_class = push_service_class_for(ref_type)
+ create_bulk_push_event = changes.size > Gitlab::CurrentSettings.push_event_activities_limit
+
changes.each do |change|
push_service_class.new(
project,
@@ -48,9 +50,20 @@ module Git
change: change,
push_options: params[:push_options],
create_pipelines: change[:index] < PIPELINE_PROCESS_LIMIT || Feature.enabled?(:git_push_create_all_pipelines, project),
- execute_project_hooks: execute_project_hooks
+ execute_project_hooks: execute_project_hooks,
+ create_push_event: !create_bulk_push_event
).execute
end
+
+ create_bulk_push_event(ref_type, action, changes) if create_bulk_push_event
+ end
+
+ def create_bulk_push_event(ref_type, action, changes)
+ EventCreateService.new.bulk_push(
+ project,
+ current_user,
+ Gitlab::DataBuilder::Push.build_bulk(action: action, ref_type: ref_type, changes: changes)
+ )
end
def push_service_class_for(ref_type)
diff --git a/app/services/notes/quick_actions_service.rb b/app/services/notes/quick_actions_service.rb
index 076df10bf6f..7e6568b5b25 100644
--- a/app/services/notes/quick_actions_service.rb
+++ b/app/services/notes/quick_actions_service.rb
@@ -50,7 +50,7 @@ module Notes
return if update_params.empty?
return unless supported?(note)
- self.class.noteable_update_service(note).new(note.parent, current_user, update_params).execute(note.noteable)
+ self.class.noteable_update_service(note).new(note.resource_parent, current_user, update_params).execute(note.noteable)
end
end
end
diff --git a/app/services/search/snippet_service.rb b/app/services/search/snippet_service.rb
index 3c8847d3c18..e686d3bf7c2 100644
--- a/app/services/search/snippet_service.rb
+++ b/app/services/search/snippet_service.rb
@@ -1,13 +1,7 @@
# frozen_string_literal: true
module Search
- class SnippetService
- attr_accessor :current_user, :params
-
- def initialize(user, params)
- @current_user, @params = user, params.dup
- end
-
+ class SnippetService < Search::GlobalService
def execute
Gitlab::SnippetSearchResults.new(current_user, params[:search])
end
diff --git a/app/views/admin/application_settings/_performance.html.haml b/app/views/admin/application_settings/_performance.html.haml
index 22458223b93..6b02521a0f0 100644
--- a/app/views/admin/application_settings/_performance.html.haml
+++ b/app/views/admin/application_settings/_performance.html.haml
@@ -25,5 +25,10 @@
= f.number_field :push_event_hooks_limit, class: 'form-control'
.form-text.text-muted
= _("Number of changes (branches or tags) in a single push to determine whether webhooks and services will be fired or not. Webhooks and services won't be submitted if it surpasses that value.")
+ .form-group
+ = f.label :push_event_activities_limit, class: 'label-bold'
+ = f.number_field :push_event_activities_limit, class: 'form-control'
+ .form-text.text-muted
+ = _('Number of changes (branches or tags) in a single push to determine whether individual push events or bulk push event will be created. Bulk push event will be created if it surpasses that value.')
= f.submit 'Save changes', class: "btn btn-success"
diff --git a/app/views/events/event/_push.html.haml b/app/views/events/event/_push.html.haml
index 21c418cb0e4..b9e88f3fc47 100644
--- a/app/views/events/event/_push.html.haml
+++ b/app/views/events/event/_push.html.haml
@@ -6,11 +6,13 @@
.event-title.d-flex.flex-wrap
= inline_event_icon(event)
- %span.event-type.d-inline-block.append-right-4.pushed #{event.action_name} #{event.ref_type}
- %span.append-right-4
- - commits_link = project_commits_path(project, event.ref_name)
- - should_link = event.tag? ? project.repository.tag_exists?(event.ref_name) : project.repository.branch_exists?(event.ref_name)
- = link_to_if should_link, event.ref_name, commits_link, class: 'ref-name'
+ - many_refs = event.ref_count.to_i > 1
+ %span.event-type.d-inline-block.append-right-4.pushed= many_refs ? "#{event.action_name} #{event.ref_count} #{event.ref_type.pluralize}" : "#{event.action_name} #{event.ref_type}"
+ - unless many_refs
+ %span.append-right-4
+ - commits_link = project_commits_path(project, event.ref_name)
+ - should_link = event.tag? ? project.repository.tag_exists?(event.ref_name) : project.repository.branch_exists?(event.ref_name)
+ = link_to_if should_link, event.ref_name, commits_link, class: 'ref-name'
= render "events/event_scope", event: event
diff --git a/app/views/groups/registry/repositories/index.html.haml b/app/views/groups/registry/repositories/index.html.haml
new file mode 100644
index 00000000000..e85b0713230
--- /dev/null
+++ b/app/views/groups/registry/repositories/index.html.haml
@@ -0,0 +1,12 @@
+- page_title _("Container Registry")
+
+%section
+ .row.registry-placeholder.prepend-bottom-10
+ .col-12
+ #js-vue-registry-images{ data: { endpoint: group_container_registries_path(@group, format: :json),
+ "help_page_path" => help_page_path('user/packages/container_registry/index'),
+ "no_containers_image" => image_path('illustrations/docker-empty-state.svg'),
+ "containers_error_image" => image_path('illustrations/docker-error-state.svg'),
+ "repository_url" => "",
+ is_group_page: true,
+ character_error: @character_error.to_s } }
diff --git a/app/views/groups/sidebar/_packages.html.haml b/app/views/groups/sidebar/_packages.html.haml
new file mode 100644
index 00000000000..16b902a18b9
--- /dev/null
+++ b/app/views/groups/sidebar/_packages.html.haml
@@ -0,0 +1,16 @@
+- if group_container_registry_nav?
+ = nav_link(path: group_packages_nav_link_paths) do
+ = link_to group_container_registries_path(@group), title: _('Container Registry') do
+ .nav-icon-container
+ = sprite_icon('package')
+ %span.nav-item-name
+ = _('Packages')
+ %ul.sidebar-sub-level-items
+ = nav_link(controller: [:packages, :repositories], html_options: { class: "fly-out-top-item" } ) do
+ = link_to group_container_registries_path(@group), title: _('Container Registry') do
+ %strong.fly-out-top-item-name
+ = _('Packages')
+ %li.divider.fly-out-top-item
+ = nav_link(controller: 'groups/container_registries') do
+ = link_to group_container_registries_path(@group), title: _('Container Registry') do
+ %span= _('Container Registry')
diff --git a/app/views/layouts/nav/sidebar/_group.html.haml b/app/views/layouts/nav/sidebar/_group.html.haml
index 7cc7d1783c4..4930c6cf5f7 100644
--- a/app/views/layouts/nav/sidebar/_group.html.haml
+++ b/app/views/layouts/nav/sidebar/_group.html.haml
@@ -118,7 +118,7 @@
%strong.fly-out-top-item-name
= _('Kubernetes')
- = render_if_exists 'groups/sidebar/packages' # EE-specific
+ = render_if_exists 'groups/sidebar/packages'
- if group_sidebar_link?(:group_members)
= nav_link(path: 'group_members#index') do
diff --git a/app/views/shared/boards/_switcher.html.haml b/app/views/shared/boards/_switcher.html.haml
index 79118630762..09a365a290a 100644
--- a/app/views/shared/boards/_switcher.html.haml
+++ b/app/views/shared/boards/_switcher.html.haml
@@ -1,4 +1,4 @@
-- parent = board.parent
+- parent = board.resource_parent
- milestone_filter_opts = { format: :json }
- milestone_filter_opts = milestone_filter_opts.merge(only_group_milestones: true) if board.group_board?
- weights = Gitlab.ee? ? ([Issue::WEIGHT_ANY] + Issue.weight_options) : []
diff --git a/app/views/shared/issuable/_board_create_list_dropdown.html.haml b/app/views/shared/issuable/_board_create_list_dropdown.html.haml
index 416b4a34651..ae0e5e45afe 100644
--- a/app/views/shared/issuable/_board_create_list_dropdown.html.haml
+++ b/app/views/shared/issuable/_board_create_list_dropdown.html.haml
@@ -3,6 +3,6 @@
Add list
.dropdown-menu.dropdown-extended-height.dropdown-menu-paging.dropdown-menu-right.dropdown-menu-issues-board-new.dropdown-menu-selectable.js-tab-container-labels
= render partial: "shared/issuable/label_page_default", locals: { show_footer: true, show_create: true, show_boards_content: true, title: "Add list" }
- - if can?(current_user, :admin_label, board.parent)
+ - if can?(current_user, :admin_label, board.resource_parent)
= render partial: "shared/issuable/label_page_create", locals: { show_add_list: true, add_list: true, add_list_class: 'd-none' }
= dropdown_loading
diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml
index c9458475aa5..9165147ef2a 100644
--- a/app/views/shared/issuable/_search_bar.html.haml
+++ b/app/views/shared/issuable/_search_bar.html.haml
@@ -2,7 +2,7 @@
- board = local_assigns.fetch(:board, nil)
- is_not_boards_modal_or_productivity_analytics = type != :boards_modal && type != :productivity_analytics
- block_css_class = is_not_boards_modal_or_productivity_analytics ? 'row-content-block second-block' : ''
-- user_can_admin_list = board && can?(current_user, :admin_list, board.parent)
+- user_can_admin_list = board && can?(current_user, :admin_list, board.resource_parent)
.issues-filters{ class: ("w-100" if type == :boards_modal) }
.issues-details-filters.filtered-search-block.d-flex.flex-column.flex-md-row{ class: block_css_class, "v-pre" => type == :boards_modal }