summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-04-01 12:08:00 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-04-01 12:08:00 +0000
commit1a0d6dbdc2ac3047f4953a359ef27ba6e26074ae (patch)
treeddb78a8a0d1350dc767f049a21e0f7d37edaa82c /app
parentb11f7057d067885619ee3e513751f180b2e8ad85 (diff)
downloadgitlab-ce-1a0d6dbdc2ac3047f4953a359ef27ba6e26074ae.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/api.js19
-rw-r--r--app/assets/javascripts/releases/components/app_edit.vue9
-rw-r--r--app/assets/javascripts/releases/components/asset_links_form.vue124
-rw-r--r--app/assets/javascripts/releases/stores/modules/detail/actions.js74
-rw-r--r--app/assets/javascripts/releases/stores/modules/detail/getters.js24
-rw-r--r--app/assets/javascripts/releases/stores/modules/detail/index.js2
-rw-r--r--app/assets/javascripts/releases/stores/modules/detail/mutation_types.js5
-rw-r--r--app/assets/javascripts/releases/stores/modules/detail/mutations.js28
-rw-r--r--app/assets/javascripts/releases/stores/modules/detail/state.js10
-rw-r--r--app/controllers/groups/settings/ci_cd_controller.rb30
-rw-r--r--app/controllers/projects/releases_controller.rb1
-rw-r--r--app/controllers/projects/settings/ci_cd_controller.rb28
-rw-r--r--app/finders/merge_requests_finder.rb1
-rw-r--r--app/helpers/releases_helper.rb7
-rw-r--r--app/policies/project_policy.rb1
-rw-r--r--app/services/groups/deploy_tokens/create_service.rb8
-rw-r--r--app/services/projects/deploy_tokens/create_service.rb8
-rw-r--r--app/views/shared/deploy_tokens/_form.html.haml2
18 files changed, 354 insertions, 27 deletions
diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js
index 14381f63e4b..797eaf629bf 100644
--- a/app/assets/javascripts/api.js
+++ b/app/assets/javascripts/api.js
@@ -41,6 +41,8 @@ const Api = {
createBranchPath: '/api/:version/projects/:id/repository/branches',
releasesPath: '/api/:version/projects/:id/releases',
releasePath: '/api/:version/projects/:id/releases/:tag_name',
+ releaseLinksPath: '/api/:version/projects/:id/releases/:tag_name/assets/links',
+ releaseLinkPath: '/api/:version/projects/:id/releases/:tag_name/assets/links/:link_id',
mergeRequestsPipeline: '/api/:version/projects/:id/merge_requests/:merge_request_iid/pipelines',
adminStatisticsPath: '/api/:version/application/statistics',
pipelineSinglePath: '/api/:version/projects/:id/pipelines/:pipeline_id',
@@ -460,6 +462,23 @@ const Api = {
return axios.put(url, release);
},
+ createReleaseLink(projectPath, tagName, link) {
+ const url = Api.buildUrl(this.releaseLinksPath)
+ .replace(':id', encodeURIComponent(projectPath))
+ .replace(':tag_name', encodeURIComponent(tagName));
+
+ return axios.post(url, link);
+ },
+
+ deleteReleaseLink(projectPath, tagName, linkId) {
+ const url = Api.buildUrl(this.releaseLinkPath)
+ .replace(':id', encodeURIComponent(projectPath))
+ .replace(':tag_name', encodeURIComponent(tagName))
+ .replace(':link_id', encodeURIComponent(linkId));
+
+ return axios.delete(url);
+ },
+
adminStatistics() {
const url = Api.buildUrl(this.adminStatisticsPath);
return axios.get(url);
diff --git a/app/assets/javascripts/releases/components/app_edit.vue b/app/assets/javascripts/releases/components/app_edit.vue
index 6f4baaa5d74..4fa0e96217a 100644
--- a/app/assets/javascripts/releases/components/app_edit.vue
+++ b/app/assets/javascripts/releases/components/app_edit.vue
@@ -7,6 +7,8 @@ import MarkdownField from '~/vue_shared/components/markdown/field.vue';
import autofocusonshow from '~/vue_shared/directives/autofocusonshow';
import { BACK_URL_PARAM } from '~/releases/constants';
import { getParameterByName } from '~/lib/utils/common_utils';
+import AssetLinksForm from './asset_links_form.vue';
+import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
export default {
name: 'ReleaseEditApp',
@@ -16,10 +18,12 @@ export default {
GlButton,
GlLink,
MarkdownField,
+ AssetLinksForm,
},
directives: {
autofocusonshow,
},
+ mixins: [glFeatureFlagsMixin()],
computed: {
...mapState('detail', [
'isFetchingRelease',
@@ -80,6 +84,9 @@ export default {
cancelPath() {
return getParameterByName(BACK_URL_PARAM) || this.releasesPagePath;
},
+ showAssetLinksForm() {
+ return this.glFeatures.releaseAssetLinkEditing;
+ },
},
created() {
this.fetchRelease();
@@ -153,6 +160,8 @@ export default {
</div>
</gl-form-group>
+ <asset-links-form v-if="showAssetLinksForm" />
+
<div class="d-flex pt-3">
<gl-button
class="mr-auto js-submit-button"
diff --git a/app/assets/javascripts/releases/components/asset_links_form.vue b/app/assets/javascripts/releases/components/asset_links_form.vue
new file mode 100644
index 00000000000..f4c92477775
--- /dev/null
+++ b/app/assets/javascripts/releases/components/asset_links_form.vue
@@ -0,0 +1,124 @@
+<script>
+import { mapState, mapActions } from 'vuex';
+import {
+ GlSprintf,
+ GlLink,
+ GlFormGroup,
+ GlButton,
+ GlIcon,
+ GlTooltipDirective,
+ GlFormInput,
+} from '@gitlab/ui';
+
+export default {
+ name: 'AssetLinksForm',
+ components: { GlSprintf, GlLink, GlFormGroup, GlButton, GlIcon, GlFormInput },
+ directives: { GlTooltip: GlTooltipDirective },
+ computed: {
+ ...mapState('detail', ['release', 'releaseAssetsDocsPath']),
+ },
+ created() {
+ this.addEmptyAssetLink();
+ },
+ methods: {
+ ...mapActions('detail', [
+ 'addEmptyAssetLink',
+ 'updateAssetLinkUrl',
+ 'updateAssetLinkName',
+ 'removeAssetLink',
+ ]),
+ onAddAnotherClicked() {
+ this.addEmptyAssetLink();
+ },
+ onRemoveClicked(linkId) {
+ this.removeAssetLink(linkId);
+ },
+ onUrlInput(linkIdToUpdate, newUrl) {
+ this.updateAssetLinkUrl({ linkIdToUpdate, newUrl });
+ },
+ onLinkTitleInput(linkIdToUpdate, newName) {
+ this.updateAssetLinkName({ linkIdToUpdate, newName });
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="d-flex flex-column release-assets-links-form">
+ <h2 class="text-4">{{ __('Release assets') }}</h2>
+ <p class="m-0">
+ <gl-sprintf
+ :message="
+ __(
+ 'Add %{linkStart}assets%{linkEnd} to your Release. GitLab automatically includes read-only assets, like source code and release evidence.',
+ )
+ "
+ >
+ <template #link="{ content }">
+ <gl-link
+ :href="releaseAssetsDocsPath"
+ target="_blank"
+ :aria-label="__('Release assets documentation')"
+ >
+ {{ content }}
+ </gl-link>
+ </template>
+ </gl-sprintf>
+ </p>
+ <h3 class="text-3">{{ __('Links') }}</h3>
+ <p>
+ {{
+ __(
+ 'Point to any links you like: documentation, built binaries, or other related materials. These can be internal or external links from your GitLab instance.',
+ )
+ }}
+ </p>
+ <div
+ v-for="(link, index) in release.assets.links"
+ :key="link.id"
+ class="d-flex flex-column flex-sm-row align-items-stretch align-items-sm-end"
+ >
+ <gl-form-group
+ class="url-field form-group flex-grow-1 mr-sm-4"
+ :label="__('URL')"
+ :label-for="`asset-url-${index}`"
+ >
+ <gl-form-input
+ :id="`asset-url-${index}`"
+ :value="link.url"
+ type="text"
+ class="form-control"
+ @change="onUrlInput(link.id, $event)"
+ />
+ </gl-form-group>
+
+ <gl-form-group
+ class="link-title-field flex-grow-1 mr-sm-4"
+ :label="__('Link title')"
+ :label-for="`asset-link-name-${index}`"
+ >
+ <gl-form-input
+ :id="`asset-link-name-${index}`"
+ :value="link.name"
+ type="text"
+ class="form-control"
+ @change="onLinkTitleInput(link.id, $event)"
+ />
+ </gl-form-group>
+
+ <gl-button
+ v-gl-tooltip
+ class="mb-5 mb-sm-3 flex-grow-0 flex-shrink-0 remove-button"
+ :aria-label="__('Remove asset link')"
+ :title="__('Remove asset link')"
+ @click="onRemoveClicked(link.id)"
+ >
+ <gl-icon class="m-0" name="remove" />
+ <span class="d-inline d-sm-none">{{ __('Remove asset link') }}</span>
+ </gl-button>
+ </div>
+ <gl-button variant="link" class="align-self-end mb-5 mb-sm-0" @click="onAddAnotherClicked">
+ {{ __('Add another link') }}
+ </gl-button>
+ </div>
+</template>
diff --git a/app/assets/javascripts/releases/stores/modules/detail/actions.js b/app/assets/javascripts/releases/stores/modules/detail/actions.js
index 1b77f01368e..7b84c18242c 100644
--- a/app/assets/javascripts/releases/stores/modules/detail/actions.js
+++ b/app/assets/javascripts/releases/stores/modules/detail/actions.js
@@ -41,20 +41,74 @@ export const receiveUpdateReleaseError = ({ commit }, error) => {
createFlash(s__('Release|Something went wrong while saving the release details'));
};
-export const updateRelease = ({ dispatch, state }) => {
+export const updateRelease = ({ dispatch, state, getters }) => {
dispatch('requestUpdateRelease');
- return api
- .updateRelease(state.projectId, state.tagName, {
- name: state.release.name,
- description: state.release.description,
- })
- .then(() => dispatch('receiveUpdateReleaseSuccess'))
- .catch(error => {
- dispatch('receiveUpdateReleaseError', error);
- });
+ const { release } = state;
+
+ return (
+ api
+ .updateRelease(state.projectId, state.tagName, {
+ name: release.name,
+ description: release.description,
+ })
+
+ /**
+ * Currently, we delete all existing links and then
+ * recreate new ones on each edit. This is because the
+ * REST API doesn't support bulk updating of Release links,
+ * and updating individual links can lead to validation
+ * race conditions (in particular, the "URLs must be unique")
+ * constraint.
+ *
+ * This isn't ideal since this is no longer an atomic
+ * operation - parts of it can fail while others succeed,
+ * leaving the Release in an inconsistent state.
+ *
+ * This logic should be refactored to use GraphQL once
+ * https://gitlab.com/gitlab-org/gitlab/-/issues/208702
+ * is closed.
+ */
+
+ .then(() => {
+ // Delete all links currently associated with this Release
+ return Promise.all(
+ getters.releaseLinksToDelete.map(l =>
+ api.deleteReleaseLink(state.projectId, release.tagName, l.id),
+ ),
+ );
+ })
+ .then(() => {
+ // Create a new link for each link in the form
+ return Promise.all(
+ getters.releaseLinksToCreate.map(l =>
+ api.createReleaseLink(state.projectId, release.tagName, l),
+ ),
+ );
+ })
+ .then(() => dispatch('receiveUpdateReleaseSuccess'))
+ .catch(error => {
+ dispatch('receiveUpdateReleaseError', error);
+ })
+ );
};
export const navigateToReleasesPage = ({ state }) => {
redirectTo(state.releasesPagePath);
};
+
+export const addEmptyAssetLink = ({ commit }) => {
+ commit(types.ADD_EMPTY_ASSET_LINK);
+};
+
+export const updateAssetLinkUrl = ({ commit }, { linkIdToUpdate, newUrl }) => {
+ commit(types.UPDATE_ASSET_LINK_URL, { linkIdToUpdate, newUrl });
+};
+
+export const updateAssetLinkName = ({ commit }, { linkIdToUpdate, newName }) => {
+ commit(types.UPDATE_ASSET_LINK_NAME, { linkIdToUpdate, newName });
+};
+
+export const removeAssetLink = ({ commit }, linkIdToRemove) => {
+ commit(types.REMOVE_ASSET_LINK, linkIdToRemove);
+};
diff --git a/app/assets/javascripts/releases/stores/modules/detail/getters.js b/app/assets/javascripts/releases/stores/modules/detail/getters.js
new file mode 100644
index 00000000000..562284dc48d
--- /dev/null
+++ b/app/assets/javascripts/releases/stores/modules/detail/getters.js
@@ -0,0 +1,24 @@
+/**
+ * @returns {Boolean} `true` if the release link is empty, i.e. it has
+ * empty (or whitespace-only) values for both `url` and `name`.
+ * Otherwise, `false`.
+ */
+const isEmptyReleaseLink = l => !/\S/.test(l.url) && !/\S/.test(l.name);
+
+/** Returns all release links that aren't empty */
+export const releaseLinksToCreate = state => {
+ if (!state.release) {
+ return [];
+ }
+
+ return state.release.assets.links.filter(l => !isEmptyReleaseLink(l));
+};
+
+/** Returns all release links that should be deleted */
+export const releaseLinksToDelete = state => {
+ if (!state.originalRelease) {
+ return [];
+ }
+
+ return state.originalRelease.assets.links;
+};
diff --git a/app/assets/javascripts/releases/stores/modules/detail/index.js b/app/assets/javascripts/releases/stores/modules/detail/index.js
index b4430cff2ab..40fdb04f2eb 100644
--- a/app/assets/javascripts/releases/stores/modules/detail/index.js
+++ b/app/assets/javascripts/releases/stores/modules/detail/index.js
@@ -1,10 +1,12 @@
import * as actions from './actions';
+import * as getters from './getters';
import mutations from './mutations';
import createState from './state';
export default initialState => ({
namespaced: true,
actions,
+ getters,
mutations,
state: createState(initialState),
});
diff --git a/app/assets/javascripts/releases/stores/modules/detail/mutation_types.js b/app/assets/javascripts/releases/stores/modules/detail/mutation_types.js
index 51c0590012a..04944b76e42 100644
--- a/app/assets/javascripts/releases/stores/modules/detail/mutation_types.js
+++ b/app/assets/javascripts/releases/stores/modules/detail/mutation_types.js
@@ -8,3 +8,8 @@ export const UPDATE_RELEASE_NOTES = 'UPDATE_RELEASE_NOTES';
export const REQUEST_UPDATE_RELEASE = 'REQUEST_UPDATE_RELEASE';
export const RECEIVE_UPDATE_RELEASE_SUCCESS = 'RECEIVE_UPDATE_RELEASE_SUCCESS';
export const RECEIVE_UPDATE_RELEASE_ERROR = 'RECEIVE_UPDATE_RELEASE_ERROR';
+
+export const ADD_EMPTY_ASSET_LINK = 'ADD_EMPTY_ASSET_LINK';
+export const UPDATE_ASSET_LINK_URL = 'UPDATE_ASSET_LINK_URL';
+export const UPDATE_ASSET_LINK_NAME = 'UPDATE_ASSET_LINK_NAME';
+export const REMOVE_ASSET_LINK = 'REMOVE_ASSET_LINK';
diff --git a/app/assets/javascripts/releases/stores/modules/detail/mutations.js b/app/assets/javascripts/releases/stores/modules/detail/mutations.js
index 913db6c2b2a..3d97e3a75c2 100644
--- a/app/assets/javascripts/releases/stores/modules/detail/mutations.js
+++ b/app/assets/javascripts/releases/stores/modules/detail/mutations.js
@@ -1,4 +1,9 @@
import * as types from './mutation_types';
+import { uniqueId, cloneDeep } from 'lodash';
+
+const findReleaseLink = (release, id) => {
+ return release.assets.links.find(l => l.id === id);
+};
export default {
[types.REQUEST_RELEASE](state) {
@@ -8,6 +13,7 @@ export default {
state.fetchError = undefined;
state.isFetchingRelease = false;
state.release = data;
+ state.originalRelease = Object.freeze(cloneDeep(state.release));
},
[types.RECEIVE_RELEASE_ERROR](state, error) {
state.fetchError = error;
@@ -33,4 +39,26 @@ export default {
state.updateError = error;
state.isUpdatingRelease = false;
},
+
+ [types.ADD_EMPTY_ASSET_LINK](state) {
+ state.release.assets.links.push({
+ id: uniqueId('new-link-'),
+ url: '',
+ name: '',
+ });
+ },
+
+ [types.UPDATE_ASSET_LINK_URL](state, { linkIdToUpdate, newUrl }) {
+ const linkToUpdate = findReleaseLink(state.release, linkIdToUpdate);
+ linkToUpdate.url = newUrl;
+ },
+
+ [types.UPDATE_ASSET_LINK_NAME](state, { linkIdToUpdate, newName }) {
+ const linkToUpdate = findReleaseLink(state.release, linkIdToUpdate);
+ linkToUpdate.name = newName;
+ },
+
+ [types.REMOVE_ASSET_LINK](state, linkIdToRemove) {
+ state.release.assets.links = state.release.assets.links.filter(l => l.id !== linkIdToRemove);
+ },
};
diff --git a/app/assets/javascripts/releases/stores/modules/detail/state.js b/app/assets/javascripts/releases/stores/modules/detail/state.js
index a19e8d044e2..b513e1bed79 100644
--- a/app/assets/javascripts/releases/stores/modules/detail/state.js
+++ b/app/assets/javascripts/releases/stores/modules/detail/state.js
@@ -5,6 +5,7 @@ export default ({
markdownDocsPath,
markdownPreviewPath,
updateReleaseApiDocsPath,
+ releaseAssetsDocsPath,
}) => ({
projectId,
tagName,
@@ -12,9 +13,18 @@ export default ({
markdownDocsPath,
markdownPreviewPath,
updateReleaseApiDocsPath,
+ releaseAssetsDocsPath,
+ /** The Release object */
release: null,
+ /**
+ * A deep clone of the Release object above.
+ * Used when editing this Release so that
+ * changes can be computed.
+ */
+ originalRelease: null,
+
isFetchingRelease: false,
fetchError: null,
diff --git a/app/controllers/groups/settings/ci_cd_controller.rb b/app/controllers/groups/settings/ci_cd_controller.rb
index ffa3f2c3364..989013df8d4 100644
--- a/app/controllers/groups/settings/ci_cd_controller.rb
+++ b/app/controllers/groups/settings/ci_cd_controller.rb
@@ -8,6 +8,7 @@ module Groups
before_action :authorize_update_max_artifacts_size!, only: [:update]
before_action do
push_frontend_feature_flag(:new_variables_ui, @group)
+ push_frontend_feature_flag(:ajax_new_deploy_token, @group)
end
before_action :define_variables, only: [:show, :create_deploy_token]
@@ -42,13 +43,30 @@ module Groups
end
def create_deploy_token
- @new_deploy_token = Groups::DeployTokens::CreateService.new(@group, current_user, deploy_token_params).execute
-
- if @new_deploy_token.persisted?
- flash.now[:notice] = s_('DeployTokens|Your new group deploy token has been created.')
+ result = Projects::DeployTokens::CreateService.new(@group, current_user, deploy_token_params).execute
+ @new_deploy_token = result[:deploy_token]
+
+ if result[:status] == :success
+ respond_to do |format|
+ format.json do
+ # IMPORTANT: It's a security risk to expose the token value more than just once here!
+ json = API::Entities::DeployTokenWithToken.represent(@new_deploy_token).as_json
+ render json: json, status: result[:http_status]
+ end
+ format.html do
+ flash.now[:notice] = s_('DeployTokens|Your new group deploy token has been created.')
+ render :show
+ end
+ end
+ else
+ respond_to do |format|
+ format.json { render json: { message: result[:message] }, status: result[:http_status] }
+ format.html do
+ flash.now[:alert] = result[:message]
+ render :show
+ end
+ end
end
-
- render 'show'
end
private
diff --git a/app/controllers/projects/releases_controller.rb b/app/controllers/projects/releases_controller.rb
index fc60f42095c..010e9411c7d 100644
--- a/app/controllers/projects/releases_controller.rb
+++ b/app/controllers/projects/releases_controller.rb
@@ -9,6 +9,7 @@ class Projects::ReleasesController < Projects::ApplicationController
push_frontend_feature_flag(:release_issue_summary, project, default_enabled: true)
push_frontend_feature_flag(:release_evidence_collection, project, default_enabled: true)
push_frontend_feature_flag(:release_show_page, project, default_enabled: true)
+ push_frontend_feature_flag(:release_asset_link_editing, project)
end
before_action :authorize_update_release!, only: %i[edit update]
diff --git a/app/controllers/projects/settings/ci_cd_controller.rb b/app/controllers/projects/settings/ci_cd_controller.rb
index aac6ecb07e4..5feb3e019c2 100644
--- a/app/controllers/projects/settings/ci_cd_controller.rb
+++ b/app/controllers/projects/settings/ci_cd_controller.rb
@@ -7,6 +7,7 @@ module Projects
before_action :define_variables
before_action do
push_frontend_feature_flag(:new_variables_ui, @project)
+ push_frontend_feature_flag(:ajax_new_deploy_token, @project)
end
def show
@@ -47,13 +48,30 @@ module Projects
end
def create_deploy_token
- @new_deploy_token = Projects::DeployTokens::CreateService.new(@project, current_user, deploy_token_params).execute
+ result = Projects::DeployTokens::CreateService.new(@project, current_user, deploy_token_params).execute
+ @new_deploy_token = result[:deploy_token]
- if @new_deploy_token.persisted?
- flash.now[:notice] = s_('DeployTokens|Your new project deploy token has been created.')
+ if result[:status] == :success
+ respond_to do |format|
+ format.json do
+ # IMPORTANT: It's a security risk to expose the token value more than just once here!
+ json = API::Entities::DeployTokenWithToken.represent(@new_deploy_token).as_json
+ render json: json, status: result[:http_status]
+ end
+ format.html do
+ flash.now[:notice] = s_('DeployTokens|Your new project deploy token has been created.')
+ render :show
+ end
+ end
+ else
+ respond_to do |format|
+ format.json { render json: { message: result[:message] }, status: result[:http_status] }
+ format.html do
+ flash.now[:alert] = result[:message]
+ render :show
+ end
+ end
end
-
- render 'show'
end
private
diff --git a/app/finders/merge_requests_finder.rb b/app/finders/merge_requests_finder.rb
index 410ad645cd9..d5e6c4783c1 100644
--- a/app/finders/merge_requests_finder.rb
+++ b/app/finders/merge_requests_finder.rb
@@ -14,6 +14,7 @@
# milestone_title: string
# release_tag: string
# author_id: integer
+# author_username: string
# assignee_id: integer
# search: string
# in: 'title', 'description', or a string joining them with comma
diff --git a/app/helpers/releases_helper.rb b/app/helpers/releases_helper.rb
index 6fbef800faa..af51427dc91 100644
--- a/app/helpers/releases_helper.rb
+++ b/app/helpers/releases_helper.rb
@@ -8,8 +8,8 @@ module ReleasesHelper
image_path(IMAGE_PATH)
end
- def help_page
- help_page_path(DOCUMENTATION_PATH)
+ def help_page(anchor: nil)
+ help_page_path(DOCUMENTATION_PATH, anchor: anchor)
end
def data_for_releases_page
@@ -29,7 +29,8 @@ module ReleasesHelper
markdown_preview_path: preview_markdown_path(@project),
markdown_docs_path: help_page_path('user/markdown'),
releases_page_path: project_releases_path(@project, anchor: @release.tag),
- update_release_api_docs_path: help_page_path('api/releases/index.md', anchor: 'update-a-release')
+ update_release_api_docs_path: help_page_path('api/releases/index.md', anchor: 'update-a-release'),
+ release_assets_docs_path: help_page(anchor: 'release-assets')
}
end
end
diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb
index 99aeca17699..e694963eac0 100644
--- a/app/policies/project_policy.rb
+++ b/app/policies/project_policy.rb
@@ -318,6 +318,7 @@ class ProjectPolicy < BasePolicy
enable :read_pod_logs
enable :destroy_deploy_token
enable :read_prometheus_alerts
+ enable :admin_terraform_state
end
rule { (mirror_available & can?(:admin_project)) | admin }.enable :admin_remote_mirror
diff --git a/app/services/groups/deploy_tokens/create_service.rb b/app/services/groups/deploy_tokens/create_service.rb
index 8c42b56ebb0..d747dabcd3c 100644
--- a/app/services/groups/deploy_tokens/create_service.rb
+++ b/app/services/groups/deploy_tokens/create_service.rb
@@ -6,7 +6,13 @@ module Groups
include DeployTokenMethods
def execute
- create_deploy_token_for(@group, params)
+ deploy_token = create_deploy_token_for(@group, params)
+
+ if deploy_token.persisted?
+ success(deploy_token: deploy_token, http_status: :ok)
+ else
+ error(deploy_token.errors.full_messages.to_sentence, :bad_request)
+ end
end
end
end
diff --git a/app/services/projects/deploy_tokens/create_service.rb b/app/services/projects/deploy_tokens/create_service.rb
index 51cb68dfb10..e943b2489ac 100644
--- a/app/services/projects/deploy_tokens/create_service.rb
+++ b/app/services/projects/deploy_tokens/create_service.rb
@@ -6,7 +6,13 @@ module Projects
include DeployTokenMethods
def execute
- create_deploy_token_for(@project, params)
+ deploy_token = create_deploy_token_for(@project, params)
+
+ if deploy_token.persisted?
+ success(deploy_token: deploy_token, http_status: :ok)
+ else
+ error(deploy_token.errors.full_messages.to_sentence, :bad_request)
+ end
end
end
end
diff --git a/app/views/shared/deploy_tokens/_form.html.haml b/app/views/shared/deploy_tokens/_form.html.haml
index 99e259ba944..c4e82d8e157 100644
--- a/app/views/shared/deploy_tokens/_form.html.haml
+++ b/app/views/shared/deploy_tokens/_form.html.haml
@@ -1,7 +1,7 @@
%p.profile-settings-content
= s_("DeployTokens|Pick a name for the application, and we'll give you a unique deploy token.")
-= form_for token, url: create_deploy_token_path(group_or_project, anchor: 'js-deploy-tokens'), method: :post do |f|
+= form_for token, url: create_deploy_token_path(group_or_project, anchor: 'js-deploy-tokens'), method: :post, remote: Feature.enabled?(:ajax_new_deploy_token, group_or_project) do |f|
= form_errors(token)
.form-group