Skip to content

Commit

Permalink
#2213: Add an Insight showing the positions that have pending assessm…
Browse files Browse the repository at this point in the history
…ents

Refactor the PendingAssessmentsNotificationWorker for this; we want to re-use most of the code.
Add an asynchronous Position search so we can use the refactored PendingAssessmentsHelper.
  • Loading branch information
gjvoosten committed Jan 28, 2021
1 parent 37a3a52 commit d0bc75b
Show file tree
Hide file tree
Showing 24 changed files with 1,180 additions and 652 deletions.
10 changes: 5 additions & 5 deletions client/src/components/Nav.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,9 +130,9 @@ const Nav = ({
id="my-tasks-nav"
>
{`My ${pluralize(taskShortLabel)}`}
{notifications?.myTasksWithPendingAssessments?.length ? (
{notifications?.tasksWithPendingAssessments?.length ? (
<NotificationBadge>
{notifications.myTasksWithPendingAssessments.length}
{notifications.tasksWithPendingAssessments.length}
</NotificationBadge>
) : null}
</SidebarLink>
Expand All @@ -142,9 +142,9 @@ const Nav = ({
id="my-counterparts-nav"
>
My Counterparts
{notifications?.myCounterpartsWithPendingAssessments?.length ? (
{notifications?.counterpartsWithPendingAssessments?.length ? (
<NotificationBadge>
{notifications.myCounterpartsWithPendingAssessments.length}
{notifications.counterpartsWithPendingAssessments.length}
</NotificationBadge>
) : null}
</SidebarLink>
Expand Down Expand Up @@ -241,7 +241,7 @@ const Nav = ({
Help
</SidebarLink>

{currentUser.isAdmin() /* FIXME: enable this again when nci-agency/anet#1463 is fixed: || currentUser.isSuperUser() */ && (
{(currentUser.isAdmin() || currentUser.isSuperUser()) && (
<NavDropdown title="Insights" id="insights" active={inInsights}>
{INSIGHTS.map(insight => (
<Link
Expand Down
299 changes: 299 additions & 0 deletions client/src/components/PendingAssessmentsByPosition.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,299 @@
import API from "api"
import { gql } from "apollo-boost"
import Fieldset from "components/Fieldset"
import LinkTo from "components/LinkTo"
import { PageDispatchersPropType, useBoilerplate } from "components/Page"
import UltimatePaginationTopDown from "components/UltimatePaginationTopDown"
import _isEmpty from "lodash/isEmpty"
import { Position, Task } from "models"
import {
getNotifications,
GRAPHQL_NOTIFICATIONS_NOTE_FIELDS
} from "notificationsUtils"
import PropTypes from "prop-types"
import React, { useState } from "react"
import { Table } from "react-bootstrap"
import Settings from "settings"

const GQL_GET_POSITION_LIST = gql`
query($positionQuery: PositionSearchQueryInput) {
positionList(query: $positionQuery) {
pageNum
pageSize
totalCount
list {
uuid
name
code
type
status
organization {
uuid
shortName
}
location {
uuid
name
}
person {
uuid
name
rank
role
avatar(size: 32)
}
associatedPositions {
uuid
name
code
type
status
organization {
uuid
shortName
}
location {
uuid
name
}
person {
uuid
name
rank
avatar(size: 32)
${GRAPHQL_NOTIFICATIONS_NOTE_FIELDS}
}
}
responsibleTasks(
query: {
status: ACTIVE
}
) {
uuid
shortName
longName
customFieldRef1 {
uuid
}
${GRAPHQL_NOTIFICATIONS_NOTE_FIELDS}
}
}
}
}
`

/*
* Component displaying positions that currently have pending assessments.
*/
const PendingAssessmentsByPosition = ({
pageDispatchers,
queryParams,
style
}) => {
const [pageNum, setPageNum] = useState(0)
const positionQuery = Object.assign({}, queryParams, { pageNum })
const { loading, error, data } = API.useApiQuery(GQL_GET_POSITION_LIST, {
positionQuery
})
const { done, result } = useBoilerplate({
loading,
error,
pageDispatchers
})
if (done) {
return result
}

const {
pageSize,
pageNum: curPage,
totalCount,
list: positions
} = data.positionList
return (
<div>
<Fieldset id="pending" title="Positions with pending assessments">
<AdvisorList
positions={positions}
pageSize={pageSize}
pageNum={curPage}
totalCount={totalCount}
goToPage={setPageNum}
/>
</Fieldset>
</div>
)
}

PendingAssessmentsByPosition.propTypes = {
pageDispatchers: PageDispatchersPropType,
queryParams: PropTypes.object,
style: PropTypes.object
}

const AdvisorList = ({
positions,
pageSize,
pageNum,
totalCount,
goToPage
}) => {
if (_isEmpty(positions)) {
return (
<em>No {Settings.fields.advisor.person.name} with pending assessments</em>
)
}
const borderStyle = { borderRight: "2px solid #ddd" }
return (
<div>
<UltimatePaginationTopDown
componentClassName="searchPagination"
className="pull-right"
pageNum={pageNum}
pageSize={pageSize}
totalCount={totalCount}
goToPage={goToPage}
>
<Table striped condensed hover responsive className="positions_table">
<thead>
<tr>
<th colSpan="3" style={borderStyle}>
{Settings.fields.advisor.person.name}
</th>
<th colSpan="3" style={borderStyle}>
{Settings.fields.principal.person.name} to assess
</th>
<th colSpan="1">{Settings.fields.task.shortLabel} to assess</th>
</tr>
<tr>
<th style={{ width: "20%" }}>Name</th>
<th style={{ width: "10%" }}>Position</th>
<th style={{ width: "10%", ...borderStyle }}>Organization</th>
<th style={{ width: "20%" }}>Name</th>
<th style={{ width: "10%" }}>Position</th>
<th style={{ width: "10%", ...borderStyle }}>Organization</th>
<th style={{ width: "20%" }}>Name</th>
</tr>
</thead>
<tbody>
{Position.map(positions, pos => {
const nameComponents = []
pos.name && nameComponents.push(pos.name)
pos.code && nameComponents.push(pos.code)
const notifications = getNotifications(pos)
return (
<tr key={pos.uuid}>
<td>
{pos.person && (
<LinkTo modelType="Person" model={pos.person} />
)}
</td>
<td>
<LinkTo modelType="Position" model={pos}>
{nameComponents.join(" - ")}
</LinkTo>
</td>
<td style={borderStyle}>
{pos.organization && (
<LinkTo
modelType="Organization"
model={pos.organization}
/>
)}
</td>
<td colSpan="3" style={borderStyle}>
<PrincipalList
positions={
notifications.counterpartsWithPendingAssessments
}
/>
</td>
<td colSpan="1">
<TaskList
tasks={notifications.tasksWithPendingAssessments}
/>
</td>
</tr>
)
})}
</tbody>
</Table>
</UltimatePaginationTopDown>
</div>
)
}

AdvisorList.propTypes = {
positions: PropTypes.array.isRequired,
totalCount: PropTypes.number,
pageNum: PropTypes.number,
pageSize: PropTypes.number,
goToPage: PropTypes.func
}

const PrincipalList = ({ positions }) => {
if (_isEmpty(positions)) {
return <em>No {Settings.fields.principal.person.name} to assess</em>
}
return (
<Table condensed responsive style={{ background: "transparent" }}>
<tbody>
{Position.map(positions, pos => {
const nameComponents = []
pos.name && nameComponents.push(pos.name)
pos.code && nameComponents.push(pos.code)
return (
<tr key={pos.uuid}>
<td style={{ width: "50%" }}>
{pos.person && <LinkTo modelType="Person" model={pos.person} />}
</td>
<td style={{ width: "25%" }}>
<LinkTo modelType="Position" model={pos}>
{nameComponents.join(" - ")}
</LinkTo>
</td>
<td style={{ width: "25%" }}>
{pos.organization && (
<LinkTo modelType="Organization" model={pos.organization} />
)}
</td>
</tr>
)
})}
</tbody>
</Table>
)
}

PrincipalList.propTypes = {
positions: PropTypes.array.isRequired
}

const TaskList = ({ tasks }) => {
if (_isEmpty(tasks)) {
return <em>No {Settings.fields.task.shortLabel} to assess</em>
}
return (
<Table condensed responsive style={{ background: "transparent" }}>
<tbody>
{Task.map(tasks, task => {
return (
<tr key={task.uuid}>
<td>
<LinkTo modelType="Task" model={task}>
{task.shortName} {task.longName}
</LinkTo>
</td>
</tr>
)
})}
</tbody>
</Table>
)
}

TaskList.propTypes = {
tasks: PropTypes.array.isRequired
}

export default PendingAssessmentsByPosition
8 changes: 8 additions & 0 deletions client/src/components/SearchFilters.js
Original file line number Diff line number Diff line change
Expand Up @@ -525,6 +525,14 @@ export const searchFilters = function() {
options: ["true", "false"],
labels: ["Yes", "No"]
}
},
"Has Pending Assessments": {
component: CheckboxFilter,
deserializer: deserializeCheckboxFilter,
props: {
queryKey: "hasPendingAssessments",
msg: "Yes"
}
}
}
}
Expand Down
5 changes: 3 additions & 2 deletions client/src/components/advancedSearch/CheckboxFilter.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import React from "react"
import { Checkbox, FormGroup } from "react-bootstrap"
import { deserializeSearchFilter } from "searchUtils"

const CheckboxFilter = ({ asFormField, queryKey, onChange }) => {
const CheckboxFilter = ({ msg, asFormField, queryKey, onChange }) => {
const defaultValue = { value: true }
const toQuery = val => {
return { [queryKey]: val.value }
Expand All @@ -17,7 +17,6 @@ const CheckboxFilter = ({ asFormField, queryKey, onChange }) => {
toQuery
)[0]

const msg = "Authorized for me"
return !asFormField ? (
<>{msg}</>
) : (
Expand All @@ -29,11 +28,13 @@ const CheckboxFilter = ({ asFormField, queryKey, onChange }) => {
)
}
CheckboxFilter.propTypes = {
msg: PropTypes.string,
queryKey: PropTypes.string.isRequired,
onChange: PropTypes.func, // eslint-disable-line react/no-unused-prop-types
asFormField: PropTypes.bool
}
CheckboxFilter.defaultProps = {
msg: "Authorized for me",
asFormField: true
}

Expand Down
Loading

0 comments on commit d0bc75b

Please sign in to comment.