This repository was archived by the owner on Oct 18, 2023. It is now read-only.
-
-
Notifications
You must be signed in to change notification settings - Fork 5
Feature/entry validation alerts #101
Merged
Merged
Changes from all commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
31f31fb
Add Slack service
mcelaney 7713486
Add a time entry description validation system and a way to alert use…
mcelaney 8c912cf
Add rake task to send Noko format notifications
mcelaney ba972c4
Refactor TimeEntry messaging to reduce complexity
mcelaney 0ed8215
Clean up rake task
mcelaney f2d2ba8
Add cron to Docker
mcelaney e49c62b
Changes from PR review
mcelaney 4cd40d9
Remove cron job setup
mcelaney 6ea9350
Merge branch 'main' into feature/entry-validation-alerts
etagwerker 720aade
bundle install and update lockfile
etagwerker f0ca5af
Fixed typo
etagwerker 7f17f90
Correct time calculation to work with the time dependent code
etagwerker f1140f2
Remove unused variable from rake task
etagwerker 597a446
Remove comments from spec file
etagwerker File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| class TimeEntry | ||
| attr_reader :grouped_entries | ||
|
|
||
| ## | ||
| # @params [Object] messaging_service Required service ex: SlackService::GroupMemberMessaging | ||
| # @params [Object] rule_handler Required ruleset ex: TimeEntry::DescriptionRules | ||
| # @option [Date] today Required date to retrieve time entries for | ||
| # @option [Integer] hour_of_day Optional hour to run alerts on a 24 hour clock (13 = 1pm) | ||
| def initialize(today, messaging_service, rule_handler, opts) | ||
| @messaging_service = messaging_service | ||
| @rule_handler = rule_handler | ||
| @today = today | ||
| @hour_to_run = opts[:hour_of_day] | ||
| end | ||
| ## | ||
| # Collects time entries for members of a given message service group and sends | ||
| # validation alerts if required | ||
| # | ||
| # @param [String] group_handle The id of a group from a messaging service: ex `ombuteam` on Slack | ||
| def invalid_time_entries_alert(group_handle) | ||
| set_messaging_service(group_handle) | ||
| set_grouped_entries | ||
|
|
||
| @grouped_entries.each { |email, entries| maybe_message(email, entries) } | ||
| :ok | ||
| end | ||
|
|
||
| private | ||
|
|
||
| def maybe_message(email, entries) | ||
| dirty_entries = entries.select { |entry| | ||
| !@rule_handler.new(entry).valid? | ||
| } | ||
|
|
||
| unless dirty_entries.empty? | ||
| @service.send_time_entry_format_warning(email, dirty_entries) | ||
| end | ||
| end | ||
|
|
||
| def set_grouped_entries | ||
| @grouped_entries = Entry | ||
| .for_users_by_email(emails_to_consider) | ||
| .where(date: @today) | ||
| .group_by(&:user_email) | ||
| end | ||
|
|
||
| def set_messaging_service(group_handle) | ||
| @service = @messaging_service.new(group_handle, @hour_to_run) | ||
| end | ||
|
|
||
| def emails_to_consider | ||
| @service.included_emails | ||
| end | ||
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| class TimeEntry::DescriptionRules | ||
| JIRA_REGEX = /(?:\s|^)([A-Z]+-[0-9]+)(?=\s|$)/ | ||
|
|
||
| def initialize(entry, ruleset = :internal_employee) | ||
| @description = entry.description | ||
| @ruleset = ruleset | ||
| end | ||
|
|
||
| def valid? | ||
| return internal_employee if @ruleset == :internal_employee | ||
| false | ||
| end | ||
|
|
||
| private | ||
|
|
||
| def has_word_count(length) | ||
| @description.split.size > length - 1 | ||
| end | ||
|
|
||
| def has_calls_tag | ||
| @description.downcase.include?("#calls") | ||
| end | ||
|
|
||
| def has_url | ||
| @description.downcase.include?("http") | ||
| end | ||
|
|
||
| # Removes square brackets and commas from the string before match as the | ||
| # "official" Jira regex won't match unless the jira id is preceeded by | ||
| # a space | ||
| def has_jira_ticket | ||
| @description.gsub(/[\[\]\,\.]/, ' ').scan(JIRA_REGEX).any? | ||
mcelaney marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| end | ||
|
|
||
| def internal_employee | ||
| min_word_count = 4 | ||
|
|
||
| has_word_count(min_word_count) && (has_calls_tag || has_url || has_jira_ticket) | ||
| end | ||
| end | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,120 @@ | ||
| ## | ||
| # This Service serves as an interface for the Slack Web API | ||
| # | ||
| # @see https://github.com/slack-ruby/slack-ruby-client | ||
|
|
||
| class SlackService | ||
| ## | ||
| # Returns the id of a usergroup | ||
| # | ||
| # @param [String] usergroup_handle The text after the `@` used to reference a | ||
| # group in slack - ex: ombuteam | ||
| # @param [Slack::Web::Client] client | ||
| # @return [[:ok, String]] if success | ||
| # @return [[:error, String]] if failure | ||
| # @see https://api.slack.com/methods/usergroups.list | ||
| def self.find_usergroup_id(usergroup_handle, client) | ||
| response = client.usergroups_list | ||
| return [:error, response["error"]] unless response["ok"] | ||
|
|
||
| usergroup = response.usergroups.find { |group| group["handle"] == usergroup_handle } | ||
| return [:error, "Usergroup handle not found"] if usergroup.nil? | ||
|
|
||
| [:ok, usergroup.id] | ||
| end | ||
|
|
||
| ## | ||
| # Returns a list of all user ids in a given group | ||
| # | ||
| # @param [String] usergroup_id ID of a Slack group | ||
| # @param [Slack::Web::Client] client | ||
| # @return [[:ok, Array<String>]] if success | ||
| # @return [[:error, String]] if failure | ||
| # @see https://api.slack.com/methods/usergroups.users.list | ||
| def self.find_usergroup_user_ids(usergroup_id, client) | ||
| begin | ||
| response = client.usergroups_users_list({usergroup: usergroup_id}) | ||
| [:ok, response["users"]] | ||
| rescue Slack::Web::Api::Errors::NoSuchSubteam | ||
| [:error, "Usergroup Not Found"] | ||
| end | ||
| end | ||
|
|
||
| ## | ||
| # Returns a SlackUser | ||
| # | ||
| # @param [String] user_id ID of a Slack user | ||
| # @param [Slack::Web::Client] client | ||
| # @return [[:ok, SlackService::SlackUser]] if success | ||
| # @return [[:error, String]] if failure | ||
| # @see https://api.slack.com/methods/users.info | ||
| def self.find_slack_user(user_id, client) | ||
| begin | ||
| response = client.users_info({user: user_id}) | ||
|
|
||
| user = SlackUser.new( | ||
| client: client, | ||
| id: response["user"]["id"], | ||
| name: response["user"]["name"], | ||
| first_name: response["user"]["profile"]["first_name"], | ||
| real_name: response["user"]["real_name"], | ||
| tz: response["user"]["tz"], | ||
| email: response["user"]["profile"]["email"] | ||
| ) | ||
|
|
||
| [:ok, user] | ||
| rescue Slack::Web::Api::Errors::UserNotFound | ||
| [:error, "User Not Found"] | ||
| end | ||
| end | ||
|
|
||
| ## | ||
| # Posts a message to a given channel | ||
| # | ||
| # The definition of "Channel" includes users in the case od DMs | ||
| # | ||
| # If `message_parts` is a simple string - it will be added as "Text" | ||
| # | ||
| # If `message_parts` is a Hash - should include `:text` but must include | ||
| # one of `text`, `attachments`, `blocks` - see attached documentation for | ||
| # more information | ||
| # | ||
| # @param [String] channel_id ID of a Slack user | ||
| # @param [Hash<>, String] message_parts | ||
| # @param [Slack::Web::Client] client | ||
| # @return [[:ok, String]] if success | ||
| # @return [[:error, String]] if failure | ||
| # @see https://api.slack.com/methods/chat.postMessage | ||
| # @see https://github.com/slack-ruby/slack-ruby-client/blob/master/lib/slack/web/api/endpoints/chat.rb | ||
| def self.send_message(channel_id, message_parts, client) | ||
| begin | ||
| client.chat_postMessage(cobble_message_params(channel_id, message_parts)) | ||
|
|
||
| [:ok, "Message Sent"] | ||
| rescue Slack::Web::Api::Errors::ChannelNotFound | ||
| [:error, "Slack Channel Not Found"] | ||
| end | ||
| end | ||
|
|
||
| ## | ||
| # Checks to ensure the keys related to a Slack message are correct and | ||
| # returns a hash formatted for `chat_postMessage/1` | ||
| def self.cobble_message_params(channel_id, message) | ||
| return {channel: channel_id, text: message} if message.kind_of?(String) | ||
|
|
||
| validate_message_keys(message) | ||
|
|
||
| message[:channel] = channel_id | ||
| message | ||
| end | ||
|
|
||
| def self.validate_message_keys(message) | ||
| contains_required_key = message.keys.inject(false) { |acc, key| | ||
| acc || [:text, :attachments, :blocks].any?(key) | ||
| } | ||
| raise MessageFormatError.new("Message missing") unless contains_required_key | ||
|
|
||
| extra_message_keys = message.keys - [:text, :attachments, :blocks] | ||
| raise MessageFormatError.new("Message format incorrect") unless extra_message_keys.empty? | ||
| end | ||
| end |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.