diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2024-01-12 21:08:59 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2024-01-12 21:08:59 +0300 |
commit | bb5a73d8962c28abeef59ea3d6e90f4b2e370f36 (patch) | |
tree | f83de42bc46951514a3bda5f9b221a1be3acd72b | |
parent | 8b5595e9f1b9e46c97a69bfc0dc109658d5dbea2 (diff) |
Add latest changes from gitlab-org/gitlab@master
19 files changed, 456 insertions, 25 deletions
diff --git a/.rubocop.yml b/.rubocop.yml index 571946be462..d776a79a80d 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -375,6 +375,9 @@ Rails/HasManyOrHasOneDependent: Rails/CreateTableWithTimestamps: Enabled: false +Rails/AvoidTimeComparison: + Enabled: true + # GitLab ################################################################### Gitlab/ModuleWithInstanceVariables: diff --git a/.rubocop_todo/rails/avoid_time_comparison.yml b/.rubocop_todo/rails/avoid_time_comparison.yml new file mode 100644 index 00000000000..e6b6e9fadaf --- /dev/null +++ b/.rubocop_todo/rails/avoid_time_comparison.yml @@ -0,0 +1,13 @@ +--- +Rails/AvoidTimeComparison: + Details: grace period + Exclude: + - 'app/services/packages/mark_package_files_for_destruction_service.rb' + - 'app/workers/container_registry/migration/enqueuer_worker.rb' + - 'app/workers/gitlab/import/advance_stage.rb' + - 'ee/app/services/incident_management/pending_escalations/process_service.rb' + - 'ee/app/workers/update_all_mirrors_worker.rb' + - 'lib/gitlab/chaos.rb' + - 'lib/gitlab/database/background_migration/batched_migration.rb' + - 'spec/lib/gitlab/ci/cron_parser_spec.rb' + - 'spec/support/helpers/wait_helpers.rb' diff --git a/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees_widget.vue b/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees_widget.vue index 4ff12824008..0ac6208c7d3 100644 --- a/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees_widget.vue +++ b/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees_widget.vue @@ -6,6 +6,7 @@ import { TYPE_ALERT, TYPE_ISSUE, TYPE_MERGE_REQUEST } from '~/issues/constants'; import { __, n__ } from '~/locale'; import UserSelect from '~/vue_shared/components/user_select/user_select.vue'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; +import { ISSUE_MR_CHANGE_ASSIGNEE } from '~/behaviors/shortcuts/keybindings'; import { assigneesQueries } from '../../queries/constants'; import SidebarEditableItem from '../sidebar_editable_item.vue'; import SidebarAssigneesRealtime from './assignees_realtime.vue'; @@ -156,6 +157,12 @@ export default { issuableAuthor() { return this.issuable?.author; }, + assigneeShortcutDescription() { + return ISSUE_MR_CHANGE_ASSIGNEE.description; + }, + assigneeShortcutKey() { + return ISSUE_MR_CHANGE_ASSIGNEE.defaultKeys[0]; + }, }, watch: { iid(_, oldIid) { @@ -246,6 +253,9 @@ export default { :loading="isSettingAssignees" :initial-loading="isAssigneesLoading" :title="assigneeText" + :edit-tooltip="`${assigneeShortcutDescription} <kbd class='flat ml-1' aria-hidden=true>${assigneeShortcutKey}</kbd>`" + :edit-aria-label="assigneeShortcutDescription" + :edit-keyshortcuts="assigneeShortcutKey" :is-dirty="isDirty" @open="showDropdown" @close="saveAssignees" diff --git a/app/assets/javascripts/sidebar/components/labels/labels_select_widget/labels_select_root.vue b/app/assets/javascripts/sidebar/components/labels/labels_select_widget/labels_select_root.vue index e0d7400f7a6..686298753e2 100644 --- a/app/assets/javascripts/sidebar/components/labels/labels_select_widget/labels_select_root.vue +++ b/app/assets/javascripts/sidebar/components/labels/labels_select_widget/labels_select_root.vue @@ -7,6 +7,7 @@ import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import { TYPE_EPIC, TYPE_ISSUE, TYPE_MERGE_REQUEST, TYPE_TEST_CASE } from '~/issues/constants'; import { __ } from '~/locale'; +import { ISSUABLE_CHANGE_LABEL } from '~/behaviors/shortcuts/keybindings'; import { issuableLabelsQueries } from '../../../queries/constants'; import SidebarEditableItem from '../../sidebar_editable_item.vue'; import { DEBOUNCE_DROPDOWN_DELAY, VARIANT_SIDEBAR } from './constants'; @@ -159,6 +160,12 @@ export default { isLockOnMergeSupported() { return this.issuableSupportsLockOnMerge || this.issuable?.supportsLockOnMerge; }, + labelShortcutDescription() { + return ISSUABLE_CHANGE_LABEL.description; + }, + labelShortcutKey() { + return ISSUABLE_CHANGE_LABEL.defaultKeys[0]; + }, }, apollo: { issuable: { @@ -375,6 +382,9 @@ export default { <sidebar-editable-item ref="editable" :title="__('Labels')" + :edit-tooltip="`${labelShortcutDescription} <kbd class='flat ml-1' aria-hidden=true>${labelShortcutKey}</kbd>`" + :edit-aria-label="labelShortcutDescription" + :edit-keyshortcuts="labelShortcutKey" :loading="isLoading" :can-edit="allowLabelEdit" @open="oldIid = null" diff --git a/app/assets/javascripts/sidebar/components/sidebar_editable_item.vue b/app/assets/javascripts/sidebar/components/sidebar_editable_item.vue index ad83866ceb2..c887d5d292e 100644 --- a/app/assets/javascripts/sidebar/components/sidebar_editable_item.vue +++ b/app/assets/javascripts/sidebar/components/sidebar_editable_item.vue @@ -1,5 +1,5 @@ <script> -import { GlButton, GlLoadingIcon } from '@gitlab/ui'; +import { GlButton, GlLoadingIcon, GlTooltipDirective } from '@gitlab/ui'; import { __ } from '~/locale'; export default { @@ -7,6 +7,9 @@ export default { unassigned: __('Unassigned'), }, components: { GlButton, GlLoadingIcon }, + directives: { + GlTooltip: GlTooltipDirective, + }, inject: { canUpdate: {}, isClassicSidebar: { @@ -58,6 +61,21 @@ export default { required: false, default: false, }, + editTooltip: { + type: String, + required: false, + default: '', + }, + editAriaLabel: { + type: String, + required: false, + default: '', + }, + editKeyshortcuts: { + type: String, + required: false, + default: '', + }, }, data() { return { @@ -68,6 +86,15 @@ export default { editButtonText() { return this.isDirty ? __('Apply') : __('Edit'); }, + editTooltipText() { + return this.isDirty ? '' : this.editTooltip; + }, + editAriaLabelText() { + return this.isDirty ? this.editButtonText : this.editAriaLabel; + }, + editKeyshortcutsText() { + return this.isDirty ? __('Escape') : this.editKeyshortcuts; + }, }, destroyed() { window.removeEventListener('click', this.collapseWhenOffClick); @@ -150,9 +177,13 @@ export default { <gl-button v-if="canUpdate && !initialLoading && canEdit" :id="buttonId" + v-gl-tooltip.viewport.html category="tertiary" size="small" class="gl-text-gray-900! gl-ml-auto hide-collapsed gl-mr-n2 shortcut-sidebar-dropdown-toggle" + :title="editTooltipText" + :aria-label="editAriaLabelText" + :aria-keyshortcuts="editKeyshortcutsText" data-testid="edit-button" :data-track-action="tracking.event" :data-track-label="tracking.label" diff --git a/app/models/namespace.rb b/app/models/namespace.rb index cc60a64bc9c..238556f0cf0 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -12,6 +12,7 @@ class Namespace < ApplicationRecord include Gitlab::Utils::StrongMemoize include Namespaces::Traversal::Recursive include Namespaces::Traversal::Linear + include Namespaces::Traversal::Cached include EachBatch include BlocksUnsafeSerialization include Ci::NamespaceSettings diff --git a/app/models/namespaces/descendants.rb b/app/models/namespaces/descendants.rb index 99abda2dd6a..8444cea9848 100644 --- a/app/models/namespaces/descendants.rb +++ b/app/models/namespaces/descendants.rb @@ -7,5 +7,24 @@ module Namespaces belongs_to :namespace validates :namespace_id, uniqueness: true + + def self.expire_for(namespace_ids) + # Union: + # - Look up all parent ids including the given ids via traversal_ids + # - Include the given ids to handle the case when the namespaces records are already deleted + sql = <<~SQL + WITH namespace_ids AS MATERIALIZED ( + ( + SELECT ids.id + FROM namespaces, UNNEST(traversal_ids) ids(id) + WHERE namespaces.id IN (?) + ) UNION + (SELECT UNNEST(ARRAY[?]) AS id) + ) + UPDATE namespace_descendants SET outdated_at = ? FROM namespace_ids WHERE namespace_descendants.namespace_id = namespace_ids.id + SQL + + connection.execute(sanitize_sql_array([sql, namespace_ids, namespace_ids, Time.current])) + end end end diff --git a/app/models/namespaces/traversal/cached.rb b/app/models/namespaces/traversal/cached.rb new file mode 100644 index 00000000000..55eaaa4667e --- /dev/null +++ b/app/models/namespaces/traversal/cached.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +module Namespaces + module Traversal + module Cached + extend ActiveSupport::Concern + extend Gitlab::Utils::Override + + included do + after_destroy :invalidate_descendants_cache + end + + private + + override :sync_traversal_ids + def sync_traversal_ids + super + return if is_a?(Namespaces::UserNamespace) + return unless Feature.enabled?(:namespace_descendants_cache_expiration, self, type: :gitlab_com_derisk) + + ids = [id] + ids.concat((saved_changes[:parent_id] - [parent_id]).compact) if saved_changes[:parent_id] + Namespaces::Descendants.expire_for(ids) + end + + def invalidate_descendants_cache + return if is_a?(Namespaces::UserNamespace) + return unless Feature.enabled?(:namespace_descendants_cache_expiration, self, type: :gitlab_com_derisk) + + Namespaces::Descendants.expire_for([parent_id, id].compact) + end + end + end +end diff --git a/config/feature_flags/gitlab_com_derisk/namespace_descendants_cache_expiration.yml b/config/feature_flags/gitlab_com_derisk/namespace_descendants_cache_expiration.yml new file mode 100644 index 00000000000..d374316e271 --- /dev/null +++ b/config/feature_flags/gitlab_com_derisk/namespace_descendants_cache_expiration.yml @@ -0,0 +1,9 @@ +--- +name: namespace_descendants_cache_expiration +feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/433482 +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/141588 +rollout_issue_url: https://gitlab.com/gitlab-com/gl-infra/production/-/issues/17388 +milestone: '16.8' +group: group::optimize +type: gitlab_com_derisk +default_enabled: false diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 22cb7531a87..fd7ff91c6eb 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -1393,6 +1393,31 @@ Input type: `AiActionInput` | <a id="mutationaiactionerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. | | <a id="mutationaiactionrequestid"></a>`requestId` | [`String`](#string) | ID of the request. | +### `Mutation.aiAgentCreate` + +WARNING: +**Introduced** in 16.8. +This feature is an Experiment. It can be changed or removed at any time. + +Input type: `AiAgentCreateInput` + +#### Arguments + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="mutationaiagentcreateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. | +| <a id="mutationaiagentcreatename"></a>`name` | [`String!`](#string) | Name of the agent. | +| <a id="mutationaiagentcreateprojectpath"></a>`projectPath` | [`ID!`](#id) | Project to which the agent belongs. | +| <a id="mutationaiagentcreateprompt"></a>`prompt` | [`String!`](#string) | Prompt for the agent. | + +#### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="mutationaiagentcreateagent"></a>`agent` | [`AiAgent`](#aiagent) | Agent after mutation. | +| <a id="mutationaiagentcreateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. | +| <a id="mutationaiagentcreateerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. | + ### `Mutation.alertSetAssignees` Input type: `AlertSetAssigneesInput` @@ -14412,6 +14437,43 @@ Information about a connected Agent. | <a id="agentmetadatapodnamespace"></a>`podNamespace` | [`String`](#string) | Namespace of the pod running the Agent. | | <a id="agentmetadataversion"></a>`version` | [`String`](#string) | Agent version tag. | +### `AiAgent` + +An AI agent. + +#### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="aiagent_links"></a>`_links` | [`AiAgentLinks!`](#aiagentlinks) | Map of links to perform actions on the agent. | +| <a id="aiagentcreatedat"></a>`createdAt` | [`Time!`](#time) | Date of creation. | +| <a id="aiagentid"></a>`id` | [`ID!`](#id) | ID of the agent. | +| <a id="aiagentname"></a>`name` | [`String!`](#string) | Name of the agent. | +| <a id="aiagentversions"></a>`versions` | [`[AiAgentVersion!]`](#aiagentversion) | Versions of the agent. | + +### `AiAgentLinks` + +Represents links to perform actions on the agent. + +#### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="aiagentlinksshowpath"></a>`showPath` | [`String`](#string) | Path to the details page of the agent. | + +### `AiAgentVersion` + +Version of an AI Agent. + +#### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="aiagentversioncreatedat"></a>`createdAt` | [`Time!`](#time) | Timestamp when the agent version was created. | +| <a id="aiagentversionid"></a>`id` | [`ID!`](#id) | ID of the agent version. | +| <a id="aiagentversionmodel"></a>`model` | [`String!`](#string) | Model of the agent. | +| <a id="aiagentversionprompt"></a>`prompt` | [`String!`](#string) | Prompt of the agent. | + ### `AiMessage` AI features communication message. diff --git a/doc/development/database_review.md b/doc/development/database_review.md index 2d02d7e25de..2bb2a6fc267 100644 --- a/doc/development/database_review.md +++ b/doc/development/database_review.md @@ -153,7 +153,7 @@ Include in the MR description: - Write the raw SQL in the MR description. Preferably formatted nicely with [pgFormatter](https://sqlformat.darold.net) or <https://paste.depesz.com> and using regular quotes - (for example, `"projects"."id"`) and avoiding smart quotes (for example, `"projects"."id"`). + (for example, `"projects"."id"`) and avoiding smart quotes (for example, `“projects”.“id”`). - In case of queries generated dynamically by using parameters, there should be one raw SQL query for each variation. For example, a finder for issues that may take as a parameter an optional filter on projects, diff --git a/lib/quality/seeders/issues.rb b/lib/quality/seeders/issues.rb index 813ff0bf097..de7b275d43e 100644 --- a/lib/quality/seeders/issues.rb +++ b/lib/quality/seeders/issues.rb @@ -45,7 +45,7 @@ module Quality created_at += 1.week - break if created_at > Time.now + break if created_at.future? end created_issues_count diff --git a/locale/gitlab.pot b/locale/gitlab.pot index d39d8a205a1..77fc5dc67e3 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -19891,6 +19891,9 @@ msgstr "" msgid "EscalationStatus|Triggered" msgstr "" +msgid "Escape" +msgstr "" + msgid "Estimate" msgstr "" diff --git a/package.json b/package.json index 7919ce67032..6c5c846fed5 100644 --- a/package.json +++ b/package.json @@ -235,11 +235,11 @@ "@gitlab/stylelint-config": "5.0.1", "@graphql-eslint/eslint-plugin": "3.20.1", "@originjs/vite-plugin-commonjs": "^1.0.3", - "@rollup/plugin-graphql": "^2.0.3", + "@rollup/plugin-graphql": "^2.0.4", "@testing-library/dom": "^7.16.2", "@types/jest": "^28.1.3", "@types/lodash": "^4.14.197", - "@vitejs/plugin-vue2": "^1.1.2", + "@vitejs/plugin-vue2": "^2.3.1", "@vue/compat": "^3.2.47", "@vue/compiler-sfc": "^3.2.47", "@vue/test-utils": "1.3.6", @@ -283,7 +283,7 @@ "stylelint": "^15.10.2", "swagger-cli": "^4.0.4", "timezone-mock": "^1.0.8", - "vite": "^5.0.0", + "vite": "^5.0.11", "vite-plugin-ruby": "^5.0.0", "vue-loader-vue3": "npm:vue-loader@17", "vue-test-utils-compat": "0.0.14", diff --git a/rubocop/cop/rails/avoid_time_comparison.rb b/rubocop/cop/rails/avoid_time_comparison.rb new file mode 100644 index 00000000000..89aa0c2ebd0 --- /dev/null +++ b/rubocop/cop/rails/avoid_time_comparison.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +module RuboCop + module Cop + module Rails + # Checks for time comparison. + # For more information see: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/133520 + # + # @example + # # bad + # datetime > Time.now + # Time.current < datetime + # datetime > Time.zone.now + # + # # good + # datetime.future? + # datetime.future? + # datetime.past? + class AvoidTimeComparison < RuboCop::Cop::Base + MSG = 'Avoid time comparison, use `.past?` or `.future?` instead.' + RESTRICT_ON_SEND = %i[< >].to_set.freeze + + def_node_matcher :comparison?, <<~PATTERN + (send _ %RESTRICT_ON_SEND _) + PATTERN + + def_node_matcher :time_now?, <<~PATTERN + { + (send + (const {nil? cbase} :Time) :now) + (send + (send + (const {nil? cbase} :Time) :zone) :now) + (send + (const {nil? cbase} :Time) :current) + } + PATTERN + + def on_send(node) + return unless comparison?(node) + + arg_check = node.arguments.find { |arg| time_now?(arg) } + receiver_check = time_now?(node.receiver) + + add_offense(node) if arg_check || receiver_check + end + end + end + end +end diff --git a/spec/models/namespaces/descendants_spec.rb b/spec/models/namespaces/descendants_spec.rb index 26d3619ea39..6c153c3307b 100644 --- a/spec/models/namespaces/descendants_spec.rb +++ b/spec/models/namespaces/descendants_spec.rb @@ -40,4 +40,29 @@ RSpec.describe Namespaces::Descendants, feature_category: :database do ) end end + + describe '.expire_for' do + it 'sets the outdated_at column for the given namespace ids' do + freeze_time do + expire_time = Time.current + + group1 = create(:group).tap do |g| + create(:namespace_descendants, namespace: g).reload.update!(outdated_at: nil) + end + group2 = create(:group, parent: group1).tap { |g| create(:namespace_descendants, namespace: g) } + group3 = create(:group, parent: group1) + + group4 = create(:group).tap do |g| + create(:namespace_descendants, namespace: g).reload.update!(outdated_at: nil) + end + + described_class.expire_for([group1.id, group2.id, group3.id]) + + expect(group1.namespace_descendants.outdated_at).to eq(expire_time) + expect(group2.namespace_descendants.outdated_at).to eq(expire_time) + expect(group3.namespace_descendants).to be_nil + expect(group4.namespace_descendants.outdated_at).to be_nil + end + end + end end diff --git a/spec/models/namespaces/traversal/cached_spec.rb b/spec/models/namespaces/traversal/cached_spec.rb new file mode 100644 index 00000000000..8263e28bb98 --- /dev/null +++ b/spec/models/namespaces/traversal/cached_spec.rb @@ -0,0 +1,104 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Namespaces::Traversal::Cached, feature_category: :database do + let_it_be_with_refind(:old_parent) { create(:group) } + let_it_be_with_refind(:new_parent) { create(:group) } + let_it_be_with_refind(:group) { create(:group, parent: old_parent) } + let_it_be_with_refind(:subgroup) { create(:group, parent: group) } + + context 'when the namespace_descendants_cache_expiration feature flag is off' do + let!(:cache) { create(:namespace_descendants, namespace: group) } + + before do + stub_feature_flags(namespace_descendants_cache_expiration: false) + end + + it 'does not invalidate the cache' do + expect { group.update!(parent: new_parent) }.not_to change { cache.reload.outdated_at } + end + + context 'when the group is deleted' do + it 'invalidates the cache' do + expect { group.destroy! }.not_to change { cache.reload.outdated_at } + end + end + end + + context 'when no cached records are present' do + it 'does nothing' do + group.parent = new_parent + + expect { group.save! }.not_to change { Namespaces::Descendants.all.to_a } + end + end + + context 'when the namespace record is UserNamespace' do + it 'does nothing' do + # we won't use the optimization for UserNamespace + namespace = create(:user_namespace) + cache = create(:namespace_descendants, namespace: namespace) + + expect { namespace.destroy! }.not_to change { cache.reload.outdated_at } + end + end + + context 'when cached record is present' do + let!(:cache) { create(:namespace_descendants, namespace: group) } + + it 'invalidates the cache' do + expect { group.update!(parent: new_parent) }.to change { cache.reload.outdated_at }.from(nil) + end + + it 'does not invalidate the cache of subgroups' do + subgroup_cache = create(:namespace_descendants, namespace: subgroup) + + expect { group.update!(parent: new_parent) }.not_to change { subgroup_cache.reload.outdated_at } + end + + context 'when a new subgroup is added' do + it 'invalidates the cache' do + expect { create(:group, parent: group) }.to change { cache.reload.outdated_at } + end + end + + context 'when a new project is added' do + it 'invalidates the cache' do + expect { create(:project, group: group) }.to change { cache.reload.outdated_at } + end + end + end + + context 'when parent group has cached record' do + it 'invalidates the parent cache' do + old_parent_cache = create(:namespace_descendants, namespace: old_parent) + new_parent_cache = create(:namespace_descendants, namespace: new_parent) + + group.update!(parent: new_parent) + + expect(old_parent_cache.reload.outdated_at).not_to be_nil + expect(new_parent_cache.reload.outdated_at).not_to be_nil + end + end + + context 'when group is destroyed' do + it 'invalidates the cache' do + cache = create(:namespace_descendants, namespace: group) + + expect { group.destroy! }.to change { cache.reload.outdated_at }.from(nil) + end + + context 'when parent group has cached record' do + it 'invalidates the parent cache' do + old_parent_cache = create(:namespace_descendants, namespace: old_parent) + new_parent_cache = create(:namespace_descendants, namespace: new_parent) + + group.destroy! + + expect(old_parent_cache.reload.outdated_at).not_to be_nil + expect(new_parent_cache.reload.outdated_at).to be_nil # no change + end + end + end +end diff --git a/spec/rubocop/cop/rails/avoid_time_comparison_spec.rb b/spec/rubocop/cop/rails/avoid_time_comparison_spec.rb new file mode 100644 index 00000000000..8ab430072b1 --- /dev/null +++ b/spec/rubocop/cop/rails/avoid_time_comparison_spec.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +require 'rubocop_spec_helper' +require_relative '../../../../rubocop/cop/rails/avoid_time_comparison' + +RSpec.describe RuboCop::Cop::Rails::AvoidTimeComparison, feature_category: :shared do + shared_examples 'using time comparison' do + let(:violation_string_length) { "datetime > #{time}".length } + + it 'flags violation' do + expect_offense(<<~RUBY) + datetime > #{time} + #{'^' * violation_string_length} Avoid time comparison, use `.past?` or `.future?` instead. + RUBY + + expect_offense(<<~RUBY) + datetime < #{time} + #{'^' * violation_string_length} Avoid time comparison, use `.past?` or `.future?` instead. + RUBY + + expect_offense(<<~RUBY) + #{time} < datetime + #{'^' * violation_string_length} Avoid time comparison, use `.past?` or `.future?` instead. + RUBY + end + end + + context 'when comparing with Time.now', :aggregate_failures do + let(:time) { 'Time.now' } + + it_behaves_like 'using time comparison' + end + + context 'when comparing with ::Time.now', :aggregate_failures do + let(:time) { '::Time.now' } + + it_behaves_like 'using time comparison' + end + + context 'when comparing with Time.zone.now', :aggregate_failures do + let(:time) { 'Time.zone.now' } + + it_behaves_like 'using time comparison' + end + + context 'when comparing with Time.current', :aggregate_failures do + let(:time) { 'Time.current' } + + it_behaves_like 'using time comparison' + end + + it 'does not flag assigning time methods to variables' do + expect_no_offenses(<<~RUBY) + datetime = Time.now + RUBY + end +end diff --git a/yarn.lock b/yarn.lock index 26184387b8a..6bff776fc07 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1943,10 +1943,10 @@ dependencies: type-fest "^2.0.0" -"@rollup/plugin-graphql@^2.0.3": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@rollup/plugin-graphql/-/plugin-graphql-2.0.3.tgz#35fea077e225e2982ce8483dd6c381e8cca03aea" - integrity sha512-IuuELo+0t29adRuLVg8izBFiUXFSFw8BmezespscynRfvfXSOV0S7g8RzQt75VzP6KHHVmNmlAgz+8qlkLur3w== +"@rollup/plugin-graphql@^2.0.4": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@rollup/plugin-graphql/-/plugin-graphql-2.0.4.tgz#240aee16b4be10f3e9f879b6146af689cc10e07c" + integrity sha512-TfaqbbK71VHodCDCoRbPnv2+Tsnlvad2OsGEviURHFl+ZBUyf5wfXgXc9RqZ+xKxSl87Z3YbPhD0z6eWYjuByw== dependencies: "@rollup/pluginutils" "^5.0.1" graphql-tag "^2.12.6" @@ -2894,10 +2894,10 @@ resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== -"@vitejs/plugin-vue2@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue2/-/plugin-vue2-1.1.2.tgz#891f0acc5a6a2b4886a74cb8d6359d42f19f968a" - integrity sha512-y6OEA+2UdJ0xrEQHodq20v9r3SpS62IOHrgN92JPLvVpNkhcissu7yvD5PXMzMESyazj0XNWGsc8UQk8+mVrjQ== +"@vitejs/plugin-vue2@^2.3.1": + version "2.3.1" + resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue2/-/plugin-vue2-2.3.1.tgz#53078d3d9d50d9863f1fbb1c1ef7791a5fcd4948" + integrity sha512-/ksaaz2SRLN11JQhLdEUhDzOn909WEk99q9t9w+N12GjQCljzv7GyvAbD/p20aBUjHkvpGOoQ+FCOkG+mjDF4A== "@vue/apollo-components@^4.0.0-beta.4": version "4.0.0-beta.4" @@ -10132,7 +10132,7 @@ multicast-dns@^7.2.4: dns-packet "^5.2.2" thunky "^1.0.2" -nanoid@^3.3.6: +nanoid@^3.3.6, nanoid@^3.3.7: version "3.3.7" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== @@ -10883,12 +10883,12 @@ postcss@^7.0.14, postcss@^7.0.36, postcss@^7.0.5, postcss@^7.0.6: picocolors "^0.2.1" source-map "^0.6.1" -postcss@^8.1.10, postcss@^8.4.14, postcss@^8.4.25, postcss@^8.4.31: - version "8.4.31" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.31.tgz#92b451050a9f914da6755af352bdc0192508656d" - integrity sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ== +postcss@^8.1.10, postcss@^8.4.14, postcss@^8.4.25, postcss@^8.4.32: + version "8.4.33" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.33.tgz#1378e859c9f69bf6f638b990a0212f43e2aaa742" + integrity sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg== dependencies: - nanoid "^3.3.6" + nanoid "^3.3.7" picocolors "^1.0.0" source-map-js "^1.0.2" @@ -13471,13 +13471,13 @@ vite-plugin-ruby@^5.0.0: debug "^4.3.4" fast-glob "^3.3.2" -vite@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/vite/-/vite-5.0.0.tgz#3bfb65acda2a97127e4fa240156664a1f234ce08" - integrity sha512-ESJVM59mdyGpsiNAeHQOR/0fqNoOyWPYesFto8FFZugfmhdHx8Fzd8sF3Q/xkVhZsyOxHfdM7ieiVAorI9RjFw== +vite@^5.0.11: + version "5.0.11" + resolved "https://registry.yarnpkg.com/vite/-/vite-5.0.11.tgz#31562e41e004cb68e1d51f5d2c641ab313b289e4" + integrity sha512-XBMnDjZcNAw/G1gEiskiM1v6yzM4GE5aMGvhWTlHAYYhxb7S3/V1s3m2LDHa8Vh6yIWYYB0iJwsEaS523c4oYA== dependencies: esbuild "^0.19.3" - postcss "^8.4.31" + postcss "^8.4.32" rollup "^4.2.0" optionalDependencies: fsevents "~2.3.3" |