diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-05-14 00:07:42 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-05-14 00:07:42 +0000 |
commit | 5225ffb5ccfe2fe0e55a3327d43f28f4ed08ae63 (patch) | |
tree | ceb6a117475daa1aa17632e1c297863b53195897 | |
parent | c0b9c14ebd1524a1e2334e656f997ec680a18966 (diff) | |
download | gitlab-ce-5225ffb5ccfe2fe0e55a3327d43f28f4ed08ae63.tar.gz |
Add latest changes from gitlab-org/gitlab@master
38 files changed, 335 insertions, 280 deletions
diff --git a/app/assets/javascripts/error_tracking/components/error_details.vue b/app/assets/javascripts/error_tracking/components/error_details.vue index 0a661d51576..ccadf940fe3 100644 --- a/app/assets/javascripts/error_tracking/components/error_details.vue +++ b/app/assets/javascripts/error_tracking/components/error_details.vue @@ -17,7 +17,11 @@ import Tracking from '~/tracking'; import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue'; import query from '../queries/details.query.graphql'; -import { trackErrorDetailsViewsOptions, trackErrorStatusUpdateOptions } from '../utils'; +import { + trackErrorDetailsViewsOptions, + trackErrorStatusUpdateOptions, + trackCreateIssueFromError, +} from '../events_tracking'; import { severityLevel, severityLevelVariant, errorStatus } from '../constants'; import Stacktrace from './stacktrace.vue'; import ErrorDetailsInfo from './error_details_info.vue'; @@ -184,6 +188,8 @@ export default { ]), createIssue() { this.issueCreationInProgress = true; + const { category, action } = trackCreateIssueFromError; + Tracking.event(category, action); this.$refs.sentryIssueForm.submit(); }, onIgnoreStatusUpdate() { diff --git a/app/assets/javascripts/error_tracking/components/error_details_info.vue b/app/assets/javascripts/error_tracking/components/error_details_info.vue index bbc7b0de7cf..f6f39f178fb 100644 --- a/app/assets/javascripts/error_tracking/components/error_details_info.vue +++ b/app/assets/javascripts/error_tracking/components/error_details_info.vue @@ -2,7 +2,7 @@ import { GlLink, GlIcon, GlCard, GlTooltipDirective } from '@gitlab/ui'; import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; import TrackEventDirective from '~/vue_shared/directives/track_event'; -import { trackClickErrorLinkToSentryOptions } from '../utils'; +import { trackClickErrorLinkToSentryOptions } from '../events_tracking'; const CARD_CLASS = 'gl-mr-7 gl-w-15p gl-min-w-fit-content'; const HEADER_CLASS = diff --git a/app/assets/javascripts/error_tracking/components/error_tracking_list.vue b/app/assets/javascripts/error_tracking/components/error_tracking_list.vue index 2a4bb88b6c2..6750f0f5411 100644 --- a/app/assets/javascripts/error_tracking/components/error_tracking_list.vue +++ b/app/assets/javascripts/error_tracking/components/error_tracking_list.vue @@ -23,7 +23,12 @@ import { __ } from '~/locale'; import Tracking from '~/tracking'; import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue'; import { sanitizeUrl } from '~/lib/utils/url_utility'; -import { trackErrorListViewsOptions, trackErrorStatusUpdateOptions } from '../utils'; +import { + trackErrorListViewsOptions, + trackErrorStatusUpdateOptions, + trackErrorStatusFilterOptions, + trackErrorSortedByField, +} from '../events_tracking'; import { I18N_ERROR_TRACKING_LIST } from '../constants'; import ErrorTrackingActions from './error_tracking_actions.vue'; @@ -237,8 +242,15 @@ export default { }, filterErrors(status, label) { this.filterValue = label; + const { category, action } = trackErrorStatusFilterOptions(status); + Tracking.event(category, action); return this.filterByStatus(status); }, + sortErrorsByField(field) { + const { category, action } = trackErrorSortedByField(field); + Tracking.event(category, action); + return this.sortByField(field); + }, updateErrosStatus({ errorId, status }) { // eslint-disable-next-line promise/catch-or-return this.updateStatus({ @@ -371,7 +383,7 @@ export default { <gl-dropdown-item v-for="(label, field) in $options.sortFields" :key="field" - @click="sortByField(field)" + @click="sortErrorsByField(field)" > <span class="d-flex"> <gl-icon diff --git a/app/assets/javascripts/error_tracking/utils.js b/app/assets/javascripts/error_tracking/events_tracking.js index afb91d3db51..aaef274d0cd 100644 --- a/app/assets/javascripts/error_tracking/utils.js +++ b/app/assets/javascripts/error_tracking/events_tracking.js @@ -34,3 +34,27 @@ export const trackErrorStatusUpdateOptions = (status) => ({ category, action: `update_${status}_status`, }); + +/** + * Tracks snowplow event when error list is filter by status + */ +export const trackErrorStatusFilterOptions = (status) => ({ + category, + action: `filter_${status}_status`, +}); + +/** + * Tracks snowplow event when error list is sorted by field + */ +export const trackErrorSortedByField = (field) => ({ + category, + action: `sort_by_${field}`, +}); + +/** + * Tracks snowplow event when the Create Issue button is clicked + */ +export const trackCreateIssueFromError = { + category, + action: 'click_create_issue_from_error', +}; diff --git a/app/assets/javascripts/import/details/components/import_details_table.vue b/app/assets/javascripts/import/details/components/import_details_table.vue index b32b5778265..813dc1f2645 100644 --- a/app/assets/javascripts/import/details/components/import_details_table.vue +++ b/app/assets/javascripts/import/details/components/import_details_table.vue @@ -34,7 +34,7 @@ export default { tdClass: 'gl-md-w-30 gl-word-break-word', }, { - key: 'url', + key: 'provider_url', label: __('URL'), tdClass: 'gl-white-space-nowrap', }, @@ -141,9 +141,9 @@ export default { <template #cell(type)="{ item: { type } }"> {{ $options.STATISTIC_ITEMS[type] }} </template> - <template #cell(url)="{ item: { url } }"> - <gl-link v-if="url" :href="url" target="_blank"> - {{ url }} + <template #cell(provider_url)="{ item: { provider_url } }"> + <gl-link v-if="provider_url" :href="provider_url" target="_blank"> + {{ provider_url }} <gl-icon name="external-link" /> </gl-link> </template> diff --git a/app/models/ci/build_trace.rb b/app/models/ci/build_trace.rb index b9a74102641..f70e1ed69ea 100644 --- a/app/models/ci/build_trace.rb +++ b/app/models/ci/build_trace.rb @@ -12,11 +12,7 @@ module Ci if stream.valid? stream.limit - @trace = Gitlab::Ci::Ansi2json.convert( - stream.stream, - state, - verify_state: Feature.enabled?(:sign_and_verify_ansi2json_state, build.project) - ) + @trace = Gitlab::Ci::Ansi2json.convert(stream.stream, state) end end diff --git a/config/feature_flags/development/import_details_page.yml b/config/feature_flags/development/import_details_page.yml index 412fe90f40a..d59e2945ae9 100644 --- a/config/feature_flags/development/import_details_page.yml +++ b/config/feature_flags/development/import_details_page.yml @@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/397650 milestone: '15.11' type: development group: group::import -default_enabled: false +default_enabled: true diff --git a/config/feature_flags/development/sign_and_verify_ansi2json_state.yml b/config/feature_flags/development/sign_and_verify_ansi2json_state.yml deleted file mode 100644 index af9286dc3cd..00000000000 --- a/config/feature_flags/development/sign_and_verify_ansi2json_state.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: sign_and_verify_ansi2json_state -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/116625 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/404718 -milestone: '15.11' -type: development -group: group::pipeline execution -default_enabled: true diff --git a/db/docs/batched_background_migrations/cleanup_personal_access_tokens_with_nil_expires_at.yml b/db/docs/batched_background_migrations/cleanup_personal_access_tokens_with_nil_expires_at.yml new file mode 100644 index 00000000000..630aeccd6e1 --- /dev/null +++ b/db/docs/batched_background_migrations/cleanup_personal_access_tokens_with_nil_expires_at.yml @@ -0,0 +1,6 @@ +--- +migration_job_name: CleanupPersonalAccessTokensWithInvalidExpiresAt +description: Updates value of expires_at column to 365 days from now when it's nil for PersonalAccessTokens +feature_category: system_access +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/120239 +milestone: 16.0 diff --git a/db/post_migrate/20221021082256_finish_reset_duplicate_ci_runners_token_values.rb b/db/post_migrate/20221021082256_finish_reset_duplicate_ci_runners_token_values.rb new file mode 100644 index 00000000000..4e6195bb3c8 --- /dev/null +++ b/db/post_migrate/20221021082256_finish_reset_duplicate_ci_runners_token_values.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +class FinishResetDuplicateCiRunnersTokenValues < Gitlab::Database::Migration[2.0] + disable_ddl_transaction! + + restrict_gitlab_migration gitlab_schema: :gitlab_ci + + def up + ensure_batched_background_migration_is_finished( + job_class_name: 'ResetDuplicateCiRunnersTokenValues', + table_name: :ci_runners, + column_name: :id, + job_arguments: [], + finalize: true + ) + end + + def down + # no-op + end +end diff --git a/db/post_migrate/20221021082255_add_unique_index_on_ci_runners_token.rb b/db/post_migrate/20221021082257_add_unique_index_on_ci_runners_token.rb index 3dfa44f9615..3858c0efe06 100644 --- a/db/post_migrate/20221021082255_add_unique_index_on_ci_runners_token.rb +++ b/db/post_migrate/20221021082257_add_unique_index_on_ci_runners_token.rb @@ -6,12 +6,10 @@ class AddUniqueIndexOnCiRunnersToken < Gitlab::Database::Migration[2.0] INDEX_NAME = 'index_uniq_ci_runners_on_token' def up - finalize_background_migration 'ResetDuplicateCiRunnersTokenValues' - add_concurrent_index :ci_runners, - :token, - name: INDEX_NAME, - unique: true + :token, + name: INDEX_NAME, + unique: true end def down diff --git a/db/post_migrate/20221021082313_finish_reset_duplicate_ci_runners_token_encrypted_values.rb b/db/post_migrate/20221021082313_finish_reset_duplicate_ci_runners_token_encrypted_values.rb new file mode 100644 index 00000000000..ba08322b1ff --- /dev/null +++ b/db/post_migrate/20221021082313_finish_reset_duplicate_ci_runners_token_encrypted_values.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +class FinishResetDuplicateCiRunnersTokenEncryptedValues < Gitlab::Database::Migration[2.0] + disable_ddl_transaction! + + restrict_gitlab_migration gitlab_schema: :gitlab_ci + + def up + ensure_batched_background_migration_is_finished( + job_class_name: 'ResetDuplicateCiRunnersTokenEncryptedValues', + table_name: :ci_runners, + column_name: :id, + job_arguments: [], + finalize: true + ) + end + + def down + # no-op + end +end diff --git a/db/post_migrate/20221021082312_add_unique_index_on_ci_runners_token_encrypted.rb b/db/post_migrate/20221021082314_add_unique_index_on_ci_runners_token_encrypted.rb index 8728c0ff20e..12fc6a72e84 100644 --- a/db/post_migrate/20221021082312_add_unique_index_on_ci_runners_token_encrypted.rb +++ b/db/post_migrate/20221021082314_add_unique_index_on_ci_runners_token_encrypted.rb @@ -6,12 +6,10 @@ class AddUniqueIndexOnCiRunnersTokenEncrypted < Gitlab::Database::Migration[2.0] INDEX_NAME = 'index_uniq_ci_runners_on_token_encrypted' def up - finalize_background_migration 'ResetDuplicateCiRunnersTokenEncryptedValues' - add_concurrent_index :ci_runners, - :token_encrypted, - name: INDEX_NAME, - unique: true + :token_encrypted, + name: INDEX_NAME, + unique: true end def down diff --git a/db/post_migrate/20230510062503_queue_cleanup_personal_access_tokens_with_nil_expires_at.rb b/db/post_migrate/20230510062503_queue_cleanup_personal_access_tokens_with_nil_expires_at.rb new file mode 100644 index 00000000000..a7dd1bda9db --- /dev/null +++ b/db/post_migrate/20230510062503_queue_cleanup_personal_access_tokens_with_nil_expires_at.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +class QueueCleanupPersonalAccessTokensWithNilExpiresAt < Gitlab::Database::Migration[2.1] + MIGRATION = "CleanupPersonalAccessTokensWithNilExpiresAt" + DELAY_INTERVAL = 2.minutes + BATCH_SIZE = 50_000 + + restrict_gitlab_migration gitlab_schema: :gitlab_main + + def up + queue_batched_background_migration( + MIGRATION, + :personal_access_tokens, + :id, + job_interval: DELAY_INTERVAL, + batch_size: BATCH_SIZE + ) + end + + def down + delete_batched_background_migration(MIGRATION, :personal_access_tokens, :id, []) + end +end diff --git a/db/schema_migrations/20221021082255 b/db/schema_migrations/20221021082255 deleted file mode 100644 index afb266271d4..00000000000 --- a/db/schema_migrations/20221021082255 +++ /dev/null @@ -1 +0,0 @@ -10caa548bccc134775ed14f85eae2b2063e83afe4a932982c353ecf1549a557d
\ No newline at end of file diff --git a/db/schema_migrations/20221021082256 b/db/schema_migrations/20221021082256 new file mode 100644 index 00000000000..0074889957f --- /dev/null +++ b/db/schema_migrations/20221021082256 @@ -0,0 +1 @@ +48c2eca5f5feea194eadc9d259f83e54fecbc7be1d219647d0b09ce4e5410eb4
\ No newline at end of file diff --git a/db/schema_migrations/20221021082257 b/db/schema_migrations/20221021082257 new file mode 100644 index 00000000000..43ca84c0427 --- /dev/null +++ b/db/schema_migrations/20221021082257 @@ -0,0 +1 @@ +1f3bf844501eee018b9594b447e55fac6c4628a22a9070cd95f37398067b03d6
\ No newline at end of file diff --git a/db/schema_migrations/20221021082312 b/db/schema_migrations/20221021082312 deleted file mode 100644 index 26007002f54..00000000000 --- a/db/schema_migrations/20221021082312 +++ /dev/null @@ -1 +0,0 @@ -86d979a179c504508fd2e9c1a62e935884297054b13b78a4c1460679d75f5b96
\ No newline at end of file diff --git a/db/schema_migrations/20221021082313 b/db/schema_migrations/20221021082313 new file mode 100644 index 00000000000..5c70993f0b4 --- /dev/null +++ b/db/schema_migrations/20221021082313 @@ -0,0 +1 @@ +9383e4f5ec51cf2971c98b4575546099c551b2a9f328f081c57866dc91838896
\ No newline at end of file diff --git a/db/schema_migrations/20221021082314 b/db/schema_migrations/20221021082314 new file mode 100644 index 00000000000..c35ab7664ef --- /dev/null +++ b/db/schema_migrations/20221021082314 @@ -0,0 +1 @@ +2711e477b81213c7221001a9c75dde169a5b8f2cc2a05534dcdae16ace9231a9
\ No newline at end of file diff --git a/db/schema_migrations/20230510062503 b/db/schema_migrations/20230510062503 new file mode 100644 index 00000000000..f6be2a73392 --- /dev/null +++ b/db/schema_migrations/20230510062503 @@ -0,0 +1 @@ +2bd476bf0389b70aa5736ff69023993d37d54c4d333e3a91de9e57370935d6ec
\ No newline at end of file diff --git a/lib/gitlab/background_migration/cleanup_personal_access_tokens_with_nil_expires_at.rb b/lib/gitlab/background_migration/cleanup_personal_access_tokens_with_nil_expires_at.rb new file mode 100644 index 00000000000..e8ee2a4c251 --- /dev/null +++ b/lib/gitlab/background_migration/cleanup_personal_access_tokens_with_nil_expires_at.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module Gitlab + module BackgroundMigration + # Clean up personal access tokens with expires_at value is nil + # and set the value to new default 365 days + class CleanupPersonalAccessTokensWithNilExpiresAt < BatchedMigrationJob + feature_category :system_access + + EXPIRES_AT_DEFAULT = 365.days.from_now + + scope_to ->(relation) { relation.where(expires_at: nil) } + operation_name :update_all + + def perform + each_sub_batch do |sub_batch| + sub_batch.update_all(expires_at: EXPIRES_AT_DEFAULT) + end + end + end + end +end diff --git a/lib/gitlab/ci/ansi2json.rb b/lib/gitlab/ci/ansi2json.rb index 70b68c7b821..79114d35916 100644 --- a/lib/gitlab/ci/ansi2json.rb +++ b/lib/gitlab/ci/ansi2json.rb @@ -4,8 +4,8 @@ module Gitlab module Ci module Ansi2json - def self.convert(ansi, state = nil, verify_state: false) - Converter.new.convert(ansi, state, verify_state: verify_state) + def self.convert(ansi, state = nil) + Converter.new.convert(ansi, state) end end end diff --git a/lib/gitlab/ci/ansi2json/converter.rb b/lib/gitlab/ci/ansi2json/converter.rb index 84541208a2f..78f6c5bf0aa 100644 --- a/lib/gitlab/ci/ansi2json/converter.rb +++ b/lib/gitlab/ci/ansi2json/converter.rb @@ -4,13 +4,9 @@ module Gitlab module Ci module Ansi2json class Converter - def convert(stream, new_state, verify_state: false) + def convert(stream, new_state) @lines = [] - @state = if verify_state - SignedState.new(new_state, stream.size) - else - State.new(new_state, stream.size) - end + @state = State.new(new_state, stream.size) append = false truncated = false diff --git a/lib/gitlab/ci/ansi2json/signed_state.rb b/lib/gitlab/ci/ansi2json/signed_state.rb deleted file mode 100644 index 98e2419f0a8..00000000000 --- a/lib/gitlab/ci/ansi2json/signed_state.rb +++ /dev/null @@ -1,74 +0,0 @@ -# frozen_string_literal: true - -require 'openssl' - -module Gitlab - module Ci - module Ansi2json - class SignedState < ::Gitlab::Ci::Ansi2json::State - include Gitlab::Utils::StrongMemoize - - SIGNATURE_KEY_SALT = 'gitlab-ci-ansi2json-state' - SEPARATOR = '--' - - def encode - encoded = super - - encoded + SEPARATOR + sign(encoded) - end - - private - - def sign(message) - ::OpenSSL::HMAC.hexdigest( - signature_digest, - signature_key, - message - ) - end - - def verify(signed_message) - signature_length = signature_digest.digest_length * 2 # a byte is exactly two hexadecimals - message_length = signed_message.length - SEPARATOR.length - signature_length - return if message_length <= 0 - - signature = signed_message.last(signature_length) - message = signed_message.first(message_length) - return unless valid_signature?(message, signature) - - message - end - - def valid_signature?(message, signature) - expected_signature = sign(message) - expected_signature.bytesize == signature.bytesize && - ::OpenSSL.fixed_length_secure_compare(signature, expected_signature) - end - - def decode_state(data) - return if data.blank? - - encoded_state = verify(data) - if encoded_state.blank? - ::Gitlab::AppLogger.warn(message: "#{self.class}: signature missing or invalid", invalid_state: data) - return - end - - decoded_state = Base64.urlsafe_decode64(encoded_state) - return unless decoded_state.present? - - ::Gitlab::Json.parse(decoded_state) - end - - def signature_digest - ::OpenSSL::Digest.new('SHA256') - end - - def signature_key - ::Gitlab::Application.key_generator.generate_key(SIGNATURE_KEY_SALT, signature_digest.block_length) - end - strong_memoize_attr :signature_key - end - end - end -end diff --git a/lib/gitlab/ci/ansi2json/state.rb b/lib/gitlab/ci/ansi2json/state.rb index 279e1929b22..3aec1cde1bc 100644 --- a/lib/gitlab/ci/ansi2json/state.rb +++ b/lib/gitlab/ci/ansi2json/state.rb @@ -1,11 +1,18 @@ # frozen_string_literal: true +require 'openssl' + # In this class we keep track of the state changes that the # Converter makes as it scans through the log stream. module Gitlab module Ci module Ansi2json class State + include Gitlab::Utils::StrongMemoize + + SIGNATURE_KEY_SALT = 'gitlab-ci-ansi2json-state' + SEPARATOR = '--' + attr_accessor :offset, :current_line, :inherited_style, :open_sections, :last_line_offset def initialize(new_state, stream_size) @@ -24,7 +31,9 @@ module Gitlab open_sections: @open_sections }.to_json - Base64.urlsafe_encode64(json, padding: false) + encoded = Base64.urlsafe_encode64(json, padding: false) + + encoded + SEPARATOR + sign(encoded) end def open_section(section, timestamp, options) @@ -86,27 +95,55 @@ module Gitlab end end - def decode_state(state) - return unless state.present? + def decode_state(data) + return if data.blank? + + encoded_state = verify(data) + if encoded_state.blank? + ::Gitlab::AppLogger.warn(message: "#{self.class}: signature missing or invalid", invalid_state: data) + return + end - decoded_state = Base64.urlsafe_decode64(state) + decoded_state = Base64.urlsafe_decode64(encoded_state) return unless decoded_state.present? ::Gitlab::Json.parse(decoded_state) - rescue ArgumentError, JSON::ParserError => error - # This rescue is so that we don't break during the rollout or rollback - # of `sign_and_verify_ansi2json_state`, because we may receive a - # signed state even when the flag is disabled, and this would result - # in invalid Base64 (ArgumentError) or invalid JSON in case the signed - # state happens to decode as valid Base64 (JSON::ParserError). - # - # Once the flag has been fully rolled out this should not - # be possible (it would imply a backend bug) and we not rescue from - # this. - ::Gitlab::AppLogger.warn(message: "#{self.class}: decode error", invalid_state: state, error: error) - - nil end + + def sign(message) + ::OpenSSL::HMAC.hexdigest( + signature_digest, + signature_key, + message + ) + end + + def verify(signed_message) + signature_length = signature_digest.digest_length * 2 # a byte is exactly two hexadecimals + message_length = signed_message.length - SEPARATOR.length - signature_length + return if message_length <= 0 + + signature = signed_message.last(signature_length) + message = signed_message.first(message_length) + return unless valid_signature?(message, signature) + + message + end + + def valid_signature?(message, signature) + expected_signature = sign(message) + expected_signature.bytesize == signature.bytesize && + ::OpenSSL.fixed_length_secure_compare(signature, expected_signature) + end + + def signature_digest + ::OpenSSL::Digest.new('SHA256') + end + + def signature_key + ::Gitlab::Application.key_generator.generate_key(SIGNATURE_KEY_SALT, signature_digest.block_length) + end + strong_memoize_attr :signature_key end end end diff --git a/lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job.rb b/lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job.rb index bb87104630c..3ed9c1743ed 100644 --- a/lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job.rb +++ b/lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job.rb @@ -147,7 +147,10 @@ module Gitlab end local cookie = cmsgpack.unpack(cookie_msgpack) cookie.deduplicated = "1" - redis.call("set", KEYS[1], cmsgpack.pack(cookie), "ex", redis.call("ttl", KEYS[1])) + local ttl = redis.call("ttl", KEYS[1]) + if ttl > 0 then + redis.call("set", KEYS[1], cmsgpack.pack(cookie), "ex", ttl) + end LUA def should_reschedule? diff --git a/spec/frontend/error_tracking/components/error_details_info_spec.js b/spec/frontend/error_tracking/components/error_details_info_spec.js index 84b926e49fa..4a741a4c31e 100644 --- a/spec/frontend/error_tracking/components/error_details_info_spec.js +++ b/spec/frontend/error_tracking/components/error_details_info_spec.js @@ -3,7 +3,7 @@ import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; import ErrorDetailsInfo from '~/error_tracking/components/error_details_info.vue'; -import { trackClickErrorLinkToSentryOptions } from '~/error_tracking/utils'; +import { trackClickErrorLinkToSentryOptions } from '~/error_tracking/events_tracking'; import Tracking from '~/tracking'; jest.mock('~/tracking'); diff --git a/spec/frontend/error_tracking/components/error_details_spec.js b/spec/frontend/error_tracking/components/error_details_spec.js index 4e73db13a17..8700301ef73 100644 --- a/spec/frontend/error_tracking/components/error_details_spec.js +++ b/spec/frontend/error_tracking/components/error_details_spec.js @@ -17,7 +17,8 @@ import ErrorDetailsInfo from '~/error_tracking/components/error_details_info.vue import { trackErrorDetailsViewsOptions, trackErrorStatusUpdateOptions, -} from '~/error_tracking/utils'; + trackCreateIssueFromError, +} from '~/error_tracking/events_tracking'; import { createAlert, VARIANT_WARNING } from '~/alert'; import { __ } from '~/locale'; import Tracking from '~/tracking'; @@ -492,17 +493,21 @@ describe('ErrorDetails', () => { }); it('should track IGNORE status update', async () => { - Tracking.event.mockClear(); await findUpdateIgnoreStatusButton().trigger('click'); const { category, action } = trackErrorStatusUpdateOptions('ignored'); expect(Tracking.event).toHaveBeenCalledWith(category, action); }); it('should track RESOLVE status update', async () => { - Tracking.event.mockClear(); await findUpdateResolveStatusButton().trigger('click'); const { category, action } = trackErrorStatusUpdateOptions('resolved'); expect(Tracking.event).toHaveBeenCalledWith(category, action); }); + + it('should track create issue button click', async () => { + await wrapper.find('[data-qa-selector="create_issue_button"]').vm.$emit('click'); + const { category, action } = trackCreateIssueFromError; + expect(Tracking.event).toHaveBeenCalledWith(category, action); + }); }); }); diff --git a/spec/frontend/error_tracking/components/error_tracking_list_spec.js b/spec/frontend/error_tracking/components/error_tracking_list_spec.js index c9a2551d11c..6d4e92cf91f 100644 --- a/spec/frontend/error_tracking/components/error_tracking_list_spec.js +++ b/spec/frontend/error_tracking/components/error_tracking_list_spec.js @@ -5,7 +5,12 @@ import Vuex from 'vuex'; import stubChildren from 'helpers/stub_children'; import ErrorTrackingActions from '~/error_tracking/components/error_tracking_actions.vue'; import ErrorTrackingList from '~/error_tracking/components/error_tracking_list.vue'; -import { trackErrorListViewsOptions, trackErrorStatusUpdateOptions } from '~/error_tracking/utils'; +import { + trackErrorListViewsOptions, + trackErrorStatusUpdateOptions, + trackErrorStatusFilterOptions, + trackErrorSortedByField, +} from '~/error_tracking/events_tracking'; import Tracking from '~/tracking'; import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import errorsList from './list_mock.json'; @@ -524,6 +529,8 @@ describe('ErrorTrackingList', () => { stubs: { GlTable: false, GlLink: false, + GlDropdown: false, + GlDropdownItem: false, }, }); }); @@ -534,7 +541,6 @@ describe('ErrorTrackingList', () => { }); it('should track status updates', async () => { - Tracking.event.mockClear(); const status = 'ignored'; findErrorActions().vm.$emit('update-issue-status', { errorId: 1, @@ -546,5 +552,19 @@ describe('ErrorTrackingList', () => { const { category, action } = trackErrorStatusUpdateOptions(status); expect(Tracking.event).toHaveBeenCalledWith(category, action); }); + + it('should track error filter', () => { + const findStatusFilter = () => findStatusFilterDropdown().find('.dropdown-item'); + findStatusFilter().trigger('click'); + const { category, action } = trackErrorStatusFilterOptions('unresolved'); + expect(Tracking.event).toHaveBeenCalledWith(category, action); + }); + + it('should track error sorting', () => { + const findSortItem = () => findSortDropdown().find('.dropdown-item'); + findSortItem().trigger('click'); + const { category, action } = trackErrorSortedByField('last_seen'); + expect(Tracking.event).toHaveBeenCalledWith(category, action); + }); }); }); diff --git a/spec/frontend/error_tracking/utils_spec.js b/spec/frontend/error_tracking/events_tracking_spec.js index a0d6f7f009d..10479d863cf 100644 --- a/spec/frontend/error_tracking/utils_spec.js +++ b/spec/frontend/error_tracking/events_tracking_spec.js @@ -1,4 +1,4 @@ -import * as errorTrackingUtils from '~/error_tracking/utils'; +import * as errorTrackingUtils from '~/error_tracking/events_tracking'; const externalUrl = 'https://sentry.io/organizations/test-sentry-nk/issues/1/?project=1'; diff --git a/spec/frontend/import/details/mock_data.js b/spec/frontend/import/details/mock_data.js index a8e0e53ed2b..67148173404 100644 --- a/spec/frontend/import/details/mock_data.js +++ b/spec/frontend/import/details/mock_data.js @@ -2,7 +2,7 @@ export const mockImportFailures = [ { type: 'pull_request', title: 'Add one cool feature', - url: 'https://github.com/USER/REPO/pull/2', + provider_url: 'https://github.com/USER/REPO/pull/2', details: { exception_class: 'ActiveRecord::RecordInvalid', exception_message: 'Record invalid', @@ -17,7 +17,7 @@ export const mockImportFailures = [ { type: 'pull_request', title: 'Add another awesome feature', - url: 'https://github.com/USER/REPO/pull/3', + provider_url: 'https://github.com/USER/REPO/pull/3', details: { exception_class: 'ActiveRecord::RecordInvalid', exception_message: 'Record invalid', @@ -32,7 +32,7 @@ export const mockImportFailures = [ { type: 'lfs_object', title: '3a9257fae9e86faee27d7208cb55e086f18e6f29f48c430bfbc26d42eb', - url: null, + provider_url: null, details: { exception_class: 'NameError', exception_message: 'some message', diff --git a/spec/lib/gitlab/background_migration/cleanup_personal_access_tokens_with_nil_expires_at_spec.rb b/spec/lib/gitlab/background_migration/cleanup_personal_access_tokens_with_nil_expires_at_spec.rb new file mode 100644 index 00000000000..ade16c0a780 --- /dev/null +++ b/spec/lib/gitlab/background_migration/cleanup_personal_access_tokens_with_nil_expires_at_spec.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::BackgroundMigration::CleanupPersonalAccessTokensWithNilExpiresAt, schema: 20230510062503, feature_category: :system_access do # rubocop:disable Layout/LineLength + let(:personal_access_tokens_table) { table(:personal_access_tokens) } + let(:users_table) { table(:users) } + let(:expires_at_default) { described_class::EXPIRES_AT_DEFAULT } + + subject(:perform_migration) do + described_class.new( + start_id: 1, + end_id: 30, + batch_table: :personal_access_tokens, + batch_column: :id, + sub_batch_size: 3, + pause_ms: 0, + connection: ActiveRecord::Base.connection + ).perform + end + + before do + user = users_table.create!(name: 'PAT_USER', email: 'pat_user@gmail.com', username: "pat_user1", projects_limit: 0) + personal_access_tokens_table.create!(user_id: user.id, name: "PAT#1", expires_at: expires_at_default + 1.day) + personal_access_tokens_table.create!(user_id: user.id, name: "PAT#2", expires_at: nil) + personal_access_tokens_table.create!(user_id: user.id, name: "PAT#3", expires_at: Time.zone.now + 2.days) + end + + it 'adds expiry to personal access tokens', :aggregate_failures do + freeze_time do + expect(ActiveRecord::QueryRecorder.new { perform_migration }.count).to eq(3) + + expect(personal_access_tokens_table.find_by_name("PAT#1").expires_at).to eq(expires_at_default.to_date + 1.day) + expect(personal_access_tokens_table.find_by_name("PAT#2").expires_at).to eq(expires_at_default.to_date) + expect(personal_access_tokens_table.find_by_name("PAT#3").expires_at).to eq(Time.zone.now.to_date + 2.days) + end + end +end diff --git a/spec/lib/gitlab/ci/ansi2json/signed_state_spec.rb b/spec/lib/gitlab/ci/ansi2json/signed_state_spec.rb deleted file mode 100644 index 671efdf5256..00000000000 --- a/spec/lib/gitlab/ci/ansi2json/signed_state_spec.rb +++ /dev/null @@ -1,67 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Gitlab::Ci::Ansi2json::SignedState, feature_category: :continuous_integration do - def build_state(state_class) - state_class.new('', 1000).tap do |state| - state.offset = 1 - state.new_line!(style: { fg: 'some-fg', bg: 'some-bg', mask: 1234 }) - state.set_last_line_offset - state.open_section('hello', 111, {}) - end - end - - let(:state) { build_state(described_class) } - - describe '#initialize' do - it 'restores valid prior state', :aggregate_failures do - new_state = described_class.new(state.encode, 1000) - - expect(new_state.offset).to eq(1) - expect(new_state.inherited_style).to eq({ - bg: 'some-bg', - fg: 'some-fg', - mask: 1234 - }) - expect(new_state.open_sections).to eq({ 'hello' => 111 }) - end - - it 'ignores unsigned prior state', :aggregate_failures do - unsigned = build_state(Gitlab::Ci::Ansi2json::State).encode - expect(::Gitlab::AppLogger).to( - receive(:warn).with( - message: a_string_matching(/signature missing or invalid/), - invalid_state: unsigned - ) - ) - - new_state = described_class.new(unsigned, 0) - - expect(new_state.offset).to eq(0) - expect(new_state.inherited_style).to eq({}) - expect(new_state.open_sections).to eq({}) - end - - it 'ignores bad input', :aggregate_failures do - expect(::Gitlab::AppLogger).to( - receive(:warn).with( - message: a_string_matching(/signature missing or invalid/), - invalid_state: 'abcd' - ) - ) - - new_state = described_class.new('abcd', 0) - - expect(new_state.offset).to eq(0) - expect(new_state.inherited_style).to eq({}) - expect(new_state.open_sections).to eq({}) - end - end - - describe '#encode' do - it 'deterministically signs the state' do - expect(state.encode).to eq state.encode - end - end -end diff --git a/spec/lib/gitlab/ci/ansi2json/state_spec.rb b/spec/lib/gitlab/ci/ansi2json/state_spec.rb index 9b14231f1be..8dd4092f3d8 100644 --- a/spec/lib/gitlab/ci/ansi2json/state_spec.rb +++ b/spec/lib/gitlab/ci/ansi2json/state_spec.rb @@ -3,8 +3,8 @@ require 'spec_helper' RSpec.describe Gitlab::Ci::Ansi2json::State, feature_category: :continuous_integration do - def build_state(state_class) - state_class.new('', 1000).tap do |state| + def build_state + described_class.new('', 1000).tap do |state| state.offset = 1 state.new_line!(style: { fg: 'some-fg', bg: 'some-bg', mask: 1234 }) state.set_last_line_offset @@ -12,7 +12,7 @@ RSpec.describe Gitlab::Ci::Ansi2json::State, feature_category: :continuous_integ end end - let(:state) { build_state(described_class) } + let(:state) { build_state } describe '#initialize' do it 'restores valid prior state', :aggregate_failures do @@ -27,57 +27,42 @@ RSpec.describe Gitlab::Ci::Ansi2json::State, feature_category: :continuous_integ expect(new_state.open_sections).to eq({ 'hello' => 111 }) end - it 'ignores signed state' do - signed_state = Gitlab::Ci::Ansi2json::SignedState.new('', 1000) - signed_state.offset = 1 - signed_state.new_line!(style: { fg: 'some-fg', bg: 'some-bg', mask: 1234 }) - signed_state.set_last_line_offset - signed_state.open_section('hello', 111, {}) + it 'ignores unsigned prior state', :aggregate_failures do + unsigned, _ = build_state.encode.split('--') - encoded = signed_state.encode expect(::Gitlab::AppLogger).to( receive(:warn).with( - message: a_string_matching(/decode error/), - invalid_state: encoded, - error: an_instance_of(JSON::ParserError) + message: a_string_matching(/signature missing or invalid/), + invalid_state: unsigned ) ) - new_state = described_class.new(encoded, 1000) + + new_state = described_class.new(unsigned, 0) + expect(new_state.offset).to eq(0) expect(new_state.inherited_style).to eq({}) expect(new_state.open_sections).to eq({}) end - it 'ignores invalid Base64 and logs a warning', :aggregate_failures do + it 'ignores bad input', :aggregate_failures do expect(::Gitlab::AppLogger).to( receive(:warn).with( - message: a_string_matching(/decode error/), - invalid_state: '.', - error: an_instance_of(ArgumentError) + message: a_string_matching(/signature missing or invalid/), + invalid_state: 'abcd' ) ) - new_state = described_class.new('.', 0) + new_state = described_class.new('abcd', 0) expect(new_state.offset).to eq(0) expect(new_state.inherited_style).to eq({}) expect(new_state.open_sections).to eq({}) end + end - it 'ignores invalid JSON and logs a warning', :aggregate_failures do - encoded = Base64.urlsafe_encode64('.') - expect(::Gitlab::AppLogger).to( - receive(:warn).with( - message: a_string_matching(/decode error/), - invalid_state: encoded, - error: an_instance_of(JSON::ParserError) - ) - ) - - new_state = described_class.new(encoded, 0) - expect(new_state.offset).to eq(0) - expect(new_state.inherited_style).to eq({}) - expect(new_state.open_sections).to eq({}) + describe '#encode' do + it 'deterministically signs the state' do + expect(state.encode).to eq state.encode end end end diff --git a/spec/lib/gitlab/ci/ansi2json_spec.rb b/spec/lib/gitlab/ci/ansi2json_spec.rb index 12eeb8f6cac..98fca40e8ea 100644 --- a/spec/lib/gitlab/ci/ansi2json_spec.rb +++ b/spec/lib/gitlab/ci/ansi2json_spec.rb @@ -6,22 +6,6 @@ RSpec.describe Gitlab::Ci::Ansi2json, feature_category: :continuous_integration subject { described_class } describe 'lines' do - describe 'verify_state' do - it 'uses SignedState when true' do - expect(Gitlab::Ci::Ansi2json::State).not_to receive(:new) - expect(Gitlab::Ci::Ansi2json::SignedState).to receive(:new).and_call_original - - described_class.convert(StringIO.new('data'), verify_state: true) - end - - it 'uses State when false' do - expect(Gitlab::Ci::Ansi2json::State).to receive(:new).and_call_original - expect(Gitlab::Ci::Ansi2json::SignedState).not_to receive(:new) - - described_class.convert(StringIO.new('data'), verify_state: false) - end - end - it 'prints non-ansi as-is' do expect(convert_json('Hello')).to eq([{ offset: 0, content: [{ text: 'Hello' }] }]) end diff --git a/spec/migrations/20230510062502_queue_cleanup_personal_access_tokens_with_nil_expires_at_spec.rb b/spec/migrations/20230510062502_queue_cleanup_personal_access_tokens_with_nil_expires_at_spec.rb new file mode 100644 index 00000000000..45ef85a49cf --- /dev/null +++ b/spec/migrations/20230510062502_queue_cleanup_personal_access_tokens_with_nil_expires_at_spec.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_migration! + +RSpec.describe QueueCleanupPersonalAccessTokensWithNilExpiresAt, feature_category: :system_access do + let!(:batched_migration) { described_class::MIGRATION } + + it 'schedules a new batched migration' do + reversible_migration do |migration| + migration.before -> { + expect(batched_migration).not_to have_scheduled_batched_migration + } + + migration.after -> { + expect(batched_migration).to have_scheduled_batched_migration( + table_name: :personal_access_tokens, + column_name: :id, + interval: described_class::DELAY_INTERVAL, + batch_size: described_class::BATCH_SIZE, + sub_batch_size: described_class::SUB_BATCH_SIZE + ) + } + end + end +end diff --git a/spec/models/ci/build_trace_spec.rb b/spec/models/ci/build_trace_spec.rb index 54b4f02fb91..30e4ef760d7 100644 --- a/spec/models/ci/build_trace_spec.rb +++ b/spec/models/ci/build_trace_spec.rb @@ -25,26 +25,6 @@ RSpec.describe Ci::BuildTrace, feature_category: :continuous_integration do it { is_expected.to delegate_method(:complete?).to(:build).with_prefix } end - describe 'FF sign_and_verify_ansi2json_state' do - before do - stub_feature_flags(sign_and_verify_ansi2json_state: false) - end - - it 'calls convert with verify_state: true when enabled for project' do - build.project = create(:project) - stub_feature_flags(sign_and_verify_ansi2json_state: build.project) - - expect(Gitlab::Ci::Ansi2json).to receive(:convert).with(stream.stream, state, verify_state: true) - - described_class.new(build: build, stream: stream, state: state) - end - - it 'calls convert with verify_state: false when disabled' do - expect(Gitlab::Ci::Ansi2json).to receive(:convert).with(stream.stream, state, verify_state: false) - described_class.new(build: build, stream: stream, state: state) - end - end - it 'returns formatted trace' do expect(subject.lines).to eq( [ |