Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release/v3.6.2 sprint 108 #545

Open
wants to merge 63 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
e414a60
- initial commit with templates
elipe17 Aug 26, 2024
447ff84
- Mostly complete tech memo
elipe17 Aug 27, 2024
030e31b
Merge branch 'develop' into 3110-filter-integration
elipe17 Aug 27, 2024
669ba45
- add summary
elipe17 Aug 28, 2024
7f5057b
- Added minimum config for prometheus
elipe17 Aug 28, 2024
b0c7823
impl iterator
jtimpe Aug 29, 2024
db6f9c0
lint
jtimpe Aug 29, 2024
79b56eb
- Added exporters for celery and postgres
elipe17 Aug 29, 2024
28f2bd1
silence logs
jtimpe Aug 29, 2024
0ec70ef
increase timeout
jtimpe Aug 29, 2024
62a30e0
- moved plg config to appropriate directories
elipe17 Aug 29, 2024
09355ef
- add todo
elipe17 Aug 29, 2024
96a8d64
Merge branch 'develop' into 3044-prometheus-grafana
elipe17 Aug 29, 2024
6663f08
Merge branch 'develop' into 3110-filter-integration
elipe17 Aug 30, 2024
5439395
- Update dashboard
elipe17 Aug 30, 2024
1a29319
wip
jtimpe Sep 4, 2024
9abf5a8
wip
jtimpe Sep 4, 2024
928bbd1
Merge branch 'develop' into 3110-filter-integration
elipe17 Sep 5, 2024
aed8b65
- Updated Grafana view to allow access based on group only since devs…
elipe17 Sep 5, 2024
3ea4f4b
- remove setting
elipe17 Sep 5, 2024
94d554e
- Update frontend vars
elipe17 Sep 5, 2024
cf693ad
local file + upload to s3
jtimpe Sep 5, 2024
7989afd
rm smart_open
jtimpe Sep 5, 2024
50a0276
Merge branch 'develop' into 3137-queryset-iterator
jtimpe Sep 5, 2024
f8f0757
- Add deploy script for pg-exporter
elipe17 Sep 5, 2024
e13d9e8
Merge branch 'develop' of https://github.com/raft-tech/TANF-app into …
elipe17 Sep 5, 2024
c7f0010
- Update lock file
elipe17 Sep 5, 2024
8cbb557
- Added promtail and loki
elipe17 Sep 5, 2024
ec545fb
- Gave datasources UIDs
elipe17 Sep 6, 2024
415cc7f
- Change language in the tdp section
elipe17 Sep 6, 2024
60cfba3
Merge branch 'develop' of https://github.com/raft-tech/TANF-app into …
elipe17 Sep 6, 2024
bdd6559
pass raw sql to celery task
jtimpe Sep 6, 2024
ec2f157
change s3 upload path
jtimpe Sep 6, 2024
e7b266a
space
jtimpe Sep 9, 2024
44fd31f
Merge branch 'develop' of https://github.com/raft-tech/TANF-app into …
elipe17 Sep 10, 2024
8c11028
- Update lockfile
elipe17 Sep 10, 2024
a354eed
- remove old TMs
elipe17 Sep 10, 2024
c49da38
docstrings + logging
jtimpe Sep 10, 2024
2ed3675
complexity
jtimpe Sep 10, 2024
47ddafe
org
jtimpe Sep 10, 2024
b8443dd
Merge branch 'develop' into 3137-queryset-iterator
jtimpe Sep 10, 2024
b466dae
revert timeout change
jtimpe Sep 10, 2024
a2f2fde
3.6.0 release notes (#3178)
victoriaatraft Sep 10, 2024
5ae9dd2
Merge branch 'develop' into 3110-filter-integration
elipe17 Sep 11, 2024
a6e7848
Merge pull request #3154 from raft-tech/3110-filter-integration
elipe17 Sep 11, 2024
e601ce6
- Updated total family fields to not include zero in the range
elipe17 Sep 11, 2024
72617c1
- Revert changes to ssp and tribal
elipe17 Sep 11, 2024
4e19d5a
definite import
jtimpe Sep 17, 2024
88c4110
Merge branch 'develop' into 3137-queryset-iterator
jtimpe Sep 17, 2024
2ff85cb
tasks patch
jtimpe Sep 17, 2024
fc9248c
unused import
jtimpe Sep 17, 2024
1514e07
Merge pull request #3185 from raft-tech/3088-s3-s4-val-fix
elipe17 Sep 18, 2024
fdaa35b
Merge branch 'develop' into 3137-queryset-iterator
jtimpe Sep 18, 2024
e372381
Merge pull request #3174 from raft-tech/3047-plg-mvp
elipe17 Sep 18, 2024
2d17a47
Merge branch 'develop' into 3044-prometheus-grafana
elipe17 Sep 18, 2024
2a5e5c3
Update dev-issue-template.md (#3186)
reitermb Sep 19, 2024
febf97f
Merge branch 'develop' into 3044-prometheus-grafana
elipe17 Sep 19, 2024
db12f08
Merge pull request #3163 from raft-tech/3044-prometheus-grafana
elipe17 Sep 19, 2024
e423b1b
Merge branch 'develop' into 3137-queryset-iterator
jtimpe Sep 20, 2024
3ac1dcd
Merge pull request #3162 from raft-tech/3137-queryset-iterator
jtimpe Sep 20, 2024
2f1a97c
- Update file handler
elipe17 Sep 23, 2024
33d4fcb
- update volume location
elipe17 Sep 23, 2024
38cbbd8
Merge pull request #3198 from raft-tech/log-file-hotfix
elipe17 Sep 23, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/ISSUE_TEMPLATE/dev-issue-template.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ assignees: ''

**Description:**
_Provide a brief background and justification for this issue_
_OFA & UX team: Please include bullets capturing the value to STT end users, related guidance, and/or talking points that should be communicated to STTs in release notes_


**Acceptance Criteria:**
Expand Down
2 changes: 1 addition & 1 deletion docs/How-We-Work/team-meetings.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,12 @@ A typical sprint schedule is described in the table below.
- External factors outside of the author spending time building the ticket (ie need external team's input, see how a feature develops, etc.)
- Ex. Waiting on X ticket to finish the scope of said unrefined ticket, problem found / unsure how big it is and knows other work will unearth it
- If we know the ACs but not the tasks, then its unrefined
- Release Notes summary is empty or incomplete as applicable, to be provided by UX/Product by default.
- Refined: Ticket is complete and is ready to be executed.
- Refined & Ready to Go (Next Sprint)
- "Earmarked" work for the upcoming sprint.
- **Labelling:**
- WIP
- Author knows the 5 W's or darn near (90%)
- Drafted ticket – either still on the author the finish their part or a short team conversation is needed.
- Administrative in nature
- Ex. Stub, ticket that doesn't feel there's enough to warrant an introduction
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
# Multi-Select Filters

**Audience**: TDP Software Engineers <br>
**Subject**: Multi-Select Filter Integration <br>
**Date**: August 27, 2024 <br>

## Summary
This technical memorandum provides the suggested guidelines for a future engineer to integrate TDP's need for multi-select filtering with Django 508. The memorandum provides some necessary background on both the TDP multi-select filters as well as Django 508 and it's purpose and effects. The [Method](#method) section provides the guidelines and updates required to integrate TDP's custom filtering needs with Django 508. Specifically, the [Django 508 Updates](#django-508-updates) section introduces the engineer to the area where the filtering and query string building occurs within Django 508 and the suggested changes. The [TDP Updates](#tdp-updates) section introduces the recommended changes to current TDP custom filtering with respect to how it can be simplified and how it could be unified with Django 508 to provide a seamless filtering experience.

## Background
TDP has been expanding it's Django Admin Console (DAC) filtering capabilities by introducing custom filters, specifically multi-select filters. This has introduced a myriad of issues because TDP does not use the default DAC. Instead, to assist with accessibility compliance TDP wraps the default DAC with [Django 508](https://github.com/raft-tech/django-admin-508) (henceforth referred to as 508) which makes various updates to the styling and functionality of the default DAC. A key change is that 508 introduces to the DAC is an `Apply Filters` button that intercepts query string parameters from default DAC filters and only applies them after clicking the button. The default DAC applies the filters as they are selected as opposed to all at once. The issue with 508's approach is that it assumes all filters are built-in Django filters (i.e. single select filters). This presents a discrepancy because Django allows developers to write custom templates and filters to add further filtering functionality (e.g. multi-select filters).

## Out of Scope
General filter template specification and general property based multi-select filtering mentioned in the [TDP Updates](#tdp-updates) section of this memorandum are out of scope for this memorandum.

## Method
To support multi-select/custom filtering in the DAC, both the TDP repository and the 508 repository will require updates.

### Django 508 Updates
508 builds the query string for all filters on the currently selected DAC page with the [dropdown-filter.js](https://github.com/raft-tech/django-admin-508/blob/main/admin_interface/static/admin_interface/508/dropdown-filter.js) JavaScript file. This file defines a JQuery function that operates on the `changelist-filter` element in the DOM. The function adds `onchange` event handlers to each filter in the `changelist-filter` element which extract the filter's query string template value that gets selected when it changes to construct a new query string. However, when custom templates and custom filters are introduced the JQuery function breaks down and cannot handle it. The implementation of the single-select and multi-select query building cannot be unified. This is because Django built-in single-select filters define a single prop on the `option` elements for the filter. The `value` prop that is defined on all the `option` elements is that `option`'s query parameter with the rest of the current query string appended to it. This same implementation cannot be achieved on multi-select filters because the query string cannot (and should not) contain multiple queries of the same type. This implies single-select and multi-select filters have to be handled in 508 differently. The update to `dropdown-filter.js` provided below serves as a guide towards a final solution for integrating multi-select filters, single-select filters, and the `Apply Filters` button. The implementation below relies on two key facts. One, all multi-select filters define `ariaMultiSelectable` and two, that all multi-select filters define two custom props: `key` and `value`. These key value pairs (e.g. `key=name__in`, `value=Bob`) are used to build the query string along with whatever the remaining single-select filters have chosen. When a user clicks the `Apply Filters` button, the code below executes and builds the query string for single and multi-select filters.

```javascript
if (typeof (django) !== 'undefined' && typeof (django.jQuery) !== 'undefined') {
(function ($) {
'use strict';
$(document).ready(function () {
const filters = document.querySelectorAll('#changelist-filter .list-filter-dropdown select')
let query = '?'

const applyFiltersButton = document.querySelector('#submit-filters');
if (applyFiltersButton) {
applyFiltersButton.onclick = function () {
for (const filter of filters) {
let conjunction = query === '?' ? '' : '&'
if (!filter.ariaMultiSelectable) {
if (filter.selectedIndex !== 0) {
// Built in Django filters append the query string to the `value` field on the element. However, when we
// have a mult-selectable filter, the select element can't have the `value` field as a query string
// because multiple options can be selected and there is no way to track that. Therefore, we strip the
// single select filters query param from the existing query string and build and entirely new query
// string from that.
let opt = filter.options[filter.selectedIndex]
let query_str = opt.value
let filter_query = ''
for (let i = 1; i < query_str.length; i++) {
if (query_str[i] === '&') {
break
}
filter_query += query_str[i]
}
query = query.concat(conjunction, filter_query)
}
}
else {
// All multi select filters are required to set the `key` and `value` fields on the option element to the
// individual options to be able to build the correct query string.
let selected = ''
for (const option of filter.options) {
if (option.selected) {
selected = selected.concat(option.value, '%2C')
}
}
selected = selected.substring(0, selected.lastIndexOf('%2C'))
if (selected !== '') {
query = query.concat(conjunction, filter.options[0].getAttribute('key'), '=', selected)
}
}
}
window.location = query
};
}
});
})(django.jQuery);
}
```

### TDP Updates
Currently, TDP implements a custom multi-select filter, with a custom template. This filter is complex and relies on a custom "filter" button to apply it's selection which is very incohesive with the `Apply Filters` button that 508 introduces. To remedy this, the current and future multi-select/custom filters implemented in TDP need to give 508 control of constructing the appropriate query string by way of supplying 508 with the appropriate key-value pairs needed in their templates. In doing so, we can also simplify and generalize the current multi-select filter available in TDP.

TDP currently utilizes three classes to implement field based multi-select filtering. This should be able to be simplified to a single class when we let 508 manage query string building. There are a few main features to note that this type of class would need. The first is the custom template used to create a multi-select drop down filter. An example template is the [multiselectdropdownfilter.html](multiselectdropdownfilter.html). The second are the unique query string parameters that would need to be defined in the `choices` method of the class. E.g the `key` and `value` parameters. These are the parameters that Django would populate into the aforementioned template and that 508 will need to parse to build the appropriate query string. Below is an example class that allows for field based multi-select filtering. The example class introduces some extra caveats to handle multi-select functionality. The `FieldListMultiSelectFilter` overrides and adds new parameters in the constructor before calling `super()`. These additions and overrides help convert the parent class `AllValuesFieldListFilter` from a single-select to a multi-select filter. Looking forward towards the future of TDP filtering, the example class implementation provides a path forward for Django model property based multi-select filtering (e.g. the `fiscal_period` property on the `DataFile` model). Leveraging the aforementioned template and building a class which sub-classes the Django `SimpleListFiter` class we have the ability to provide general multi-select filtering for Django model properties by implementing the correct `queryset`, `choices`, and `lookups` methods.

```python
class FieldListMultiSelectFilter(admin.AllValuesFieldListFilter):
"""Multi select dropdown filter for all kind of fields."""

template = 'multiselectdropdownfilter.html'

def __init__(self, field, request, params, model, model_admin, field_path):
self.lookup_kwarg = '%s__in' % field_path
self.lookup_kwarg_isnull = '%s__isnull' % field_path
lookup_vals = request.GET.get(self.lookup_kwarg)
self.lookup_vals = lookup_vals.split(',') if lookup_vals else list()
self.lookup_val_isnull = request.GET.get(self.lookup_kwarg_isnull)
self.empty_value_display = model_admin.get_empty_value_display()
parent_model, reverse_path = reverse_field_path(model, field_path)
# Obey parent ModelAdmin queryset when deciding which options to show
if model == parent_model:
queryset = model_admin.get_queryset(request)
else:
queryset = parent_model._default_manager.all()
self.lookup_choices = (queryset
.distinct()
.order_by(field.name)
.values_list(field.name, flat=True))
super(admin.AllValuesFieldListFilter, self).__init__(field, request, params, model, model_admin, field_path)

def queryset(self, request, queryset):
"""Build queryset based on choices."""
params = Q()
for lookup_arg, value in self.used_parameters.items():
params |= Q(**{lookup_arg: value})
try:
return queryset.filter(params)
except (ValueError, ValidationError) as e:
# Fields may raise a ValueError or ValidationError when converting
# the parameters to the correct type.
raise IncorrectLookupParameters(e)

def prepare_querystring_value(self, value):
"""Mask commas."""
return str(value).replace(',', '%~')

def choices(self, changelist):
"""Generate choices."""
add_facets = getattr(changelist, "add_facets", False)
facet_counts = self.get_facet_queryset(changelist) if add_facets else None
query_string = changelist.get_query_string({}, [self.lookup_kwarg, self.lookup_kwarg_isnull])
yield {
'selected': not self.lookup_vals and self.lookup_val_isnull is None,
'query_string': query_string,
'display': _('All'),
}
include_none = False
count = None
empty_title = self.empty_value_display
for i, val in enumerate(self.lookup_choices):
if add_facets:
count = facet_counts[f"{i}__c"]
if val is None:
include_none = True
empty_title = f"{empty_title} ({count})" if add_facets else empty_title
continue

val = str(val)
qval = self.prepare_querystring_value(val)
yield {
'selected': qval in self.lookup_vals,
'query_string': query_string,
"display": f"{val} ({count})" if add_facets else val,
'value': urllib.parse.quote_plus(val),
'key': self.lookup_kwarg,
}
if include_none:
yield {
'selected': bool(self.lookup_val_isnull),
'query_string': query_string,
"display": empty_title,
'value': 'True',
'key': self.lookup_kwarg_isnull,
}
```


## Affected Systems
- Django 508
- TANF-App

## Use and Test cases to consider
- Consider adding 508 integration tests for all/most Django fields for the suggested `FieldListMultiSelectFilter`
- Test having multiple Django built-in and `FieldListMultiSelectFilter`'s on the same page and verify the query string

Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{% load i18n admin_urls %}
<div class="list-filter-dropdown">
<h3>{% blocktrans with filter_title=title %} By {{ filter_title }} {% endblocktrans %}</h3>

<div>
<select id="{{ title|slugify }}_select" multiple style="color:#000000;width:100%" aria-multiselectable="true" aria-label="{{ title|slugify }}s" size="{% if choices|length < 7 %} {{ choices|length }} {% else %} 8 {% endif %}">
{% for choice in choices|slice:"1:" %}
<option{% if choice.selected %} selected{% endif %}
value="{{ choice.value }}"
key="{{ choice.key }}"
style="color:#000000"
{% if choice.selected %} aria-checked="true" {% else %} aria-checked="false" {% endif %}>
{{ choice.display }}
</option>
{% endfor %}
</select>
</div>
</div>
26 changes: 26 additions & 0 deletions docs/Technical-Documentation/tech-memos/tm-template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# TITLE

**Audience**: TDP Software Engineers <br>
**Subject**: SUBJECT/TITLE <br>
**Date**: August 8, 2024 <br>

## Summary
This is a template to use to create new technical memorandums.

## Background (Optional)
Background for the feature if necessary.

## Out of Scope
Call out what is out of scope for this technical memorandum and should be considered in a different technical memorandum.

## Method/Design
This section should contain sub sections that provide general implementation details surrounding key components required to implement the feature.

### Sub header (piece of the design, can be many of these)
sub header content describing component.

## Affected Systems
provide a list of systems this feature will depend on/change.

## Use and Test cases to consider
provide a list of use cases and test cases to be considered when the feature is being implemented.
Loading