Skip to content

Commit

Permalink
Added a new view to the fancy graph: number of active members with a …
Browse files Browse the repository at this point in the history
…Tapir account.
  • Loading branch information
Theophile-Madet committed Jan 28, 2025
1 parent dbbf591 commit 44df5e1
Show file tree
Hide file tree
Showing 6 changed files with 200 additions and 0 deletions.
27 changes: 27 additions & 0 deletions schema.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,33 @@ paths:
schema:
type: integer
description: ''
/statistics/number_of_active_members_with_account_at_date:
get:
operationId: statistics_number_of_active_members_with_account_at_date_retrieve
description: Verify that the current user is authenticated.
parameters:
- in: query
name: at_date
schema:
type: string
format: date
required: true
- in: query
name: relative
schema:
type: boolean
required: true
tags:
- statistics
security:
- cookieAuth: []
responses:
'200':
content:
application/json:
schema:
type: integer
description: ''
/statistics/number_of_co_purchasers_at_date:
get:
operationId: statistics_number_of_co_purchasers_at_date_retrieve
Expand Down
57 changes: 57 additions & 0 deletions src/api-client/apis/StatisticsApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ export interface StatisticsNumberOfActiveMembersAtDateRetrieveRequest {
relative: boolean;
}

export interface StatisticsNumberOfActiveMembersWithAccountAtDateRetrieveRequest {
atDate: Date;
relative: boolean;
}

export interface StatisticsNumberOfCoPurchasersAtDateRetrieveRequest {
atDate: Date;
relative: boolean;
Expand Down Expand Up @@ -204,6 +209,58 @@ export class StatisticsApi extends runtime.BaseAPI {
return await response.value();
}

/**
* Verify that the current user is authenticated.
*/
async statisticsNumberOfActiveMembersWithAccountAtDateRetrieveRaw(requestParameters: StatisticsNumberOfActiveMembersWithAccountAtDateRetrieveRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<number>> {
if (requestParameters['atDate'] == null) {
throw new runtime.RequiredError(
'atDate',
'Required parameter "atDate" was null or undefined when calling statisticsNumberOfActiveMembersWithAccountAtDateRetrieve().'
);
}

if (requestParameters['relative'] == null) {
throw new runtime.RequiredError(
'relative',
'Required parameter "relative" was null or undefined when calling statisticsNumberOfActiveMembersWithAccountAtDateRetrieve().'
);
}

const queryParameters: any = {};

if (requestParameters['atDate'] != null) {
queryParameters['at_date'] = (requestParameters['atDate'] as any).toISOString().substring(0,10);
}

if (requestParameters['relative'] != null) {
queryParameters['relative'] = requestParameters['relative'];
}

const headerParameters: runtime.HTTPHeaders = {};

const response = await this.request({
path: `/statistics/number_of_active_members_with_account_at_date`,
method: 'GET',
headers: headerParameters,
query: queryParameters,
}, initOverrides);

if (this.isJsonMime(response.headers.get('content-type'))) {
return new runtime.JSONApiResponse<number>(response);
} else {
return new runtime.TextApiResponse(response) as any;
}
}

/**
* Verify that the current user is authenticated.
*/
async statisticsNumberOfActiveMembersWithAccountAtDateRetrieve(requestParameters: StatisticsNumberOfActiveMembersWithAccountAtDateRetrieveRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<number> {
const response = await this.statisticsNumberOfActiveMembersWithAccountAtDateRetrieveRaw(requestParameters, initOverrides);
return await response.value();
}

/**
* Verify that the current user is authenticated.
*/
Expand Down
12 changes: 12 additions & 0 deletions src/statistics/datasets.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ declare let gettext: (english_text: string) => string;

const datasetNumberOfMembers = "number_of_members";
const datasetNumberOfActiveMembers = "number_of_active_members";
const datasetNumberOfActiveMembersWithAccount = "number_of_active_members_with_account";
const datasetNumberOfInvestingMembers = "number_of_investing_members";
const datasetNumberOfPausedMembers = "number_of_paused_members";
const datasetNumberOfWorkingMembers = "number_of_working_members";
Expand Down Expand Up @@ -66,6 +67,17 @@ export const datasets: { [key: string]: Dataset } = {
color: "#9d1f2f",
pointStyle: "cross",
},
[datasetNumberOfActiveMembersWithAccount]: {
display_name: gettext("Active members with Tapir account"),
description: gettext(
"Same as active members, but also had an account at the given date. Some members declare themselves active when joining the coop but never come to activate their account.",
),
apiCall: api.statisticsNumberOfActiveMembersAtDateRetrieve,
chart_type: "line",
relative: false,
color: "#bd3f4f",
pointStyle: "circle",
},
[datasetNumberOfInvestingMembers]: {
display_name: gettext("Investing members"),
apiCall: api.statisticsNumberOfInvestingMembersAtDateRetrieve,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import datetime

from django.utils import timezone

from tapir.accounts.tests.factories.factories import TapirUserFactory
from tapir.coop.models import ShareOwnership
from tapir.coop.tests.factories import ShareOwnerFactory
from tapir.statistics.views.fancy_graph.number_of_active_members_with_account_view import (
NumberOfActiveMembersWithAccountAtDateView,
)
from tapir.utils.tests_utils import (
TapirFactoryTestBase,
mock_timezone_now,
)


class TestNumberOfActiveMembersView(TapirFactoryTestBase):
NOW = datetime.datetime(year=2023, month=7, day=2, hour=18)
REFERENCE_TIME = timezone.make_aware(
datetime.datetime(year=2022, month=4, day=8, hour=10)
)

def setUp(self) -> None:
super().setUp()
self.NOW = mock_timezone_now(self, self.NOW)

def create_test_user(self, is_investing=False, date_joined=None):
if date_joined is None:
date_joined = self.REFERENCE_TIME - datetime.timedelta(days=1)

TapirUserFactory.create(
share_owner__nb_shares=1,
share_owner__is_investing=is_investing,
date_joined=date_joined,
)
ShareOwnership.objects.update(
start_date=self.REFERENCE_TIME.date() - datetime.timedelta(days=1)
)

def test_calculateDatapoint_memberIsNotActive_notCounted(self):
self.create_test_user(is_investing=True)

result = NumberOfActiveMembersWithAccountAtDateView().calculate_datapoint(
self.REFERENCE_TIME
)

self.assertEqual(0, result)

def test_calculateDatapoint_memberHasNoAccount_notCounted(self):
ShareOwnerFactory.create(nb_shares=1, is_investing=False)
ShareOwnership.objects.update(
start_date=self.REFERENCE_TIME.date() - datetime.timedelta(days=1)
)

result = NumberOfActiveMembersWithAccountAtDateView().calculate_datapoint(
self.REFERENCE_TIME
)

self.assertEqual(0, result)

def test_calculateDatapoint_memberCreatedAccountAfterDate_notCounted(self):
self.create_test_user(
date_joined=self.REFERENCE_TIME + datetime.timedelta(days=1)
)

result = NumberOfActiveMembersWithAccountAtDateView().calculate_datapoint(
self.REFERENCE_TIME
)

self.assertEqual(0, result)

def test_calculateDatapoint_memberCreatedAccountBeforeDate_counted(self):
self.create_test_user(
date_joined=self.REFERENCE_TIME - datetime.timedelta(days=1)
)

result = NumberOfActiveMembersWithAccountAtDateView().calculate_datapoint(
self.REFERENCE_TIME
)

self.assertEqual(1, result)
6 changes: 6 additions & 0 deletions tapir/statistics/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import tapir.statistics.views.fancy_graph.base_view
import tapir.statistics.views.fancy_graph.number_of_abcd_members_view
import tapir.statistics.views.fancy_graph.number_of_active_members_view
import tapir.statistics.views.fancy_graph.number_of_active_members_with_account_view
import tapir.statistics.views.fancy_graph.number_of_co_purchasers_view
import tapir.statistics.views.fancy_graph.number_of_created_resignations_view
import tapir.statistics.views.fancy_graph.number_of_exempted_members_that_work_view
Expand Down Expand Up @@ -77,6 +78,11 @@
fancy_graph.number_of_active_members_view.NumberOfActiveMembersAtDateView.as_view(),
name="number_of_active_members_at_date",
),
path(
"number_of_active_members_with_account_at_date",
fancy_graph.number_of_active_members_with_account_view.NumberOfActiveMembersWithAccountAtDateView.as_view(),
name="number_of_active_members_with_account_at_date",
),
path(
"number_of_working_members_at_date",
fancy_graph.number_of_working_members_view.NumberOfWorkingMembersAtDateView.as_view(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import datetime

from tapir.accounts.models import TapirUser
from tapir.coop.models import ShareOwner, MemberStatus
from tapir.statistics.views.fancy_graph.base_view import DatapointView


class NumberOfActiveMembersWithAccountAtDateView(DatapointView):
def calculate_datapoint(self, reference_time: datetime.datetime) -> int:
reference_date = reference_time.date()
active_members = ShareOwner.objects.with_status(
MemberStatus.ACTIVE, reference_date
).distinct()
active_members_with_account = TapirUser.objects.filter(
share_owner__in=active_members, date_joined__lte=reference_date
)
return active_members_with_account.count()

0 comments on commit 44df5e1

Please sign in to comment.