Skip to content

Commit 7382dcb

Browse files
committed
HPC-9947: Add new plan element for plan caseload trend charts
1 parent 14e03d1 commit 7382dcb

39 files changed

+2222
-968
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
3+
namespace Drupal\ghi_base_objects\Entity;
4+
5+
use Drupal\ghi_base_objects\ApiObjects\Country;
6+
7+
/**
8+
* Provides an interface for defining base object that have a focus country.
9+
*
10+
* @ingroup ghi_base_objects
11+
*/
12+
interface BaseObjectFocusCountryInterface {
13+
14+
/**
15+
* Get the focus country for the plan.
16+
*
17+
* @return \Drupal\ghi_base_objects\Entity\BaseObjectInterface|null
18+
* The country base object or NULL.
19+
*/
20+
public function getFocusCountry();
21+
22+
/**
23+
* Get the focus country override for the plan.
24+
*
25+
* @return object|null
26+
* A latLng object or NULL.
27+
*/
28+
public function getFocusCountryOverride();
29+
30+
/**
31+
* Get the focus country map location for the plan.
32+
*
33+
* @return \Drupal\ghi_base_objects\ApiObjects\Country|null
34+
* An object describing the map location or NULL.
35+
*/
36+
public function getFocusCountryMapLocation(?Country $default_country = NULL);
37+
38+
}

html/modules/custom/ghi_base_objects/src/Entity/BaseObjectInterface.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
use Drupal\Core\Entity\EntityChangedInterface;
77

88
/**
9-
* Provides an interface for defining Base object entities.
9+
* Provides an interface for defining base object entities.
1010
*
1111
* @ingroup ghi_base_objects
1212
*/

html/modules/custom/ghi_base_objects/src/Entity/BaseObjectMetaDataInterface.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
namespace Drupal\ghi_base_objects\Entity;
44

55
/**
6-
* Provides an interface for defining Base object entities.
6+
* Provides an interface for defining base object entities with meta data.
77
*
88
* @ingroup ghi_base_objects
99
*/

html/modules/custom/ghi_base_objects/tests/src/Traits/BaseObjectTestTrait.php

+8-6
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,14 @@ protected function createBaseObjectType(array $values = []) {
3939
'label' => $this->randomString(),
4040
'hasYear' => FALSE,
4141
];
42-
$base_object_type = BaseObjectType::create($values);
43-
$this->assertSame(SAVED_NEW, $base_object_type->save());
44-
$this->assertInstanceOf(BaseObjectTypeInterface::class, $base_object_type);
45-
$this->createField('base_object', $base_object_type->id(), 'integer', 'field_original_id', 'Source id');
46-
if (!empty($values['hasYear'])) {
47-
$this->createField('base_object', $base_object_type->id(), 'integer', 'field_year', 'Year');
42+
$base_object_type = BaseObjectType::load($values['id']) ?: BaseObjectType::create($values);
43+
if ($base_object_type->isNew()) {
44+
$this->assertSame(SAVED_NEW, $base_object_type->save());
45+
$this->assertInstanceOf(BaseObjectTypeInterface::class, $base_object_type);
46+
$this->createField('base_object', $base_object_type->id(), 'integer', 'field_original_id', 'Source id');
47+
if (!empty($values['hasYear'])) {
48+
$this->createField('base_object', $base_object_type->id(), 'integer', 'field_year', 'Year');
49+
}
4850
}
4951
return $base_object_type;
5052
}

html/modules/custom/ghi_blocks/src/Plugin/Block/GlobalPage/PlanTable.php

+21-65
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use Drupal\ghi_blocks\Traits\GlobalSettingsTrait;
1313
use Drupal\ghi_blocks\Traits\PlanFootnoteTrait;
1414
use Drupal\ghi_blocks\Traits\TableSoftLimitTrait;
15+
use Drupal\ghi_blocks\Traits\TableTrait;
1516
use Drupal\ghi_plans\ApiObjects\Mocks\PlanOverviewPlanMock;
1617
use Drupal\ghi_plans\Traits\FtsLinkTrait;
1718
use Drupal\hpc_common\Helpers\ArrayHelper;
@@ -44,6 +45,7 @@ class PlanTable extends GHIBlockBase implements HPCDownloadExcelInterface, HPCDo
4445
use GlobalPlanOverviewBlockTrait;
4546
use GlobalSettingsTrait;
4647
use PlanFootnoteTrait;
48+
use TableTrait;
4749
use TableSoftLimitTrait;
4850
use BlockCommentTrait;
4951
use FtsLinkTrait;
@@ -142,62 +144,22 @@ public function buildTableData($export = FALSE) {
142144
$header += [
143145
'name' => $this->t('Plans'),
144146
'type' => $this->t('Plan type'),
145-
'inneed' => [
146-
'data' => $this->t('People in need'),
147-
'data-column-type' => 'amount',
148-
],
149-
'targeted' => [
150-
'data' => $this->t('People targeted'),
151-
'data-column-type' => 'amount',
152-
],
153-
'expected_reach' => [
154-
'data' => $this->t('Estimated Reach'),
155-
'data-column-type' => 'amount',
156-
],
157-
'expected_reached' => [
158-
'data' => $this->t('% Reached'),
159-
'data-column-type' => 'amount',
160-
],
161-
'latest_reach' => [
162-
'data' => $this->t('People reached'),
163-
'data-column-type' => 'percentage',
164-
],
165-
'reached' => [
166-
'data' => $this->t('% Reached'),
167-
'data-column-type' => 'percentage',
168-
],
169-
'requirements' => [
170-
'data' => $this->t('Requirements'),
171-
'data-column-type' => 'currency',
172-
],
173-
'funding' => [
174-
'data' => $this->t('Funding'),
175-
'data-column-type' => 'currency',
176-
],
177-
'coverage' => [
178-
'data' => $this->t('% Funded'),
179-
'data-column-type' => 'percentage',
180-
],
181-
'status' => [
182-
'data' => $this->t('Status'),
183-
'data-column-type' => 'status',
184-
'sortable' => FALSE,
185-
],
147+
'inneed' => $this->buildHeaderColumn($this->t('People in need'), 'amount'),
148+
'targeted' => $this->buildHeaderColumn($this->t('People targeted'), 'amount'),
149+
'expected_reach' => $this->buildHeaderColumn($this->t('Estimated Reach'), 'amount'),
150+
'expected_reached' => $this->buildHeaderColumn($this->t('% Reached'), 'percentage'),
151+
'latest_reach' => $this->buildHeaderColumn($this->t('People reached'), 'percentage'),
152+
'reached' => $this->buildHeaderColumn($this->t('% Reached'), 'percentage'),
153+
'requirements' => $this->buildHeaderColumn($this->t('Requirements'), 'currency'),
154+
'funding' => $this->buildHeaderColumn($this->t('Funding'), 'currency'),
155+
'coverage' => $this->buildHeaderColumn($this->t('% Funded'), 'percentage'),
156+
'status' => $this->buildHeaderColumn($this->t('Status'), 'status'),
186157
];
187158
if ($export) {
188159
$header['in_gho'] = $this->t('In GHO');
189-
$header['document'] = [
190-
'data' => $this->t('Document'),
191-
'data-column-type' => 'document',
192-
];
193-
$header['link_ha'] = [
194-
'data' => $this->t('Link to HA page'),
195-
'data-column-type' => 'document',
196-
];
197-
$header['link_fts'] = [
198-
'data' => $this->t('Link to FTS page'),
199-
'data-column-type' => 'document',
200-
];
160+
$header['document'] = $this->buildHeaderColumn($this->t('Document'), 'document');
161+
$header['link_ha'] = $this->buildHeaderColumn($this->t('Link to HA page'), 'document');
162+
$header['link_fts'] = $this->buildHeaderColumn($this->t('Link to FTS page'), 'document');
201163
}
202164

203165
$cache_tags = [];
@@ -241,19 +203,15 @@ public function buildTableData($export = FALSE) {
241203
$document_uri = $plan->getPlanDocumentUri();
242204

243205
// Setup the column values.
244-
$value_in_need = $in_need ? [
206+
$value_in_need = [
245207
'#theme' => 'hpc_amount',
246-
'#amount' => $in_need,
208+
'#amount' => $in_need ?: '-',
247209
'#decimals' => $decimals,
248-
] : [
249-
'#markup' => '-',
250210
];
251-
$value_targeted = $target ? [
211+
$value_targeted = [
252212
'#theme' => 'hpc_amount',
253-
'#amount' => $target,
213+
'#amount' => $target ?: '-',
254214
'#decimals' => $decimals,
255-
] : [
256-
'#markup' => '-',
257215
];
258216
$value_expected_reach = [
259217
'#theme' => 'hpc_amount',
@@ -264,12 +222,10 @@ public function buildTableData($export = FALSE) {
264222
'#theme' => 'hpc_percent',
265223
'#ratio' => $expected_reached / 100,
266224
];
267-
$value_latest_reached = $latest_reached !== NULL ? [
225+
$value_latest_reached = [
268226
'#theme' => 'hpc_amount',
269-
'#amount' => $latest_reached,
227+
'#amount' => $latest_reached ?? $this->t('Pending'),
270228
'#decimals' => $decimals,
271-
] : [
272-
'#markup' => $this->t('Pending'),
273229
];
274230
$value_reached = $reached_percent ? [
275231
'#theme' => 'hpc_percent',

html/modules/custom/ghi_blocks/src/Plugin/Block/Menu/SectionSwitcher.php

+3-153
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
use Drupal\Core\Block\BlockBase;
66
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
77
use Drupal\ghi_base_objects\Traits\ShortNameTrait;
8-
use Drupal\ghi_plans\Entity\Plan;
98
use Drupal\ghi_sections\Entity\SectionNodeInterface;
109
use Drupal\ghi_sections\Traits\SectionPathTrait;
1110
use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -113,46 +112,15 @@ public function build() {
113112
/**
114113
* Build the section switcher options.
115114
*
116-
* @return \Drupal\ghi_sections\Entity\SectionNodeInterface[]
117-
* An array of section nodes to be used as options.
115+
* @return \Drupal\ghi_sections\Entity\SectionNodeInterface[]|null
116+
* An array of section nodes to be used as options or NULL.
118117
*/
119118
private function buildSectionSwitcherOptions() {
120-
121119
$section_node = $this->getSectionNode();
122120
if (!$section_node) {
123121
return NULL;
124122
}
125-
126-
$base_object = $section_node->getBaseObject();
127-
$sections = [];
128-
if (!$section_node->get('field_year')->isEmpty()) {
129-
// This is either a global section page or a section page with a base
130-
// object that needs an additional year specified.
131-
$args = array_filter([
132-
'type' => $section_node->bundle(),
133-
'field_base_object' => $base_object?->id(),
134-
]);
135-
$candidates = $this->entityTypeManager->getStorage($section_node->getEntityTypeId())->loadByProperties($args);
136-
foreach ($candidates as $candidate) {
137-
$year = $candidate->get('field_year')->value;
138-
$sections[$year] = $candidate;
139-
}
140-
}
141-
elseif ($base_object && $base_object->hasField('field_focus_country')) {
142-
// This is a section page with no year but with a focus country field,
143-
// e.g. a plan based section page.
144-
$sections = $this->getSectionsByBaseObjectFocusCountry();
145-
}
146-
elseif ($base_object) {
147-
// This is a section page with no year, e.g. a plan based section page.
148-
$sections = $this->getSectionsByBaseObjectCountryReference();
149-
}
150-
151-
if (empty($sections)) {
152-
return NULL;
153-
}
154-
155-
return $sections;
123+
return $this->sectionManager->getRelatedSections($section_node);
156124
}
157125

158126
/**
@@ -180,122 +148,4 @@ private function getSectionNode() {
180148
return $section_node instanceof SectionNodeInterface ? $section_node : NULL;
181149
}
182150

183-
/**
184-
* Get switcher options by a country reference on the sections base objects.
185-
*
186-
* @return \Drupal\ghi_sections\Entity\SectionNodeInterface[]
187-
* An array of section nodes keyed by the base object original id
188-
*/
189-
private function getSectionsByBaseObjectFocusCountry() {
190-
$options = [];
191-
$section_node = $this->getSectionNode();
192-
$base_object = $section_node->getBaseObject();
193-
if (!$base_object || !$base_object->hasField('field_focus_country') || $base_object->get('field_focus_country')->isEmpty()) {
194-
return $options;
195-
}
196-
$focus_country = $base_object->get('field_focus_country')->entity;
197-
198-
// Find other object candidates that have the same focus country.
199-
/** @var \Drupal\ghi_base_objects\Entity\BaseObjectInterface[] $base_object_candidates */
200-
$base_object_candidates = $this->entityTypeManager->getStorage($base_object->getEntityTypeId())->loadByProperties([
201-
'type' => $base_object->bundle(),
202-
'field_focus_country' => $focus_country->id(),
203-
]);
204-
205-
// If base object is a plan, thus looking for other plan base objects,
206-
// apply filtering based on the plan type.
207-
if ($base_object instanceof Plan) {
208-
$base_object_candidates = array_filter($base_object_candidates, function (Plan $base_object_candidate) use ($base_object) {
209-
// If the current base object is of type RRP, we want to retain only
210-
// candidates that are also RRPs. If it's not an RRP, we only want
211-
// other candiates that are not RRPs either.
212-
return $base_object->isRrp() ? $base_object_candidate->isRrp() : !$base_object_candidate->isRrp();
213-
});
214-
}
215-
216-
return $this->getSectionOptionsForBaseObjects($section_node, $base_object_candidates);
217-
}
218-
219-
/**
220-
* Get switcher options by a country reference on the sections base objects.
221-
*
222-
* @return \Drupal\ghi_sections\Entity\SectionNodeInterface[]
223-
* An array of section nodes keyed by the base object original id
224-
*/
225-
private function getSectionsByBaseObjectCountryReference() {
226-
$options = [];
227-
$section_node = $this->getSectionNode();
228-
$base_object = $section_node->getBaseObject();
229-
if (!$base_object || !$base_object->hasField('field_country') || $base_object->get('field_country')->isEmpty()) {
230-
return $options;
231-
}
232-
233-
// Get the list of all countries associated with this object.
234-
$country_ids = array_map(function ($country) {
235-
return $country->id();
236-
}, $base_object->get('field_country')->referencedEntities());
237-
238-
// Find other object candidates that have at least one of these countries
239-
// associated.
240-
/** @var \Drupal\ghi_base_objects\Entity\BaseObjectInterface[] $base_object_candidates */
241-
$base_object_candidates = $this->entityTypeManager->getStorage($base_object->getEntityTypeId())->loadByProperties([
242-
'type' => $base_object->bundle(),
243-
'field_country' => $country_ids,
244-
]);
245-
246-
// Then filter out the ones that don't share the full set of countries.
247-
$base_object_candidates = array_filter($base_object_candidates, function ($base_object_candidate) use ($country_ids) {
248-
$candidate_country_ids = array_map(function ($country) {
249-
return $country->id();
250-
}, $base_object_candidate->get('field_country')->referencedEntities());
251-
return empty(array_diff($country_ids, $candidate_country_ids)) && count($candidate_country_ids) == count($country_ids);
252-
});
253-
if (empty($base_object_candidates)) {
254-
return $options;
255-
}
256-
return $this->getSectionOptionsForBaseObjects($section_node, $base_object_candidates);
257-
}
258-
259-
/**
260-
* Get the section options for the given base object.
261-
*
262-
* @param \Drupal\ghi_sections\Entity\SectionNodeInterface $section_node
263-
* The current section node.
264-
* @param \Drupal\ghi_base_objects\Entity\BaseObjectInterface[] $base_objects
265-
* The base objects.
266-
*
267-
* @return \Drupal\ghi_sections\Entity\SectionNodeInterface[]
268-
* An array of section nodes keyed by the base object original id.
269-
*/
270-
private function getSectionOptionsForBaseObjects(SectionNodeInterface $section_node, array $base_objects) {
271-
$base_object = $section_node->getBaseObject();
272-
// Then load the sections associated to these objects.
273-
/** @var \Drupal\ghi_sections\Entity\SectionNodeInterface[] $section_candidates */
274-
$section_candidates = $this->entityTypeManager->getStorage($section_node->getEntityTypeId())->loadByProperties([
275-
'type' => $section_node->bundle(),
276-
'field_base_object' => array_keys($base_objects),
277-
]);
278-
foreach ($section_candidates as $section_candidate) {
279-
if (!$section_candidate->access('view')) {
280-
continue;
281-
}
282-
$options[$section_candidate->getBaseObject()->getSourceId()] = $section_candidate;
283-
}
284-
285-
// Sort the options.
286-
if ($base_object->hasField('field_year')) {
287-
// If the base object has a year field, use that for sorting.
288-
usort($options, function ($section_a, $section_b) {
289-
$year_a = $section_a->getBaseObject()->get('field_year')->value;
290-
$year_b = $section_b->getBaseObject()->get('field_year')->value;
291-
return $year_a - $year_b;
292-
});
293-
}
294-
else {
295-
// Otherwise just use the base objects original id as a best guess.
296-
ksort($options);
297-
}
298-
return array_reverse($options);
299-
}
300-
301151
}

0 commit comments

Comments
 (0)