From 6e3e314c997ad4e97ec1b68ec7a503a7a59cfa2b Mon Sep 17 00:00:00 2001 From: Iain McNulty Date: Sat, 13 Mar 2021 20:13:16 +0000 Subject: [PATCH 1/3] Add default config for github_changelog_generator --- .github_changelog_generator | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .github_changelog_generator diff --git a/.github_changelog_generator b/.github_changelog_generator new file mode 100644 index 0000000..e3f4094 --- /dev/null +++ b/.github_changelog_generator @@ -0,0 +1,3 @@ +project=spage +user=nulty +exclude-labels=duplicate,question,invalid,wontfix,weekly-digest From c90a6f28d5482352a55659ade9ab9a47d0e5cbb4 Mon Sep 17 00:00:00 2001 From: Iain McNulty Date: Sat, 13 Mar 2021 22:52:34 +0000 Subject: [PATCH 2/3] Make scheduled_reminded_at accessible in Incident --- lib/spage/resources/incident.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/spage/resources/incident.rb b/lib/spage/resources/incident.rb index a8b64e2..aba0b84 100644 --- a/lib/spage/resources/incident.rb +++ b/lib/spage/resources/incident.rb @@ -54,7 +54,7 @@ def initialize(attrs) # rubocop: disable Layout/LineLength attr_reader :id, :created_at, :impact, :incident_updates, :monitoring_at, :page_id, :postmortem_body, :postmortem_body_last_updated_at, :postmortem_ignored, :postmortem_notified_subscribers, :postmortem_notified_twitter, :postmortem_published_at, :resolved_at, :shortlink, :updated_at - attr_accessor :name, :status, :impact_override, :scheduled_for, :scheduled_until, :scheduled_remind_prior, :scheduled_auto_in_progress, :scheduled_auto_completed, :metadata, :deliver_notifications, :auto_transition_deliver_notifications_at_end, :auto_transition_deliver_notifications_at_start, :auto_transition_to_maintenance_state, :auto_transition_to_operational_state, :auto_tweet_at_beginning, :auto_tweet_on_completion, :auto_tweet_on_creation, :auto_tweet_one_hour_before, :backfill_date, :backfilled, :body, :components, :component_ids, :scheduled_auto_transition + attr_accessor :name, :status, :impact_override, :scheduled_for, :scheduled_until, :scheduled_remind_prior, :scheduled_reminded_at, :scheduled_auto_in_progress, :scheduled_auto_completed, :metadata, :deliver_notifications, :auto_transition_deliver_notifications_at_end, :auto_transition_deliver_notifications_at_start, :auto_transition_to_maintenance_state, :auto_transition_to_operational_state, :auto_tweet_at_beginning, :auto_tweet_on_completion, :auto_tweet_on_creation, :auto_tweet_one_hour_before, :backfill_date, :backfilled, :body, :components, :component_ids, :scheduled_auto_transition # rubocop: enable Layout/LineLength private From dcb68ae62ad8372b8cc6e35f5d4a5e933b6eb37a Mon Sep 17 00:00:00 2001 From: Iain McNulty Date: Sun, 26 Jun 2022 00:05:08 +0100 Subject: [PATCH 3/3] Add IncidentUpdate --- lib/spage.rb | 3 + lib/spage/api/incident_update.rb | 40 +++++++++++ lib/spage/client.rb | 4 ++ lib/spage/resources/incident.rb | 6 +- lib/spage/resources/incident_update.rb | 57 +++++++++++++++ lib/spage/serializers/incident_update.rb | 53 ++++++++++++++ spec/api/incident_update_spec.rb | 35 +++++++++ .../update_200_incident_update_is_updated.yml | 71 +++++++++++++++++++ 8 files changed, 266 insertions(+), 3 deletions(-) create mode 100644 lib/spage/api/incident_update.rb create mode 100644 lib/spage/resources/incident_update.rb create mode 100644 lib/spage/serializers/incident_update.rb create mode 100644 spec/api/incident_update_spec.rb create mode 100644 spec/fixtures/vcr_cassettes/incident_updates/update_200_incident_update_is_updated.yml diff --git a/lib/spage.rb b/lib/spage.rb index 6fe55ce..127c782 100644 --- a/lib/spage.rb +++ b/lib/spage.rb @@ -16,17 +16,20 @@ class Error < StandardError; end autoload :Api, 'spage/api' autoload :Page, 'spage/resources/page' autoload :Incident, 'spage/resources/incident' + autoload :IncidentUpdate, 'spage/resources/incident_update' autoload :Component, 'spage/resources/component' module Api autoload :Page, 'spage/api/page' autoload :Incident, 'spage/api/incident' + autoload :IncidentUpdate, 'spage/api/incident_update' autoload :Component, 'spage/api/component' end module Serializers autoload :Page, 'spage/serializers/page' autoload :Incident, 'spage/serializers/incident' + autoload :IncidentUpdate, 'spage/serializers/incident_update' autoload :Component, 'spage/serializers/component' end diff --git a/lib/spage/api/incident_update.rb b/lib/spage/api/incident_update.rb new file mode 100644 index 0000000..871610f --- /dev/null +++ b/lib/spage/api/incident_update.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +module Spage + # Api Module + # + module Api + # Incident resources in the statuspage.io API + # + class IncidentUpdate + include Api + + def update(incident_update, id, page_id:) + json = Spage::Serializers::IncidentUpdate.new(incident_update, + update: true).to_json + + response = client.patch( + "pages/#{page_id}/incidents/#{incident_update.incident_id}/incident_updates", id, json + ) + + handle_response(response) do + Spage::IncidentUpdate.new(response.body) + end + end + end + + private + + def handle_response(response) + case response + when Net::HTTPSuccess + yield + when Net::HTTPUnauthorized + raise(Error, 'Unauthorized: wrong API Key') + else + # Net::HTTPBadRequest, Net::HTTPUnprocessableEntity, Net::HTTPForbidden + raise(Error, response.body) + end + end + end +end diff --git a/lib/spage/client.rb b/lib/spage/client.rb index 87f8f77..4652caf 100644 --- a/lib/spage/client.rb +++ b/lib/spage/client.rb @@ -22,6 +22,10 @@ def put(resource, id, body) make_request(Net::HTTP::Put, resource, id, body) end + def patch(resource, id, body) + make_request(Net::HTTP::Patch, resource, id, body) + end + def post(resource, body) make_request(Net::HTTP::Post, resource, nil, body) end diff --git a/lib/spage/resources/incident.rb b/lib/spage/resources/incident.rb index aba0b84..c73ef4b 100644 --- a/lib/spage/resources/incident.rb +++ b/lib/spage/resources/incident.rb @@ -4,13 +4,13 @@ module Spage # Page resource in statuspage.io # class Incident - # rubocop: disable Metrics/MethodLength, Metrics/AbcSize, Layout/LineLength + # rubocop: disable Metrics/MethodLength, Metrics/AbcSize def initialize(attrs) @id = attrs['id'] @components = attrs['components'] # Array of Component @impact = attrs['impact'] @impact_override = attrs['impact_override'] - @incident_updates = attrs['incident_updates'] # Array of IncidentUpdate + @incident_updates = attrs['incident_updates']&.map { |iu| IncidentUpdate.new(iu) } @metadata = attrs['metadata'] # Hash of values @name = attrs['name'] @page_id = attrs['page_id'] @@ -49,7 +49,7 @@ def initialize(attrs) @component_ids = attrs['component_ids'] @scheduled_auto_transition = attrs['scheduled_auto_transition'] end - # rubocop: enable Metrics/MethodLength, Metrics/AbcSize, Layout/LineLength + # rubocop: enable Metrics/MethodLength, Metrics/AbcSize # rubocop: disable Layout/LineLength attr_reader :id, :created_at, :impact, :incident_updates, :monitoring_at, :page_id, :postmortem_body, :postmortem_body_last_updated_at, :postmortem_ignored, :postmortem_notified_subscribers, :postmortem_notified_twitter, :postmortem_published_at, :resolved_at, :shortlink, :updated_at diff --git a/lib/spage/resources/incident_update.rb b/lib/spage/resources/incident_update.rb new file mode 100644 index 0000000..1276888 --- /dev/null +++ b/lib/spage/resources/incident_update.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +module Spage + # Page resource in statuspage.io + # + class IncidentUpdate + # rubocop: disable Metrics/MethodLength, Metrics/AbcSize + def initialize(attrs) + @id = attrs['id'] + @incident_id = attrs['incident_id'] + @affected_components = attrs['affected_components'] + @body = attrs['body'] + @custom_tweet = bool(attrs['custom_tweet']) + @deliver_notifications = attrs['deliver_notifications'] + @display_at = date_parse(attrs['display_at']) + @status = attrs['status'] + @tweet_id = attrs['tweet_id'] + @twitter_updated_at = date_parse(attrs['twitter_updated_at']) + @wants_twitter_update = bool(attrs['wants_twitter_update']) + @created_at = date_parse(attrs['created_at']) + @updated_at = date_parse(attrs['updated_at']) + + # vars only used to update/create incident_update + end + # rubocop: enable Metrics/MethodLength, Metrics/AbcSize + + attr_accessor :incident_id, + :body, + :deliver_notifications, + :display_at, + :wants_twitter_update + attr_reader :id, + :affected_components, + :custom_tweet, + :status, + :tweet_id, + :twitter_updated_at, + :updated_at, + :created_at + + private + + def date_parse(str) + return str if str.nil? + return str if str.is_a?(DateTime) + + DateTime.parse(str) + end + + def bool(val) + case val + when '1' then true + when '0' then false + end + end + end +end diff --git a/lib/spage/serializers/incident_update.rb b/lib/spage/serializers/incident_update.rb new file mode 100644 index 0000000..023ba13 --- /dev/null +++ b/lib/spage/serializers/incident_update.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +module Spage + module Serializers + class IncidentUpdate + def initialize(incident_update, update: false) + @incident_update = incident_update + @ivars = ivars(update) + end + + def to_json(obj = nil) + as_json.to_json(obj) + end + + def as_json + to_hash + end + + def to_hash + { + 'incident_update' => Hash[ + @ivars.map do |name| + [name[1..-1], @incident_update.instance_variable_get(name)] + end + ] + } + end + + private + + def ivars(update) + if update + @incident_update.instance_variables.select do |name| + processed_name = name[1..-1] + update_attrs.include?(processed_name) && + !@incident_update.send(processed_name).nil? + end + else + @incident_update.instance_variables + end + end + + def update_attrs + %w[ + body + deliver_notifications + display_at + wants_twitter_update + ] + end + end + end +end diff --git a/spec/api/incident_update_spec.rb b/spec/api/incident_update_spec.rb new file mode 100644 index 0000000..1606dcc --- /dev/null +++ b/spec/api/incident_update_spec.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Spage::Api::IncidentUpdate do + describe '#update' do + context '200 incident_update is updated' do + it 'updates status from investigating to identified' do + incident_update = Spage::IncidentUpdate.new( + 'id' => 'tkd7x7m9t9wc', + 'incident_id' => 'yqr926fhwmh3', + 'body' => 'Empathize with those', + 'twitter_updated_at' => nil, + 'delivery_notifications' => true, + 'wants_twitter_update' => false, + 'affected_components' => [], + 'display_at' => '2022-06-17T19:30:00+00:00', + 'updated_at' => '2022-06-17T19:30:00+00:00', + 'created_at' => '2022-06-17T19:30:00+00:00', + 'page_id' => 'fmd7kj5n71y9' + ) + + VCR.use_cassette('incident_updates/update_200_incident_update_is_updated') do + updated = Spage::Api::IncidentUpdate.new + .update(incident_update, + 'tkd7x7m9t9wc', + page_id: 'fmd7kj5n71y9') + + expect(updated.id).to eq('tkd7x7m9t9wc') + expect(updated.body).to eq('Empathize with those') + end + end + end + end +end diff --git a/spec/fixtures/vcr_cassettes/incident_updates/update_200_incident_update_is_updated.yml b/spec/fixtures/vcr_cassettes/incident_updates/update_200_incident_update_is_updated.yml new file mode 100644 index 0000000..8c301d7 --- /dev/null +++ b/spec/fixtures/vcr_cassettes/incident_updates/update_200_incident_update_is_updated.yml @@ -0,0 +1,71 @@ +--- +http_interactions: +- request: + method: patch + uri: https://api.statuspage.io/v1/pages/fmd7kj5n71y9/incidents/yqr926fhwmh3/incident_updates/tkd7x7m9t9wc + body: + encoding: UTF-8 + string: '{"incident_update":{"body":"Empathize with those","display_at":"2022-06-17T19:30:00+00:00"}}' + headers: + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + User-Agent: + - Ruby + Host: + - api.statuspage.io + Authorization: + - "" + Content-Type: + - application/json + response: + status: + code: 200 + message: OK + headers: + Date: + - Sat, 25 Jun 2022 22:44:35 GMT + Content-Type: + - application/json + X-Statuspage-Version: + - 66b04009d8d9a40176eb70c8dca337b87db29ac5 + Vary: + - Origin, Accept-Encoding + Etag: + - W/"582d82eab5bd982b1bd9274d46d2b594" + Cache-Control: + - max-age=0, private, must-revalidate + X-Request-Id: + - d39271bc-65b5-4f20-a173-b807f383b867 + X-Runtime: + - '0.127589' + X-Envoy-Upstream-Service-Time: + - '282' + Server: + - globaledge-envoy + Expect-Ct: + - report-uri="https://web-security-reports.services.atlassian.com/expect-ct-report/status-page-web-api", + max-age=86400 + Strict-Transport-Security: + - max-age=63072000; preload + X-Content-Type-Options: + - nosniff + X-Xss-Protection: + - 1; mode=block + Atl-Traceid: + - 47dbd1b06505c38e + Report-To: + - '{"group": "endpoint-1", "max_age": 600, "endpoints": [{"url": "https://dj9s4kmieytgz.cloudfront.net"}], + "include_subdomains": true}' + Nel: + - '{"report_to": "endpoint-1", "max_age": 600, "include_subdomains": true, "failure_fraction": + 0.001}' + Transfer-Encoding: + - chunked + body: + encoding: ASCII-8BIT + string: '{"id":"tkd7x7m9t9wc","incident_id":"yqr926fhwmh3","affected_components":null,"body":"Empathize + with those","created_at":"2022-06-18T19:14:19.578Z","custom_tweet":null,"deliver_notifications":true,"display_at":"2022-06-17T19:30:00.000Z","status":"resolved","tweet_id":null,"twitter_updated_at":null,"updated_at":"2022-06-25T22:44:35.908Z","wants_twitter_update":false}' + recorded_at: Sat, 25 Jun 2022 22:44:36 GMT +recorded_with: VCR 6.0.0