FEATURE: Admin > Subscriptions Pagination (#50)
Meta topic: https://meta.discourse.org/t/subscriptions-add-pagination-to-admin-subscriptions-view/172500 This adds support for pagination using our `{{load-more}}` component in core. Implementation on the backend was a bit tricky because we don't return all results from Stripe, only those that match local subscriptions stored in the `DiscourseSubscriptions::Subscription` model.
This commit is contained in:
parent
eaf1729f6f
commit
a282475da3
|
@ -7,14 +7,33 @@ module DiscourseSubscriptions
|
||||||
include DiscourseSubscriptions::Group
|
include DiscourseSubscriptions::Group
|
||||||
before_action :set_api_key
|
before_action :set_api_key
|
||||||
|
|
||||||
|
PAGE_LIMIT = 10
|
||||||
|
|
||||||
def index
|
def index
|
||||||
begin
|
begin
|
||||||
subscription_ids = Subscription.all.pluck(:external_id)
|
subscription_ids = Subscription.all.pluck(:external_id)
|
||||||
subscriptions = []
|
subscriptions = {
|
||||||
|
has_more: false,
|
||||||
|
data: [],
|
||||||
|
length: 0,
|
||||||
|
last_record: params[:last_record]
|
||||||
|
}
|
||||||
|
|
||||||
if subscription_ids.present? && is_stripe_configured?
|
if subscription_ids.present? && is_stripe_configured?
|
||||||
subscriptions = ::Stripe::Subscription.list(expand: ['data.plan.product'])
|
while subscriptions[:length] < PAGE_LIMIT
|
||||||
subscriptions = subscriptions.select { |sub| subscription_ids.include?(sub[:id]) }
|
current_set = get_subscriptions(subscriptions[:last_record])
|
||||||
|
|
||||||
|
until valid_subscriptions = find_valid_subscriptions(current_set[:data], subscription_ids) do
|
||||||
|
current_set = get_subscriptions(current_set[:data].last)
|
||||||
|
break if current_set[:has_more] == false
|
||||||
|
end
|
||||||
|
|
||||||
|
subscriptions[:data] = subscriptions[:data].concat(valid_subscriptions.to_a)
|
||||||
|
subscriptions[:last_record] = current_set[:data].last[:id] if current_set[:data].present?
|
||||||
|
subscriptions[:length] = subscriptions[:data].length
|
||||||
|
subscriptions[:has_more] = current_set[:has_more]
|
||||||
|
break if subscriptions[:has_more] == false
|
||||||
|
end
|
||||||
elsif !is_stripe_configured?
|
elsif !is_stripe_configured?
|
||||||
subscriptions = nil
|
subscriptions = nil
|
||||||
end
|
end
|
||||||
|
@ -54,6 +73,15 @@ module DiscourseSubscriptions
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def get_subscriptions(start)
|
||||||
|
::Stripe::Subscription.list(expand: ['data.plan.product'], limit: PAGE_LIMIT, starting_after: start)
|
||||||
|
end
|
||||||
|
|
||||||
|
def find_valid_subscriptions(data, ids)
|
||||||
|
valid = data.select { |sub| ids.include?(sub[:id]) }
|
||||||
|
valid.empty? ? nil : valid
|
||||||
|
end
|
||||||
|
|
||||||
# this will only refund the most recent subscription payment
|
# this will only refund the most recent subscription payment
|
||||||
def refund_subscription(subscription_id)
|
def refund_subscription(subscription_id)
|
||||||
subscription = ::Stripe::Subscription.retrieve(subscription_id)
|
subscription = ::Stripe::Subscription.retrieve(subscription_id)
|
||||||
|
|
|
@ -1,12 +1,30 @@
|
||||||
|
import AdminSubscription from "discourse/plugins/discourse-subscriptions/discourse/models/admin-subscription";
|
||||||
import Controller from "@ember/controller";
|
import Controller from "@ember/controller";
|
||||||
import showModal from "discourse/lib/show-modal";
|
import showModal from "discourse/lib/show-modal";
|
||||||
|
|
||||||
export default Controller.extend({
|
export default Controller.extend({
|
||||||
|
loading: false,
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
showCancelModal(subscription) {
|
showCancelModal(subscription) {
|
||||||
showModal("admin-cancel-subscription", {
|
showModal("admin-cancel-subscription", {
|
||||||
model: subscription,
|
model: subscription,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
loadMore() {
|
||||||
|
if (!this.loading && this.model.has_more) {
|
||||||
|
this.set("loading", true);
|
||||||
|
|
||||||
|
return AdminSubscription.loadMore(this.model.last_record).then(
|
||||||
|
(result) => {
|
||||||
|
const updated = this.model.data.concat(result.data);
|
||||||
|
this.set("model", result);
|
||||||
|
this.set("model.data", updated);
|
||||||
|
this.set("loading", false);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -38,9 +38,20 @@ AdminSubscription.reopenClass({
|
||||||
if (result === null) {
|
if (result === null) {
|
||||||
return { unconfigured: true };
|
return { unconfigured: true };
|
||||||
}
|
}
|
||||||
return result.map((subscription) =>
|
result.data = result.data.map((subscription) =>
|
||||||
AdminSubscription.create(subscription)
|
AdminSubscription.create(subscription)
|
||||||
);
|
);
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
loadMore(lastRecord) {
|
||||||
|
return ajax(`/s/admin/subscriptions?last_record=${lastRecord}`, {
|
||||||
|
method: "get",
|
||||||
|
}).then((result) => {
|
||||||
|
result.data = result.data.map((subscription) =>
|
||||||
|
AdminSubscription.create(subscription)
|
||||||
|
);
|
||||||
|
return result;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,42 +2,45 @@
|
||||||
<p>{{i18n 'discourse_subscriptions.admin.unconfigured'}}</p>
|
<p>{{i18n 'discourse_subscriptions.admin.unconfigured'}}</p>
|
||||||
<p><a href="https://meta.discourse.org/t/discourse-subscriptions/140818/">Discourse Subscriptions on Meta</a></p>
|
<p><a href="https://meta.discourse.org/t/discourse-subscriptions/140818/">Discourse Subscriptions on Meta</a></p>
|
||||||
{{else}}
|
{{else}}
|
||||||
<table class="table discourse-patrons-table">
|
{{#load-more selector=".discourse-patrons-table tr" action=(action "loadMore")}}
|
||||||
<thead>
|
<table class="table discourse-patrons-table">
|
||||||
<tr>
|
<thead>
|
||||||
<th>{{i18n 'discourse_subscriptions.admin.subscriptions.subscription.user'}}</th>
|
<tr>
|
||||||
<th>{{i18n 'discourse_subscriptions.admin.subscriptions.subscription.subscription_id'}}</th>
|
<th>{{i18n 'discourse_subscriptions.admin.subscriptions.subscription.user'}}</th>
|
||||||
<th>{{i18n 'discourse_subscriptions.admin.subscriptions.subscription.customer'}}</th>
|
<th>{{i18n 'discourse_subscriptions.admin.subscriptions.subscription.subscription_id'}}</th>
|
||||||
<th>{{i18n 'discourse_subscriptions.admin.subscriptions.subscription.product'}}</th>
|
<th>{{i18n 'discourse_subscriptions.admin.subscriptions.subscription.customer'}}</th>
|
||||||
<th>{{i18n 'discourse_subscriptions.admin.subscriptions.subscription.plan'}}</th>
|
<th>{{i18n 'discourse_subscriptions.admin.subscriptions.subscription.product'}}</th>
|
||||||
<th>{{i18n 'discourse_subscriptions.admin.subscriptions.subscription.status'}}</th>
|
<th>{{i18n 'discourse_subscriptions.admin.subscriptions.subscription.plan'}}</th>
|
||||||
<th class="td-right">{{i18n 'discourse_subscriptions.admin.subscriptions.subscription.created_at'}}</th>
|
<th>{{i18n 'discourse_subscriptions.admin.subscriptions.subscription.status'}}</th>
|
||||||
<th></th>
|
<th class="td-right">{{i18n 'discourse_subscriptions.admin.subscriptions.subscription.created_at'}}</th>
|
||||||
</tr>
|
<th></th>
|
||||||
</thead>
|
</tr>
|
||||||
{{#each model as |subscription|}}
|
</thead>
|
||||||
<tr>
|
{{#each model.data as |subscription|}}
|
||||||
<td>
|
<tr>
|
||||||
{{#if subscription.metadataUserExists}}
|
<td>
|
||||||
<a href="{{unbound subscription.subscriptionUserPath}}">
|
{{#if subscription.metadataUserExists}}
|
||||||
{{subscription.metadata.username}}
|
<a href="{{unbound subscription.subscriptionUserPath}}">
|
||||||
</a>
|
{{subscription.metadata.username}}
|
||||||
{{/if}}
|
</a>
|
||||||
</td>
|
{{/if}}
|
||||||
<td>{{subscription.id}}</td>
|
</td>
|
||||||
<td>{{subscription.customer}}</td>
|
<td>{{subscription.id}}</td>
|
||||||
<td>{{subscription.plan.product.name}}</td>
|
<td>{{subscription.customer}}</td>
|
||||||
<td>{{subscription.plan.nickname}}</td>
|
<td>{{subscription.plan.product.name}}</td>
|
||||||
<td>{{subscription.status}}</td>
|
<td>{{subscription.plan.nickname}}</td>
|
||||||
<td class="td-right">{{format-unix-date subscription.created}}</td>
|
<td>{{subscription.status}}</td>
|
||||||
<td class="td-right">
|
<td class="td-right">{{format-unix-date subscription.created}}</td>
|
||||||
{{#if subscription.loading}}
|
<td class="td-right">
|
||||||
{{loading-spinner size="small"}}
|
{{#if subscription.loading}}
|
||||||
{{else}}
|
{{loading-spinner size="small"}}
|
||||||
{{d-button disabled=subscription.canceled label="cancel" action=(action "showCancelModal" subscription) icon="times"}}
|
{{else}}
|
||||||
{{/if}}
|
{{d-button disabled=subscription.canceled label="cancel" action=(action "showCancelModal" subscription) icon="times"}}
|
||||||
</td>
|
{{/if}}
|
||||||
</tr>
|
</td>
|
||||||
{{/each}}
|
</tr>
|
||||||
</table>
|
{{/each}}
|
||||||
|
</table>
|
||||||
|
{{/load-more}}
|
||||||
|
{{conditional-loading-spinner condition=loading}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
|
@ -13,6 +13,7 @@ module DiscourseSubscriptions
|
||||||
|
|
||||||
before do
|
before do
|
||||||
Fabricate(:subscription, external_id: "sub_12345", customer_id: customer.id)
|
Fabricate(:subscription, external_id: "sub_12345", customer_id: customer.id)
|
||||||
|
Fabricate(:subscription, external_id: "sub_77777", customer_id: customer.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'unauthenticated' do
|
context 'unauthenticated' do
|
||||||
|
@ -34,21 +35,44 @@ module DiscourseSubscriptions
|
||||||
before { sign_in(admin) }
|
before { sign_in(admin) }
|
||||||
|
|
||||||
describe "index" do
|
describe "index" do
|
||||||
it "gets the subscriptions and products" do
|
before do
|
||||||
SiteSetting.discourse_subscriptions_public_key = "public-key"
|
SiteSetting.discourse_subscriptions_public_key = "public-key"
|
||||||
SiteSetting.discourse_subscriptions_secret_key = "secret-key"
|
SiteSetting.discourse_subscriptions_secret_key = "secret-key"
|
||||||
::Stripe::Subscription.expects(:list).with(expand: ['data.plan.product']).returns(
|
end
|
||||||
[
|
|
||||||
{ id: "sub_12345" },
|
it "gets the subscriptions and products" do
|
||||||
{ id: "sub_nope" }
|
::Stripe::Subscription.expects(:list)
|
||||||
]
|
.with(expand: ['data.plan.product'], limit: 10, starting_after: nil)
|
||||||
)
|
.returns(
|
||||||
|
has_more: false,
|
||||||
|
data: [
|
||||||
|
{ id: "sub_12345" },
|
||||||
|
{ id: "sub_nope" }
|
||||||
|
]
|
||||||
|
)
|
||||||
get "/s/admin/subscriptions.json"
|
get "/s/admin/subscriptions.json"
|
||||||
subscriptions = response.parsed_body[0]["id"]
|
subscriptions = response.parsed_body["data"][0]["id"]
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
expect(subscriptions).to eq("sub_12345")
|
expect(subscriptions).to eq("sub_12345")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "handles starting at a different point in the set" do
|
||||||
|
::Stripe::Subscription.expects(:list)
|
||||||
|
.with(expand: ['data.plan.product'], limit: 10, starting_after: 'sub_nope')
|
||||||
|
.returns(
|
||||||
|
has_more: false,
|
||||||
|
data: [
|
||||||
|
{ id: "sub_77777" },
|
||||||
|
{ id: "sub_yepnoep" }
|
||||||
|
]
|
||||||
|
)
|
||||||
|
get "/s/admin/subscriptions.json", params: { last_record: 'sub_nope' }
|
||||||
|
subscriptions = response.parsed_body["data"][0]["id"]
|
||||||
|
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
expect(subscriptions).to eq("sub_77777")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "destroy" do
|
describe "destroy" do
|
||||||
|
|
Loading…
Reference in New Issue