diff options
69 files changed, 825 insertions, 241 deletions
@@ -165,7 +165,7 @@ gem 'html-pipeline', '~> 2.13.2' gem 'deckar01-task_list', '2.3.1' gem 'gitlab-markup', '~> 1.8.0' gem 'github-markup', '~> 1.7.0', require: 'github/markup' -gem 'commonmarker', '~> 0.23.4' +gem 'commonmarker', '~> 0.23.6' gem 'kramdown', '~> 2.3.1' gem 'RedCloth', '~> 4.3.2' gem 'rdoc', '~> 6.3.2' diff --git a/Gemfile.checksum b/Gemfile.checksum index 89864b6a182..66bc388c922 100644 --- a/Gemfile.checksum +++ b/Gemfile.checksum @@ -79,7 +79,7 @@ {"name":"claide-plugins","version":"0.9.2","platform":"ruby","checksum":"c7ea78bc067ab23bce8515497cdcdcb8f01c86dadfbe13c44644e382922c1c2e"}, {"name":"coderay","version":"1.1.3","platform":"ruby","checksum":"dc530018a4684512f8f38143cd2a096c9f02a1fc2459edcfe534787a7fc77d4b"}, {"name":"colored2","version":"3.1.2","platform":"ruby","checksum":"b13c2bd7eeae2cf7356a62501d398e72fde78780bd26aec6a979578293c28b4a"}, -{"name":"commonmarker","version":"0.23.4","platform":"ruby","checksum":"95d9cb050576376374a66d71a4feab3562e0955aab9d80a3e8606a5cf5e9c7ce"}, +{"name":"commonmarker","version":"0.23.6","platform":"ruby","checksum":"c8aeaaaff4ba497bf180f762db63a0069794fafb6eff221224c9c8199d337b38"}, {"name":"concurrent-ruby","version":"1.1.10","platform":"ruby","checksum":"244cb1ca0d91ec2c15ca2209507c39fb163336994428e16fbd3f465c87bd8e68"}, {"name":"connection_pool","version":"2.2.5","platform":"ruby","checksum":"13a8fc3921ce4df8e04fb65f1037251decb08d74757b41163688bd1c1feccd39"}, {"name":"contracts","version":"0.11.0","platform":"ruby","checksum":"df6e438efa89c31dd3095851c3f7a25dfdae36b35ff1d4547f1d92941b3c7286"}, diff --git a/Gemfile.lock b/Gemfile.lock index 0b4e32e6257..355cffc292d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -282,7 +282,7 @@ GEM open4 (~> 1.3) coderay (1.1.3) colored2 (3.1.2) - commonmarker (0.23.4) + commonmarker (0.23.6) concurrent-ruby (1.1.10) connection_pool (2.2.5) contracts (0.11.0) @@ -1557,7 +1557,7 @@ DEPENDENCIES capybara-screenshot (~> 1.0.22) carrierwave (~> 1.3) charlock_holmes (~> 0.7.7) - commonmarker (~> 0.23.4) + commonmarker (~> 0.23.6) concurrent-ruby (~> 1.1) connection_pool (~> 2.0) countries (~> 3.0) diff --git a/app/assets/javascripts/api/user_api.js b/app/assets/javascripts/api/user_api.js index c743b18d572..369abe95d49 100644 --- a/app/assets/javascripts/api/user_api.js +++ b/app/assets/javascripts/api/user_api.js @@ -1,5 +1,5 @@ import { DEFAULT_PER_PAGE } from '~/api'; -import createFlash from '~/flash'; +import { createAlert } from '~/flash'; import { __ } from '~/locale'; import axios from '../lib/utils/axios_utils'; import { buildApiUrl } from './api_utils'; @@ -55,7 +55,7 @@ export function getUserProjects(userId, query, options, callback) { }) .then(({ data }) => callback(data)) .catch(() => - createFlash({ + createAlert({ message: __('Something went wrong while fetching projects'), }), ); diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js index a3ffb4df7b7..9ab1d6bfd80 100644 --- a/app/assets/javascripts/awards_handler.js +++ b/app/assets/javascripts/awards_handler.js @@ -7,7 +7,7 @@ import { getEmojiScoreWithIntent } from '~/emoji/utils'; import { getCookie, setCookie, scrollToElement } from '~/lib/utils/common_utils'; import * as Emoji from '~/emoji'; import { dispose, fixTitle } from '~/tooltips'; -import createFlash from './flash'; +import { createAlert } from '~/flash'; import axios from './lib/utils/axios_utils'; import { isInVueNoteablePage } from './lib/utils/dom_utils'; import { __ } from './locale'; @@ -491,7 +491,7 @@ export class AwardsHandler { } }) .catch(() => - createFlash({ + createAlert({ message: __('Something went wrong on our end.'), }), ); diff --git a/app/assets/javascripts/badges/components/badge_form.vue b/app/assets/javascripts/badges/components/badge_form.vue index d1570e16639..f68666f8a0c 100644 --- a/app/assets/javascripts/badges/components/badge_form.vue +++ b/app/assets/javascripts/badges/components/badge_form.vue @@ -2,7 +2,7 @@ import { GlLoadingIcon, GlFormInput, GlFormGroup, GlButton, GlSafeHtmlDirective } from '@gitlab/ui'; import { escape, debounce } from 'lodash'; import { mapActions, mapState } from 'vuex'; -import createFlash from '~/flash'; +import { createAlert, VARIANT_INFO } from '~/flash'; import { s__, sprintf } from '~/locale'; import createEmptyBadge from '../empty_badge'; import Badge from './badge.vue'; @@ -136,14 +136,14 @@ export default { if (this.isEditing) { return this.saveBadge() .then(() => { - createFlash({ + createAlert({ message: s__('Badges|Badge saved.'), - type: 'notice', + variant: VARIANT_INFO, }); this.wasValidated = false; }) .catch((error) => { - createFlash({ + createAlert({ message: s__( 'Badges|Saving the badge failed, please check the entered URLs and try again.', ), @@ -154,14 +154,14 @@ export default { return this.addBadge() .then(() => { - createFlash({ + createAlert({ message: s__('Badges|New badge added.'), - type: 'notice', + variant: VARIANT_INFO, }); this.wasValidated = false; }) .catch((error) => { - createFlash({ + createAlert({ message: s__( 'Badges|Adding the badge failed, please check the entered URLs and try again.', ), diff --git a/app/assets/javascripts/badges/components/badge_settings.vue b/app/assets/javascripts/badges/components/badge_settings.vue index 0303930de5d..a7a21d65475 100644 --- a/app/assets/javascripts/badges/components/badge_settings.vue +++ b/app/assets/javascripts/badges/components/badge_settings.vue @@ -1,7 +1,7 @@ <script> import { GlSprintf, GlModal } from '@gitlab/ui'; import { mapState, mapActions } from 'vuex'; -import createFlash from '~/flash'; +import { createAlert, VARIANT_INFO } from '~/flash'; import { __, s__ } from '~/locale'; import Badge from './badge.vue'; import BadgeForm from './badge_form.vue'; @@ -40,13 +40,13 @@ export default { onSubmitModal() { this.deleteBadge(this.badgeInModal) .then(() => { - createFlash({ + createAlert({ message: s__('Badges|The badge was deleted.'), - type: 'notice', + variant: VARIANT_INFO, }); }) .catch((error) => { - createFlash({ + createAlert({ message: s__('Badges|Deleting the badge failed, please try again.'), }); throw error; diff --git a/app/assets/javascripts/batch_comments/stores/modules/batch_comments/actions.js b/app/assets/javascripts/batch_comments/stores/modules/batch_comments/actions.js index 2b0aaa74e83..feac6f10b1e 100644 --- a/app/assets/javascripts/batch_comments/stores/modules/batch_comments/actions.js +++ b/app/assets/javascripts/batch_comments/stores/modules/batch_comments/actions.js @@ -1,5 +1,5 @@ import { isEmpty } from 'lodash'; -import createFlash from '~/flash'; +import { createAlert } from '~/flash'; import { scrollToElement } from '~/lib/utils/common_utils'; import { __ } from '~/locale'; import { CHANGES_TAB, DISCUSSION_TAB, SHOW_TAB } from '../../../constants'; @@ -18,7 +18,7 @@ export const addDraftToDiscussion = ({ commit }, { endpoint, data }) => return res; }) .catch(() => { - createFlash({ + createAlert({ message: __('An error occurred adding a draft to the thread.'), }); }); @@ -32,7 +32,7 @@ export const createNewDraft = ({ commit }, { endpoint, data }) => return res; }) .catch(() => { - createFlash({ + createAlert({ message: __('An error occurred adding a new draft.'), }); }); @@ -44,7 +44,7 @@ export const deleteDraft = ({ commit, getters }, draft) => commit(types.DELETE_DRAFT, draft.id); }) .catch(() => - createFlash({ + createAlert({ message: __('An error occurred while deleting the comment'), }), ); @@ -62,7 +62,7 @@ export const fetchDrafts = ({ commit, getters, state, dispatch }) => }); }) .catch(() => - createFlash({ + createAlert({ message: __('An error occurred while fetching pending comments'), }), ); @@ -122,7 +122,7 @@ export const updateDraft = ( .then((data) => commit(types.RECEIVE_DRAFT_UPDATE_SUCCESS, data)) .then(callback) .catch(() => - createFlash({ + createAlert({ message: __('An error occurred while updating the comment'), }), ); diff --git a/app/assets/javascripts/behaviors/preview_markdown.js b/app/assets/javascripts/behaviors/preview_markdown.js index 679940d1317..68f5180cc03 100644 --- a/app/assets/javascripts/behaviors/preview_markdown.js +++ b/app/assets/javascripts/behaviors/preview_markdown.js @@ -1,7 +1,7 @@ /* eslint-disable func-names */ import $ from 'jquery'; -import createFlash from '~/flash'; +import { createAlert } from '~/flash'; import axios from '~/lib/utils/axios_utils'; import { __ } from '~/locale'; @@ -80,7 +80,7 @@ MarkdownPreview.prototype.fetchMarkdownPreview = function (text, url, success) { success(data); }) .catch(() => - createFlash({ + createAlert({ message: __('An error occurred while fetching Markdown preview'), }), ); diff --git a/app/assets/javascripts/blob/file_template_mediator.js b/app/assets/javascripts/blob/file_template_mediator.js index 991f98c89e7..adc2649e5df 100644 --- a/app/assets/javascripts/blob/file_template_mediator.js +++ b/app/assets/javascripts/blob/file_template_mediator.js @@ -2,7 +2,7 @@ import $ from 'jquery'; import Api from '~/api'; import initPopover from '~/blob/suggest_gitlab_ci_yml'; -import createFlash from '~/flash'; +import { createAlert } from '~/flash'; import { __ } from '~/locale'; import toast from '~/vue_shared/plugins/global_toast'; @@ -155,7 +155,7 @@ export default class FileTemplateMediator { } }) .catch((err) => - createFlash({ + createAlert({ message: __(`An error occurred while fetching the template: ${err}`), }), ); diff --git a/app/assets/javascripts/blob/openapi/index.js b/app/assets/javascripts/blob/openapi/index.js index 4c497db9842..44b75cc3e68 100644 --- a/app/assets/javascripts/blob/openapi/index.js +++ b/app/assets/javascripts/blob/openapi/index.js @@ -1,5 +1,5 @@ import { SwaggerUIBundle } from 'swagger-ui-dist'; -import createFlash from '~/flash'; +import { createAlert } from '~/flash'; import { __ } from '~/locale'; export default () => { @@ -15,7 +15,7 @@ export default () => { }); }) .catch((error) => { - createFlash({ + createAlert({ message: __('Something went wrong while initializing the OpenAPI viewer'), }); throw error; diff --git a/app/assets/javascripts/blob/viewer/index.js b/app/assets/javascripts/blob/viewer/index.js index 5ca3f131d99..8d323c335d3 100644 --- a/app/assets/javascripts/blob/viewer/index.js +++ b/app/assets/javascripts/blob/viewer/index.js @@ -1,6 +1,6 @@ import $ from 'jquery'; import '~/behaviors/markdown/render_gfm'; -import createFlash from '~/flash'; +import { createAlert } from '~/flash'; import { __ } from '~/locale'; import { REPO_BLOB_LOAD_VIEWER_START, @@ -69,7 +69,7 @@ export const handleBlobRichViewer = (viewer, type) => { loadRichBlobViewer(type) .then((module) => module?.default(viewer)) .catch((error) => { - createFlash({ + createAlert({ message: __('Error loading file viewer.'), }); throw error; @@ -221,7 +221,7 @@ export class BlobViewer { }); }) .catch(() => - createFlash({ + createAlert({ message: __('Error loading viewer'), }), ); diff --git a/app/assets/javascripts/blob_edit/blob_bundle.js b/app/assets/javascripts/blob_edit/blob_bundle.js index d73e1cc43b0..1c9c99dcc2f 100644 --- a/app/assets/javascripts/blob_edit/blob_bundle.js +++ b/app/assets/javascripts/blob_edit/blob_bundle.js @@ -2,7 +2,7 @@ import $ from 'jquery'; import initPopover from '~/blob/suggest_gitlab_ci_yml'; -import createFlash from '~/flash'; +import { createAlert } from '~/flash'; import { disableButtonIfEmptyField, setCookie } from '~/lib/utils/common_utils'; import Tracking from '~/tracking'; import BlobFileDropzone from '../blob/blob_file_dropzone'; @@ -79,7 +79,7 @@ export default () => { initPopovers(); }) .catch((e) => - createFlash({ + createAlert({ message: e, }), ); diff --git a/app/assets/javascripts/blob_edit/edit_blob.js b/app/assets/javascripts/blob_edit/edit_blob.js index 2ee2e199358..78e3f934183 100644 --- a/app/assets/javascripts/blob_edit/edit_blob.js +++ b/app/assets/javascripts/blob_edit/edit_blob.js @@ -3,7 +3,7 @@ import { SourceEditorExtension } from '~/editor/extensions/source_editor_extensi import { FileTemplateExtension } from '~/editor/extensions/source_editor_file_template_ext'; import { ToolbarExtension } from '~/editor/extensions/source_editor_toolbar_ext'; import SourceEditor from '~/editor/source_editor'; -import createFlash from '~/flash'; +import { createAlert } from '~/flash'; import axios from '~/lib/utils/axios_utils'; import { addEditorMarkdownListeners } from '~/lib/utils/text_markdown'; import { insertFinalNewline } from '~/lib/utils/text_utility'; @@ -44,7 +44,7 @@ export default class EditBlob { }, ]); } catch (e) { - createFlash({ + createAlert({ message: `${BLOB_EDITOR_ERROR}: ${e}`, }); } @@ -130,7 +130,7 @@ export default class EditBlob { currentPane.renderGFM(); }) .catch(() => - createFlash({ + createAlert({ message: BLOB_PREVIEW_ERROR, }), ); diff --git a/app/assets/javascripts/deploy_tokens/components/new_deploy_token.vue b/app/assets/javascripts/deploy_tokens/components/new_deploy_token.vue new file mode 100644 index 00000000000..639dd21bd7b --- /dev/null +++ b/app/assets/javascripts/deploy_tokens/components/new_deploy_token.vue @@ -0,0 +1,332 @@ +<script> +import { + GlFormGroup, + GlFormInput, + GlFormCheckbox, + GlButton, + GlDatepicker, + GlFormInputGroup, + GlSprintf, + GlLink, +} from '@gitlab/ui'; +import { createAlert, VARIANT_INFO } from '~/flash'; +import axios from '~/lib/utils/axios_utils'; +import { formatDate } from '~/lib/utils/datetime_utility'; +import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; +import { s__ } from '~/locale'; + +function defaultData() { + return { + expiresAt: null, + name: '', + newTokenDetails: null, + readRepository: false, + writeRepository: false, + readRegistry: false, + writeRegistry: false, + readPackageRegistry: false, + writePackageRegistry: false, + username: '', + placeholders: { + link: { link: ['link_start', 'link_end'] }, + i: { i: ['i_start', 'i_end'] }, + code: { code: ['code_start', 'code_end'] }, + }, + }; +} + +export default { + components: { + GlFormGroup, + GlFormInput, + GlDatepicker, + GlFormCheckbox, + GlButton, + GlFormInputGroup, + ClipboardButton, + GlSprintf, + GlLink, + }, + + props: { + createNewTokenPath: { + type: String, + required: true, + }, + deployTokensHelpUrl: { + type: String, + required: true, + }, + containerRegistryEnabled: { + type: Boolean, + required: true, + }, + packagesRegistryEnabled: { + type: Boolean, + required: true, + }, + tokenType: { + type: String, + required: true, + }, + }, + + data() { + return defaultData(); + }, + translations: { + addTokenButton: s__('DeployTokens|Create deploy token'), + addTokenExpiryLabel: s__('DeployTokens|Expiration date (optional)'), + addTokenExpiryDescription: s__( + 'DeployTokens|Enter an expiration date for your token. Defaults to never expire.', + ), + addTokenHeader: s__('DeployTokens|New deploy token'), + addTokenDescription: s__( + 'DeployTokens|Create a new deploy token for all projects in this group. %{link_start}What are deploy tokens?%{link_end}', + ), + addTokenNameLabel: s__('DeployTokens|Name'), + addTokenNameDescription: s__('DeployTokens|Enter a unique name for your deploy token.'), + addTokenScopesLabel: s__('DeployTokens|Scopes (select at least one)'), + addTokenUsernameDescription: s__( + 'DeployTokens|Enter a username for your token. Defaults to %{code_start}gitlab+deploy-token-{n}%{code_end}.', + ), + addTokenUsernameLabel: s__('DeployTokens|Username (optional)'), + newTokenCopyMessage: s__('DeployTokens|Copy deploy token'), + newProjectTokenCreated: s__('DeployTokens|Your new project deploy token has been created.'), + newGroupTokenCreated: s__('DeployTokens|Your new group deploy token has been created.'), + newTokenDescription: s__( + 'DeployTokens|Use this token as a password. Save it. This password can %{i_start}not%{i_end} be recovered.', + ), + newTokenMessage: s__('DeployTokens|Your New Deploy Token'), + newTokenUsernameCopy: s__('DeployTokens|Copy username'), + newTokenUsernameDescription: s__( + 'DeployTokens|This username supports access. %{link_start}What kind of access?%{link_end}', + ), + readRepositoryHelp: s__('DeployTokens|Allows read-only access to the repository.'), + readRegistryHelp: s__('DeployTokens|Allows read-only access to registry images.'), + writeRegistryHelp: s__('DeployTokens|Allows read and write access to registry images.'), + readPackageRegistryHelp: s__('DeployTokens|Allows read-only access to the package registry.'), + writePackageRegistryHelp: s__( + 'DeployTokens|Allows read and write access to the package registry.', + ), + }, + computed: { + formattedExpiryDate() { + return formatDate(this.expiresAt, 'yyyy-mm-dd'); + }, + newTokenCreatedMessage() { + return this.tokenType === 'group' + ? this.$options.translations.newGroupTokenCreated + : this.$options.translations.newProjectTokenCreated; + }, + }, + methods: { + createDeployToken() { + return axios + .post(this.createNewTokenPath, { + deploy_token: { + expires_at: this.expiresAt, + name: this.name, + read_repository: this.readRepository, + read_registry: this.readRegistry, + username: this.username, + }, + }) + .then((response) => { + this.newTokenDetails = response.data; + this.resetData(); + createAlert({ + variant: VARIANT_INFO, + message: this.newTokenCreatedMessage, + }); + }) + .catch((error) => { + createAlert({ + message: error.response.data.message, + }); + }); + }, + resetData() { + const newData = defaultData(); + delete newData.newTokenDetails; + Object.keys(newData).forEach((k) => { + this[k] = newData[k]; + }); + }, + }, +}; +</script> +<template> + <div> + <div v-if="newTokenDetails" class="created-deploy-token-container info-well"> + <div class="well-segment"> + <h5>{{ $options.translations.newTokenMessage }}</h5> + <gl-form-group> + <template #description> + <div class="deploy-token-help-block gl-mt-2 text-success"> + <gl-sprintf + :message="$options.translations.newTokenUsernameDescription" + :placeholders="placeholders.link" + > + <template #link="{ content }"> + <gl-link :href="deployTokensHelpUrl" target="_blank">{{ content }}</gl-link> + </template> + </gl-sprintf> + </div> + </template> + <gl-form-input-group + name="deploy-token-user" + :value="newTokenDetails.username" + select-on-click + readonly + > + <template #append> + <clipboard-button + :text="newTokenDetails.username" + :title="$options.translations.newTokenUsernameCopy" + /> + </template> + </gl-form-input-group> + </gl-form-group> + <gl-form-group> + <template #description> + <div class="deploy-token-help-block gl-mt-2 text-danger"> + <gl-sprintf + :message="$options.translations.newTokenDescription" + :placeholders="placeholders.i" + > + <template #i="{ content }"> + <i>{{ content }}</i> + </template> + </gl-sprintf> + </div> + </template> + <gl-form-input-group :value="newTokenDetails.token" name="deploy-token" readonly> + <template #append> + <clipboard-button + :text="newTokenDetails.token" + :title="$options.translations.newTokenCopyMessage" + /> + </template> + </gl-form-input-group> + </gl-form-group> + </div> + </div> + <h5>{{ $options.translations.addTokenHeader }}</h5> + <p class="profile-settings-content"> + <gl-sprintf + :message="$options.translations.addTokenDescription" + :placeholders="placeholders.link" + > + <template #link="{ content }"> + <gl-link :href="deployTokensHelpUrl" target="_blank">{{ content }}</gl-link> + </template> + </gl-sprintf> + </p> + <gl-form-group + :label="$options.translations.addTokenNameLabel" + :description="$options.translations.addTokenNameDescription" + label-for="deploy_token_name" + > + <gl-form-input + id="deploy_token_name" + v-model="name" + name="deploy_token_name" + class="qa-deploy-token-name" + data-qa-selector="deploy_token_name_field" + /> + </gl-form-group> + <gl-form-group + :label="$options.translations.addTokenExpiryLabel" + :description="$options.translations.addTokenExpiryDescription" + label-for="deploy_token_expires_at" + > + <gl-form-input + id="deploy_token_expires_at" + name="deploy_token_expires_at" + :value="formattedExpiryDate" + data-qa-selector="deploy_token_expires_at_field" + /> + </gl-form-group> + <gl-form-group + :label="$options.translations.addTokenUsernameLabel" + label-for="deploy_token_username" + > + <template #description> + <gl-sprintf + :message="$options.translations.addTokenUsernameDescription" + :placeholders="placeholders.code" + > + <template #code="{ content }"> + <code>{{ content }}</code> + </template> + </gl-sprintf> + </template> + <gl-form-input id="deploy_token_username" v-model="username" /> + </gl-form-group> + <gl-form-group + :label="$options.translations.addTokenScopesLabel" + label-for="deploy-token-scopes" + > + <div id="deploy-token-scopes"> + <!-- eslint-disable @gitlab/vue-require-i18n-strings --> + <gl-form-checkbox + id="deploy_token_read_repository" + v-model="readRepository" + name="deploy_token_read_repository" + data-qa-selector="deploy_token_read_repository_checkbox" + > + read_repository + <template #help>{{ $options.translations.readRepositoryHelp }}</template> + </gl-form-checkbox> + <gl-form-checkbox + v-if="containerRegistryEnabled" + id="deploy_token_read_registry" + v-model="readRegistry" + name="deploy_token_read_registry" + data-qa-selector="deploy_token_read_registry_checkbox" + > + read_registry + <template #help>{{ $options.translations.readRegistryHelp }}</template> + </gl-form-checkbox> + <gl-form-checkbox + v-if="containerRegistryEnabled" + id="deploy_token_write_registry" + v-model="writeRegistry" + name="deploy_token_write_registry" + data-qa-selector="deploy_token_write_registry_checkbox" + > + write_registry + <template #help>{{ $options.translations.writeRegistryHelp }}</template> + </gl-form-checkbox> + <gl-form-checkbox + v-if="packagesRegistryEnabled" + id="deploy_token_read_package_registry" + v-model="readPackageRegistry" + name="deploy_token_read_package_registry" + data-qa-selector="deploy_token_read_package_registry_checkbox" + > + read_package_registry + <template #help>{{ $options.translations.readPackageRegistryHelp }}</template> + </gl-form-checkbox> + <gl-form-checkbox + v-if="packagesRegistryEnabled" + id="deploy_token_write_package_registry" + v-model="writePackageRegistry" + name="deploy_token_write_package_registry" + data-qa-selector="deploy_token_write_package_registry_checkbox" + > + write_package_registry + <template #help>{{ $options.translations.writePackageRegistryHelp }}</template> + </gl-form-checkbox> + <!-- eslint-enable @gitlab/vue-require-i18n-strings --> + </div> + </gl-form-group> + <div> + <gl-button variant="success" @click="createDeployToken"> + {{ $options.translations.addTokenButton }} + </gl-button> + </div> + <gl-datepicker v-model="expiresAt" target="#deploy_token_expires_at" container="body" /> + </div> +</template> diff --git a/app/assets/javascripts/deploy_tokens/index.js b/app/assets/javascripts/deploy_tokens/index.js new file mode 100644 index 00000000000..334c9930f4b --- /dev/null +++ b/app/assets/javascripts/deploy_tokens/index.js @@ -0,0 +1,33 @@ +import Vue from 'vue'; +import NewDeployToken from './components/new_deploy_token.vue'; + +export default function initDeployTokens() { + const el = document.getElementById('js-new-deploy-token'); + + if (el == null) return null; + + const { + createNewTokenPath, + deployTokensHelpUrl, + containerRegistryEnabled, + packagesRegistryEnabled, + tokenType, + } = el.dataset; + return new Vue({ + el, + components: { + NewDeployToken, + }, + render(createElement) { + return createElement(NewDeployToken, { + props: { + createNewTokenPath, + deployTokensHelpUrl, + containerRegistryEnabled: containerRegistryEnabled !== undefined, + packagesRegistryEnabled: packagesRegistryEnabled !== undefined, + tokenType, + }, + }); + }, + }); +} diff --git a/app/assets/javascripts/notes/components/discussion_counter.vue b/app/assets/javascripts/notes/components/discussion_counter.vue index 6521b86edbb..37935e9c3c6 100644 --- a/app/assets/javascripts/notes/components/discussion_counter.vue +++ b/app/assets/javascripts/notes/components/discussion_counter.vue @@ -81,16 +81,18 @@ export default { :class="{ 'gl-bg-orange-50': blocksMerge && !allResolved, 'gl-bg-gray-50': !blocksMerge || allResolved, - 'gl-pr-2': !allResolved, }" data-testid="discussions-counter-text" > <template v-if="allResolved"> {{ __('All threads resolved!') }} <gl-dropdown + v-gl-tooltip:discussionCounter.hover.bottom size="small" category="tertiary" right + :title="__('Thread options')" + :aria-label="__('Thread options')" toggle-class="btn-icon" class="gl-pt-0! gl-px-2 gl-h-full gl-ml-2" > @@ -133,9 +135,12 @@ export default { @click="jumpNext" /> <gl-dropdown + v-gl-tooltip:discussionCounter.hover.bottom size="small" category="tertiary" right + :title="__('Thread options')" + :aria-label="__('Thread options')" toggle-class="btn-icon" class="gl-pt-0! gl-px-2" > diff --git a/app/assets/javascripts/packages_and_registries/settings/project/components/cleanup_image_tags.vue b/app/assets/javascripts/packages_and_registries/settings/project/components/cleanup_image_tags.vue index 72e68aca070..b8405b09840 100644 --- a/app/assets/javascripts/packages_and_registries/settings/project/components/cleanup_image_tags.vue +++ b/app/assets/javascripts/packages_and_registries/settings/project/components/cleanup_image_tags.vue @@ -57,6 +57,9 @@ export default { isEnabled() { return this.containerExpirationPolicy || this.enableHistoricEntries; }, + isLoading() { + return this.$apollo.queries.containerExpirationPolicy.loading; + }, showDisabledFormMessage() { return !this.isEnabled && !this.fetchSettingsError; }, @@ -86,10 +89,10 @@ export default { <container-expiration-policy-form v-if="isEnabled" v-model="workingCopy" - :is-loading="$apollo.queries.containerExpirationPolicy.loading" + :is-loading="isLoading" :is-edited="isEdited" /> - <template v-else> + <template v-if="!isLoading"> <gl-alert v-if="showDisabledFormMessage" :dismissible="false" diff --git a/app/assets/javascripts/packages_and_registries/settings/project/components/container_expiration_policy.vue b/app/assets/javascripts/packages_and_registries/settings/project/components/container_expiration_policy.vue index b003b6aeb6b..1dd88d69d30 100644 --- a/app/assets/javascripts/packages_and_registries/settings/project/components/container_expiration_policy.vue +++ b/app/assets/javascripts/packages_and_registries/settings/project/components/container_expiration_policy.vue @@ -110,7 +110,7 @@ export default { {{ cleanupRulesButtonText }} </gl-button> </gl-card> - <template v-else> + <template v-if="!$apollo.queries.containerExpirationPolicy.loading"> <gl-alert v-if="showDisabledFormMessage" :dismissible="false" diff --git a/app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js b/app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js index bf77d968e7d..b1a1cc21764 100644 --- a/app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js +++ b/app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js @@ -2,9 +2,11 @@ import initStaleRunnerCleanupSetting from 'ee_else_ce/group_settings/stale_runne import initVariableList from '~/ci_variable_list'; import initSharedRunnersForm from '~/group_settings/mount_shared_runners'; import initSettingsPanels from '~/settings_panels'; +import initDeployTokens from '~/deploy_tokens'; // Initialize expandable settings panels initSettingsPanels(); +initDeployTokens(); initSharedRunnersForm(); initStaleRunnerCleanupSetting(); diff --git a/app/assets/javascripts/pages/groups/settings/index.js b/app/assets/javascripts/pages/groups/settings/index.js index cb787c60002..7e97cd865b7 100644 --- a/app/assets/javascripts/pages/groups/settings/index.js +++ b/app/assets/javascripts/pages/groups/settings/index.js @@ -1,5 +1,7 @@ import initRevokeButton from '~/deploy_tokens/init_revoke_button'; import initSearchSettings from '~/search_settings'; +import initDeployTokens from '~/deploy_tokens'; +initDeployTokens(); initSearchSettings(); initRevokeButton(); diff --git a/app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js b/app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js index 6a9bd34db22..8909ff1f221 100644 --- a/app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js +++ b/app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js @@ -10,6 +10,7 @@ import initSharedRunnersToggle from '~/projects/settings/mount_shared_runners_to import initSettingsPanels from '~/settings_panels'; import { initTokenAccess } from '~/token_access'; import { initCiSecureFiles } from '~/ci_secure_files'; +import initDeployTokens from '~/deploy_tokens'; // Initialize expandable settings panels initSettingsPanels(); @@ -34,6 +35,7 @@ document.querySelector('.js-toggle-extra-settings').addEventListener('click', (e }); registrySettingsApp(); +initDeployTokens(); initDeployFreeze(); initSettingsPipelinesTriggers(); diff --git a/app/assets/javascripts/pages/projects/settings/index.js b/app/assets/javascripts/pages/projects/settings/index.js index cb787c60002..7e97cd865b7 100644 --- a/app/assets/javascripts/pages/projects/settings/index.js +++ b/app/assets/javascripts/pages/projects/settings/index.js @@ -1,5 +1,7 @@ import initRevokeButton from '~/deploy_tokens/init_revoke_button'; import initSearchSettings from '~/search_settings'; +import initDeployTokens from '~/deploy_tokens'; +initDeployTokens(); initSearchSettings(); initRevokeButton(); diff --git a/app/assets/javascripts/vue_merge_request_widget/components/extensions/base.vue b/app/assets/javascripts/vue_merge_request_widget/components/extensions/base.vue index 7d69a2d78b1..3d03dbd9db3 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/extensions/base.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/extensions/base.vue @@ -315,7 +315,6 @@ export default { data-qa-selector="mr_widget_extension" > <state-container - :mr="mr" :status="statusIconName" :is-loading="isLoadingSummary" :class="{ 'gl-cursor-pointer': isCollapsible }" diff --git a/app/assets/javascripts/vue_merge_request_widget/components/state_container.vue b/app/assets/javascripts/vue_merge_request_widget/components/state_container.vue index 822c5a68093..932659f3c89 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/state_container.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/state_container.vue @@ -16,7 +16,8 @@ export default { props: { mr: { type: Object, - required: true, + required: false, + default: null, }, isLoading: { type: Boolean, @@ -80,6 +81,7 @@ export default { </div> </div> <div + v-if="mr" class="gl-md-display-none gl-border-l-1 gl-border-l-solid gl-border-gray-100 gl-ml-3 gl-pl-3 gl-h-6 gl-mt-1" > <gl-button diff --git a/app/views/ci/variables/_index.html.haml b/app/views/ci/variables/_index.html.haml index 02c468cebd7..9ca11b35064 100644 --- a/app/views/ci/variables/_index.html.haml +++ b/app/views/ci/variables/_index.html.haml @@ -1,9 +1,11 @@ - save_endpoint = local_assigns.fetch(:save_endpoint, nil) - if ci_variable_protected_by_default? - %p.settings-message.text-center - - link_start = '<a href="%{url}">'.html_safe % { url: help_page_path('ci/variables/index', anchor: 'protected-cicd-variables') } - = s_('Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default.').html_safe % { link_start: link_start, link_end: '</a>'.html_safe } + = render Pajamas::AlertComponent.new(variant: :warning, show_icon: false, dismissible: false, + alert_options: { class: 'gl-mb-3'}) do |c| + = c.body do + - link_start = '<a href="%{url}">'.html_safe % { url: help_page_path('ci/variables/index', anchor: 'protected-cicd-variables') } + = _('Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default.').html_safe % { link_start: link_start, link_end: '</a>'.html_safe } - is_group = !@group.nil? - is_project = !@project.nil? diff --git a/app/views/projects/merge_requests/_close_reopen_draft_report_toggle.html.haml b/app/views/projects/merge_requests/_close_reopen_draft_report_toggle.html.haml index 22571b11639..880017dbe36 100644 --- a/app/views/projects/merge_requests/_close_reopen_draft_report_toggle.html.haml +++ b/app/views/projects/merge_requests/_close_reopen_draft_report_toggle.html.haml @@ -1,8 +1,7 @@ - display_issuable_type = issuable_display_type(@merge_request) .float-left.btn-group.gl-md-ml-3.gl-display-flex.dropdown.gl-new-dropdown.gl-md-w-auto.gl-w-full - = button_tag type: 'button', class: "btn dropdown-toggle btn-default btn-md gl-button gl-dropdown-toggle btn-default-tertiary dropdown-icon-only dropdown-toggle-no-caret gl-display-none! gl-md-display-inline-flex!", data: { 'toggle' => 'dropdown' } do - %span.gl-sr-only= _('Toggle dropdown') + = button_tag type: 'button', class: "btn dropdown-toggle btn-default btn-md gl-button gl-dropdown-toggle btn-default-tertiary dropdown-icon-only dropdown-toggle-no-caret has-tooltip gl-display-none! gl-md-display-inline-flex!", data: { 'toggle' => 'dropdown' }, title: _('Merge request actions') , 'aria-label': _('Merge request actions') do = sprite_icon "ellipsis_v", size: 16, css_class: "dropdown-icon gl-icon" = button_tag type: 'button', class: "btn dropdown-toggle btn-default btn-md btn-block gl-button gl-dropdown-toggle gl-md-display-none!", data: { 'toggle' => 'dropdown' } do %span.gl-new-dropdown-button-text= _('Merge request actions') diff --git a/app/views/shared/deploy_tokens/_index.html.haml b/app/views/shared/deploy_tokens/_index.html.haml index 79bf35e2726..faec379e42b 100644 --- a/app/views/shared/deploy_tokens/_index.html.haml +++ b/app/views/shared/deploy_tokens/_index.html.haml @@ -8,10 +8,20 @@ %p = description .settings-content - - if @created_deploy_token - = render 'shared/deploy_tokens/new_deploy_token', deploy_token: @created_deploy_token - %h5.gl-mt-0 - = s_('DeployTokens|New deploy token') - = render 'shared/deploy_tokens/form', group_or_project: group_or_project, token: @new_deploy_token, presenter: @deploy_tokens + - if Feature.enabled?(:ajax_new_deploy_token, group_or_project) + #js-new-deploy-token{ data: { + container_registry_enabled: container_registry_enabled?(group_or_project), + packages_registry_enabled: packages_registry_enabled?(group_or_project), + create_new_token_path: create_deploy_token_path(group_or_project), + token_type: group_or_project.is_a?(Group) ? 'group' : 'project', + deploy_tokens_help_url: help_page_path('user/project/deploy_tokens/index.md') + } + } + - else + - if @created_deploy_token + = render 'shared/deploy_tokens/new_deploy_token', deploy_token: @created_deploy_token + %h5.gl-mt-0 + = s_('DeployTokens|New deploy token') + = render 'shared/deploy_tokens/form', group_or_project: group_or_project, token: @new_deploy_token, presenter: @deploy_tokens %hr = render 'shared/deploy_tokens/table', group_or_project: group_or_project, active_tokens: @deploy_tokens diff --git a/config/feature_flags/undefined/gitaly_simplify_find_local_branches_response.yml b/config/feature_flags/undefined/gitaly_simplify_find_local_branches_response.yml deleted file mode 100644 index c82f8ee26b7..00000000000 --- a/config/feature_flags/undefined/gitaly_simplify_find_local_branches_response.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: gitaly_simplify_find_local_branches_response -introduced_by_url: https://gitlab.com/gitlab-org/gitaly/-/merge_requests/4850 -rollout_issue_url: https://gitlab.com/gitlab-org/gitaly/-/issues/4452 -milestone: '15.4' -type: undefined -group: group::gitaly -default_enabled: false diff --git a/config/initializers/sawyer_patch.rb b/config/initializers/sawyer_patch.rb index 08d249645cc..34d2843d165 100644 --- a/config/initializers/sawyer_patch.rb +++ b/config/initializers/sawyer_patch.rb @@ -24,6 +24,16 @@ module SawyerClassPatch end else define_method attribute do + Gitlab::Import::Logger.warn( + Gitlab::ApplicationContext.current.merge( + { + message: 'Sawyer attribute called', + attribute: attribute, + caller: Gitlab::BacktraceCleaner.clean_backtrace(caller) + } + ) + ) + @attrs[attribute.to_sym] end diff --git a/doc/administration/auth/cognito.md b/doc/administration/auth/cognito.md index 6ef020034c1..bb06d5d1a58 100644 --- a/doc/administration/auth/cognito.md +++ b/doc/administration/auth/cognito.md @@ -8,35 +8,36 @@ info: To determine the technical writer assigned to the Stage/Group associated w # Amazon Web Services Cognito **(FREE SELF)** Amazon Cognito lets you add user sign-up, sign-in, and access control to your GitLab instance. -The following documentation enables Cognito as an OAuth2 provider. +The following documentation enables Cognito as an OAuth 2.0 provider. ## Configure AWS Cognito -To enable the [AWS Cognito](https://aws.amazon.com/cognito/) OAuth2 OmniAuth provider, register your application with Cognito. This process generates a Client ID and Client Secret for your application. -Any settings you configure in the following procedure can be modified later. -The following steps enable AWS Cognito as an authentication provider: +To enable the [AWS Cognito](https://aws.amazon.com/cognito/) OAuth 2.0 OmniAuth provider, register your application with Cognito. This process generates a Client ID and Client Secret for your application. +To enable AWS Cognito as an authentication provider, complete the following steps. You can modify any settings you configure later. 1. Sign in to the [AWS console](https://console.aws.amazon.com/console/home). -1. Select **Cognito** from the **Services** menu. -1. Select **Manage User Pools**, and select the **Create a user pool** button in the top right corner. -1. Enter the pool name and then select the **Step through settings** button. +1. From the **Services** menu, select **Cognito**. +1. Select **Manage User Pools** and then select **Create a user pool** in the top right corner. +1. Enter the user pool name and then select **Step through settings**. 1. Under **How do you want your end users to sign in?**, select **Email address or phone number** and **Allow email addresses**. 1. Under **Which standard attributes do you want to require?**, select **email**. -1. Go to the next steps of configuration and set the rest of the settings to suit your needs - in the basic setup they are not related to GitLab configuration. -1. In the **App clients** settings, select **Add an app client**, add **App client name** and select the **Enable username password based authentication** checkbox. +1. Configure the remaining settings to suit your needs. In the basic setup, these settings do not affect GitLab configuration. +1. In the **App clients** settings: + 1. Select **Add an app client**. + 1. Add the **App client name**. + 1. Select the **Enable username password based authentication** checkbox. 1. Select **Create app client**. -1. In the next step, you can set up AWS Lambda functions for sending emails. You can then finish creating the pool. +1. Set up the AWS Lambda functions for sending emails and finish creating the user pool. 1. After creating the user pool, go to **App client settings** and provide the required information: - **Enabled Identity Providers** - select all - - **Callback URL** - `https://gitlab.example.com/users/auth/cognito/callback` - - Substitute the URL of your GitLab instance for `gitlab.example.com` + - **Callback URL** - `https://<your_gitlab_instance_url>/users/auth/cognito/callback` - **Allowed OAuth Flows** - Authorization code grant - **Allowed OAuth2 Scopes** - `email`, `openid`, and `profile` 1. Save changes for the app client settings. -1. Under **Domain name** include the AWS domain name for your AWS Cognito application. -1. Under **App Clients**, find your app client ID and app client secret. These values correspond to the OAuth2 Client ID and Client Secret. Save these values. +1. Under **Domain name**, include the AWS domain name for your AWS Cognito application. +1. Under **App Clients**, find your app client ID. Select **Show details* to display the app client secret. These values correspond to the OAuth 2.0 Client ID and Client Secret. Save these values. ## Configure GitLab @@ -49,8 +50,13 @@ The following steps enable AWS Cognito as an authentication provider: sudo editor /etc/gitlab/gitlab.rb ``` -1. In the following code block, substitute the Client ID (`app_id`), Client Secret (`app_secret`), and the Amazon domain name (`site`) for your AWS Cognito application. -Include the code block in the `/etc/gitlab/gitlab.rb` file: +1. In the following code block, enter your AWS Cognito application information in the following parameters: + + - `app_id`: Your client ID. + - `app_secret`: Your client secret. + - `site`: Your Amazon domain and region. + + Include the code block in the `/etc/gitlab/gitlab.rb` file: ```ruby gitlab_rails['omniauth_allow_single_sign_on'] = ['cognito'] @@ -59,12 +65,12 @@ Include the code block in the `/etc/gitlab/gitlab.rb` file: name: "cognito", label: "Provider name", # optional label for login button, defaults to "Cognito" icon: nil, # Optional icon URL - app_id: "CLIENT ID", - app_secret: "CLIENT SECRET", + app_id: "<client_id>", + app_secret: "<client_secret>", args: { scope: "openid profile email", client_options: { - site: "https://your_domain.auth.your_region.amazoncognito.com", + site: "https://<your_domain>.auth.<your_region>.amazoncognito.com", authorize_url: "/oauth2/authorize", token_url: "/oauth2/token", user_info_url: "/oauth2/userInfo" @@ -84,8 +90,9 @@ Include the code block in the `/etc/gitlab/gitlab.rb` file: 1. Save the configuration file. 1. Save the file and [reconfigure](../restart_gitlab.md#omnibus-gitlab-reconfigure) GitLab for the changes to take effect. -Your sign-in page should now display a Cognito button below the regular sign-in form. -To begin the authentication process, select the icon, and AWS Cognito asks the user to sign in and authorize the GitLab application. -If successful, the user is redirected and signed in to your GitLab instance. +Your sign-in page should now display a Cognito option below the regular sign-in form. +Select this option to begin the authentication process. +AWS Cognito then asks you to sign in and authorize the GitLab application. +If the authorization is successful, you're redirected and signed in to your GitLab instance. For more information, see [Configure initial settings](../../integration/omniauth.md#configure-initial-settings). diff --git a/doc/user/profile/index.md b/doc/user/profile/index.md index fe0d0d53d8b..7cee2b2bb0a 100644 --- a/doc/user/profile/index.md +++ b/doc/user/profile/index.md @@ -229,12 +229,12 @@ To set the busy status indicator, either: - Set it directly: 1. On the top bar, in the top-right corner, select your avatar. 1. Select **Set status** or, if you have already set a status, **Edit status**. - 1. Select the **Busy** checkbox. + 1. Select the **Set yourself as busy** checkbox. - Set it on your profile: 1. On the top bar, in the top-right corner, select your avatar. 1. Select **Edit profile**. - 1. In the **Current status** section, select the **Busy** checkbox. + 1. In the **Current status** section, select the **Set yourself as busy** checkbox. The busy status is displayed in the user interface. diff --git a/lib/banzai/filter/pathological_markdown_filter.rb b/lib/banzai/filter/pathological_markdown_filter.rb deleted file mode 100644 index 0f94150c7a1..00000000000 --- a/lib/banzai/filter/pathological_markdown_filter.rb +++ /dev/null @@ -1,27 +0,0 @@ -# frozen_string_literal: true - -module Banzai - module Filter - class PathologicalMarkdownFilter < HTML::Pipeline::TextFilter - # It's not necessary for this to be precise - we just need to detect - # when there are a non-trivial number of unclosed image links. - # So we don't really care about code blocks, etc. - # See https://gitlab.com/gitlab-org/gitlab/-/issues/370428 - REGEX = /!\[(?:[^\]])+?!\[/.freeze - DETECTION_MAX = 10 - - def call - count = 0 - - @text.scan(REGEX) do |_match| - count += 1 - break if count > DETECTION_MAX - end - - return @text if count <= DETECTION_MAX - - "_Unable to render markdown - too many unclosed markdown image links detected._" - end - end - end -end diff --git a/lib/banzai/pipeline/plain_markdown_pipeline.rb b/lib/banzai/pipeline/plain_markdown_pipeline.rb index fb6f6e9077d..1da0f72996b 100644 --- a/lib/banzai/pipeline/plain_markdown_pipeline.rb +++ b/lib/banzai/pipeline/plain_markdown_pipeline.rb @@ -5,7 +5,6 @@ module Banzai class PlainMarkdownPipeline < BasePipeline def self.filters FilterArray[ - Filter::PathologicalMarkdownFilter, Filter::MarkdownPreEscapeFilter, Filter::MarkdownFilter, Filter::MarkdownPostEscapeFilter diff --git a/lib/gitlab/gitaly_client/ref_service.rb b/lib/gitlab/gitaly_client/ref_service.rb index bb6bc3121bd..d2b702f3a6d 100644 --- a/lib/gitlab/gitaly_client/ref_service.rb +++ b/lib/gitlab/gitaly_client/ref_service.rb @@ -270,15 +270,13 @@ module Gitlab end def consume_find_local_branches_response(response) - if Feature.enabled?(:gitaly_simplify_find_local_branches_response, type: :undefined) - response.flat_map do |message| + response.flat_map do |message| + if message.local_branches.present? message.local_branches.map do |branch| target_commit = Gitlab::Git::Commit.decorate(@repository, branch.target_commit) Gitlab::Git::Branch.new(@repository, branch.name, branch.target_commit.id, target_commit) end - end - else - response.flat_map do |message| + else message.branches.map do |gitaly_branch| Gitlab::Git::Branch.new( @repository, diff --git a/lib/gitlab/github_import/client.rb b/lib/gitlab/github_import/client.rb index 6cff15a204f..ce229b6d094 100644 --- a/lib/gitlab/github_import/client.rb +++ b/lib/gitlab/github_import/client.rb @@ -69,7 +69,7 @@ module Gitlab # # username - The username of the user. def user(username) - with_rate_limit { octokit.user(username) } + with_rate_limit { octokit.user(username).to_h } end def pull_request_reviews(repo_name, iid) @@ -88,7 +88,7 @@ module Gitlab end def pull_request(repo_name, iid) - with_rate_limit { octokit.pull_request(repo_name, iid) } + with_rate_limit { octokit.pull_request(repo_name, iid).to_h } end def labels(*args) @@ -150,7 +150,7 @@ module Gitlab each_page(method, *args) do |page| page.objects.each do |object| - yield object + yield object.to_h end end end diff --git a/lib/gitlab/github_import/importer/protected_branches_importer.rb b/lib/gitlab/github_import/importer/protected_branches_importer.rb index b5be823d5ab..4372477f55d 100644 --- a/lib/gitlab/github_import/importer/protected_branches_importer.rb +++ b/lib/gitlab/github_import/importer/protected_branches_importer.rb @@ -11,9 +11,9 @@ module Gitlab def each_object_to_import repo = project.import_source - protected_branches = client.branches(repo).select { |branch| branch.protection&.enabled } + protected_branches = client.branches(repo).select { |branch| branch.dig(:protection, :enabled) } protected_branches.each do |protected_branch| - object = client.branch_protection(repo, protected_branch.name) + object = client.branch_protection(repo, protected_branch[:name]) next if object.nil? || already_imported?(object) yield object @@ -44,7 +44,7 @@ module Gitlab end def id_for_already_imported_cache(protected_branch) - protected_branch.name + protected_branch[:name] end end end diff --git a/lib/gitlab/github_import/importer/pull_requests_reviews_importer.rb b/lib/gitlab/github_import/importer/pull_requests_reviews_importer.rb index 0025c708717..543c29a21a0 100644 --- a/lib/gitlab/github_import/importer/pull_requests_reviews_importer.rb +++ b/lib/gitlab/github_import/importer/pull_requests_reviews_importer.rb @@ -48,6 +48,8 @@ module Gitlab def each_object_to_import(&block) each_review_page do |page, merge_request| page.objects.each do |review| + review = review.to_h + next if already_imported?(review) Gitlab::GithubImport::ObjectCounter.increment(project, object_type, :fetched) diff --git a/lib/gitlab/github_import/parallel_scheduling.rb b/lib/gitlab/github_import/parallel_scheduling.rb index bf5046de36c..1196fe610b1 100644 --- a/lib/gitlab/github_import/parallel_scheduling.rb +++ b/lib/gitlab/github_import/parallel_scheduling.rb @@ -125,6 +125,8 @@ module Gitlab next unless page_counter.set(page.number) page.objects.each do |object| + object = object.to_h + next if already_imported?(object) Gitlab::GithubImport::ObjectCounter.increment(project, object_type, :fetched) diff --git a/lib/gitlab/github_import/representation/diff_note.rb b/lib/gitlab/github_import/representation/diff_note.rb index 3e9f23c9704..f3be90834c7 100644 --- a/lib/gitlab/github_import/representation/diff_note.rb +++ b/lib/gitlab/github_import/representation/diff_note.rb @@ -19,7 +19,7 @@ module Gitlab # Builds a diff note from a GitHub API response. # - # note - An instance of `Sawyer::Resource` containing the note details. + # note - An instance of `Hash` containing the note details. def self.from_api_response(note, additional_data = {}) matches = note[:html_url].match(NOTEABLE_ID_REGEX) diff --git a/lib/gitlab/github_import/representation/issue.rb b/lib/gitlab/github_import/representation/issue.rb index 240e45b437e..e878aeaf3b9 100644 --- a/lib/gitlab/github_import/representation/issue.rb +++ b/lib/gitlab/github_import/representation/issue.rb @@ -15,7 +15,7 @@ module Gitlab # Builds an issue from a GitHub API response. # - # issue - An instance of `Sawyer::Resource` containing the issue + # issue - An instance of `Hash` containing the issue # details. def self.from_api_response(issue, additional_data = {}) user = diff --git a/lib/gitlab/github_import/representation/issue_event.rb b/lib/gitlab/github_import/representation/issue_event.rb index f088e324144..7d5ad01976d 100644 --- a/lib/gitlab/github_import/representation/issue_event.rb +++ b/lib/gitlab/github_import/representation/issue_event.rb @@ -34,7 +34,7 @@ module Gitlab class << self # Builds an event from a GitHub API response. # - # event - An instance of `Sawyer::Resource` containing the event details. + # event - An instance of `Hash` containing the event details. def from_api_response(event, additional_data = {}) new( id: event[:id], diff --git a/lib/gitlab/github_import/representation/note.rb b/lib/gitlab/github_import/representation/note.rb index 0518b1386fc..14379e8a4e9 100644 --- a/lib/gitlab/github_import/representation/note.rb +++ b/lib/gitlab/github_import/representation/note.rb @@ -16,7 +16,7 @@ module Gitlab # Builds a note from a GitHub API response. # - # note - An instance of `Sawyer::Resource` containing the note details. + # note - An instance of `Hash` containing the note details. def self.from_api_response(note, additional_data = {}) matches = note[:html_url].match(NOTEABLE_TYPE_REGEX) diff --git a/lib/gitlab/github_import/representation/protected_branch.rb b/lib/gitlab/github_import/representation/protected_branch.rb index ab8909d955c..293d9be9f3d 100644 --- a/lib/gitlab/github_import/representation/protected_branch.rb +++ b/lib/gitlab/github_import/representation/protected_branch.rb @@ -16,12 +16,12 @@ module Gitlab # https://docs.github.com/en/rest/branches/branch-protection#get-branch-protection # branch_protection - An instance of `Sawyer::Resource` containing the protection details. def self.from_api_response(branch_protection, _additional_object_data = {}) - branch_name = branch_protection.url.match(%r{/branches/(\S{1,255})/protection$})[1] + branch_name = branch_protection[:url].match(%r{/branches/(\S{1,255})/protection$})[1] hash = { id: branch_name, - allow_force_pushes: branch_protection.allow_force_pushes.enabled, - required_conversation_resolution: branch_protection.required_conversation_resolution.enabled + allow_force_pushes: branch_protection.dig(:allow_force_pushes, :enabled), + required_conversation_resolution: branch_protection.dig(:required_conversation_resolution, :enabled) } new(hash) diff --git a/lib/gitlab/github_import/representation/pull_request.rb b/lib/gitlab/github_import/representation/pull_request.rb index c78478d4a03..4b8ae1f8eab 100644 --- a/lib/gitlab/github_import/representation/pull_request.rb +++ b/lib/gitlab/github_import/representation/pull_request.rb @@ -17,7 +17,7 @@ module Gitlab # Builds a PR from a GitHub API response. # - # issue - An instance of `Sawyer::Resource` containing the PR details. + # issue - An instance of `Hash` containing the PR details. def self.from_api_response(pr, additional_data = {}) assignee = Representation::User.from_api_response(pr[:assignee]) if pr[:assignee] user = Representation::User.from_api_response(pr[:user]) if pr[:user] diff --git a/lib/gitlab/github_import/representation/pull_request_review.rb b/lib/gitlab/github_import/representation/pull_request_review.rb index 691e7cce678..8fb57ae89a4 100644 --- a/lib/gitlab/github_import/representation/pull_request_review.rb +++ b/lib/gitlab/github_import/representation/pull_request_review.rb @@ -13,7 +13,7 @@ module Gitlab # Builds a PullRequestReview from a GitHub API response. # - # review - An instance of `Sawyer::Resource` containing the note details. + # review - An instance of `Hash` containing the note details. def self.from_api_response(review, additional_data = {}) user = Representation::User.from_api_response(review[:user]) if review[:user] diff --git a/lib/gitlab/github_import/representation/user.rb b/lib/gitlab/github_import/representation/user.rb index c06eaf514e3..02cbe037384 100644 --- a/lib/gitlab/github_import/representation/user.rb +++ b/lib/gitlab/github_import/representation/user.rb @@ -13,7 +13,7 @@ module Gitlab # Builds a user from a GitHub API response. # - # user - An instance of `Sawyer::Resource` containing the user details. + # user - An instance of `Hash` containing the user details. def self.from_api_response(user, additional_data = {}) new( id: user[:id], diff --git a/lib/gitlab/github_import/user_finder.rb b/lib/gitlab/github_import/user_finder.rb index f1b18070440..b8751def08f 100644 --- a/lib/gitlab/github_import/user_finder.rb +++ b/lib/gitlab/github_import/user_finder.rb @@ -39,7 +39,7 @@ module Gitlab # # If the object has no author ID we'll use the ID of the GitLab ghost # user. - # object - An instance of `Sawyer::Resource` or a `Github::Representer` + # object - An instance of `Hash` or a `Github::Representer` def author_id_for(object, author_key: :author) user_info = case author_key when :actor @@ -70,7 +70,7 @@ module Gitlab # Returns the GitLab user ID for a GitHub user. # - # user - An instance of `Gitlab::GithubImport::Representation::User` or `Sawyer::Resource`. + # user - An instance of `Gitlab::GithubImport::Representation::User` or `Hash`. def user_id_for(user) find(user[:id], user[:login]) if user.present? end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 291644836db..bc3d29757e0 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -13130,6 +13130,9 @@ msgstr "" msgid "DeployTokens|Active Deploy Tokens (%{active_tokens})" msgstr "" +msgid "DeployTokens|Allows read and write access to registry images." +msgstr "" + msgid "DeployTokens|Allows read and write access to the package registry." msgstr "" @@ -13175,6 +13178,9 @@ msgstr "" msgid "DeployTokens|Enter an expiration date for your token. Defaults to never expire." msgstr "" +msgid "DeployTokens|Expiration date (optional)" +msgstr "" + msgid "DeployTokens|Expires" msgstr "" @@ -13196,6 +13202,9 @@ msgstr "" msgid "DeployTokens|Scopes" msgstr "" +msgid "DeployTokens|Scopes (select at least one)" +msgstr "" + msgid "DeployTokens|This %{entity_type} has no active Deploy Tokens." msgstr "" @@ -13211,6 +13220,12 @@ msgstr "" msgid "DeployTokens|Username" msgstr "" +msgid "DeployTokens|Username (optional)" +msgstr "" + +msgid "DeployTokens|Your New Deploy Token" +msgstr "" + msgid "DeployTokens|Your new Deploy Token username" msgstr "" @@ -41346,6 +41361,9 @@ msgstr "" msgid "This will remove the fork relationship between this project and other projects in the fork network." msgstr "" +msgid "Thread options" +msgstr "" + msgid "Thread to reply to cannot be found" msgstr "" @@ -41944,9 +41962,6 @@ msgstr "" msgid "Toggle commit list" msgstr "" -msgid "Toggle dropdown" -msgstr "" - msgid "Toggle emoji award" msgstr "" @@ -42211,9 +42226,6 @@ msgstr "" msgid "Trials|Go back to GitLab" msgstr "" -msgid "Trials|Hey there" -msgstr "" - msgid "Trials|Skip Trial" msgstr "" @@ -42229,6 +42241,11 @@ msgstr "" msgid "Trials|You won't get a free trial right now but you can always resume this process by selecting your avatar and choosing 'Start an Ultimate trial'" msgstr "" +msgid "Trials|You've got %{daysRemaining} day remaining on GitLab %{planName}!" +msgid_plural "Trials|You've got %{daysRemaining} days remaining on GitLab %{planName}!" +msgstr[0] "" +msgstr[1] "" + msgid "Trials|Your trial ends on %{boldStart}%{trialEndDate}%{boldEnd}. We hope you’re enjoying the features of GitLab %{planName}. To keep those features after your trial ends, you’ll need to buy a subscription. (You can also choose GitLab Premium if it meets your needs.)" msgstr "" diff --git a/spec/features/groups/settings/repository_spec.rb b/spec/features/groups/settings/repository_spec.rb index 159deb2a4e3..f6b8bbdd35f 100644 --- a/spec/features/groups/settings/repository_spec.rb +++ b/spec/features/groups/settings/repository_spec.rb @@ -2,14 +2,17 @@ require 'spec_helper' -RSpec.describe 'Group Repository settings' do +RSpec.describe 'Group Repository settings', :js do include WaitForRequests - let(:user) { create(:user) } - let(:group) { create(:group) } + let_it_be(:user) { create(:user) } + let_it_be(:group, reload: true) { create(:group) } - before do + before_all do group.add_owner(user) + end + + before do sign_in(user) end @@ -20,9 +23,26 @@ RSpec.describe 'Group Repository settings' do stub_container_registry_config(enabled: true) end - it_behaves_like 'a deploy token in settings' do - let(:entity_type) { 'group' } - let(:page_path) { group_settings_repository_path(group) } + context 'when ajax deploy tokens is enabled' do + before do + stub_feature_flags(ajax_new_deploy_token: true) + end + + it_behaves_like 'a deploy token in settings' do + let(:entity_type) { 'group' } + let(:page_path) { group_settings_repository_path(group) } + end + end + + context 'when ajax deploy tokens is disabled' do + before do + stub_feature_flags(ajax_new_deploy_token: false) + end + + it_behaves_like 'a deploy token in settings' do + let(:entity_type) { 'group' } + let(:page_path) { group_settings_repository_path(group) } + end end end diff --git a/spec/features/merge_request/close_reopen_report_toggle_spec.rb b/spec/features/merge_request/close_reopen_report_toggle_spec.rb index dea9a10a4ec..9f39cf758df 100644 --- a/spec/features/merge_request/close_reopen_report_toggle_spec.rb +++ b/spec/features/merge_request/close_reopen_report_toggle_spec.rb @@ -24,14 +24,14 @@ RSpec.describe 'Issuables Close/Reopen/Report toggle' do context 'close/reopen/report toggle' do it 'opens a dropdown when toggle is clicked' do - click_button 'Toggle dropdown' + click_button 'Merge request actions' expect(container).to have_link("Close merge request") expect(container).to have_link('Report abuse') end it 'links to Report Abuse' do - click_button 'Toggle dropdown' + click_button 'Merge request actions' click_link 'Report abuse' expect(page).to have_content('Report abuse to admin') @@ -42,7 +42,7 @@ RSpec.describe 'Issuables Close/Reopen/Report toggle' do let(:issuable) { create(:merge_request, :opened, source_project: project) } it 'shows the `Edit` and `Mark as draft` buttons' do - click_button 'Toggle dropdown' + click_button 'Merge request actions' expect(container).to have_link('Edit') expect(container).to have_link('Mark as draft') @@ -56,7 +56,7 @@ RSpec.describe 'Issuables Close/Reopen/Report toggle' do let(:issuable) { create(:merge_request, :closed, source_project: project) } it 'shows both the `Edit` and `Reopen` button' do - click_button 'Toggle dropdown' + click_button 'Merge request actions' expect(container).to have_link('Edit') expect(container).to have_link('Report abuse') @@ -68,7 +68,7 @@ RSpec.describe 'Issuables Close/Reopen/Report toggle' do let(:issuable) { create(:merge_request, :closed, source_project: project, author: user) } it 'shows both the `Edit` and `Reopen` button' do - click_button 'Toggle dropdown' + click_button 'Merge request actions' expect(container).to have_link('Edit') expect(container).to have_link('Reopen merge request') diff --git a/spec/features/merge_request/merge_request_discussion_lock_spec.rb b/spec/features/merge_request/merge_request_discussion_lock_spec.rb index a7bc2a062af..99a6e18a770 100644 --- a/spec/features/merge_request/merge_request_discussion_lock_spec.rb +++ b/spec/features/merge_request/merge_request_discussion_lock_spec.rb @@ -90,7 +90,7 @@ RSpec.describe 'Merge Request Discussion Lock', :js do end it 'the user can lock the merge_request' do - click_button 'Toggle dropdown' + click_button 'Merge request actions' expect(page).to have_content('Lock merge request') end @@ -103,7 +103,7 @@ RSpec.describe 'Merge Request Discussion Lock', :js do end it 'the user can unlock the merge_request' do - click_button 'Toggle dropdown' + click_button 'Merge request actions' expect(page).to have_content('Unlock merge request') end diff --git a/spec/features/merge_request/user_manages_subscription_spec.rb b/spec/features/merge_request/user_manages_subscription_spec.rb index ec22201b88f..2161507e435 100644 --- a/spec/features/merge_request/user_manages_subscription_spec.rb +++ b/spec/features/merge_request/user_manages_subscription_spec.rb @@ -43,7 +43,7 @@ RSpec.describe 'User manages subscription', :js do it 'toggles subscription' do wait_for_requests - click_button 'Toggle dropdown' + click_button 'Merge request actions' expect(page).to have_selector('.gl-toggle:not(.is-checked)') find('[data-testid="notifications-toggle"] .gl-toggle').click diff --git a/spec/features/merge_request/user_marks_merge_request_as_draft_spec.rb b/spec/features/merge_request/user_marks_merge_request_as_draft_spec.rb index c3a61476442..12169055606 100644 --- a/spec/features/merge_request/user_marks_merge_request_as_draft_spec.rb +++ b/spec/features/merge_request/user_marks_merge_request_as_draft_spec.rb @@ -16,12 +16,12 @@ RSpec.describe 'Merge request > User marks merge request as draft', :js do end it 'toggles draft status' do - click_button 'Toggle dropdown' + click_button 'Merge request actions' click_link 'Mark as draft' expect(page).to have_content("Draft: #{merge_request.title}") - click_button 'Toggle dropdown' + click_button 'Merge request actions' page.within('.detail-page-header-actions') do click_link 'Mark as ready' diff --git a/spec/features/projects/settings/repository_settings_spec.rb b/spec/features/projects/settings/repository_settings_spec.rb index 63e0ee4a251..d9bdbf7aa1a 100644 --- a/spec/features/projects/settings/repository_settings_spec.rb +++ b/spec/features/projects/settings/repository_settings_spec.rb @@ -25,7 +25,7 @@ RSpec.describe 'Projects > Settings > Repository settings' do context 'for maintainer' do let(:role) { :maintainer } - context 'Deploy tokens' do + context 'Deploy tokens', :js do let!(:deploy_token) { create(:deploy_token, projects: [project]) } before do diff --git a/spec/frontend/deploy_tokens/components/new_deploy_token_spec.js b/spec/frontend/deploy_tokens/components/new_deploy_token_spec.js new file mode 100644 index 00000000000..19e9ba8b268 --- /dev/null +++ b/spec/frontend/deploy_tokens/components/new_deploy_token_spec.js @@ -0,0 +1,103 @@ +import { shallowMount } from '@vue/test-utils'; +import { nextTick } from 'vue'; +import { GlButton, GlFormCheckbox, GlFormInput, GlFormInputGroup, GlDatepicker } from '@gitlab/ui'; +import MockAdapter from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; +import { TEST_HOST } from 'helpers/test_constants'; +import NewDeployToken from '~/deploy_tokens/components/new_deploy_token.vue'; +import waitForPromises from 'helpers/wait_for_promises'; + +const createNewTokenPath = `${TEST_HOST}/create`; +const deployTokensHelpUrl = `${TEST_HOST}/help`; +describe('New Deploy Token', () => { + let wrapper; + + const factory = (options = {}) => { + const defaults = { + containerRegistryEnabled: true, + packagesRegistryEnabled: true, + tokenType: 'project', + }; + const { containerRegistryEnabled, packagesRegistryEnabled, tokenType } = { + ...defaults, + ...options, + }; + return shallowMount(NewDeployToken, { + propsData: { + deployTokensHelpUrl, + containerRegistryEnabled, + packagesRegistryEnabled, + createNewTokenPath, + tokenType, + }, + }); + }; + + afterEach(() => { + wrapper.destroy(); + }); + + describe('without a container registry', () => { + beforeEach(() => { + wrapper = factory({ containerRegistryEnabled: false }); + }); + + it('should not show the read registry scope', () => { + wrapper + .findAllComponents(GlFormCheckbox) + .wrappers.forEach((checkbox) => expect(checkbox.text()).not.toBe('read_registry')); + }); + }); + + describe('with a container registry', () => { + beforeEach(() => { + wrapper = factory(); + }); + + it('should show the read registry scope', () => { + const checkbox = wrapper.findAllComponents(GlFormCheckbox).at(1); + expect(checkbox.text()).toBe('read_registry'); + }); + + it('should make a request to create a token on submit', () => { + const mockAxios = new MockAdapter(axios); + + const date = new Date(); + const formInputs = wrapper.findAllComponents(GlFormInput); + const name = formInputs.at(0); + const username = formInputs.at(2); + name.vm.$emit('input', 'test name'); + username.vm.$emit('input', 'test username'); + + const datepicker = wrapper.findAllComponents(GlDatepicker).at(0); + datepicker.vm.$emit('input', date); + + const [readRepo, readRegistry] = wrapper.findAllComponents(GlFormCheckbox).wrappers; + readRepo.vm.$emit('input', true); + readRegistry.vm.$emit('input', true); + + mockAxios + .onPost(createNewTokenPath, { + deploy_token: { + name: 'test name', + expires_at: date.toISOString(), + username: 'test username', + read_repository: true, + read_registry: true, + }, + }) + .replyOnce(200, { username: 'test token username', token: 'test token' }); + + wrapper.findAllComponents(GlButton).at(0).vm.$emit('click'); + + return waitForPromises() + .then(() => nextTick()) + .then(() => { + const [tokenUsername, tokenValue] = wrapper.findAllComponents(GlFormInputGroup).wrappers; + + expect(tokenUsername.props('value')).toBe('test token username'); + expect(tokenValue.props('value')).toBe('test token'); + }); + }); + }); +}); diff --git a/spec/frontend/packages_and_registries/settings/project/settings/components/cleanup_image_tags_spec.js b/spec/frontend/packages_and_registries/settings/project/settings/components/cleanup_image_tags_spec.js index 8b60f31512b..2bb99fb8e8f 100644 --- a/spec/frontend/packages_and_registries/settings/project/settings/components/cleanup_image_tags_spec.js +++ b/spec/frontend/packages_and_registries/settings/project/settings/components/cleanup_image_tags_spec.js @@ -19,6 +19,7 @@ import { expirationPolicyPayload, emptyExpirationPolicyPayload, containerExpirationPolicyData, + nullExpirationPolicyPayload, } from '../mock_data'; describe('Cleanup image tags project settings', () => { @@ -98,15 +99,30 @@ describe('Cleanup image tags project settings', () => { expect(findDescription().text()).toMatchInterpolatedText(CONTAINER_CLEANUP_POLICY_DESCRIPTION); }); + it('when loading does not render form or alert components', () => { + mountComponentWithApollo({ + resolver: jest.fn().mockResolvedValue(), + }); + + expect(findFormComponent().exists()).toBe(false); + expect(findAlert().exists()).toBe(false); + }); + describe('the form is disabled', () => { - it('hides the form', () => { - mountComponent(); + it('hides the form', async () => { + mountComponentWithApollo({ + resolver: jest.fn().mockResolvedValue(nullExpirationPolicyPayload()), + }); + await waitForPromises(); expect(findFormComponent().exists()).toBe(false); }); - it('shows an alert', () => { - mountComponent(); + it('shows an alert', async () => { + mountComponentWithApollo({ + resolver: jest.fn().mockResolvedValue(nullExpirationPolicyPayload()), + }); + await waitForPromises(); const text = findAlert().text(); expect(text).toContain(UNAVAILABLE_FEATURE_INTRO_TEXT); @@ -114,8 +130,12 @@ describe('Cleanup image tags project settings', () => { }); describe('an admin is visiting the page', () => { - it('shows the admin part of the alert message', () => { - mountComponent({ ...defaultProvidedValues, isAdmin: true }); + it('shows the admin part of the alert message', async () => { + mountComponentWithApollo({ + provide: { ...defaultProvidedValues, isAdmin: true }, + resolver: jest.fn().mockResolvedValue(nullExpirationPolicyPayload()), + }); + await waitForPromises(); const sprintf = findAlert().findComponent(GlSprintf); expect(sprintf.text()).toBe('administration settings'); diff --git a/spec/frontend/packages_and_registries/settings/project/settings/components/container_expiration_policy_spec.js b/spec/frontend/packages_and_registries/settings/project/settings/components/container_expiration_policy_spec.js index 35baeaeac61..43484d26d76 100644 --- a/spec/frontend/packages_and_registries/settings/project/settings/components/container_expiration_policy_spec.js +++ b/spec/frontend/packages_and_registries/settings/project/settings/components/container_expiration_policy_spec.js @@ -16,7 +16,11 @@ import { import expirationPolicyQuery from '~/packages_and_registries/settings/project/graphql/queries/get_expiration_policy.query.graphql'; import SettingsBlock from '~/vue_shared/components/settings/settings_block.vue'; -import { expirationPolicyPayload, emptyExpirationPolicyPayload } from '../mock_data'; +import { + expirationPolicyPayload, + emptyExpirationPolicyPayload, + nullExpirationPolicyPayload, +} from '../mock_data'; describe('Container expiration policy project settings', () => { let wrapper; @@ -78,15 +82,30 @@ describe('Container expiration policy project settings', () => { expect(findButton().attributes('href')).toBe(defaultProvidedValues.cleanupSettingsPath); }); + it('when loading does not render form or alert components', () => { + mountComponentWithApollo({ + resolver: jest.fn().mockResolvedValue(), + }); + + expect(findFormComponent().exists()).toBe(false); + expect(findAlert().exists()).toBe(false); + }); + describe('the form is disabled', () => { - it('the form is hidden', () => { - mountComponent(); + it('hides the form', async () => { + mountComponentWithApollo({ + resolver: jest.fn().mockResolvedValue(nullExpirationPolicyPayload()), + }); + await waitForPromises(); expect(findFormComponent().exists()).toBe(false); }); - it('shows an alert', () => { - mountComponent(); + it('shows an alert', async () => { + mountComponentWithApollo({ + resolver: jest.fn().mockResolvedValue(nullExpirationPolicyPayload()), + }); + await waitForPromises(); const text = findAlert().text(); expect(text).toContain(UNAVAILABLE_FEATURE_INTRO_TEXT); @@ -94,8 +113,12 @@ describe('Container expiration policy project settings', () => { }); describe('an admin is visiting the page', () => { - it('shows the admin part of the alert message', () => { - mountComponent({ ...defaultProvidedValues, isAdmin: true }); + it('shows the admin part of the alert message', async () => { + mountComponentWithApollo({ + provide: { ...defaultProvidedValues, isAdmin: true }, + resolver: jest.fn().mockResolvedValue(nullExpirationPolicyPayload()), + }); + await waitForPromises(); const sprintf = findAlert().findComponent(GlSprintf); expect(sprintf.text()).toBe('administration settings'); diff --git a/spec/frontend/packages_and_registries/settings/project/settings/mock_data.js b/spec/frontend/packages_and_registries/settings/project/settings/mock_data.js index 0696144215c..3204ca01f99 100644 --- a/spec/frontend/packages_and_registries/settings/project/settings/mock_data.js +++ b/spec/frontend/packages_and_registries/settings/project/settings/mock_data.js @@ -29,6 +29,15 @@ export const emptyExpirationPolicyPayload = () => ({ }, }); +export const nullExpirationPolicyPayload = () => ({ + data: { + project: { + id: '1', + containerExpirationPolicy: null, + }, + }, +}); + export const expirationPolicyMutationPayload = ({ override, errors = [] } = {}) => ({ data: { updateContainerExpirationPolicy: { diff --git a/spec/initializers/sawyer_patch_spec.rb b/spec/initializers/sawyer_patch_spec.rb index dc922654d7d..b3c10e63460 100644 --- a/spec/initializers/sawyer_patch_spec.rb +++ b/spec/initializers/sawyer_patch_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'fast_spec_helper' +require 'spec_helper' require 'sawyer' require_relative '../../config/initializers/sawyer_patch' @@ -64,6 +64,28 @@ RSpec.describe 'sawyer_patch' do expect(sawyer_resource.count_total).to eq(1) expect(sawyer_resource.count_total?).to eq(true) expect(sawyer_resource.count_total + 1).to eq(2) + sawyer_resource.count_total = 3 + expect(sawyer_resource.count_total).to eq(3) expect(sawyer_resource.user.name).to eq('User name') end + + it 'logs when a sawyer resource dynamic method is called' do + sawyer_resource = Sawyer::Resource.new( + Sawyer::Agent.new(''), + { + count_total: 1, + user: { name: 'User name' } + } + ) + expected_attributes = [] + allow(Gitlab::Import::Logger).to receive(:warn) do |params| + expected_attributes.push(params[:attribute]) + end + + sawyer_resource.count_total + sawyer_resource.user + sawyer_resource.user.name + + expect(expected_attributes).to match_array(%i[count_total user user name]) + end end diff --git a/spec/lib/banzai/filter/pathological_markdown_filter_spec.rb b/spec/lib/banzai/filter/pathological_markdown_filter_spec.rb deleted file mode 100644 index e0a07d1ea77..00000000000 --- a/spec/lib/banzai/filter/pathological_markdown_filter_spec.rb +++ /dev/null @@ -1,27 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Banzai::Filter::PathologicalMarkdownFilter do - include FilterSpecHelper - - let_it_be(:short_text) { '![a' * 5 } - let_it_be(:long_text) { ([short_text] * 10).join(' ') } - let_it_be(:with_images_text) { "![One ![one](one.jpg) #{'and\n' * 200} ![two ![two](two.jpg)" } - - it 'detects a significat number of unclosed image links' do - msg = <<~TEXT - _Unable to render markdown - too many unclosed markdown image links detected._ - TEXT - - expect(filter(long_text)).to eq(msg.strip) - end - - it 'does nothing when there are only a few unclosed image links' do - expect(filter(short_text)).to eq(short_text) - end - - it 'does nothing when there are only a few unclosed image links and images' do - expect(filter(with_images_text)).to eq(with_images_text) - end -end diff --git a/spec/lib/banzai/pipeline/full_pipeline_spec.rb b/spec/lib/banzai/pipeline/full_pipeline_spec.rb index c07f99dc9fc..1a0f5a53a23 100644 --- a/spec/lib/banzai/pipeline/full_pipeline_spec.rb +++ b/spec/lib/banzai/pipeline/full_pipeline_spec.rb @@ -168,15 +168,13 @@ RSpec.describe Banzai::Pipeline::FullPipeline do end end - describe 'unclosed image links' do - it 'detects a significat number of unclosed image links' do - markdown = '![a ' * 30 - msg = <<~TEXT - Unable to render markdown - too many unclosed markdown image links detected. - TEXT - output = described_class.to_html(markdown, project: nil) - - expect(output).to include(msg.strip) + describe 'cmark-gfm and autlolinks' do + it 'does not hang with significant number of unclosed image links' do + markdown = '![a ' * 300000 + + expect do + Timeout.timeout(2.seconds) { described_class.to_html(markdown, project: nil) } + end.not_to raise_error end end end diff --git a/spec/lib/gitlab/config_checker/external_database_checker_spec.rb b/spec/lib/gitlab/config_checker/external_database_checker_spec.rb index bf9c738a3e1..9af6aed2b02 100644 --- a/spec/lib/gitlab/config_checker/external_database_checker_spec.rb +++ b/spec/lib/gitlab/config_checker/external_database_checker_spec.rb @@ -11,7 +11,7 @@ RSpec.describe Gitlab::ConfigChecker::ExternalDatabaseChecker do let(:new_database) { instance_double(Gitlab::Database::Reflection) } before do - allow(Gitlab::Database::Reflection).to receive(:new).and_call_original + allow(Gitlab::Database::Reflection).to receive(:new).and_return(new_database) allow(old_database).to receive(:postgresql_minimum_supported_version?).and_return(false) allow(old_database).to receive(:version).and_return(old_database_version) allow(new_database).to receive(:postgresql_minimum_supported_version?).and_return(true) diff --git a/spec/lib/gitlab/gitaly_client/ref_service_spec.rb b/spec/lib/gitlab/gitaly_client/ref_service_spec.rb index b7c21516c77..5ce88b06241 100644 --- a/spec/lib/gitlab/gitaly_client/ref_service_spec.rb +++ b/spec/lib/gitlab/gitaly_client/ref_service_spec.rb @@ -3,7 +3,8 @@ require 'spec_helper' RSpec.describe Gitlab::GitalyClient::RefService do - let(:project) { create(:project, :repository) } + let_it_be(:project) { create(:project, :repository) } + let(:storage_name) { project.repository_storage } let(:relative_path) { project.disk_path + '.git' } let(:repository) { project.repository } @@ -179,13 +180,22 @@ RSpec.describe Gitlab::GitalyClient::RefService do ) ) end + local_branches = target_commits.each_with_index.map do |gitaly_commit, i| Gitaly::Branch.new(name: "#{remote_name}/#{i}", target_commit: gitaly_commit) end - response = [ - Gitaly::FindLocalBranchesResponse.new(branches: branches[0, 2], local_branches: local_branches[0, 2]), - Gitaly::FindLocalBranchesResponse.new(branches: branches[2, 2], local_branches: local_branches[2, 2]) - ] + + response = if set_local_branches + [ + Gitaly::FindLocalBranchesResponse.new(local_branches: local_branches[0, 2]), + Gitaly::FindLocalBranchesResponse.new(local_branches: local_branches[2, 2]) + ] + else + [ + Gitaly::FindLocalBranchesResponse.new(branches: branches[0, 2]), + Gitaly::FindLocalBranchesResponse.new(branches: branches[2, 2]) + ] + end expect_any_instance_of(Gitaly::RefService::Stub) .to receive(:find_local_branches) @@ -220,18 +230,14 @@ RSpec.describe Gitlab::GitalyClient::RefService do end end - context 'when feature flag :gitaly_simplify_find_local_branches_response is enabled' do - before do - stub_feature_flags(gitaly_simplify_find_local_branches_response: true) - end + context 'when local_branches variable is not set' do + let(:set_local_branches) { false } it_behaves_like 'common examples' end - context 'when feature flag :gitaly_simplify_find_local_branches_response is disabled' do - before do - stub_feature_flags(gitaly_simplify_find_local_branches_response: false) - end + context 'when local_branches variable is set' do + let(:set_local_branches) { true } it_behaves_like 'common examples' end diff --git a/spec/lib/gitlab/github_import/client_spec.rb b/spec/lib/gitlab/github_import/client_spec.rb index c88bb6de859..473eaeef59a 100644 --- a/spec/lib/gitlab/github_import/client_spec.rb +++ b/spec/lib/gitlab/github_import/client_spec.rb @@ -152,6 +152,22 @@ RSpec.describe Gitlab::GithubImport::Client do end end + describe '#each_object' do + it 'converts each object into a hash' do + client = described_class.new('foo') + + stub_request(:get, 'https://api.github.com/rate_limit') + .to_return(status: 200, headers: { 'X-RateLimit-Limit' => 5000, 'X-RateLimit-Remaining' => 5000 }) + + stub_request(:get, 'https://api.github.com/repos/foo/bar/releases?per_page=100') + .to_return(status: 200, body: [{ id: 1 }].to_json, headers: { 'Content-Type' => 'application/json' }) + + client.each_object(:releases, 'foo/bar') do |release| + expect(release).to eq({ id: 1 }) + end + end + end + describe '#each_page' do let(:client) { described_class.new('foo') } let(:object1) { double(:object1) } diff --git a/spec/lib/gitlab/github_import/parallel_scheduling_spec.rb b/spec/lib/gitlab/github_import/parallel_scheduling_spec.rb index d57f1b2e761..17ead67390d 100644 --- a/spec/lib/gitlab/github_import/parallel_scheduling_spec.rb +++ b/spec/lib/gitlab/github_import/parallel_scheduling_spec.rb @@ -309,7 +309,7 @@ RSpec.describe Gitlab::GithubImport::ParallelScheduling do describe '#each_object_to_import' do let(:importer) { importer_class.new(project, client) } - let(:object) { double(:object) } + let(:object) { {} } before do expect(importer) diff --git a/spec/support/shared_examples/features/deploy_token_shared_examples.rb b/spec/support/shared_examples/features/deploy_token_shared_examples.rb index 79ad5bd6c7f..9fe08e5c996 100644 --- a/spec/support/shared_examples/features/deploy_token_shared_examples.rb +++ b/spec/support/shared_examples/features/deploy_token_shared_examples.rb @@ -14,32 +14,32 @@ RSpec.shared_examples 'a deploy token in settings' do end end - it 'add a new deploy token' do + it 'add a new deploy token', :js do visit page_path - fill_in 'deploy_token_name', with: 'new_deploy_key' - fill_in 'deploy_token_expires_at', with: (Date.today + 1.month).to_s - fill_in 'deploy_token_username', with: 'deployer' - check 'deploy_token_read_repository' - check 'deploy_token_read_registry' + fill_in _('Name'), with: 'new_deploy_key' + fill_in _('Expiration date (optional)'), with: (Date.today + 1.month).to_s + fill_in _('Username (optional)'), with: 'deployer' + check 'read_repository' + check 'read_registry' click_button 'Create deploy token' expect(page).to have_content("Your new #{entity_type} deploy token has been created") within('.created-deploy-token-container') do - expect(page).to have_selector("input[name='deploy-token-user'][value='deployer']") - expect(page).to have_selector("input[name='deploy-token'][readonly='readonly']") + expect(find("input[name='deploy-token-user']").value).to eq("deployer") + expect(find("input[name='deploy-token'][readonly='readonly']")).to be_visible end - expect(find("input#deploy_token_name").value).to eq nil + expect(find("input#deploy_token_name").value).to be_empty expect(find("input#deploy_token_read_repository").checked?).to eq false end - context "with form errors" do + context "with form errors", :js do before do visit page_path - fill_in "deploy_token_name", with: "new_deploy_key" - fill_in "deploy_token_username", with: "deployer" + fill_in _('Name'), with: "new_deploy_key" + fill_in _('Username (optional)'), with: "deployer" click_button "Create deploy token" end diff --git a/workhorse/go.mod b/workhorse/go.mod index 5f92023d17b..5fa9ca7a52d 100644 --- a/workhorse/go.mod +++ b/workhorse/go.mod @@ -7,7 +7,7 @@ require ( github.com/BurntSushi/toml v1.2.0 github.com/FZambia/sentinel v1.1.1 github.com/alecthomas/chroma/v2 v2.3.0 - github.com/aws/aws-sdk-go v1.43.31 + github.com/aws/aws-sdk-go v1.44.107 github.com/disintegration/imaging v1.6.2 github.com/getsentry/raven-go v0.2.0 github.com/golang-jwt/jwt/v4 v4.4.2 diff --git a/workhorse/go.sum b/workhorse/go.sum index 88cf873cf4c..3275e43115e 100644 --- a/workhorse/go.sum +++ b/workhorse/go.sum @@ -178,8 +178,9 @@ github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevB github.com/aws/aws-sdk-go v1.15.27/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= github.com/aws/aws-sdk-go v1.17.4/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.37.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= -github.com/aws/aws-sdk-go v1.43.31 h1:yJZIr8nMV1hXjAvvOLUFqZRJcHV7udPQBfhJqawDzI0= github.com/aws/aws-sdk-go v1.43.31/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +github.com/aws/aws-sdk-go v1.44.107 h1:VP7Rq3wzsOV7wrfHqjAAKRksD4We58PaoVSDPKhm8nw= +github.com/aws/aws-sdk-go v1.44.107/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/aws/aws-sdk-go-v2 v1.16.2 h1:fqlCk6Iy3bnCumtrLz9r3mJ/2gUT0pJ0wLFVIdWh+JA= github.com/aws/aws-sdk-go-v2 v1.16.2/go.mod h1:ytwTPBG6fXTZLxxeeCCWj2/EMYp/xDUgX+OET6TLNNU= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.1 h1:SdK4Ppk5IzLs64ZMvr6MrSficMtjY2oS0WOORXTlxwU= |