1
0
Fork 0

merge in pro-release branch

Dieser Commit ist enthalten in:
Robert Barrow 2021-10-13 14:22:33 +01:00
Commit a13096c8f1
24 geänderte Dateien mit 310 neuen und 130 gelöschten Zeilen

Datei anzeigen

@ -18,4 +18,4 @@ export default Component.extend({
subscribedTitle(subscribed) {
return `admin.wizard.subscription_container.${subscribed ? 'subscribed' : 'not_subscribed'}.title`;
}
})
});

Datei anzeigen

@ -1,10 +1,10 @@
import Component from "@ember/component";
import discourseComputed from "discourse-common/utils/decorators";
import { notEmpty, not } from "@ember/object/computed";
import { not, notEmpty } from "@ember/object/computed";
import I18n from "I18n";
export default Component.extend({
classNameBindings: [':wizard-notice', 'notice.type', 'dismissed', 'expired'],
classNameBindings: [':wizard-notice', 'notice.type', 'dismissed', 'expired', 'resolved'],
showFull: false,
resolved: notEmpty('notice.expired_at'),
dismissed: notEmpty('notice.dismissed_at'),
@ -18,14 +18,16 @@ export default Component.extend({
@discourseComputed('notice.type')
icon(type) {
return {
warning: 'exclamation-circle',
plugin_status_warning: 'exclamation-circle',
plugin_status_connection_error: 'bolt',
subscription_messages_connection_error: 'bolt',
info: 'info-circle'
}[type];
},
actions: {
dismiss() {
this.set('dismissing', true)
this.set('dismissing', true);
this.notice.dismiss().then(() => {
this.set('dismissing', false);
});

Datei anzeigen

@ -0,0 +1,3 @@
{{#if importantNotice}}
{{wizard-notice notice=importantNotice importantOnDashboard=true}}
{{/if}}

Datei anzeigen

@ -0,0 +1,16 @@
import { getOwner } from "discourse-common/lib/get-owner";
export default {
shouldRender(attrs, ctx) {
return ctx.siteSettings.wizard_important_notices_on_dashboard;
},
setupComponent() {
const controller = getOwner(this).lookup('controller:admin-dashboard');
const importantNotice = controller.get('customWizardImportantNotice');
if (importantNotice) {
this.set('importantNotice', importantNotice);
}
}
};

Datei anzeigen

@ -1,3 +0,0 @@
{{#if wizardWarningNotice}}
{{wizard-notice notice=wizardWarningNotice}}
{{/if}}

Datei anzeigen

@ -1,12 +0,0 @@
import { getOwner } from "discourse-common/lib/get-owner";
export default {
setupComponent() {
const controller = getOwner(this).lookup('controller:admin-dashboard')
const wizardWarningNotice = controller.get('wizardWarningNotice');
if (wizardWarningNotice) {
this.set('wizardWarningNotice', wizardWarningNotice);
}
}
}

Datei anzeigen

@ -38,7 +38,7 @@ export default Controller.extend({
unauthorize() {
this.set("unauthorizing", true);
CustomWizardPro.unauthorize()
CustomWizardSubscription.unauthorize()
.then((result) => {
if (result.success) {
this.setProperties({

Datei anzeigen

@ -1,6 +1,5 @@
import DiscourseURL from "discourse/lib/url";
import { withPluginApi } from "discourse/lib/plugin-api";
import { ajax } from "discourse/lib/ajax";
import CustomWizardNotice from "../models/custom-wizard-notice";
import { A } from "@ember/array";
@ -31,12 +30,13 @@ export default {
});
},
setupController(controller, model) {
setupController(controller) {
if (this.notices) {
let warningNotices = this.notices.filter(n => n.type === 'warning');
let pluginStatusConnectionError = this.notices.filter(n => n.type === 'plugin_status_connection_error')[0];
let pluginStatusWarning = this.notices.filter(n => n.type === 'plugin_status_warning')[0];
if (warningNotices.length) {
controller.set('wizardWarningNotice', warningNotices[0]);
if (pluginStatusConnectionError || pluginStatusWarning) {
controller.set('customWizardImportantNotice', pluginStatusConnectionError || pluginStatusWarning);
}
}

Datei anzeigen

@ -10,13 +10,13 @@ CustomWizardNotice.reopen({
if (result.success) {
this.set('dismissed_at', result.dismissed_at);
}
}).catch(popupAjaxError)
}).catch(popupAjaxError);
}
});
CustomWizardNotice.reopenClass({
list() {
return ajax('/admin/wizards/notice').catch(popupAjaxError)
return ajax('/admin/wizards/notice').catch(popupAjaxError);
}
});

Datei anzeigen

@ -1,5 +1,5 @@
<div class=subscription-header>
<h3>{{i18n 'admin.wizard.subscription_container.title'}}</h3>
<div class="subscription-header">
<h3>{{i18n "admin.wizard.subscription_container.title"}}</h3>
<a href="/admin/wizards/subscription" title={{i18n subscribedTitle}}>
{{d-icon subscribedIcon}}

Datei anzeigen

@ -13,11 +13,19 @@
</div>
<div class="notice-created-at notice-badge" title={{notice.created_at}}>
{{d-icon "calendar-alt"}}
{{d-icon "far-clock"}}
<span class="notice-issued">{{i18n "admin.wizard.notice.issued"}}</span>
{{format-date notice.created_at leaveAgo="true"}}
</div>
{{#if notice.updated_at}}
<div class="notice-updated-at notice-badge" title={{notice.updated_at}}>
{{d-icon "calendar-alt"}}
<span class="notice-updated">{{i18n "admin.wizard.notice.updated"}}</span>
{{format-date notice.updated_at leaveAgo="true"}}
</div>
{{/if}}
<div class="notice-plugin notice-badge" title={{i18n "admin.wizard.notice.plugin"}}>
{{d-icon "plug"}}
<span>{{i18n "admin.wizard.notice.plugin"}}</span>
@ -28,6 +36,12 @@
{{{notice.message}}}
</div>
{{#if importantOnDashboard}}
<a href="/admin/site_settings/category/all_results?filter=wizard_important_notices_on_dashboard" class="disable-important">
{{i18n "admin.wizard.notice.disable_important_on_dashboard"}}
</a>
{{/if}}
{{#if canDismiss}}
{{#if dismissing}}
{{loading-spinner size="small"}}

Datei anzeigen

@ -911,13 +911,18 @@
padding: 1em;
margin-bottom: 1em;
border: 1px solid var(--primary);
border-radius: 4px;
position: relative;
&.dismissed {
display: none;
}
&.resolved .notice-badge:not(.notice-expired-at),
&.resolved a,
&.resolved p {
color: var(--primary-medium) !important;
}
.d-icon {
margin-right: .4em;
}
@ -931,7 +936,6 @@
display: inline-flex;
align-items: center;
padding: 0 .5em;
border-radius: 4px;
margin-right: 1em;
font-size: .9em;
line-height: 25px;
@ -957,7 +961,8 @@
}
}
.notice-issued {
.notice-issued,
.notice-resolved {
margin-right: .3em;
}
@ -976,6 +981,13 @@
position: absolute;
top: 1em;
right: 1em;
color: var(--primary);
color: var(--primary-medium);
}
.disable-important {
position: absolute;
right: 3em;
top: 1em;
color: var(--primary-medium);
}
}

Datei anzeigen

@ -485,9 +485,13 @@ en:
notice:
plugin: Custom Wizard Plugin
issued: Issued
update: Updated
resolved: Resolved
title:
warning: Warning Notice
plugin_status_warning: Warning Notice
plugin_status_connection_error: Connection Notice
subscription_messages_connection_error: Connection Notice
disable_important_on_dashboard: disable
wizard_js:
group:

Datei anzeigen

@ -53,16 +53,22 @@ en:
subscription: "%{type} %{property} is subscription only"
notice:
connection_error: "Failed to connect to [%{server}](http://%{server})"
connection_error: "Failed to connect to http://%{domain}"
compatibility_issue: >
The Custom Wizard Plugin may have a compatibility issue with the latest version of Discourse.
Please check the Custom Wizard Plugin status on [%{server}](http://%{server}) before updating Discourse.
plugin_status_connection_error_limit: >
We're unable to connect to the plugin status server to determine whether there are any compatibility issues with the latest version of Discourse.
Please contact <a href="mailto:support@thepavilion.io">support@thepavilion.io</a> for further assistance.
The Custom Wizard Plugin has a compatibility issue with the latest version of Discourse.
Please check the Custom Wizard Plugin status on [%{domain}](http://%{domain}) before updating Discourse.
plugin_status:
connection_error_limit: >
We're unable to connect to the Pavilion Plugin Status Server. Please check the Custom Wizard Plugin status on [%{domain}](http://%{domain}) before updating Discourse or the plugin.
If this connection issue persists please contact <a href="mailto:support@thepavilion.io">support@thepavilion.io</a> for further assistance.
subscription_messages:
connection_error_limit: >
We're unable to connect to the Pavilion Subscription Server. This will not affect the operation of the plugin.
If this connection issue persists please contact <a href="mailto:support@thepavilion.io">support@thepavilion.io</a> for further assistance.
site_settings:
custom_wizard_enabled: "Enable custom wizards."
wizard_redirect_exclude_paths: "Routes excluded from wizard redirects."
wizard_recognised_image_upload_formats: "File types which will result in upload displaying an image preview"
wizard_apis_enabled: "Enable API features (experimental)."
wizard_important_notices_on_dashboard: "Show important notices about the custom wizard plugin on the admin dashboard."

Datei anzeigen

@ -15,3 +15,6 @@ plugins:
refresh: true
type: list
list_type: compact
wizard_important_notices_on_dashboard:
client: true
default: true

Datei anzeigen

@ -4,7 +4,7 @@ class CustomWizard::AdminNoticeController < CustomWizard::AdminController
before_action :find_notice, only: [:dismiss]
def index
render_serialized(CustomWizard::Notice.list, CustomWizard::NoticeSerializer)
render_serialized(CustomWizard::Notice.list(include_recently_expired: true), CustomWizard::NoticeSerializer)
end
def dismiss

Datei anzeigen

@ -1,5 +1,5 @@
{
"result": {
"line": 92.3
"line": 92.45
}
}

Datei anzeigen

@ -3,7 +3,14 @@
class CustomWizard::Notice
include ActiveModel::Serialization
PLUGIN_STATUS_DOMAINS = {
"tests-passed" => "plugins.thepavilion.io",
"stable" => "stable.plugins.thepavilion.io"
}
SUBSCRIPTION_MESSAGES_DOMAIN = "test.thepavilion.io"
LOCALHOST_DOMAIN = "localhost:3000"
PLUGIN_STATUSES_TO_WARN = %w(incompatible tests_failing)
CHECK_PLUGIN_STATUS_ON_BRANCH = %w(tests-passed main stable)
attr_reader :id,
:message,
@ -11,6 +18,7 @@ class CustomWizard::Notice
:created_at
attr_accessor :retrieved_at,
:updated_at,
:dismissed_at,
:expired_at
@ -19,6 +27,7 @@ class CustomWizard::Notice
@message = attrs[:message]
@type = attrs[:type].to_i
@created_at = attrs[:created_at]
@updated_at = attrs[:updated_at]
@retrieved_at = attrs[:retrieved_at]
@dismissed_at = attrs[:dismissed_at]
@expired_at = attrs[:expired_at]
@ -52,7 +61,6 @@ class CustomWizard::Notice
attrs = {
expired_at: expired_at,
created_at: created_at,
expired_at: expired_at,
message: message,
type: type
}
@ -67,14 +75,9 @@ class CustomWizard::Notice
def self.types
@types ||= Enum.new(
info: 0,
warning: 1
)
end
def self.connection_types
@connection_types ||= Enum.new(
plugin_status: 0,
subscription: 1
plugin_status_warning: 1,
plugin_status_connection_error: 2,
subscription_messages_connection_error: 3
)
end
@ -82,23 +85,20 @@ class CustomWizard::Notice
notices = []
if !skip_subscription
subscription_messages = request(subscription_messages_url)
subscription_messages = request(:subscription_messages)
if subscription_messages.present?
subscription_notices = convert_subscription_messages_to_notices(subscription_messages[:messages])
notices.push(*subscription_notices)
end
end
if !skip_plugin && (Discourse.git_branch === 'tests-passed' || (Rails.env.test? || Rails.env.development?))
plugin_status = request(plugin_status_url)
if !skip_plugin && request_plugin_status?
plugin_status = request(:plugin_status)
if plugin_status.present? && plugin_status[:status].present? && plugin_status[:status].is_a?(Hash)
plugin_notice = convert_plugin_status_to_notice(plugin_status[:status])
notices.push(plugin_notice) if plugin_notice
expire_connection_errors(connection_types[:plugin_status])
else
create_connection_error(connection_types[:plugin_status])
end
end
@ -107,14 +107,6 @@ class CustomWizard::Notice
notice.retrieved_at = Time.now
notice.save
end
if reached_connection_error_limit(connection_types[:plugin_status])
new(
message: I18n.t("wizard.notice.plugin_status_connection_error_limit"),
type: types[:warning],
created_at: Time.now
)
end
end
def self.convert_subscription_messages_to_notices(messages)
@ -133,19 +125,46 @@ class CustomWizard::Notice
if PLUGIN_STATUSES_TO_WARN.include?(plugin_status[:status])
notice = {
message: PrettyText.cook(I18n.t('wizard.notice.compatibility_issue', server: plugin_status_domain)),
type: types[:warning],
message: I18n.t('wizard.notice.compatibility_issue', domain: plugin_status_domain),
type: types[:plugin_status_warning],
created_at: plugin_status[:status_changed_at]
}
else
list(types[:warning]).each(&:expire)
expire_notices(types[:plugin_status_warning])
end
notice
end
def self.notify_connection_errors(connection_type_key)
domain = self.send("#{connection_type_key.to_s}_domain")
message = I18n.t("wizard.notice.#{connection_type_key.to_s}.connection_error_limit", domain: domain)
notices = list(type: types[:connection_error], message: message)
if notices.any?
notice = notices.first
notice.updated_at = Time.now
notice.save
else
notice = new(
message: message,
type: types["#{connection_type_key}_connection_error".to_sym],
created_at: Time.now
)
notice.save
end
end
def self.expire_notices(type)
list(type: type).each(&:expire)
end
def self.request_plugin_status?
CHECK_PLUGIN_STATUS_ON_BRANCH.include?(Discourse.git_branch) || Rails.env.test? || Rails.env.development?
end
def self.subscription_messages_domain
"localhost:3000"
(Rails.env.test? || Rails.env.development?) ? LOCALHOST_DOMAIN : SUBSCRIPTION_MESSAGES_DOMAIN
end
def self.subscription_messages_url
@ -153,17 +172,23 @@ class CustomWizard::Notice
end
def self.plugin_status_domain
"localhost:4200"
return LOCALHOST_DOMAIN if (Rails.env.test? || Rails.env.development?)
PLUGIN_STATUS_DOMAINS[Discourse.git_branch]
end
def self.plugin_status_url
"http://#{plugin_status_domain}/plugin-manager/status/discourse-custom-wizard"
end
def self.request(url)
def self.request(type)
url = self.send("#{type.to_s}_url")
response = Excon.get(url)
connection_error = CustomWizard::Notice::ConnectionError.new(type)
if response.status == 200
connection_error.expire!
expire_notices(types["#{type}_connection_error".to_sym])
begin
data = JSON.parse(response.body).deep_symbolize_keys
rescue JSON::ParserError
@ -172,6 +197,9 @@ class CustomWizard::Notice
data
else
connection_error.create!
notify_connection_errors(type) if connection_error.reached_limit?
nil
end
end
@ -180,10 +208,6 @@ class CustomWizard::Notice
"#{CustomWizard::PLUGIN_NAME}_notice"
end
def self.namespace_connection
"#{CustomWizard::PLUGIN_NAME}_notice_connection"
end
def self.find(id)
raw = PluginStore.get(namespace, id)
new(raw.symbolize_keys) if raw.present?
@ -193,42 +217,16 @@ class CustomWizard::Notice
PluginStore.set(namespace, id, raw_notice)
end
def self.plugin_status_connection_error_limit
5
end
def self.list_connection_query(type)
query = PluginStoreRow.where(plugin_name: namespace_connection)
query.where("(value::json->>'type')::integer = ?", type)
end
def self.expire_connection_errors(type)
list_connection_query(type).update_all("value = jsonb_set(value::jsonb, '{ expired_at }', (to_char(current_timestamp, 'HH12:MI:SS'))::jsonb)")
end
def self.create_connection_error(type)
id = SecureRandom.hex(16)
attrs = {
message: I18n.t("wizard.notice.connection_error", domain: self.send("#{type}_domain")),
type: type,
created_at: Time.now
}
PluginStore.set(namespace_connection, id, attrs)
end
def self.reached_connection_error_limit(type)
list_connection_query(type).size >= self.send("#{connection_types.key(type)}_connection_error_limit")
end
def self.list_query(type = nil)
def self.list_query(type: nil, message: nil, include_recently_expired: false)
query = PluginStoreRow.where(plugin_name: namespace)
query = query.where("(value::json->>'expired_at') IS NULL OR (value::json->>'expired_at')::date > now()::date - 1")
query = query.where("(value::json->>'expired_at') IS NULL#{include_recently_expired ? " OR (value::json->>'expired_at')::date > now()::date - 1" : ""}")
query = query.where("(value::json->>'type')::integer = ?", type) if type
query = query.where("(value::json->>'message')::text = ?", message) if message
query.order("value::json->>'created_at' DESC")
end
def self.list(type = nil)
list_query(type)
def self.list(type: nil, message: nil, include_recently_expired: false)
list_query(type: type, message: message, include_recently_expired: include_recently_expired)
.map { |r| self.new(JSON.parse(r.value).symbolize_keys) }
end
end

Datei anzeigen

@ -0,0 +1,81 @@
# frozen_string_literal: true
class CustomWizard::Notice::ConnectionError
attr_reader :type_key
def initialize(type_key)
@type_key = type_key
end
def create!
id = "#{type_key.to_s}_error"
if attrs = PluginStore.get(namespace, id)
attrs['updated_at'] = Time.now
attrs['count'] = attrs['count'].to_i + 1
else
domain = CustomWizard::Notice.send("#{type_key.to_s}_domain")
attrs = {
message: I18n.t("wizard.notice.connection_error", domain: domain),
type: self.class.types[type_key],
created_at: Time.now,
count: 1
}
end
PluginStore.set(namespace, id, attrs)
@errors = nil
end
def expire!
if errors.exists?
errors.each do |error_row|
error = JSON.parse(error_row.value)
error['expired_at'] = Time.now
error_row.value = error.to_json
error_row.save
end
end
end
def self.types
@types ||= Enum.new(
plugin_status: 0,
subscription_messages: 1
)
end
def plugin_status_limit
5
end
def subscription_messages_limit
10
end
def limit
self.send("#{type_key.to_s}_limit")
end
def reached_limit?
return false unless errors.exists?
current_error['count'].to_i >= limit
end
def current_error
JSON.parse(errors.first.value)
end
def namespace
"#{CustomWizard::PLUGIN_NAME}_notice_connection"
end
def errors
@errors ||= begin
query = PluginStoreRow.where(plugin_name: namespace)
query = query.where("(value::json->>'type')::integer = ?", self.class.types[type_key])
query.where("(value::json->>'expired_at') IS NULL")
end
end
end

Datei anzeigen

@ -40,7 +40,7 @@ if respond_to?(:register_svg_icon)
register_svg_icon "comment-alt"
register_svg_icon "far-life-ring"
register_svg_icon "arrow-right"
register_svg_icon "shield-virus"
register_svg_icon "bolt"
end
class ::Sprockets::DirectiveProcessor
@ -98,6 +98,7 @@ after_initialize do
../lib/custom_wizard/template.rb
../lib/custom_wizard/wizard.rb
../lib/custom_wizard/notice.rb
../lib/custom_wizard/notice/connection_error.rb
../lib/custom_wizard/subscription.rb
../lib/custom_wizard/subscription/subscription.rb
../lib/custom_wizard/subscription/authentication.rb
@ -242,10 +243,9 @@ after_initialize do
end
AdminDashboardData.add_problem_check do
warning_notices = CustomWizard::Notice.list(CustomWizard::Notice.types[:warning])
warning_notices = CustomWizard::Notice.list(type: CustomWizard::Notice.types[:plugin_status_warning])
warning_notices.any? ? ActionView::Base.full_sanitizer.sanitize(warning_notices.first.message, tags: %w(a)) : nil
end
Jobs.enqueue(:custom_wizard_update_notices)
DiscourseEvent.trigger(:custom_wizard_ready)
end

Datei anzeigen

@ -6,6 +6,7 @@ class CustomWizard::NoticeSerializer < ApplicationSerializer
:type,
:created_at,
:expired_at,
:updated_at,
:dismissed_at,
:retrieved_at,
:dismissable
@ -17,4 +18,8 @@ class CustomWizard::NoticeSerializer < ApplicationSerializer
def type
CustomWizard::Notice.types.key(object.type)
end
def messsage
PrettyText.cook(object.message)
end
end

Datei anzeigen

@ -39,7 +39,7 @@ describe CustomWizard::Notice do
stub_request(:get, described_class.subscription_messages_url).to_return(status: 200, body: { messages: [subscription_message] }.to_json)
described_class.update(skip_plugin: true)
notice = described_class.list.first
notice = described_class.list(include_recently_expired: true).first
expect(notice.expired?).to eq(true)
end
end
@ -51,20 +51,20 @@ describe CustomWizard::Notice do
described_class.update(skip_subscription: true)
end
it "converts plugin statuses to warn into notices" do
it "converts warning into notice" do
notice = described_class.list.first
expect(notice.type).to eq(described_class.types[:warning])
expect(notice.message).to eq(PrettyText.cook(I18n.t("wizard.notice.compatibility_issue", server: described_class.plugin_status_domain)))
expect(notice.type).to eq(described_class.types[:plugin_status_warning])
expect(notice.message).to eq(I18n.t("wizard.notice.compatibility_issue", domain: described_class.plugin_status_domain))
expect(notice.created_at.to_datetime).to be_within(1.second).of (plugin_status[:status_changed_at].to_datetime)
end
it "expires unexpired warning notices if status is recommended or compatible" do
it "expires warning notices if status is recommended or compatible" do
plugin_status[:status] = 'compatible'
plugin_status[:status_changed_at] = Time.now
stub_request(:get, described_class.plugin_status_url).to_return(status: 200, body: { status: plugin_status }.to_json)
described_class.update(skip_subscription: true)
notice = described_class.list(described_class.types[:warning]).first
notice = described_class.list(type: described_class.types[:plugin_status_warning], include_recently_expired: true).first
expect(notice.expired?).to eq(true)
end
end
@ -75,6 +75,57 @@ describe CustomWizard::Notice do
stub_request(:get, described_class.plugin_status_url).to_return(status: 200, body: { status: plugin_status }.to_json)
described_class.update
expect(described_class.list.length).to eq(2)
expect(described_class.list(include_recently_expired: true).length).to eq(2)
end
context "connection errors" do
before do
freeze_time
end
it "creates an error if connection to notice server fails" do
stub_request(:get, described_class.plugin_status_url).to_return(status: 400, body: { status: plugin_status }.to_json)
described_class.update(skip_subscription: true)
error = CustomWizard::Notice::ConnectionError.new(:plugin_status)
expect(error.errors.exists?).to eq(true)
end
it "only creates one connection error per type at a time" do
stub_request(:get, described_class.subscription_messages_url).to_return(status: 400, body: { messages: [subscription_message] }.to_json)
stub_request(:get, described_class.plugin_status_url).to_return(status: 400, body: { status: plugin_status }.to_json)
5.times { described_class.update }
plugin_status_errors = CustomWizard::Notice::ConnectionError.new(:plugin_status)
subscription_message_errors = CustomWizard::Notice::ConnectionError.new(:subscription_messages)
expect(plugin_status_errors.errors.length).to eq(1)
expect(subscription_message_errors.errors.length).to eq(1)
end
it "creates a connection error notice if connection errors reach limit" do
stub_request(:get, described_class.plugin_status_url).to_return(status: 400, body: { status: plugin_status }.to_json)
error = CustomWizard::Notice::ConnectionError.new(:plugin_status)
error.limit.times { described_class.update(skip_subscription: true) }
notice = described_class.list(type: described_class.types[:plugin_status_connection_error]).first
expect(error.current_error['count']).to eq(error.limit)
expect(notice.type).to eq(described_class.types[:plugin_status_connection_error])
end
it "expires a connection error notice if connection succeeds" do
stub_request(:get, described_class.plugin_status_url).to_return(status: 400, body: { status: plugin_status }.to_json)
error = CustomWizard::Notice::ConnectionError.new(:plugin_status)
error.limit.times { described_class.update(skip_subscription: true) }
stub_request(:get, described_class.plugin_status_url).to_return(status: 200, body: { status: plugin_status }.to_json)
described_class.update(skip_subscription: true)
notice = described_class.list(type: described_class.types[:plugin_status_connection_error], include_recently_expired: true).first
expect(notice.type).to eq(described_class.types[:plugin_status_connection_error])
expect(notice.expired_at.present?).to eq(true)
end
end
end