diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 79142ab..dfcfb51 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,6 +1,6 @@ # This configuration was generated by # `rubocop --auto-gen-config` -# on 2024-11-10 15:13:28 UTC using RuboCop version 1.67.0. +# on 2024-11-10 21:20:44 UTC using RuboCop version 1.68.0. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new @@ -19,16 +19,8 @@ Capybara/SpecificActions: - 'spec/integration/subscribe_spec.rb' - 'spec/integration/update_cc_spec.rb' -# Offense count: 1 -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyleAlignWith, Severity. -# SupportedStylesAlignWith: keyword, variable, start_of_line -Layout/EndAlignment: - Exclude: - - 'slack-strava/models/user_activity.rb' - # Offense count: 3 -# Configuration parameters: IgnoreLiteralBranches, IgnoreConstantBranches. +# Configuration parameters: IgnoreLiteralBranches, IgnoreConstantBranches, IgnoreDuplicateElseBranch. Lint/DuplicateBranch: Exclude: - 'slack-strava/models/activity_methods.rb' @@ -46,14 +38,6 @@ Lint/RedundantDirGlobSort: - 'slack-strava.rb' - 'spec/spec_helper.rb' -# Offense count: 2 -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AllowedMethods. -# AllowedMethods: present?, blank?, presence, try, try! -Lint/SafeNavigationConsistency: - Exclude: - - 'slack-strava/models/activity_methods.rb' - # Offense count: 1 # Configuration parameters: ExpectMatchingDefinition, CheckDefinitionPathHierarchy, CheckDefinitionPathHierarchyRoots, Regex, IgnoreExecutableScripts, AllowedAcronyms. # CheckDefinitionPathHierarchyRoots: lib, spec, test, src @@ -98,7 +82,7 @@ Naming/VariableNumber: RSpec/AnyInstance: Enabled: false -# Offense count: 189 +# Offense count: 192 # Configuration parameters: Prefixes, AllowedPatterns. # Prefixes: when, with, without RSpec/ContextWording: @@ -130,7 +114,7 @@ RSpec/EmptyExampleGroup: Exclude: - 'spec/models/athlete_spec.rb' -# Offense count: 161 +# Offense count: 163 # Configuration parameters: CountAsOne. RSpec/ExampleLength: Max: 32 @@ -203,7 +187,7 @@ RSpec/NamedSubject: - 'spec/api/endpoints/teams_endpoint_spec.rb' - 'spec/slack-strava/app_spec.rb' -# Offense count: 99 +# Offense count: 102 # Configuration parameters: AllowedGroups. RSpec/NestedGroups: Max: 7 @@ -356,7 +340,7 @@ Style/StringConcatenation: - 'slack-strava/api/helpers/error_helpers.rb' - 'slack-strava/models/team.rb' -# Offense count: 189 +# Offense count: 190 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, AllowedPatterns. # URISchemes: http, https diff --git a/CHANGELOG.md b/CHANGELOG.md index a38e1ad..647a589 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,8 @@ ### Changelog -* 2025/10/09: Upgraded to Ruby 3.3.5 - [@dblock](https://github.com/dblock). -* 2025/10/09: Fixed incorrect local time - [@dblock](https://github.com/dblock). +* 2024/11/10: Added medal field - [@dblock](https://github.com/dblock). +* 2024/10/09: Upgraded to Ruby 3.3.5 - [@dblock](https://github.com/dblock). +* 2024/10/09: Fixed incorrect local time - [@dblock](https://github.com/dblock). * 2024/10/05: Upgraded to open-weather-ruby-client 0.5.0, using OneCall API 3.0 - [@dblock](https://github.com/dblock). * 2024/09/29: Upgraded to Ruby 3.3.3 - [@dblock](https://github.com/dblock). * 2024/07/28: Fixed syncing activities that are created out of order - [@dblock](https://github.com/dblock). diff --git a/slack-strava/models/activity_fields.rb b/slack-strava/models/activity_fields.rb index b2d9d10..e639a22 100644 --- a/slack-strava/models/activity_fields.rb +++ b/slack-strava/models/activity_fields.rb @@ -23,15 +23,16 @@ class ActivityFields define :DESCRIPTION, 'Description' define :URL, 'Url' define :USER, 'User' + define :MEDAL, 'Medal' define :ATHLETE, 'Athlete' define :DATE, 'Date' HEADER_VALUES = %w[ - Title Description Url User Athlete Date + Title Description Url User Medal Athlete Date ].freeze DEFAULT_VALUES = [ - 'Title', 'Description', 'Url', 'User', 'Athlete', 'Date', + 'Title', 'Description', 'Url', 'User', 'Medal', 'Athlete', 'Date', 'Type', 'Distance', 'Time', 'Moving Time', 'Elapsed Time', 'Pace', 'Speed', 'Elevation', 'Weather' ].freeze diff --git a/slack-strava/models/team_leaderboard.rb b/slack-strava/models/team_leaderboard.rb index 7213804..5503410 100644 --- a/slack-strava/models/team_leaderboard.rb +++ b/slack-strava/models/team_leaderboard.rb @@ -93,6 +93,13 @@ def aggregate! end end + def find(user_id) + position = aggregate!.find_index do |row| + row[:_id][:user_id] == user_id + end + position && position >= 0 ? position + 1 : nil + end + def to_s top = aggregate!.map { |row| next unless row[metric_field] > 0 diff --git a/slack-strava/models/user.rb b/slack-strava/models/user.rb index a26e7ba..be6e4f0 100644 --- a/slack-strava/models/user.rb +++ b/slack-strava/models/user.rb @@ -317,6 +317,17 @@ def team_admin? activated_user? || is_admin? || is_owner? end + def medal_s + case team.leaderboard(metric: 'Distance').find(_id) + when 1 + '🥇' + when 2 + '🥈' + when 3 + '🥉' + end + end + before_destroy :try_to_revoke_access_token private diff --git a/slack-strava/models/user_activity.rb b/slack-strava/models/user_activity.rb index bfb4a73..13fc721 100644 --- a/slack-strava/models/user_activity.rb +++ b/slack-strava/models/user_activity.rb @@ -123,7 +123,9 @@ def to_slack_attachment result_text = [ if display_field?(ActivityFields::USER) || display_field?(ActivityFields::DATE) [ - display_field?(ActivityFields::USER) ? "<@#{user.user_name}>" : nil, + if display_field?(ActivityFields::USER) + ["<@#{user.user_name}>", display_field?(ActivityFields::MEDAL) ? user.medal_s : nil].compact.join(' ') + end, display_field?(ActivityFields::DATE) ? start_date_local_s : nil ].compact.join(' on ') end, @@ -217,6 +219,6 @@ def weather_s ].join(ActivityMethods::UNIT_SEPARATOR), main ].compact.join(' ') - end + end end end diff --git a/spec/models/activity_fields_spec.rb b/spec/models/activity_fields_spec.rb index a1ee248..3c47a40 100644 --- a/spec/models/activity_fields_spec.rb +++ b/spec/models/activity_fields_spec.rb @@ -39,11 +39,11 @@ end it 'raise an error on an invalid value' do - expect { ActivityFields.parse_s('invalid, elapsed time') }.to raise_error SlackStrava::Error, 'Invalid field: invalid, possible values are Default, All, None, Type, Distance, Time, Moving Time, Elapsed Time, Pace, Speed, Elevation, Max Speed, Heart Rate, Max Heart Rate, PR Count, Calories, Weather, Title, Description, Url, User, Athlete and Date.' + expect { ActivityFields.parse_s('invalid, elapsed time') }.to raise_error SlackStrava::Error, 'Invalid field: invalid, possible values are Default, All, None, Type, Distance, Time, Moving Time, Elapsed Time, Pace, Speed, Elevation, Max Speed, Heart Rate, Max Heart Rate, PR Count, Calories, Weather, Title, Description, Url, User, Medal, Athlete and Date.' end it 'raise an error on invalid fields' do - expect { ActivityFields.parse_s('invalid, elapsed time, whatever') }.to raise_error SlackStrava::Error, 'Invalid fields: invalid and whatever, possible values are Default, All, None, Type, Distance, Time, Moving Time, Elapsed Time, Pace, Speed, Elevation, Max Speed, Heart Rate, Max Heart Rate, PR Count, Calories, Weather, Title, Description, Url, User, Athlete and Date.' + expect { ActivityFields.parse_s('invalid, elapsed time, whatever') }.to raise_error SlackStrava::Error, 'Invalid fields: invalid and whatever, possible values are Default, All, None, Type, Distance, Time, Moving Time, Elapsed Time, Pace, Speed, Elevation, Max Speed, Heart Rate, Max Heart Rate, PR Count, Calories, Weather, Title, Description, Url, User, Medal, Athlete and Date.' end end end diff --git a/spec/models/user_activity_spec.rb b/spec/models/user_activity_spec.rb index 582f035..c88ba65 100644 --- a/spec/models/user_activity_spec.rb +++ b/spec/models/user_activity_spec.rb @@ -205,7 +205,7 @@ fallback: "#{activity.name} via #{activity.user.slack_mention} 14.01mi 2h6m26s 9m02s/mi", title: activity.name, title_link: "https://www.strava.com/activities/#{activity.strava_id}", - text: "<@#{activity.user.user_name}> on Tuesday, February 20, 2018 at 10:02 AM\n\nGreat run!", + text: "<@#{activity.user.user_name}> 🥇 on Tuesday, February 20, 2018 at 10:02 AM\n\nGreat run!", image_url: "https://slava.playplay.io/api/maps/#{activity.map.id}.png", fields: [ { title: 'Type', value: 'Run 🏃', short: true }, @@ -237,7 +237,7 @@ fallback: "#{activity.name} via #{activity.user.slack_mention} 14.01mi 2h6m26s 9m02s/mi", title: activity.name, title_link: "https://www.strava.com/activities/#{activity.strava_id}", - text: "<@#{activity.user.user_name}> on Tuesday, February 20, 2018 at 10:02 AM\n\nGreat run!", + text: "<@#{activity.user.user_name}> 🥇 on Tuesday, February 20, 2018 at 10:02 AM\n\nGreat run!", image_url: "https://slava.playplay.io/api/maps/#{activity.map.id}.png", fields: [ { title: 'Type', value: 'Run 🏃', short: true }, @@ -275,7 +275,7 @@ fallback: "#{activity.name} via #{activity.user.slack_mention}", title: activity.name, title_link: "https://www.strava.com/activities/#{activity.strava_id}", - text: "<@#{activity.user.user_name}> on Tuesday, February 20, 2018 at 10:02 AM\n\nGreat run!", + text: "<@#{activity.user.user_name}> 🥇 on Tuesday, February 20, 2018 at 10:02 AM\n\nGreat run!", image_url: "https://slava.playplay.io/api/maps/#{activity.map.id}.png", author_name: user.athlete.name, author_link: user.athlete.strava_url, @@ -309,6 +309,53 @@ end end + context 'with all header fields and medal' do + before do + team.activity_fields = %w[Title Url User Medal Description Date Athlete] + end + + it 'to_slack' do + expect(activity.to_slack).to eq( + attachments: [ + { + fallback: "#{activity.name} via #{activity.user.slack_mention}", + title: activity.name, + title_link: "https://www.strava.com/activities/#{activity.strava_id}", + text: "<@#{activity.user.user_name}> 🥇 on Tuesday, February 20, 2018 at 10:02 AM\n\nGreat run!", + image_url: "https://slava.playplay.io/api/maps/#{activity.map.id}.png", + author_name: user.athlete.name, + author_link: user.athlete.strava_url, + author_icon: user.athlete.profile_medium + } + ] + ) + end + end + + context 'ranked second' do + before do + team.activity_fields = %w[Title Url User Medal Description Date Athlete] + Fabricate(:user_activity, user: Fabricate(:user, team: team), distance: activity.distance + 1) + end + + it 'to_slack' do + expect(activity.to_slack).to eq( + attachments: [ + { + fallback: "#{activity.name} via #{activity.user.slack_mention}", + title: activity.name, + title_link: "https://www.strava.com/activities/#{activity.strava_id}", + text: "<@#{activity.user.user_name}> 🥈 on Tuesday, February 20, 2018 at 10:02 AM\n\nGreat run!", + image_url: "https://slava.playplay.io/api/maps/#{activity.map.id}.png", + author_name: user.athlete.name, + author_link: user.athlete.strava_url, + author_icon: user.athlete.profile_medium + } + ] + ) + end + end + context 'without athlete' do before do team.activity_fields = %w[Title Url User Description Date] @@ -438,7 +485,7 @@ fallback: "#{activity.name} via #{activity.user.slack_mention} 14.01mi 2h6m26s 9m02s/mi", title: activity.name, title_link: "https://www.strava.com/activities/#{activity.strava_id}", - text: "<@#{activity.user.user_name}> on Tuesday, February 20, 2018 at 10:02 AM\n\nGreat run!", + text: "<@#{activity.user.user_name}> 🥇 on Tuesday, February 20, 2018 at 10:02 AM\n\nGreat run!", image_url: "https://slava.playplay.io/api/maps/#{activity.map.id}.png", fields: [ { title: 'Type', value: 'Run 🏃', short: true }, @@ -468,7 +515,7 @@ fallback: "#{activity.name} via #{activity.user.slack_mention} 14.01mi 2h6m26s", title: activity.name, title_link: "https://www.strava.com/activities/#{activity.strava_id}", - text: "<@#{activity.user.user_name}> on Tuesday, February 20, 2018 at 10:02 AM\n\nGreat run!", + text: "<@#{activity.user.user_name}> 🥇 on Tuesday, February 20, 2018 at 10:02 AM\n\nGreat run!", image_url: "https://slava.playplay.io/api/maps/#{activity.map.id}.png", fields: [ { title: 'Type', value: 'Run 🏃', short: true }, @@ -500,7 +547,7 @@ fallback: "#{activity.name} via #{activity.user.slack_mention} 22.54km 2h6m26s 5m37s/km", title: activity.name, title_link: "https://www.strava.com/activities/#{activity.strava_id}", - text: "<@#{activity.user.user_name}> on Tuesday, February 20, 2018 at 10:02 AM\n\nGreat run!", + text: "<@#{activity.user.user_name}> 🥇 on Tuesday, February 20, 2018 at 10:02 AM\n\nGreat run!", image_url: "https://slava.playplay.io/api/maps/#{activity.map.id}.png", fields: [ { title: 'Type', value: 'Run 🏃', short: true }, @@ -533,7 +580,7 @@ fallback: "#{activity.name} via #{activity.user.slack_mention} 14.01mi 22.54km 2h6m26s 9m02s/mi 5m37s/km", title: activity.name, title_link: "https://www.strava.com/activities/#{activity.strava_id}", - text: "<@#{activity.user.user_name}> on Tuesday, February 20, 2018 at 10:02 AM\n\nGreat run!", + text: "<@#{activity.user.user_name}> 🥇 on Tuesday, February 20, 2018 at 10:02 AM\n\nGreat run!", image_url: "https://slava.playplay.io/api/maps/#{activity.map.id}.png", fields: [ { title: 'Type', value: 'Run 🏃', short: true }, @@ -566,7 +613,7 @@ fallback: "#{activity.name} via #{activity.user.slack_mention} 2050yd 37m 1m48s/100yd", title: activity.name, title_link: "https://www.strava.com/activities/#{activity.strava_id}", - text: "<@#{activity.user.user_name}> on Tuesday, February 20, 2018 at 10:02 AM", + text: "<@#{activity.user.user_name}> 🥇 on Tuesday, February 20, 2018 at 10:02 AM", fields: [ { title: 'Type', value: 'Swim 🏊', short: true }, { title: 'Distance', value: '2050yd', short: true }, @@ -595,7 +642,7 @@ fallback: "#{activity.name} via #{activity.user.slack_mention} 1874m 37m 1m58s/100m", title: activity.name, title_link: "https://www.strava.com/activities/#{activity.strava_id}", - text: "<@#{activity.user.user_name}> on Tuesday, February 20, 2018 at 10:02 AM", + text: "<@#{activity.user.user_name}> 🥇 on Tuesday, February 20, 2018 at 10:02 AM", fields: [ { title: 'Type', value: 'Swim 🏊', short: true }, { title: 'Distance', value: '1874m', short: true }, @@ -624,7 +671,7 @@ fallback: "#{activity.name} via #{activity.user.slack_mention} 2050yd 1874m 37m 1m48s/100yd 1m58s/100m", title: activity.name, title_link: "https://www.strava.com/activities/#{activity.strava_id}", - text: "<@#{activity.user.user_name}> on Tuesday, February 20, 2018 at 10:02 AM", + text: "<@#{activity.user.user_name}> 🥇 on Tuesday, February 20, 2018 at 10:02 AM", fields: [ { title: 'Type', value: 'Swim 🏊', short: true }, { title: 'Distance', value: '2050yd 1874m', short: true }, @@ -653,7 +700,7 @@ fallback: "#{activity.name} via #{activity.user.slack_mention} 28.1km 1h10m7s 2m30s/km", title: activity.name, title_link: "https://www.strava.com/activities/#{activity.strava_id}", - text: "<@#{activity.user.user_name}> on Tuesday, February 20, 2018 at 10:02 AM", + text: "<@#{activity.user.user_name}> 🥇 on Tuesday, February 20, 2018 at 10:02 AM", fields: [ { title: 'Type', value: 'Ride 🚴', short: true }, { title: 'Distance', value: '28.1km', short: true }, @@ -683,7 +730,7 @@ fallback: "#{activity.name} via #{activity.user.slack_mention} 17.46mi 28.1km 1h10m7s 4m01s/mi 2m30s/km", title: activity.name, title_link: "https://www.strava.com/activities/#{activity.strava_id}", - text: "<@#{activity.user.user_name}> on Tuesday, February 20, 2018 at 10:02 AM", + text: "<@#{activity.user.user_name}> 🥇 on Tuesday, February 20, 2018 at 10:02 AM", fields: [ { title: 'Type', value: 'Ride 🚴', short: true }, { title: 'Distance', value: '17.46mi 28.1km', short: true }, diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index dce591a..ad13808 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -318,7 +318,7 @@ it 'retrieves last activity details and rebrags it with udpated description' do updated_last_activity = last_activity.to_slack - updated_last_activity[:attachments].first[:text] = "<@#{user.user_name}> on #{last_activity.start_date_local_s}\n\ndetailed description" + updated_last_activity[:attachments].first[:text] = "<@#{user.user_name}> 🥇 on #{last_activity.start_date_local_s}\n\ndetailed description" expect_any_instance_of(User).to receive(:update!).with( updated_last_activity, last_activity.channel_messages @@ -649,5 +649,44 @@ user.destroy end end + + describe '#medal_s' do + let!(:user) { Fabricate(:user) } + + it 'no activities' do + expect(user.medal_s).to be_nil + end + + context 'with an activity' do + let!(:activity) { Fabricate(:user_activity, user: user) } + + context 'ranked first' do + before do + Fabricate(:user_activity, user: Fabricate(:user, team: user.team), distance: activity.distance - 1) + end + + it 'returns a gold medal' do + expect(user.medal_s).to eq '🥇' + end + end + + { + 0 => '🥇', + 1 => '🥈', + 2 => '🥉', + 3 => nil + }.each_pair do |count, medal| + context "ranked #{count + 1}" do + before do + count.times { Fabricate(:user_activity, user: Fabricate(:user, team: user.team), distance: activity.distance + 1) } + end + + it "returns #{medal}" do + expect(user.medal_s).to eq medal + end + end + end + end + end end end diff --git a/spec/slack-strava/commands/set_spec.rb b/spec/slack-strava/commands/set_spec.rb index b3e3290..8ea9ff6 100644 --- a/spec/slack-strava/commands/set_spec.rb +++ b/spec/slack-strava/commands/set_spec.rb @@ -315,7 +315,7 @@ it 'sets to invalid fields' do expect(message: "#{SlackRubyBot.config.user} set fields Time, Foo, Bar").to respond_with_slack_message( - 'Invalid fields: Foo and Bar, possible values are Default, All, None, Type, Distance, Time, Moving Time, Elapsed Time, Pace, Speed, Elevation, Max Speed, Heart Rate, Max Heart Rate, PR Count, Calories, Weather, Title, Description, Url, User, Athlete and Date.' + 'Invalid fields: Foo and Bar, possible values are Default, All, None, Type, Distance, Time, Moving Time, Elapsed Time, Pace, Speed, Elevation, Max Speed, Heart Rate, Max Heart Rate, PR Count, Calories, Weather, Title, Description, Url, User, Medal, Athlete and Date.' ) expect(team.reload.activity_fields).to eq ['Default'] end