diff options
46 files changed, 371 insertions, 223 deletions
diff --git a/app/assets/javascripts/issue_show/components/app.vue b/app/assets/javascripts/issue_show/components/app.vue index de2a9664cde..9ca38d6bbfa 100644 --- a/app/assets/javascripts/issue_show/components/app.vue +++ b/app/assets/javascripts/issue_show/components/app.vue @@ -55,6 +55,11 @@ export default { required: false, default: true, }, + zoomMeetingUrl: { + type: String, + required: false, + default: null, + }, issuableRef: { type: String, required: true, @@ -342,7 +347,7 @@ export default { :title-text="state.titleText" :show-inline-edit-button="showInlineEditButton" /> - <pinned-links :description-html="state.descriptionHtml" /> + <pinned-links :zoom-meeting-url="zoomMeetingUrl" /> <description-component v-if="state.descriptionHtml" :can-update="canUpdate" diff --git a/app/assets/javascripts/issue_show/components/pinned_links.vue b/app/assets/javascripts/issue_show/components/pinned_links.vue index 7a54b26bc2b..965e8a3d751 100644 --- a/app/assets/javascripts/issue_show/components/pinned_links.vue +++ b/app/assets/javascripts/issue_show/components/pinned_links.vue @@ -8,40 +8,19 @@ export default { GlLink, }, props: { - descriptionHtml: { + zoomMeetingUrl: { type: String, - required: true, - }, - }, - computed: { - linksInDescription() { - const el = document.createElement('div'); - el.innerHTML = this.descriptionHtml; - return [...el.querySelectorAll('a')].map(a => a.href); - }, - // Detect links matching the following formats: - // Zoom Start links: https://zoom.us/s/<meeting-id> - // Zoom Join links: https://zoom.us/j/<meeting-id> - // Personal Zoom links: https://zoom.us/my/<meeting-id> - // Vanity Zoom links: https://gitlab.zoom.us/j/<meeting-id> (also /s and /my) - zoomHref() { - const zoomRegex = /^https:\/\/([\w\d-]+\.)?zoom\.us\/(s|j|my)\/.+/; - return this.linksInDescription.reduce((acc, currentLink) => { - let lastLink = acc; - if (zoomRegex.test(currentLink)) { - lastLink = currentLink; - } - return lastLink; - }, ''); + required: false, + default: null, }, }, }; </script> <template> - <div v-if="zoomHref" class="border-bottom mb-3 mt-n2"> + <div v-if="zoomMeetingUrl" class="border-bottom mb-3 mt-n2"> <gl-link - :href="zoomHref" + :href="zoomMeetingUrl" target="_blank" class="btn btn-inverted btn-secondary btn-sm text-dark mb-3" > diff --git a/app/assets/stylesheets/framework/animations.scss b/app/assets/stylesheets/framework/animations.scss index 6bc5632365f..6f5a2e561af 100644 --- a/app/assets/stylesheets/framework/animations.scss +++ b/app/assets/stylesheets/framework/animations.scss @@ -104,7 +104,7 @@ } .btn { - @include transition(border-color); + @include transition(background-color, border-color, color, box-shadow); } .dropdown-menu-toggle, diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss index e0b6da31261..767832e242c 100644 --- a/app/assets/stylesheets/framework/buttons.scss +++ b/app/assets/stylesheets/framework/buttons.scss @@ -24,11 +24,12 @@ border-radius: $border-radius-default; font-size: $gl-font-size; font-weight: $gl-font-weight-normal; - padding: $gl-bordered-btn-vert-padding $gl-bordered-btn-horz-padding; + padding: $gl-vert-padding $gl-btn-padding; &:focus, &:active { background-color: $btn-active-gray; + box-shadow: $gl-btn-active-background; } } @@ -49,89 +50,77 @@ color: $text; } - &:not(:disabled):not(.disabled) { - &:hover { - box-shadow: inset 0 0 0 1px $hover-border, 0 2px 2px 0 $gl-btn-hover-shadow-light; - } + &:hover, + &:focus { + background-color: $hover-background; + border-color: $hover-border; + color: $hover-text; - &:focus { - box-shadow: inset 0 0 0 1px $hover-border, 0 0 4px 1px $blue-300; + > .icon { + color: $hover-text; } + } - &:hover, - &:focus { - background-color: $hover-background; - border-color: $hover-border; - color: $hover-text; + &:focus { + box-shadow: 0 0 4px 1px $blue-300; + } - > .icon { - color: $hover-text; - } - } + &:active { + background-color: $active-background; + border-color: $active-border; + box-shadow: inset 0 2px 4px 0 rgba($black, 0.2); + color: $active-text; - &:active, - &:active:focus { - background-color: $active-background; - border-color: $active-border; - box-shadow: inset 0 0 0 1px $hover-border, inset 0 2px 4px 0 rgba($black, 0.2); + > .icon { color: $active-text; + } - > .icon { - color: $active-text; - } + &:focus { + box-shadow: inset 0 2px 4px 0 rgba($black, 0.2); } } } -@mixin btn-color($light, $border-light, $normal, $border-normal, $dark, $border-dark, $color, $hover-shadow-color: $gl-btn-hover-shadow-dark) { +@mixin btn-color($light, $border-light, $normal, $border-normal, $dark, $border-dark, $color) { background-color: $light; border-color: $border-light; color: $color; - &:not(:disabled):not(.disabled) { - &:hover { - box-shadow: inset 0 0 0 1px $border-normal, 0 2px 2px 0 $hover-shadow-color; - } - - &:focus { - box-shadow: inset 0 0 0 1px $border-normal, 0 0 4px 1px $blue-300; - } + &:hover, + &:focus { + background-color: $normal; + border-color: $border-normal; + color: $color; + } - &:hover, - &:focus { - background-color: $normal; - border-color: $border-normal; - color: $color; - } + &:active, + &.active { + box-shadow: $gl-btn-active-background; - &:active, - &.active { - box-shadow: inset 0 2px 4px 0 $gl-btn-hover-shadow-dark; - background-color: $dark; - border-color: $border-dark; - color: $color; - } + background-color: $dark; + border-color: $border-dark; + color: $color; } } @mixin btn-green { - @include btn-color($green-500, $green-600, $green-500, $green-700, $green-600, $green-800, $white-light); + @include btn-color($green-500, $green-600, $green-600, $green-700, $green-700, $green-800, $white-light); } @mixin btn-blue { - @include btn-color($blue-500, $blue-600, $blue-500, $blue-700, $blue-600, $blue-800, $white-light); + @include btn-color($blue-500, $blue-600, $blue-600, $blue-700, $blue-700, $blue-800, $white-light); } @mixin btn-orange { - @include btn-color($orange-500, $orange-600, $orange-500, $orange-700, $orange-600, $orange-800, $white-light); + @include btn-color($orange-500, $orange-600, $orange-600, $orange-700, $orange-700, $orange-800, $white-light); } @mixin btn-red { - @include btn-color($red-500, $red-600, $red-500, $red-700, $red-600, $red-800, $white-light); + @include btn-color($red-500, $red-600, $red-600, $red-700, $red-700, $red-800, $white-light); } @mixin btn-white { - @include btn-color($white-light, $gray-400, $gray-200, $gray-400, $gl-gray-200, $gray-500, $gl-text-color, $gl-btn-hover-shadow-light); + @include btn-color($white-light, $border-color, $white-normal, $border-white-normal, $white-dark, $border-gray-dark, $gl-text-color); } @mixin btn-with-margin { @@ -160,20 +149,21 @@ color: $gl-text-color; white-space: nowrap; - line-height: $gl-btn-line-height; &:focus:active { outline: 0; } - &.btn-xs { - font-size: $gl-btn-xs-font-size; - line-height: $gl-btn-xs-line-height; + &.btn-sm { + padding: 4px 10px; + font-size: $gl-btn-small-font-size; + line-height: $gl-btn-small-line-height; } - &.btn-sm, &.btn-xs { - padding: 3px $gl-bordered-btn-vert-padding; + padding: 2px $gl-btn-padding; + font-size: $gl-btn-xs-font-size; + line-height: $gl-btn-xs-line-height; } &.btn-success, @@ -249,7 +239,7 @@ &.dropdown-toggle { .fa-caret-down { - margin: 0; + margin-left: 3px; } } @@ -282,7 +272,10 @@ } svg { - @include btn-svg; + height: 15px; + width: 15px; + position: relative; + top: 2px; } svg, @@ -337,12 +330,6 @@ &.btn-grouped { @include btn-with-margin; } - - .btn { - border-radius: $border-radius-default; - font-size: $gl-font-size; - line-height: $gl-btn-line-height; - } } .btn-clipboard { @@ -500,25 +487,18 @@ &:active, &:focus { color: $gl-text-color-secondary; - border: 1px solid $border-gray-normal-dashed; background-color: $white-normal; } } -.btn-svg { - padding: $gl-bordered-btn-vert-padding; - - svg { - @include btn-svg; - display: block; - } +.btn-svg svg { + @include btn-svg; } // All disabled buttons, regardless of color, type, etc %disabled { background-color: $gray-light !important; border-color: $gray-200 !important; - box-shadow: none; color: $gl-text-color-disabled !important; opacity: 1 !important; cursor: default !important; diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index 05afcecca05..29f63e9578d 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -8,6 +8,12 @@ } } +@mixin chevron-active { + .fa-chevron-down { + color: $gray-darkest; + } +} + @mixin set-visible { transform: translateY(0); display: block; @@ -43,6 +49,7 @@ .dropdown-toggle, .dropdown-menu-toggle { + @include chevron-active; border-color: $gray-darkest; } @@ -58,12 +65,12 @@ .dropdown-toggle, .confidential-merge-request-fork-group .dropdown-toggle { - padding: $gl-bordered-btn-vert-padding $gl-bordered-btn-horz-padding; + padding: 6px 8px 6px 10px; background-color: $white-light; color: $gl-text-color; font-size: 14px; - line-height: $gl-btn-line-height; text-align: left; + border: 1px solid $border-color; border-radius: $border-radius-base; white-space: nowrap; @@ -96,6 +103,10 @@ padding-right: 25px; } + .fa { + color: $gray-darkest; + } + .fa-chevron-down { font-size: $dropdown-chevron-size; position: relative; @@ -104,10 +115,12 @@ } &:hover { + @include chevron-active; border-color: $gray-darkest; } &:focus:active { + @include chevron-active; border-color: $dropdown-toggle-active-border-color; outline: 0; } diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 047a9799c3f..c108f45622f 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -405,8 +405,6 @@ $tanuki-yellow: #fca326; */ $green-500-focus: rgba($green-500, 0.4); $gl-btn-active-background: rgba(0, 0, 0, 0.16); -$gl-btn-hover-shadow-dark: rgba($black, 0.2); -$gl-btn-hover-shadow-light: rgba($black, 0.1); $gl-btn-active-gradient: inset 0 2px 3px $gl-btn-active-background; /* @@ -483,8 +481,6 @@ $gl-btn-padding: 10px; $gl-btn-line-height: 16px; $gl-btn-vert-padding: 8px; $gl-btn-horz-padding: 12px; -$gl-bordered-btn-vert-padding: $gl-btn-vert-padding - 1px; -$gl-bordered-btn-horz-padding: $gl-btn-horz-padding - 1px; $gl-btn-small-font-size: 13px; $gl-btn-small-line-height: 18px; $gl-btn-xs-font-size: 13px; diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss index 3d11aa58871..0b0a4e50146 100644 --- a/app/assets/stylesheets/pages/commits.scss +++ b/app/assets/stylesheets/pages/commits.scss @@ -214,10 +214,10 @@ .label, .btn { - padding: $gl-bordered-btn-vert-padding $gl-bordered-btn-horz-padding; + padding: $gl-vert-padding $gl-btn-padding; border: 1px $border-color solid; font-size: $gl-font-size; - line-height: $gl-btn-line-height; + line-height: $line-height-base; border-radius: 0; display: flex; align-items: center; diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index 66ea70e79da..6a0127eb51c 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -929,6 +929,10 @@ margin: 0; } } + + .dropdown-toggle > .icon { + margin: 0 3px; + } } .right-sidebar-collapsed { diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss index e51ca44476c..8359a60ec9f 100644 --- a/app/assets/stylesheets/pages/issues.scss +++ b/app/assets/stylesheets/pages/issues.scss @@ -267,6 +267,7 @@ ul.related-merge-requests > li { .fa-caret-down { pointer-events: none; color: inherit; + margin-left: 0; } } } diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss index 1d57a4a4784..c6bac33e888 100644 --- a/app/assets/stylesheets/pages/note_form.scss +++ b/app/assets/stylesheets/pages/note_form.scss @@ -417,6 +417,7 @@ table { i { color: $white-light; + padding-right: 2px; margin-top: 2px; } diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 09a576c11cb..c80beceae52 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -303,7 +303,7 @@ .count-badge-count, .count-badge-button { - border: 1px solid $gray-400; + border: 1px solid $border-color; line-height: 1; } @@ -429,7 +429,7 @@ padding: 0; background: transparent; border: 0; - line-height: 2; + line-height: 34px; margin: 0; > li + li::before { @@ -792,6 +792,7 @@ .btn { margin-top: $gl-padding; + padding: $gl-btn-vert-padding $gl-btn-padding; line-height: $gl-btn-line-height; .icon { diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss index 5c732ab0d1f..5664f46484e 100644 --- a/app/assets/stylesheets/pages/tree.scss +++ b/app/assets/stylesheets/pages/tree.scss @@ -90,7 +90,7 @@ .add-to-tree { vertical-align: top; - padding: $gl-bordered-btn-vert-padding; + padding: 8px; svg { top: 0; diff --git a/app/helpers/dropdowns_helper.rb b/app/helpers/dropdowns_helper.rb index fd94f07cc2c..64c5fae7d96 100644 --- a/app/helpers/dropdowns_helper.rb +++ b/app/helpers/dropdowns_helper.rb @@ -46,7 +46,7 @@ module DropdownsHelper def dropdown_toggle(toggle_text, data_attr, options = {}) default_label = data_attr[:default_label] - content_tag(:button, disabled: options[:disabled], class: "dropdown-menu-toggle btn #{options[:toggle_class] if options.key?(:toggle_class)}", id: (options[:id] if options.key?(:id)), type: "button", data: data_attr) do + content_tag(:button, disabled: options[:disabled], class: "dropdown-menu-toggle #{options[:toggle_class] if options.key?(:toggle_class)}", id: (options[:id] if options.key?(:id)), type: "button", data: data_attr) do output = content_tag(:span, toggle_text, class: "dropdown-toggle-text #{'is-default' if toggle_text == default_label}") output << icon('chevron-down') output.html_safe diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index 67685ba4e1d..e2e007eee50 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -282,6 +282,10 @@ module IssuablesHelper data[:hasClosingMergeRequest] = issuable.merge_requests_count(current_user) != 0 if issuable.is_a?(Issue) + zoom_links = Gitlab::ZoomLinkExtractor.new(issuable.description).links + + data[:zoomMeetingUrl] = zoom_links.last if zoom_links.any? + if parent.is_a?(Group) data[:groupPath] = parent.path else diff --git a/app/views/devise/shared/_signup_box.html.haml b/app/views/devise/shared/_signup_box.html.haml index 034273558bb..074edf645ba 100644 --- a/app/views/devise/shared/_signup_box.html.haml +++ b/app/views/devise/shared/_signup_box.html.haml @@ -7,26 +7,26 @@ = render "devise/shared/error_messages", resource: resource .name.form-group = f.label :name, _('Full name'), class: 'label-bold' - = f.text_field :name, class: "form-control top qa-new-user-name js-block-emoji js-validate-length", :data => { :max_length => max_name_length, :max_length_message => s_("SignUp|Name is too long (maximum is %{max_length} characters).") % { max_length: max_name_length } }, required: true, title: _("This field is required.") + = f.text_field :name, class: "form-control top js-block-emoji js-validate-length", :data => { :max_length => max_name_length, :max_length_message => s_("SignUp|Name is too long (maximum is %{max_length} characters).") % { max_length: max_name_length }, :qa_selector => 'new_user_name_field' }, required: true, title: _("This field is required.") .username.form-group = f.label :username, class: 'label-bold' - = f.text_field :username, class: "form-control middle qa-new-user-username js-block-emoji js-validate-length js-validate-username", :data => { :max_length => max_username_length, :max_length_message => s_("SignUp|Username is too long (maximum is %{max_length} characters).") % { max_length: max_username_length } }, pattern: Gitlab::PathRegex::NAMESPACE_FORMAT_REGEX_JS, required: true, title: _("Please create a username with only alphanumeric characters.") + = f.text_field :username, class: "form-control middle js-block-emoji js-validate-length js-validate-username", :data => { :max_length => max_username_length, :max_length_message => s_("SignUp|Username is too long (maximum is %{max_length} characters).") % { max_length: max_username_length }, :qa_selector => 'new_user_username_field' }, pattern: Gitlab::PathRegex::NAMESPACE_FORMAT_REGEX_JS, required: true, title: _("Please create a username with only alphanumeric characters.") %p.validation-error.gl-field-error-ignore.field-validation.hide= _('Username is already taken.') %p.validation-success.gl-field-error-ignore.field-validation.hide= _('Username is available.') %p.validation-pending.gl-field-error-ignore.field-validation.hide= _('Checking username availability...') .form-group = f.label :email, class: 'label-bold' - = f.email_field :email, class: "form-control middle qa-new-user-email", required: true, title: _("Please provide a valid email address.") + = f.email_field :email, class: "form-control middle", data: { qa_selector: 'new_user_email_field' }, required: true, title: _("Please provide a valid email address.") .form-group = f.label :email_confirmation, class: 'label-bold' - = f.email_field :email_confirmation, class: "form-control middle qa-new-user-email-confirmation", required: true, title: _("Please retype the email address.") + = f.email_field :email_confirmation, class: "form-control middle", data: { qa_selector: 'new_user_email_confirmation_field' }, required: true, title: _("Please retype the email address.") .form-group.append-bottom-20#password-strength = f.label :password, class: 'label-bold' - = f.password_field :password, class: "form-control bottom qa-new-user-password", required: true, pattern: ".{#{@minimum_password_length},}", title: _("Minimum length is %{minimum_password_length} characters.") % { minimum_password_length: @minimum_password_length } + = f.password_field :password, class: "form-control bottom", data: { qa_selector: 'new_user_password_field' }, required: true, pattern: ".{#{@minimum_password_length},}", title: _("Minimum length is %{minimum_password_length} characters.") % { minimum_password_length: @minimum_password_length } %p.gl-field-hint.text-secondary= _('Minimum length is %{minimum_password_length} characters') % { minimum_password_length: @minimum_password_length } - if Gitlab::CurrentSettings.current_application_settings.enforce_terms? .form-group - = check_box_tag :terms_opt_in, '1', false, required: true, class: 'qa-new-user-accept-terms' + = check_box_tag :terms_opt_in, '1', false, required: true, data: { qa_selector: 'new_user_accept_terms_checkbox' } = label_tag :terms_opt_in do - terms_link = link_to s_("I accept the|Terms of Service and Privacy Policy"), terms_path, target: "_blank" - accept_terms_label = _("I accept the %{terms_link}") % { terms_link: terms_link } @@ -36,4 +36,4 @@ - if show_recaptcha_sign_up? = recaptcha_tags .submit-container - = f.submit _("Register"), class: "btn-register btn qa-new-user-register-button" + = f.submit _("Register"), class: "btn-register btn", data: { qa_selector: 'new_user_register_button' } diff --git a/app/views/doorkeeper/authorizations/new.html.haml b/app/views/doorkeeper/authorizations/new.html.haml index dae9a7acf6b..5d57337a568 100644 --- a/app/views/doorkeeper/authorizations/new.html.haml +++ b/app/views/doorkeeper/authorizations/new.html.haml @@ -46,4 +46,4 @@ = hidden_field_tag :response_type, @pre_auth.response_type = hidden_field_tag :scope, @pre_auth.scope = hidden_field_tag :nonce, @pre_auth.nonce - = submit_tag _("Authorize"), class: "btn btn-success prepend-left-10" + = submit_tag _("Authorize"), class: "btn btn-success prepend-left-10", data: { qa_selector: 'authorization_button' } diff --git a/app/views/projects/issues/import_csv/_button.html.haml b/app/views/projects/issues/import_csv/_button.html.haml index 8442a53ed61..acc2c50294f 100644 --- a/app/views/projects/issues/import_csv/_button.html.haml +++ b/app/views/projects/issues/import_csv/_button.html.haml @@ -1,6 +1,6 @@ - type = local_assigns.fetch(:type, :icon) -%button.csv-import-button.btn.btn-svg{ title: _('Import CSV'), class: ('has-tooltip' if type == :icon), +%button.csv-import-button.btn{ title: _('Import CSV'), class: ('has-tooltip' if type == :icon), data: { toggle: 'modal', target: '.issues-import-modal' } } - if type == :icon = sprite_icon('upload') diff --git a/app/views/shared/issuable/_feed_buttons.html.haml b/app/views/shared/issuable/_feed_buttons.html.haml index c9506a3295c..83f60fa6fe2 100644 --- a/app/views/shared/issuable/_feed_buttons.html.haml +++ b/app/views/shared/issuable/_feed_buttons.html.haml @@ -1,4 +1,4 @@ -= link_to safe_params.merge(rss_url_options), class: 'btn btn-svg has-tooltip js-rss-button', data: { container: 'body' }, title: _('Subscribe to RSS feed') do - = sprite_icon('rss') -= link_to safe_params.merge(calendar_url_options), class: 'btn btn-svg has-tooltip', data: { container: 'body' }, title: _('Subscribe to calendar') do - = sprite_icon('calendar') += link_to safe_params.merge(rss_url_options), class: 'btn has-tooltip', data: { container: 'body' }, title: _('Subscribe to RSS feed') do + = icon('rss') += link_to safe_params.merge(calendar_url_options), class: 'btn has-tooltip', data: { container: 'body' }, title: _('Subscribe to calendar') do + = custom_icon('icon_calendar') diff --git a/changelogs/unreleased/61145-fix-button-dimensions.yml b/changelogs/unreleased/61145-fix-button-dimensions.yml deleted file mode 100644 index 8f209ceaa8e..00000000000 --- a/changelogs/unreleased/61145-fix-button-dimensions.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Updating button dimensions according to design spec -merge_request: 28545 -author: -type: fixed diff --git a/changelogs/unreleased/button-bug-fixes.yml b/changelogs/unreleased/button-bug-fixes.yml deleted file mode 100644 index b63bfdf24ad..00000000000 --- a/changelogs/unreleased/button-bug-fixes.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix Project Badge Button Styles -merge_request: 30678 -author: -type: fixed diff --git a/changelogs/unreleased/issue-zoom-url.yml b/changelogs/unreleased/issue-zoom-url.yml new file mode 100644 index 00000000000..e0bd5478192 --- /dev/null +++ b/changelogs/unreleased/issue-zoom-url.yml @@ -0,0 +1,5 @@ +--- +title: Extract zoom link from issue and pass to frontend +merge_request: 29910 +author: raju249 +type: added diff --git a/doc/administration/geo/disaster_recovery/background_verification.md b/doc/administration/geo/disaster_recovery/background_verification.md index 8eee9427b56..27866b7536e 100644 --- a/doc/administration/geo/disaster_recovery/background_verification.md +++ b/doc/administration/geo/disaster_recovery/background_verification.md @@ -171,14 +171,21 @@ If the **primary** and **secondary** nodes have a checksum verification mismatch ## Current limitations -Until [issue #5064][ee-5064] is completed, background verification doesn't cover -CI job artifacts and traces, LFS objects, or user uploads in file storage. -Verify their integrity manually by following [these instructions][foreground-verification] -on both nodes, and comparing the output between them. +Automatic background verification doesn't cover attachments, LFS objects, +job artifacts, and user uploads in file storage. You can keep track of the +progress to include them in [ee-1430]. For now, you can verify their integrity +manually by following [these instructions][foreground-verification] on both +nodes, and comparing the output between them. + +In GitLab EE 12.1, Geo calculates checksums for attachments, LFS objects and +archived traces on secondary nodes after the transfer, compares it with the +stored checksums, and rejects transfers if mismatched. Please note that Geo +currently does not support an automatic way to verify these data if they have +been synced before GitLab EE 12.1. Data in object storage is **not verified**, as the object store is responsible for ensuring the integrity of the data. [reset-verification]: background_verification.md#reset-verification-for-projects-where-verification-has-failed [foreground-verification]: ../../raketasks/check.md -[ee-5064]: https://gitlab.com/gitlab-org/gitlab-ee/issues/5064 +[ee-1430]: https://gitlab.com/groups/gitlab-org/-/epics/1430 diff --git a/doc/api/users.md b/doc/api/users.md index 54641f4c862..fdc84826680 100644 --- a/doc/api/users.md +++ b/doc/api/users.md @@ -147,6 +147,21 @@ GET /users ] ``` +Users on GitLab [Starter, Bronze, or higher](https://about.gitlab.com/pricing/) will also see the `shared_runners_minutes_limit`, `extra_shared_runners_minutes_limit`, and `note` parameters. + +```json +[ + { + "id": 1, + ... + "shared_runners_minutes_limit": 133, + "extra_shared_runners_minutes_limit": 133, + "note": "DMCA Request: 2018-11-05 | DMCA Violation | Abuse | https://gitlab.zendesk.com/agent/tickets/123" + ... + } +] +``` + Users on GitLab [Silver or higher](https://about.gitlab.com/pricing/) will also see the `group_saml` provider option: @@ -284,14 +299,15 @@ Example Responses: ``` Users on GitLab [Starter, Bronze, or higher](https://about.gitlab.com/pricing/) will also see -the `shared_runners_minutes_limit` and `extra_shared_runners_minutes_limit` parameters. +the `shared_runners_minutes_limit`, `extra_shared_runners_minutes_limit`, and `note` parameters. ```json { "id": 1, "username": "john_smith", "shared_runners_minutes_limit": 133, - "extra_shared_runners_minutes_limit": 133 + "extra_shared_runners_minutes_limit": 133, + "note": "DMCA Request: 2018-11-05 | DMCA Violation | Abuse | https://gitlab.zendesk.com/agent/tickets/123" ... } ``` @@ -304,7 +320,8 @@ see the `group_saml` option: "id": 1, "username": "john_smith", "shared_runners_minutes_limit": 133, - "extra_shared_runners_minutes_limit": 133 + "extra_shared_runners_minutes_limit": 133, + "note": "DMCA Request: 2018-11-05 | DMCA Violation | Abuse | https://gitlab.zendesk.com/agent/tickets/123" "identities": [ {"provider": "github", "extern_uid": "2435223452345"}, {"provider": "bitbucket", "extern_uid": "john.smith"}, @@ -399,6 +416,7 @@ Parameters: - `private_profile` (optional) - User's profile is private - true or false (default) - `shared_runners_minutes_limit` (optional) - Pipeline minutes quota for this user **(STARTER)** - `extra_shared_runners_minutes_limit` (optional) - Extra pipeline minutes quota for this user **(STARTER)** +- `note` (optional) - Admin notes for this user **(STARTER)** On password update, user will be forced to change it upon next login. Note, at the moment this method does only return a `404` error, diff --git a/doc/ci/docker/using_docker_images.md b/doc/ci/docker/using_docker_images.md index 83a9035c001..f3896c5232c 100644 --- a/doc/ci/docker/using_docker_images.md +++ b/doc/ci/docker/using_docker_images.md @@ -35,8 +35,8 @@ sudo gitlab-runner register \ --description "docker-ruby-2.1" \ --executor "docker" \ --docker-image ruby:2.1 \ - --docker-postgres latest \ - --docker-mysql latest + --docker-services postgres:latest \ + --docker-services mysql:latest ``` The registered runner will use the `ruby:2.1` Docker image and will run two diff --git a/doc/ci/examples/README.md b/doc/ci/examples/README.md index 5a302392c54..9295dcfd4e0 100644 --- a/doc/ci/examples/README.md +++ b/doc/ci/examples/README.md @@ -37,6 +37,7 @@ The following table lists examples with step-by-step tutorials that are containe | Python on Heroku | [Test and deploy a Python application with GitLab CI/CD](test-and-deploy-python-application-to-heroku.md). | | Ruby on Heroku | [Test and deploy a Ruby application with GitLab CI/CD](test-and-deploy-ruby-application-to-heroku.md). | | Scala on Heroku | [Test and deploy a Scala application to Heroku](test-scala-application.md). | +| Parallel testing Ruby & JS | [GitLab CI parallel jobs testing for Ruby & JavaScript projects](https://docs.knapsackpro.com/2019/how-to-run-parallel-jobs-for-rspec-tests-on-gitlab-ci-pipeline-and-speed-up-ruby-javascript-testing). | ### Contributing examples diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index b1b193701c3..001f951ebb8 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -1779,6 +1779,10 @@ test: parallel: 5 ``` +TIP: **Tip:** +Parallelize tests suites across parallel jobs. +Different languages have different tools to facilitate this. + ### `trigger` **(PREMIUM)** > [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/8997) in [GitLab Premium](https://about.gitlab.com/pricing/) 11.8. diff --git a/doc/user/application_security/dependency_scanning/index.md b/doc/user/application_security/dependency_scanning/index.md index 0dd0fd3f136..09bd306363c 100644 --- a/doc/user/application_security/dependency_scanning/index.md +++ b/doc/user/application_security/dependency_scanning/index.md @@ -149,6 +149,8 @@ using environment variables. | `DS_DOCKER_CLIENT_NEGOTIATION_TIMEOUT` | Time limit for Docker client negotiation. Timeouts are parsed using Go's [`ParseDuration`](https://golang.org/pkg/time/#ParseDuration). Valid time units are `ns`, `us` (or `µs`), `ms`, `s`, `m`, `h`. For example, `300ms`, `1.5h`, or `2h45m`. | | `DS_PULL_ANALYZER_IMAGE_TIMEOUT` | Time limit when pulling the image of an analyzer. Timeouts are parsed using Go's [`ParseDuration`](https://golang.org/pkg/time/#ParseDuration). Valid time units are `ns`, `us` (or `µs`), `ms`, `s`, `m`, `h`. For example, `300ms`, `1.5h`, or `2h45m`. | | `DS_RUN_ANALYZER_TIMEOUT` | Time limit when running an analyzer. Timeouts are parsed using Go's [`ParseDuration`](https://golang.org/pkg/time/#ParseDuration). Valid time units are `ns`, `us` (or `µs`), `ms`, `s`, `m`, `h`. For example, `300ms`, `1.5h`, or `2h45m`. | +| `PIP_INDEX_URL` | Base URL of Python Package Index (default https://pypi.org/simple). | +| `PIP_EXTRA_INDEX_URL` | Array of [extra URLs](https://pip.pypa.io/en/stable/reference/pip_install/#cmdoption-extra-index-url) of package indexes to use in addition to `PIP_INDEX_URL`. Comma separated. | ## Reports JSON format diff --git a/doc/user/project/autocomplete_characters.md b/doc/user/project/autocomplete_characters.md new file mode 100644 index 00000000000..9ebf7f821a1 --- /dev/null +++ b/doc/user/project/autocomplete_characters.md @@ -0,0 +1,48 @@ +# Autocomplete characters + +The autocomplete characters provide a quick way of entering field values into +Markdown fields. When you start typing a word in a Markdown field with one of +the following characters, GitLab progressively autocompletes against a set of +matching values. The string matching is not case sensitive. + +| Character | Autocompletes | +| :-------- | :------------ | +| `~` | Labels | +| `%` | Milestones | +| `@` | Users and groups | +| `#` | Issues | +| `!` | Merge requests | +| `&` | Epics | +| `$` | Snippets | +| `:` | Emoji | +| `/` | Quick Actions | + +Up to 5 of the most relevant matches are displayed in a popup list. When you +select an item from the list, the value is entered in the field. The more +characters you enter, the more precise the matches are. + +Autocomplete characters are useful when combined with [Quick Actions](quick_actions.md). + +## Example + +Assume your GitLab instance includes the following users: + +| Username | Name | +| :-------------- | :--- | +| alessandra | Rosy Grant | +| lawrence.white | Kelsey Kerluke | +| leanna | Rosemarie Rogahn | +| logan_gutkowski | Lee Wuckert | +| shelba | Josefine Haley | + +In an Issue comment, entering `@l` results in the following popup list +appearing. Note that user `shelba` is not included, because the list includes +only the 5 users most relevant to the Issue. + +![Popup list which includes users whose username or name contains the letter `l`](img/autocomplete_characters_example1_v12_0.png) + +If you continue to type, `@le`, the popup list changes to the following. The +popup now only includes users where `le` appears in their username, or a word in +their name. + +![Popup list which includes users whose username or name contains the string `le`](img/autocomplete_characters_example2_v12_0.png) diff --git a/doc/user/project/img/autocomplete_characters_example1_v12_0.png b/doc/user/project/img/autocomplete_characters_example1_v12_0.png Binary files differnew file mode 100755 index 00000000000..9c6fa923b80 --- /dev/null +++ b/doc/user/project/img/autocomplete_characters_example1_v12_0.png diff --git a/doc/user/project/img/autocomplete_characters_example2_v12_0.png b/doc/user/project/img/autocomplete_characters_example2_v12_0.png Binary files differnew file mode 100755 index 00000000000..b2e8a782a0b --- /dev/null +++ b/doc/user/project/img/autocomplete_characters_example2_v12_0.png diff --git a/doc/user/project/index.md b/doc/user/project/index.md index 0ffa69b6b78..7307c5b8991 100644 --- a/doc/user/project/index.md +++ b/doc/user/project/index.md @@ -52,6 +52,9 @@ When you create a project in GitLab, you'll have access to a large number of templates for issue and merge request description fields for your project - [Slash commands (quick actions)](quick_actions.md): Textual shortcuts for common actions on issues or merge requests +- [Autocomplete characters](autocomplete_characters.md): Autocomplete + references to users, groups, issues, merge requests, and other GitLab + elements. - [Web IDE](web_ide/index.md) **GitLab CI/CD:** diff --git a/doc/user/project/pages/custom_domains_ssl_tls_certification/index.md b/doc/user/project/pages/custom_domains_ssl_tls_certification/index.md index 6c0d3e9e9d3..219e141d72e 100644 --- a/doc/user/project/pages/custom_domains_ssl_tls_certification/index.md +++ b/doc/user/project/pages/custom_domains_ssl_tls_certification/index.md @@ -179,15 +179,26 @@ From that page, you can view, add, and remove them. ### Redirecting `www.domain.com` to `domain.com` with Cloudflare -If you use Cloudflare, you can redirect `www` to `domain.com` without adding both -`www.domain.com` and `domain.com` to GitLab. This happens due to a [Cloudflare feature that creates -a 301 redirect as a "page rule"](https://gitlab.com/gitlab-org/gitlab-ce/issues/48848#note_87314849) for redirecting `www.domain.com` to `domain.com`. In this case, -you can use the following setup: +If you use Cloudflare, you can redirect `www` to `domain.com` +without adding both `www.domain.com` and `domain.com` to GitLab. + +To do so, you can use Cloudflare's page rules associated to a +CNAME record to redirect `www.domain.com` to `domain.com`. You +can use the following setup: 1. In Cloudflare, create a DNS `A` record pointing `domain.com` to `35.185.44.232`. -1. In GitLab, add the domain to GitLab Pages. +1. In GitLab, add the domain to GitLab Pages and get the verification code. 1. In Cloudflare, create a DNS `TXT` record to verify your domain. +1. In GitLab, verify your domain. 1. In Cloudflare, create a DNS `CNAME` record pointing `www` to `domain.com`. +1. In Cloudflare, add a Page Rule pointing `www.domain,com` to `domain.com`: + - Navigate to your domain's dashboard and click **Page Rules** + on the top nav. + - Click **Create Page Rule**. + - Enter the domain `www.domain.com` and click **+ Add a Setting**. + - From the dropdown menu, choose **Forwarding URL**, then select the + status code **301 - Permanent Redirect**. + - Enter the destination URL `https://domain.com`. ## Adding an SSL/TLS certificate to Pages diff --git a/doc/user/project/pipelines/settings.md b/doc/user/project/pipelines/settings.md index e60da6a3e59..df82daa3da3 100644 --- a/doc/user/project/pipelines/settings.md +++ b/doc/user/project/pipelines/settings.md @@ -89,6 +89,22 @@ in the jobs table. A few examples of known coverage tools for a variety of languages can be found in the pipelines settings page. +### Removing color codes + +Some test coverage tools output with ANSI color codes that won't be +parsed correctly by the regular expression and will cause coverage +parsing to fail. + +If your coverage tool doesn't provide an option to disable color +codes in the output, you can pipe the output of the coverage tool through a +small one line script that will strip the color codes off. + +For example: + +```bash +lein cloverage | perl -pe 's/\e\[?.*?[\@-~]//g' +``` + ## Visibility of pipelines Access to pipelines and job details (including output of logs and artifacts) diff --git a/doc/workflow/time_tracking.md b/doc/workflow/time_tracking.md index 4286a3625a2..b55c6b2e3df 100644 --- a/doc/workflow/time_tracking.md +++ b/doc/workflow/time_tracking.md @@ -75,7 +75,7 @@ Default conversion rates are 1mo = 4w, 1w = 5d and 1d = 8h. ### Limit displayed units to hours -> Introduced in GitLab 12.0. +> Introduced in GitLab 12.1. The display of time units can be limited to hours through the option in **Admin Area > Settings > Preferences** under 'Localization'. diff --git a/lib/api/users.rb b/lib/api/users.rb index 30a278fdff1..a4ac5b629b8 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -148,7 +148,7 @@ module API end desc 'Create a user. Available only for admins.' do - success Entities::UserPublic + success Entities::UserWithAdmin end params do requires :email, type: String, desc: 'The email of the user' @@ -168,7 +168,7 @@ module API user = ::Users::CreateService.new(current_user, params).execute(skip_authorization: true) if user.persisted? - present user, with: Entities::UserPublic, current_user: current_user + present user, with: Entities::UserWithAdmin, current_user: current_user else conflict!('Email has already been taken') if User .by_any_email(user.email.downcase) @@ -183,7 +183,7 @@ module API end desc 'Update a user. Available only for admins.' do - success Entities::UserPublic + success Entities::UserWithAdmin end params do requires :id, type: Integer, desc: 'The ID of the user' @@ -215,7 +215,7 @@ module API result = ::Users::UpdateService.new(current_user, user_params.merge(user: user)).execute if result[:status] == :success - present user, with: Entities::UserPublic, current_user: current_user + present user, with: Entities::UserWithAdmin, current_user: current_user else render_validation_error!(user) end diff --git a/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml index f176771775e..89eccce69f6 100644 --- a/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml @@ -41,6 +41,8 @@ dependency_scanning: DS_PULL_ANALYZER_IMAGE_TIMEOUT \ DS_RUN_ANALYZER_TIMEOUT \ DS_PYTHON_VERSION \ + PIP_INDEX_URL \ + PIP_EXTRA_INDEX_URL \ ) \ --volume "$PWD:/code" \ --volume /var/run/docker.sock:/var/run/docker.sock \ diff --git a/lib/gitlab/zoom_link_extractor.rb b/lib/gitlab/zoom_link_extractor.rb new file mode 100644 index 00000000000..d9994898a08 --- /dev/null +++ b/lib/gitlab/zoom_link_extractor.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +# Detect links matching the following formats: +# Zoom Start links: https://zoom.us/s/<meeting-id> +# Zoom Join links: https://zoom.us/j/<meeting-id> +# Personal Zoom links: https://zoom.us/my/<meeting-id> +# Vanity Zoom links: https://gitlab.zoom.us/j/<meeting-id> (also /s and /my) + +module Gitlab + class ZoomLinkExtractor + ZOOM_REGEXP = %r{https://(?:[\w-]+\.)?zoom\.us/(?:s|j|my)/\S+}.freeze + + def initialize(text) + @text = text.to_s + end + + def links + @text.scan(ZOOM_REGEXP) + end + end +end diff --git a/qa/qa/page/main/oauth.rb b/qa/qa/page/main/oauth.rb index 5f6ddb9a114..2b1a9ab2b6a 100644 --- a/qa/qa/page/main/oauth.rb +++ b/qa/qa/page/main/oauth.rb @@ -5,7 +5,7 @@ module QA module Main class OAuth < Page::Base view 'app/views/doorkeeper/authorizations/new.html.haml' do - element :authorization_button, 'submit_tag _("Authorize")' # rubocop:disable QA/ElementWithPattern + element :authorization_button end def needs_authorization? @@ -13,7 +13,7 @@ module QA end def authorize! - click_button 'Authorize' + click_element :authorization_button end end end diff --git a/qa/qa/page/main/sign_up.rb b/qa/qa/page/main/sign_up.rb index 46a105003d0..c47d2ce9c74 100644 --- a/qa/qa/page/main/sign_up.rb +++ b/qa/qa/page/main/sign_up.rb @@ -5,28 +5,28 @@ module QA module Main class SignUp < Page::Base view 'app/views/devise/shared/_signup_box.html.haml' do - element :new_user_name - element :new_user_username - element :new_user_email - element :new_user_email_confirmation - element :new_user_password + element :new_user_name_field + element :new_user_username_field + element :new_user_email_field + element :new_user_email_confirmation_field + element :new_user_password_field element :new_user_register_button - element :new_user_accept_terms + element :new_user_accept_terms_checkbox end def sign_up!(user) - fill_element :new_user_name, user.name - fill_element :new_user_username, user.username - fill_element :new_user_email, user.email - fill_element :new_user_email_confirmation, user.email - fill_element :new_user_password, user.password + fill_element :new_user_name_field, user.name + fill_element :new_user_username_field, user.username + fill_element :new_user_email_field, user.email + fill_element :new_user_email_confirmation_field, user.email + fill_element :new_user_password_field, user.password - check_element :new_user_accept_terms if has_element?(:new_user_accept_terms) + check_element :new_user_accept_terms_checkbox if has_element?(:new_user_accept_terms_checkbox) signed_in = retry_until do click_element :new_user_register_button - Page::Main::Menu.act { has_personal_area? } + Page::Main::Menu.perform(&:has_personal_area?) end raise "Failed to register and sign in" unless signed_in diff --git a/qa/qa/page/project/sub_menus/common.rb b/qa/qa/page/project/sub_menus/common.rb index c94e1e85256..3c9e8085748 100644 --- a/qa/qa/page/project/sub_menus/common.rb +++ b/qa/qa/page/project/sub_menus/common.rb @@ -12,7 +12,11 @@ module QA end def within_submenu - within('.fly-out-list') do + if has_css?('.fly-out-list') + within('.fly-out-list') do + yield + end + else yield end end diff --git a/qa/qa/resource/merge_request.rb b/qa/qa/resource/merge_request.rb index 45cb317e0eb..7969de726e4 100644 --- a/qa/qa/resource/merge_request.rb +++ b/qa/qa/resource/merge_request.rb @@ -9,6 +9,7 @@ module QA :description, :source_branch, :target_branch, + :target_new_branch, :assignee, :milestone, :labels, @@ -27,6 +28,7 @@ module QA Repository::ProjectPush.fabricate! do |resource| resource.project = project resource.branch_name = 'master' + resource.new_branch = @target_new_branch resource.remote_branch = target_branch end end @@ -52,6 +54,7 @@ module QA @labels = [] @file_name = "added_file.txt" @file_content = "File Added" + @target_new_branch = true end def fabricate! diff --git a/spec/frontend/issue_show/components/pinned_links_spec.js b/spec/frontend/issue_show/components/pinned_links_spec.js index 50041667a61..77da3390918 100644 --- a/spec/frontend/issue_show/components/pinned_links_spec.js +++ b/spec/frontend/issue_show/components/pinned_links_spec.js @@ -5,10 +5,6 @@ import PinnedLinks from '~/issue_show/components/pinned_links.vue'; const localVue = createLocalVue(); const plainZoomUrl = 'https://zoom.us/j/123456789'; -const vanityZoomUrl = 'https://gitlab.zoom.us/j/123456789'; -const startZoomUrl = 'https://zoom.us/s/123456789'; -const personalZoomUrl = 'https://zoom.us/my/hunter-zoloman'; -const randomUrl = 'https://zoom.us.com'; describe('PinnedLinks', () => { let wrapper; @@ -27,7 +23,7 @@ describe('PinnedLinks', () => { localVue, sync: false, propsData: { - descriptionHtml: '', + zoomMeetingUrl: null, ...props, }, }); @@ -35,55 +31,15 @@ describe('PinnedLinks', () => { it('displays Zoom link', () => { createComponent({ - descriptionHtml: `<a href="${plainZoomUrl}">Zoom</a>`, + zoomMeetingUrl: `<a href="${plainZoomUrl}">Zoom</a>`, }); expect(link.text).toBe('Join Zoom meeting'); }); - it('detects plain Zoom link', () => { + it('does not render if there are no links', () => { createComponent({ - descriptionHtml: `<a href="${plainZoomUrl}">Zoom</a>`, - }); - - expect(link.href).toBe(plainZoomUrl); - }); - - it('detects vanity Zoom link', () => { - createComponent({ - descriptionHtml: `<a href="${vanityZoomUrl}">Zoom</a>`, - }); - - expect(link.href).toBe(vanityZoomUrl); - }); - - it('detects Zoom start meeting link', () => { - createComponent({ - descriptionHtml: `<a href="${startZoomUrl}">Zoom</a>`, - }); - - expect(link.href).toBe(startZoomUrl); - }); - - it('detects personal Zoom room link', () => { - createComponent({ - descriptionHtml: `<a href="${personalZoomUrl}">Zoom</a>`, - }); - - expect(link.href).toBe(personalZoomUrl); - }); - - it('only renders final Zoom link in description', () => { - createComponent({ - descriptionHtml: `<a href="${plainZoomUrl}">Zoom</a><a href="${vanityZoomUrl}">Zoom</a>`, - }); - - expect(link.href).toBe(vanityZoomUrl); - }); - - it('does not render for other links', () => { - createComponent({ - descriptionHtml: `<a href="${randomUrl}">Some other link</a>`, + zoomMeetingUrl: null, }); expect(wrapper.find(GlLink).exists()).toBe(false); diff --git a/spec/helpers/issuables_helper_spec.rb b/spec/helpers/issuables_helper_spec.rb index 1d1446eaa30..3c8179460ac 100644 --- a/spec/helpers/issuables_helper_spec.rb +++ b/spec/helpers/issuables_helper_spec.rb @@ -202,5 +202,46 @@ describe IssuablesHelper do } expect(helper.issuable_initial_data(issue)).to match(hash_including(expected_data)) end + + describe '#zoomMeetingUrl in issue' do + let(:issue) { create(:issue, author: user, description: description) } + + before do + assign(:project, issue.project) + end + + context 'no zoom links in the issue description' do + let(:description) { 'issue text' } + + it 'does not set zoomMeetingUrl' do + expect(helper.issuable_initial_data(issue)) + .not_to include(:zoomMeetingUrl) + end + end + + context 'no zoom links in the issue description if it has link but not a zoom link' do + let(:description) { 'issue text https://stackoverflow.com/questions/22' } + + it 'does not set zoomMeetingUrl' do + expect(helper.issuable_initial_data(issue)) + .not_to include(:zoomMeetingUrl) + end + end + + context 'with two zoom links in description' do + let(:description) do + <<~TEXT + issue text and + zoom call on https://zoom.us/j/123456789 this url + and new zoom url https://zoom.us/s/lastone and some more text + TEXT + end + + it 'sets zoomMeetingUrl value to the last url' do + expect(helper.issuable_initial_data(issue)) + .to include(zoomMeetingUrl: 'https://zoom.us/s/lastone') + end + end + end end end diff --git a/spec/lib/gitlab/zoom_link_extractor_spec.rb b/spec/lib/gitlab/zoom_link_extractor_spec.rb new file mode 100644 index 00000000000..52387fc3688 --- /dev/null +++ b/spec/lib/gitlab/zoom_link_extractor_spec.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::ZoomLinkExtractor do + describe "#links" do + using RSpec::Parameterized::TableSyntax + + where(:text, :links) do + 'issue text https://zoom.us/j/123 and https://zoom.us/s/1123433' | %w[https://zoom.us/j/123 https://zoom.us/s/1123433] + 'https://zoom.us/j/1123433 issue text' | %w[https://zoom.us/j/1123433] + 'issue https://zoom.us/my/1123433 text' | %w[https://zoom.us/my/1123433] + 'issue https://gitlab.com and https://gitlab.zoom.us/s/1123433' | %w[https://gitlab.zoom.us/s/1123433] + 'https://gitlab.zoom.us/j/1123433' | %w[https://gitlab.zoom.us/j/1123433] + 'https://gitlab.zoom.us/my/1123433' | %w[https://gitlab.zoom.us/my/1123433] + end + + with_them do + subject { described_class.new(text).links } + + it { is_expected.to eq(links) } + end + end +end diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index 0ad50e5347a..af2bee4563a 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -448,6 +448,7 @@ describe API::Users do it "returns 201 Created on success" do post api("/users", admin), params: attributes_for(:user, projects_limit: 3) + expect(response).to match_response_schema('public_api/v4/user/admin') expect(response).to have_gitlab_http_status(201) end @@ -643,6 +644,13 @@ describe API::Users do describe "PUT /users/:id" do let!(:admin_user) { create(:admin) } + it "returns 200 OK on success" do + put api("/users/#{user.id}", admin), params: { bio: 'new test bio' } + + expect(response).to match_response_schema('public_api/v4/user/admin') + expect(response).to have_gitlab_http_status(200) + end + it "updates user with new bio" do put api("/users/#{user.id}", admin), params: { bio: 'new test bio' } diff --git a/spec/support/features/rss_shared_examples.rb b/spec/support/features/rss_shared_examples.rb index 02d310a9afa..0de92aedba5 100644 --- a/spec/support/features/rss_shared_examples.rb +++ b/spec/support/features/rss_shared_examples.rb @@ -6,7 +6,7 @@ end shared_examples "it has an RSS button with current_user's feed token" do it "shows the RSS button with current_user's feed token" do - expect(page).to have_css("a:has(.fa-rss)[href*='feed_token=#{user.feed_token}'], .js-rss-button[href*='feed_token=#{user.feed_token}']") + expect(page).to have_css("a:has(.fa-rss)[href*='feed_token=#{user.feed_token}']") end end @@ -18,6 +18,6 @@ end shared_examples "it has an RSS button without a feed token" do it "shows the RSS button without a feed token" do - expect(page).to have_css("a:has(.fa-rss):not([href*='feed_token']), .js-rss-button:not([href*='feed_token'])") + expect(page).to have_css("a:has(.fa-rss):not([href*='feed_token'])") end end |