diff --git a/.markdownlint.yml b/.markdownlint.yml index e1e2b246314..7f71c933d96 100644 --- a/.markdownlint.yml +++ b/.markdownlint.yml @@ -139,6 +139,8 @@ proper-names: "unicorn-worker-killer", "URL", "WebdriverIO", + "Workload Identity Pool", + "Workload Identity Provider", "YAML", "YouTrack" ] diff --git a/app/assets/images/auth_buttons/atlassian_64.png b/app/assets/images/auth_buttons/atlassian_64.png index 548f1c93318..63169b9a81b 100644 Binary files a/app/assets/images/auth_buttons/atlassian_64.png and b/app/assets/images/auth_buttons/atlassian_64.png differ diff --git a/app/assets/images/auth_buttons/facebook_64.png b/app/assets/images/auth_buttons/facebook_64.png index 71ffb1c6a1f..34b75de4498 100644 Binary files a/app/assets/images/auth_buttons/facebook_64.png and b/app/assets/images/auth_buttons/facebook_64.png differ diff --git a/app/assets/javascripts/emoji/awards_app/store/actions.js b/app/assets/javascripts/emoji/awards_app/store/actions.js index f0340209248..f83bfe614dd 100644 --- a/app/assets/javascripts/emoji/awards_app/store/actions.js +++ b/app/assets/javascripts/emoji/awards_app/store/actions.js @@ -33,20 +33,51 @@ export const fetchAwards = async ({ commit, dispatch, state }, page = '1') => { } }; +/** + * Creates an intermediary award, used for display + * until the real award is loaded from the backend. + */ +const newOptimisticAward = (name, state) => { + const freeId = Math.min(...state.awards.map((a) => a.id), Number.MAX_SAFE_INTEGER) - 1; + return { + id: freeId, + name, + user: { + id: window.gon.current_user_id, + name: window.gon.current_user_fullname, + username: window.gon.current_username, + }, + }; +}; + export const toggleAward = async ({ commit, state }, name) => { const award = state.awards.find((a) => a.name === name && a.user.id === state.currentUserId); try { if (award) { - await axios.delete(joinPaths(gon.relative_url_root || '', `${state.path}/${award.id}`)); - commit(REMOVE_AWARD, award.id); + await axios + .delete(joinPaths(gon.relative_url_root || '', `${state.path}/${award.id}`)) + .catch((err) => { + commit(ADD_NEW_AWARD, award); + + throw err; + }); + showToast(__('Award removed')); } else { - const { data } = await axios.post(joinPaths(gon.relative_url_root || '', state.path), { - name, - }); + const optimisticAward = newOptimisticAward(name, state); + + commit(ADD_NEW_AWARD, optimisticAward); + + const { data } = await axios + .post(joinPaths(gon.relative_url_root || '', state.path), { + name, + }) + .finally(() => { + commit(REMOVE_AWARD, optimisticAward.id); + }); commit(ADD_NEW_AWARD, data); diff --git a/doc/api/status_checks.md b/doc/api/status_checks.md index e2849063475..0cded150474 100644 --- a/doc/api/status_checks.md +++ b/doc/api/status_checks.md @@ -31,7 +31,7 @@ GET /projects/:id/merge_requests/:merge_request_iid/status_checks "id": 2, "name": "Rule 1", "external_url": "https://gitlab.com/test-endpoint", - "status": "approved" + "status": "pass" }, { "id": 1, @@ -60,6 +60,7 @@ POST /projects/:id/merge_requests/:merge_request_iid/status_check_responses | `merge_request_iid` | integer | yes | IID of a merge request | | `sha` | string | yes | SHA at `HEAD` of the source branch | | `external_status_check_id` | integer | yes | ID of an external status check | +| `status` | string | no | Set to `pass` to pass the check | NOTE: `sha` must be the SHA at the `HEAD` of the merge request's source branch. diff --git a/doc/integration/oauth_provider.md b/doc/integration/oauth_provider.md index 373981c83e4..d091de09ee4 100644 --- a/doc/integration/oauth_provider.md +++ b/doc/integration/oauth_provider.md @@ -105,6 +105,8 @@ Existing: **Expire access tokens** to enable them. - Tokens must be [revoked](../api/oauth2.md#revoke-a-token) or they don't expire. +When applications are deleted, all grants and tokens associated with the application are also deleted. + ## Authorized applications Every application you authorize with your GitLab credentials is shown diff --git a/qa/qa/specs/features/browser_ui/5_package/package_registry/maven_gradle_repository_spec.rb b/qa/qa/specs/features/browser_ui/5_package/package_registry/maven_gradle_repository_spec.rb index 908376ca2ca..45693ecee41 100644 --- a/qa/qa/specs/features/browser_ui/5_package/package_registry/maven_gradle_repository_spec.rb +++ b/qa/qa/specs/features/browser_ui/5_package/package_registry/maven_gradle_repository_spec.rb @@ -13,10 +13,10 @@ module QA let(:package_version) { '1.3.7' } let(:package_type) { 'maven_gradle' } - where(:authentication_token_type, :maven_header_name, :testcase) do - :personal_access_token | 'Private-Token' | 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347601' - :ci_job_token | 'Job-Token' | 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347603' - :project_deploy_token | 'Deploy-Token' | 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347602' + where(:case_name, :authentication_token_type, :maven_header_name, :testcase) do + 'using personal access token' | :personal_access_token | 'Private-Token' | 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347601' + 'using ci job token' | :ci_job_token | 'Job-Token' | 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347603' + 'using project deploy token' | :project_deploy_token | 'Deploy-Token' | 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347602' end with_them do @@ -31,7 +31,7 @@ module QA end end - it "pushes and pulls a maven package via gradle using #{params[:authentication_token_type]}", testcase: params[:testcase] do + it 'pushes and pulls a maven package via gradle', testcase: params[:testcase] do Support::Retrier.retry_on_exception(max_attempts: 3, sleep_interval: 2) do Resource::Repository::Commit.fabricate_via_api! do |commit| gradle_upload_yaml = ERB.new(read_fixture('package_managers/maven', 'gradle_upload_package.yaml.erb')).result(binding) diff --git a/spec/features/merge_request/user_awards_emoji_spec.rb b/spec/features/merge_request/user_awards_emoji_spec.rb index 240b8f996c8..35eadb34799 100644 --- a/spec/features/merge_request/user_awards_emoji_spec.rb +++ b/spec/features/merge_request/user_awards_emoji_spec.rb @@ -27,6 +27,7 @@ RSpec.describe 'Merge request > User awards emoji', :js do it 'removes award from merge request' do first('[data-testid="award-button"]').click + expect(first('[data-testid="award-button"]')).to have_content '1' find('[data-testid="award-button"].selected').click expect(first('[data-testid="award-button"]')).to have_content '0' diff --git a/spec/frontend/emoji/awards_app/store/actions_spec.js b/spec/frontend/emoji/awards_app/store/actions_spec.js index 02b643244d2..0761256ed23 100644 --- a/spec/frontend/emoji/awards_app/store/actions_spec.js +++ b/spec/frontend/emoji/awards_app/store/actions_spec.js @@ -87,6 +87,26 @@ describe('Awards app actions', () => { describe('toggleAward', () => { let mock; + const optimisticAwardId = Number.MAX_SAFE_INTEGER - 1; + const makeOptimisticAddMutation = ( + id = optimisticAwardId, + name = null, + userId = window.gon.current_user_id, + ) => ({ + type: 'ADD_NEW_AWARD', + payload: { + id, + name, + user: { + id: userId, + }, + }, + }); + const makeOptimisticRemoveMutation = (id = optimisticAwardId) => ({ + type: 'REMOVE_AWARD', + payload: id, + }); + beforeEach(() => { mock = new MockAdapter(axios); }); @@ -110,8 +130,10 @@ describe('Awards app actions', () => { mock.onPost(`${relativeRootUrl || ''}/awards`).reply(200, { id: 1 }); }); - it('commits ADD_NEW_AWARD', async () => { + it('adds an optimistic award, removes it, and then commits ADD_NEW_AWARD', async () => { testAction(actions.toggleAward, null, { path: '/awards', awards: [] }, [ + makeOptimisticAddMutation(), + makeOptimisticRemoveMutation(), { type: 'ADD_NEW_AWARD', payload: { id: 1 } }, ]); }); @@ -127,7 +149,7 @@ describe('Awards app actions', () => { actions.toggleAward, null, { path: '/awards', awards: [] }, - [], + [makeOptimisticAddMutation(), makeOptimisticRemoveMutation()], [], () => { expect(Sentry.captureException).toHaveBeenCalled(); @@ -137,7 +159,7 @@ describe('Awards app actions', () => { }); }); - describe('removing a award', () => { + describe('removing an award', () => { const mockData = { id: 1, name: 'thumbsup', user: { id: 1 } }; describe('success', () => { @@ -160,6 +182,9 @@ describe('Awards app actions', () => { }); describe('error', () => { + const currentUserId = 1; + const name = 'thumbsup'; + beforeEach(() => { mock.onDelete(`${relativeRootUrl || ''}/awards/1`).reply(500); }); @@ -167,13 +192,13 @@ describe('Awards app actions', () => { it('calls Sentry.captureException', async () => { await testAction( actions.toggleAward, - 'thumbsup', + name, { path: '/awards', - currentUserId: 1, + currentUserId, awards: [mockData], }, - [], + [makeOptimisticRemoveMutation(1), makeOptimisticAddMutation(1, name, currentUserId)], [], () => { expect(Sentry.captureException).toHaveBeenCalled();