Merge pull request #416 from dod-ccpo/request-filters

Searching, filtering and sorting for requests page
This commit is contained in:
richard-dds 2018-11-01 15:04:34 -04:00 committed by GitHub
commit 9c22a57780
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 203 additions and 41 deletions

View File

@ -207,3 +207,7 @@ class Requests(object):
comment = RequestInternalComment(request=request, text=comment_text, user=user)
RequestsQuery.add_and_commit(comment)
return request
@classmethod
def possible_statuses(cls):
return [s[1].value for s in RequestStatus.__members__.items()]

View File

@ -15,10 +15,12 @@ class RequestsIndex(object):
Permissions.REVIEW_AND_APPROVE_JEDI_WORKSPACE_REQUEST
in self.user.atat_permissions
):
return self._ccpo_view(self.user)
context = self._ccpo_view(self.user)
else:
return self._non_ccpo_view(self.user)
context = self._non_ccpo_view(self.user)
return {**context, "possible_statuses": Requests.possible_statuses()}
def _ccpo_view(self, user):
requests = Requests.get_many()
@ -55,6 +57,14 @@ class RequestsIndex(object):
"extended_view": False,
}
def _workspace_link_for_request(self, request):
if request.is_approved:
return url_for(
"workspaces.workspace_projects", workspace_id=request.workspace_id
)
else:
return None
def _map_request(self, request, viewing_role):
time_created = pendulum.instance(request.time_created)
is_new = time_created.add(days=1) > pendulum.now()
@ -67,6 +77,7 @@ class RequestsIndex(object):
"workspace_id": request.workspace.id if request.workspace else None,
"name": request.displayname,
"is_new": is_new,
"is_approved": request.is_approved,
"status": request.status_displayname,
"app_count": app_count,
"last_submission_timestamp": request.last_submission_timestamp,
@ -76,6 +87,7 @@ class RequestsIndex(object):
"edit_link": url_for("requests.edit", request_id=request.id),
"action_required": request.action_required_by == viewing_role,
"dod_component": request.latest_revision.dod_component,
"workspace_link": self._workspace_link_for_request(request),
}

View File

@ -0,0 +1,139 @@
import LocalDatetime from '../../components/local_datetime'
import { formatDollars } from '../../lib/dollars'
import { parse } from 'date-fns'
import { compose, partial, indexBy, prop, sortBy, reverse, pipe } from 'ramda'
export default {
name: 'requests-list',
components: {
LocalDatetime,
},
props: {
requests: {
type: Array,
default: [],
},
isExtended: {
type: Boolean,
default: false,
},
statuses: {
type: Array,
default: [],
},
},
data: function () {
const defaultSort = (sort, requests) => sortBy(prop(sort.columnName), requests)
const dateSort = (sort, requests) => {
const parseDate = compose(partial(parse), prop(sort.columnName))
return sortBy(parseDate, requests)
}
const columnList = [
{
displayName: 'JEDI Cloud Request Name',
attr: 'name',
sortFunc: defaultSort,
},
{
displayName: 'Date Request Submitted',
attr: 'last_submission_timestamp',
sortFunc: dateSort,
},
{
displayName: 'Date Request Last Edited',
attr: 'last_edited_timestamp',
extendedOnly: true,
sortFunc: dateSort,
},
{
displayName: 'Requester',
attr: 'full_name',
extendedOnly: true,
sortFunc: defaultSort,
},
{
displayName: 'Projected Annual Usage ($)',
attr: 'annual_usage',
sortFunc: defaultSort,
},
{
displayName: 'Request Status',
attr: 'status',
sortFunc: defaultSort,
},
{
displayName: 'DOD Component',
attr: 'dod_component',
extendedOnly: true,
sortFunc: defaultSort,
},
]
return {
searchValue: '',
statusValue: '',
sort: {
columnName: '',
isAscending: true
},
columns: indexBy(prop('attr'), columnList),
}
},
computed: {
filteredRequests: function () {
return pipe(
partial(this.applySearch, [this.searchValue]),
partial(this.applyFilters, [this.statusValue]),
partial(this.applySort, [this.sort]),
)(this.requests)
}
},
methods: {
getColumns: function() {
return Object.values(this.columns)
.filter((column) => !column.extendedOnly || this.isExtended)
},
applySearch: (query, requests) => {
return requests.filter(
(request) => query !== '' ?
request.name.toLowerCase().includes(query.toLowerCase()) :
true
)
},
applyFilters: (status, requests) => {
return requests.filter(
(request) => status !== '' ?
request.status === status :
true
)
},
applySort: function(sort, requests) {
if (sort.columnName === '') {
return requests
} else {
const { sortFunc } = this.columns[sort.columnName]
const sorted = sortFunc(sort, requests)
return sort.isAscending ?
sorted :
reverse(sorted)
}
},
dollars: (value) => formatDollars(value, false),
updateSortValue: function(columnName) {
if (!this.isExtended) { return }
// toggle ascending / descending if column is clicked twice
if (columnName === this.sort.columnName) {
this.sort.isAscending = !this.sort.isAscending
}
this.sort.columnName = columnName;
},
},
}

View File

@ -22,6 +22,7 @@ import SpendTable from './components/tables/spend_table'
import CcpoApproval from './components/forms/ccpo_approval'
import MembersList from './components/forms/members_list'
import LocalDatetime from './components/local_datetime'
import RequestsList from './components/forms/requests_list'
Vue.use(VTooltip)
@ -46,6 +47,7 @@ const app = new Vue({
LocalDatetime,
EditEnvironmentRole,
EditProjectRoles,
RequestsList,
},
mounted: function() {

View File

@ -17,6 +17,7 @@
"date-fns": "^1.29.0",
"npm": "^6.0.1",
"parcel": "^1.9.7",
"ramda": "^0.25.0",
"svg-innerhtml": "^1.1.0",
"text-mask-addons": "^3.8.0",
"uswds": "^1.6.3",

View File

@ -3,6 +3,7 @@
{% from "components/alert.html" import Alert %}
{% from "components/modal.html" import Modal %}
{% from "components/empty_state.html" import EmptyState %}
{% from "components/icon.html" import Icon %}
{% block content %}
@ -36,6 +37,9 @@
</div>
{% endcall %}
<requests-list inline-template v-bind:requests='{{ requests | tojson }}' v-bind:is-extended='{{ extended_view | tojson }}' v-bind:statuses='{{ possible_statuses | tojson }}'>
<div>
{% if num_action_required %}
{% set title -%}
Action required on {{ num_action_required }} requests.
@ -55,7 +59,6 @@
) }}
{% else %}
{% if extended_view %}
<div class="row kpi">
<div class="kpi__item col col--grow">
@ -76,22 +79,21 @@
<div class="col col--grow">
{% if extended_view %}
<form class='search-bar'>
<form @submit.prevent class='search-bar'>
<div class='usa-input search-input'>
<label for='requests-search'>Search requests by Order ID</label>
<input type='search' id='requests-search' name='requests-search' placeholder="Search by Order ID"/>
<button type="submit">
<label for='requests-search'>Search requests by name</label>
<input v-model='searchValue' type='search' id='requests-search' name='requests-search' placeholder="Search by name"/>
<button>
<span class="hide">Search</span>
</button>
</div>
<div class='usa-input'>
<label for='filter-status'>Filter requests by status</label>
<select id="filter-status" name="filter-status">
<select v-model="statusValue" id="filter-status" name="filter-status">
<option value="" selected disabled>Filter by status</option>
<option value="">Active</option>
<option value="">Pending</option>
<option value="">Denied</option>
<option value="">All</option>
<option v-for="status in statuses" :value="status">!{ status }</option>
</select>
</div>
</form>
@ -101,52 +103,49 @@
<table>
<thead>
<tr>
<th scope="col">JEDI Cloud Request Name</th>
<th scope="col">Date Request Submitted</th>
{% if extended_view %}
<th scope="col">Date Request Last Edited</th>
<th scope="col">Requester</th>
{% endif %}
<th scope="col">Projected Annual Usage ($)</th>
<th scope="col">Request Status</th>
{% if extended_view %}
<th scope="col">DOD Component</th>
{% endif %}
<th @click.prevent="updateSortValue(column.attr)" v-for="column in getColumns()"scope="col">
!{ column.displayName }
<span v-if="column.attr === sort.columnName && sort.isAscending">
{{ Icon("caret_down") }}
</span>
<span v-else-if="column.attr === sort.columnName && !sort.isAscending">
{{ Icon("caret_up") }}
</span>
</th>
</tr>
</thead>
<tbody>
{% for r in requests %}
<tbody v-for="r in filteredRequests">
<tr>
<th scope="row">
<a class='icon-link icon-link--large' href="{{ r.edit_link }}">{{ r.name }}</a>
{% if r.action_required %}<span class="label label--info">Action Required</span>{% endif %}
<a class='icon-link icon-link--large' :href="r.edit_link">!{ r.name }</a>
<span v-if="r.action_required" class="label label--info">Action Required</span>
</th>
<td>{{ r.last_submission_timestamp | formattedDate }}</td>
{% if extended_view %}
<td>{{ r.last_edited_timestamp | formattedDate }}</td>
<td>{{ r.full_name }}</td>
<td><local-datetime :timestamp="r.last_submission_timestamp" format="M/D/YYYY"></td>
{% if extended_view %}
<td><local-datetime :timestamp="r.last_edited_timestamp" format="M/D/YYYY"></td>
<td>!{ r.full_name }</td>
{% endif %}
<td>{{ r.annual_usage | dollars }}</td>
<td>!{ dollars(r.annual_usage) }</td>
<td>
{% if r.status == 'Approved' %}
<a class="icon-link icon-link--large" href="{{ url_for('workspaces.workspace_projects', workspace_id=r.workspace_id) }}">
{{ r.status }}
</a>
{% else %}
{{ r.status }}
{% endif %}
<a v-if="r.is_approved" class="icon-link icon-link--large" :href="r.workspace_link">
!{ r.status }
</a>
<span v-else>
!{ r.status }
</span>
</td>
{% if extended_view %}
<td>{{ r.dod_component }}</td>
<td>!{ r.dod_component }</td>
{% endif %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endif %}
</div>
</requests-list>
{% endblock %}

View File

@ -6101,6 +6101,11 @@ qw@~1.0.1:
resolved "https://registry.yarnpkg.com/qw/-/qw-1.0.1.tgz#efbfdc740f9ad054304426acb183412cc8b996d4"
integrity sha1-77/cdA+a0FQwRCassYNBLMi5ltQ=
ramda@^0.25.0:
version "0.25.0"
resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.25.0.tgz#8fdf68231cffa90bc2f9460390a0cb74a29b29a9"
integrity sha512-GXpfrYVPwx3K7RQ6aYT8KPS8XViSXUVJT1ONhoKPE9VAleW42YE+U+8VEyGWt41EnEQW7gwecYJriTI0pKoecQ==
randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5:
version "2.0.6"
resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.0.6.tgz#d302c522948588848a8d300c932b44c24231da80"