diff options
Diffstat (limited to 'app/assets/javascripts/releases/stores/modules/edit_new/actions.js')
-rw-r--r-- | app/assets/javascripts/releases/stores/modules/edit_new/actions.js | 252 |
1 files changed, 142 insertions, 110 deletions
diff --git a/app/assets/javascripts/releases/stores/modules/edit_new/actions.js b/app/assets/javascripts/releases/stores/modules/edit_new/actions.js index 8dc2083dd2b..b312c2a7506 100644 --- a/app/assets/javascripts/releases/stores/modules/edit_new/actions.js +++ b/app/assets/javascripts/releases/stores/modules/edit_new/actions.js @@ -1,14 +1,12 @@ -import api from '~/api'; import { deprecatedCreateFlash as createFlash } from '~/flash'; import { redirectTo } from '~/lib/utils/url_utility'; import { s__ } from '~/locale'; -import oneReleaseQuery from '~/releases/queries/one_release.query.graphql'; -import { - releaseToApiJson, - apiJsonToRelease, - gqClient, - convertOneReleaseGraphQLResponse, -} from '~/releases/util'; +import createReleaseMutation from '~/releases/graphql/mutations/create_release.mutation.graphql'; +import createReleaseAssetLinkMutation from '~/releases/graphql/mutations/create_release_link.mutation.graphql'; +import deleteReleaseAssetLinkMutation from '~/releases/graphql/mutations/delete_release_link.mutation.graphql'; +import updateReleaseMutation from '~/releases/graphql/mutations/update_release.mutation.graphql'; +import oneReleaseForEditingQuery from '~/releases/graphql/queries/one_release_for_editing.query.graphql'; +import { gqClient, convertOneReleaseGraphQLResponse } from '~/releases/util'; import * as types from './mutation_types'; export const initializeRelease = ({ commit, dispatch, getters }) => { @@ -24,38 +22,25 @@ export const initializeRelease = ({ commit, dispatch, getters }) => { return Promise.resolve(); }; -export const fetchRelease = ({ commit, state, rootState }) => { +export const fetchRelease = async ({ commit, state }) => { commit(types.REQUEST_RELEASE); - if (rootState.featureFlags?.graphqlIndividualReleasePage) { - return gqClient - .query({ - query: oneReleaseQuery, - variables: { - fullPath: state.projectPath, - tagName: state.tagName, - }, - }) - .then((response) => { - const { data: release } = convertOneReleaseGraphQLResponse(response); - - commit(types.RECEIVE_RELEASE_SUCCESS, release); - }) - .catch((error) => { - commit(types.RECEIVE_RELEASE_ERROR, error); - createFlash(s__('Release|Something went wrong while getting the release details.')); - }); - } - - return api - .release(state.projectId, state.tagName) - .then(({ data }) => { - commit(types.RECEIVE_RELEASE_SUCCESS, apiJsonToRelease(data)); - }) - .catch((error) => { - commit(types.RECEIVE_RELEASE_ERROR, error); - createFlash(s__('Release|Something went wrong while getting the release details.')); + try { + const fetchResponse = await gqClient.query({ + query: oneReleaseForEditingQuery, + variables: { + fullPath: state.projectPath, + tagName: state.tagName, + }, }); + + const { data: release } = convertOneReleaseGraphQLResponse(fetchResponse); + + commit(types.RECEIVE_RELEASE_SUCCESS, release); + } catch (error) { + commit(types.RECEIVE_RELEASE_ERROR, error); + createFlash(s__('Release|Something went wrong while getting the release details.')); + } }; export const updateReleaseTagName = ({ commit }, tagName) => @@ -94,9 +79,9 @@ export const removeAssetLink = ({ commit }, linkIdToRemove) => { commit(types.REMOVE_ASSET_LINK, linkIdToRemove); }; -export const receiveSaveReleaseSuccess = ({ commit }, release) => { +export const receiveSaveReleaseSuccess = ({ commit }, urlToRedirectTo) => { commit(types.RECEIVE_SAVE_RELEASE_SUCCESS); - redirectTo(release._links.self); + redirectTo(urlToRedirectTo); }; export const saveRelease = ({ commit, dispatch, getters }) => { @@ -105,83 +90,130 @@ export const saveRelease = ({ commit, dispatch, getters }) => { dispatch(getters.isExistingRelease ? 'updateRelease' : 'createRelease'); }; -export const createRelease = ({ commit, dispatch, state, getters }) => { - const apiJson = releaseToApiJson( - { - ...state.release, - assets: { - links: getters.releaseLinksToCreate, - }, - }, - state.createFrom, - ); +/** + * Tests a GraphQL mutation response for the existence of any errors-as-data + * (See https://docs.gitlab.com/ee/development/fe_guide/graphql.html#errors-as-data). + * If any errors occurred, throw a JavaScript `Error` object, so that this can be + * handled by the global error handler. + * + * @param {Object} gqlResponse The response object returned by the GraphQL client + * @param {String} mutationName The name of the mutation that was executed + * @param {String} messageIfError An message to build into the error object if something went wrong + */ +const checkForErrorsAsData = (gqlResponse, mutationName, messageIfError) => { + const allErrors = gqlResponse.data[mutationName].errors; + if (allErrors.length > 0) { + const allErrorMessages = JSON.stringify(allErrors); + throw new Error(`${messageIfError}: ${allErrorMessages}`); + } +}; - return api - .createRelease(state.projectId, apiJson) - .then(({ data }) => { - dispatch('receiveSaveReleaseSuccess', apiJsonToRelease(data)); - }) - .catch((error) => { - commit(types.RECEIVE_SAVE_RELEASE_ERROR, error); - createFlash(s__('Release|Something went wrong while creating a new release')); +export const createRelease = async ({ commit, dispatch, state, getters }) => { + try { + const response = await gqClient.mutate({ + mutation: createReleaseMutation, + variables: getters.releaseCreateMutatationVariables, }); + + checkForErrorsAsData( + response, + 'releaseCreate', + `Something went wrong while creating a new release with projectPath "${state.projectPath}" and tagName "${state.release.tagName}"`, + ); + + dispatch('receiveSaveReleaseSuccess', response.data.releaseCreate.release.links.selfUrl); + } catch (error) { + commit(types.RECEIVE_SAVE_RELEASE_ERROR, error); + createFlash(s__('Release|Something went wrong while creating a new release.')); + } }; -export const updateRelease = ({ commit, dispatch, state, getters }) => { - const apiJson = releaseToApiJson({ - ...state.release, - assets: { - links: getters.releaseLinksToCreate, +/** + * Deletes a single release link. + * Throws an error if any network or validation errors occur. + */ +const deleteReleaseLinks = async ({ state, id }) => { + const deleteResponse = await gqClient.mutate({ + mutation: deleteReleaseAssetLinkMutation, + variables: { + input: { id }, }, }); - let updatedRelease = null; - - return ( - api - .updateRelease(state.projectId, state.tagName, apiJson) - - /** - * 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(({ data }) => { - // Save this response since we need it later in the Promise chain - updatedRelease = data; - - // Delete all links currently associated with this Release - return Promise.all( - getters.releaseLinksToDelete.map((l) => - api.deleteReleaseLink(state.projectId, state.release.tagName, l.id), - ), - ); - }) - .then(() => { - // Create a new link for each link in the form - return Promise.all( - apiJson.assets.links.map((l) => - api.createReleaseLink(state.projectId, state.release.tagName, l), - ), - ); - }) - .then(() => { - dispatch('receiveSaveReleaseSuccess', apiJsonToRelease(updatedRelease)); - }) - .catch((error) => { - commit(types.RECEIVE_SAVE_RELEASE_ERROR, error); - createFlash(s__('Release|Something went wrong while saving the release details')); - }) + checkForErrorsAsData( + deleteResponse, + 'releaseAssetLinkDelete', + `Something went wrong while deleting release asset link for release with projectPath "${state.projectPath}", tagName "${state.tagName}", and link id "${id}"`, ); }; + +/** + * Creates a single release link. + * Throws an error if any network or validation errors occur. + */ +const createReleaseLink = async ({ state, link }) => { + const createResponse = await gqClient.mutate({ + mutation: createReleaseAssetLinkMutation, + variables: { + input: { + projectPath: state.projectPath, + tagName: state.tagName, + name: link.name, + url: link.url, + linkType: link.linkType.toUpperCase(), + }, + }, + }); + + checkForErrorsAsData( + createResponse, + 'releaseAssetLinkCreate', + `Something went wrong while creating a release asset link for release with projectPath "${state.projectPath}" and tagName "${state.tagName}"`, + ); +}; + +export const updateRelease = async ({ commit, dispatch, state, getters }) => { + try { + /** + * Currently, we delete all existing links and then + * recreate new ones on each edit. This is because the + * backend 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 take place entirely + * in the backend. This is being discussed in + * https://gitlab.com/gitlab-org/gitlab/-/merge_requests/50300 + */ + const updateReleaseResponse = await gqClient.mutate({ + mutation: updateReleaseMutation, + variables: getters.releaseUpdateMutatationVariables, + }); + + checkForErrorsAsData( + updateReleaseResponse, + 'releaseUpdate', + `Something went wrong while updating release with projectPath "${state.projectPath}" and tagName "${state.tagName}"`, + ); + + // Delete all links currently associated with this Release + await Promise.all( + getters.releaseLinksToDelete.map(({ id }) => deleteReleaseLinks({ state, id })), + ); + + // Create a new link for each link in the form + await Promise.all( + getters.releaseLinksToCreate.map((link) => createReleaseLink({ state, link })), + ); + + dispatch('receiveSaveReleaseSuccess', state.release._links.self); + } catch (error) { + commit(types.RECEIVE_SAVE_RELEASE_ERROR, error); + createFlash(s__('Release|Something went wrong while saving the release details.')); + } +}; |