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

Add MART reports importer #5032

Draft
wants to merge 21 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
12 changes: 12 additions & 0 deletions application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,18 @@ anet:
resource: ${ANET_KEYCLOAK_CLIENT_PUBLIC:ANET-Client-public}
show-logout-link: true

# Settings for the Exchange server to receive MART reports
mart:
hostname: ${ANET_MART_EXCHANGE_SERVER:localhost}
username: ${ANET_MART_EXCHANGE_USERNAME}
password: ${ANET_MART_EXCHANGE_PASSWORD}
trusted-sender: ${ANET_MART_TRUSTED_SENDER}
disable-certificate-validation: ${ANET_MART_DISABLE_CERTIFICATE_VALIDATION:false}
mark-as-read: ${ANET_MART_MARK_AS_READ:true}
disabled: ${ANET_MART_DISABLE:true}
mail-polling-delay-in-seconds: 10
max-number-emails-pulled: 40

# Set path of dictionary to be loaded
# If you want to place the dictionary file under a subfolder of main application folder,
# please do not forget to update this parameter!
Expand Down
12 changes: 12 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,13 @@ run.environment("ANET_SMTP_SSLTRUST", run.environment["ANET_SMTP_SSLTRUST"] ?: r
run.environment("ANET_SMTP_HTTP_PORT", run.environment["ANET_SMTP_HTTP_PORT"] ?: isTestEnv ? 1180 : 1080)
run.environment("ANET_DICTIONARY_NAME", run.environment["ANET_DICTIONARY_NAME"] ?: "anet-dictionary.yml")
run.environment("ANET_TEST_MODE", run.environment["ANET_TEST_MODE"] ?: isTestEnv ? "true" : "false")
run.environment("ANET_MART_EXCHANGE_SERVER", run.environment["ANET_MART_EXCHANGE_SERVER"] ?: "test-ex-09.nsf.kfor.nato.int")
run.environment("ANET_MART_EXCHANGE_USERNAME", run.environment["ANET_MART_EXCHANGE_USERNAME"] ?: "[email protected]")
run.environment("ANET_MART_EXCHANGE_PASSWORD", run.environment["ANET_MART_EXCHANGE_PASSWORD"] ?: "123")
run.environment("ANET_MART_TRUSTED_SENDER", run.environment["ANET_MART_TRUSTED_SENDER"] ?: "[email protected]")
run.environment("ANET_MART_DISABLE_CERTIFICATE_VALIDATION", run.environment["ANET_MART_DISABLE_CERTIFICATE_VALIDATION"] ?: "false")
run.environment("ANET_MART_MARK_AS_READ", run.environment["ANET_MART_MARK_AS_READ"] ?: "true")
run.environment("ANET_MART_DISABLE", run.environment["ANET_MART_DISABLE"] ?: isTestEnv ? "true" : "false")
String NON_DEFAULT_DB_PROFILE = run.environment["ANET_DB_NAME"] in [DEV_DB, TEST_DB] ? "" : "prod"
run.environment("SPRING_PROFILES_ACTIVE", run.environment["SPRING_PROFILES_ACTIVE"] ?: isTestEnv ? "test" : (NON_DEFAULT_DB_PROFILE ?: "dev"))

Expand Down Expand Up @@ -148,6 +155,11 @@ dependencies {
implementation "org.springframework.security:spring-security-config"
implementation "org.springframework.shell:spring-shell-starter:${springShellVersion}"

// Dependencies for MART importer
implementation 'com.microsoft.ews-java-api:ews-java-api:2.0'
// For EWS (uses javax.xml.ws.http.HTTPException)
implementation 'javax.xml.ws:jaxws-api:2.3.1'

// The graphql-java-client-runtime module aggregates all dependencies for the generated code,
// including the plugin runtime
testImplementation 'com.graphql-java-generator:graphql-java-client-runtime:2.8'
Expand Down
7 changes: 7 additions & 0 deletions client/src/components/Nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,13 @@ const Navigation = ({
>
Pending emails
</SidebarLink>
<SidebarLink
id="mart-imported-reports"
linkTo="/admin/martImportedReports"
handleOnClick={resetPages}
>
MART importer
</SidebarLink>
<SidebarLink
id="graphQL-nav"
linkTo="/admin/graphiql"
Expand Down
5 changes: 5 additions & 0 deletions client/src/pages/Routing.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import AppContext from "components/AppContext"
import _isEmpty from "lodash/isEmpty"
import AdminIndex from "pages/admin/Index"
import MartImportedReportsShow from "pages/admin/martImportedReports/Show"
import MergeLocations from "pages/admin/merge/MergeLocations"
import MergeOrganizations from "pages/admin/merge/MergeOrganizations"
import MergePeople from "pages/admin/merge/MergePeople"
Expand Down Expand Up @@ -171,6 +172,10 @@ const Routing = () => {
<Route path="overTime" element={<UserActivitiesOverTime />} />
</Route>
<Route path="pendingEmails" element={<PendingEmailsShow />} />
<Route
path="martImportedReports"
element={<MartImportedReportsShow />}
/>
<Route path="graphiql" element={<GraphiQL />} />
</Route>
)}
Expand Down
183 changes: 183 additions & 0 deletions client/src/pages/admin/martImportedReports/Show.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
import { gql } from "@apollo/client"
import { Icon } from "@blueprintjs/core"
import { IconNames } from "@blueprintjs/icons"
import { DEFAULT_PAGE_PROPS, DEFAULT_SEARCH_PROPS } from "actions"
import API from "api"
import Fieldset from "components/Fieldset"
import LinkTo from "components/LinkTo"
import { GRAPHQL_ENTITY_AVATAR_FIELDS } from "components/Model"
import {
mapPageDispatchersToProps,
PageDispatchersPropType,
useBoilerplate,
usePageTitle
} from "components/Page"
import UltimatePaginationTopDown from "components/UltimatePaginationTopDown"
import _isEmpty from "lodash/isEmpty"
import moment from "moment"
import React, { useState } from "react"
import { FormSelect, Table } from "react-bootstrap"
import { connect } from "react-redux"
import Settings from "settings"

const GQL_GET_MART_REPORTS_IMPORTED = gql`
query ($pageNum: Int!, $pageSize: Int!) {
martImportedReports(pageNum: $pageNum, pageSize: $pageSize) {
pageNum
pageSize
totalCount
list {
person {
uuid
name
rank
${GRAPHQL_ENTITY_AVATAR_FIELDS}
}
report {
uuid
intent
}
success
createdAt
errors
}
}
}
`

const PAGESIZES = [10, 25, 50, 100]
const DEFAULT_PAGESIZE = 25

interface MartImportedReportsShowProps {
pageDispatchers?: PageDispatchersPropType
}
const MartImportedReportsShow = ({
pageDispatchers
}: MartImportedReportsShowProps) => {
usePageTitle("MART reports imported")
const [pageNum, setPageNum] = useState(0)
const [pageSize, setPageSize] = useState(DEFAULT_PAGESIZE)
const { loading, error, data } = API.useApiQuery(
GQL_GET_MART_REPORTS_IMPORTED,
{
pageNum,
pageSize
}
)
const { done, result } = useBoilerplate({
loading,
error,
pageProps: DEFAULT_PAGE_PROPS,
searchProps: DEFAULT_SEARCH_PROPS,
pageDispatchers
})
if (done) {
return result
}

const { totalCount = 0, list: martImportedReports = [] } =
data.martImportedReports

return (
<Fieldset
title="MART reports imported"
action={
<div className="float-end">
Number per page:
<FormSelect
defaultValue={pageSize}
onChange={e =>
changePageSize(parseInt(e.target.value, 10) || DEFAULT_PAGESIZE)}
>
{PAGESIZES.map(size => (
<option key={size} value={size}>
{size}
</option>
))}
</FormSelect>
</div>
}
>
{_isEmpty(martImportedReports) ? (
<em>No mart reports imported found</em>
) : (
<UltimatePaginationTopDown
componentClassName="searchPagination"
className="float-end"
pageNum={pageNum}
pageSize={pageSize}
totalCount={totalCount}
goToPage={setPageNum}
>
<Table striped hover responsive>
<thead>
<tr>
<th>Author</th>
<th>Report</th>
<th>Insert Date</th>
<th>Success?</th>
<th>Errors</th>
</tr>
</thead>
<tbody>
{martImportedReports.map((martImportedReport, index) => {
return (
<tr key={index}>
<td>
<LinkTo
modelType="Person"
model={martImportedReport.person}
/>
</td>
<td>
{martImportedReport.report && (
<LinkTo
modelType="Report"
model={martImportedReport.report}
/>
)}
</td>
<td>
{moment(martImportedReport.createdAt).format(
Settings.dateFormats.forms.displayLong.withTime
)}
</td>
<td>
<Icon
icon={
martImportedReport.success
? IconNames.TICK
: IconNames.CROSS
}
className={
martImportedReport.success
? "text-success"
: "text-danger"
}
/>
</td>
<td>
<div
dangerouslySetInnerHTML={{
__html: martImportedReport.errors
}}
/>
</td>
</tr>
)
})}
</tbody>
</Table>
</UltimatePaginationTopDown>
)}
</Fieldset>
)

function changePageSize(newPageSize) {
const newPageNum = Math.floor((pageNum * pageSize) / newPageSize)
setPageNum(newPageNum)
setPageSize(newPageSize)
}
}

export default connect(null, mapPageDispatchersToProps)(MartImportedReportsShow)
15 changes: 15 additions & 0 deletions client/tests/webdriver/baseSpecs/showMartImportedReports.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { expect } from "chai"
import ShowMartImportedReports from "../pages/showMartImportedReports.page"

describe("Show Mart imported reports page", () => {
describe("When on the Mart imported reports page we should see one imported report", () => {
it("We should see rows in the organizations table", async() => {
await ShowMartImportedReports.openAsAdminUser()
await (await ShowMartImportedReports.getTable()).waitForExist()
await (await ShowMartImportedReports.getTable()).waitForDisplayed()
expect(
await ShowMartImportedReports.getTableRows()
).to.have.lengthOf.above(0)
})
})
})
18 changes: 18 additions & 0 deletions client/tests/webdriver/pages/showMartImportedReports.page.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import Page from "./page"

const PAGE_URL = "/admin/martImportedReports"

class ShowMartImportedReports extends Page {
async openAsAdminUser() {
await super.openAsAdminUser(PAGE_URL)
}

async getTable() {
return browser.$("table")
}

async getTableRows() {
return (await this.getTable()).$$("tbody tr")
}
}
export default new ShowMartImportedReports()
5 changes: 5 additions & 0 deletions insertBaseData-psql.sql
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ TRUNCATE TABLE "emailAddresses" CASCADE;
TRUNCATE TABLE "entityAvatars" CASCADE;
TRUNCATE TABLE "jobHistory" CASCADE;
TRUNCATE TABLE "locationRelationships" CASCADE;
TRUNCATE TABLE "martImportedReports" CASCADE;
TRUNCATE TABLE "mergedEntities" CASCADE;
TRUNCATE TABLE "noteRelatedObjects" CASCADE;
TRUNCATE TABLE "notes" CASCADE;
Expand Down Expand Up @@ -1399,6 +1400,10 @@ INSERT INTO "attachmentRelatedObjects" ("attachmentUuid", "relatedObjectType", "
INSERT INTO "entityAvatars" ("relatedObjectType", "relatedObjectUuid", "attachmentUuid", "applyCrop", "cropLeft", "cropTop", "cropWidth", "cropHeight") VALUES
('people', '46ba6a73-0cd7-4efb-8e99-215e98cc5987', '3187ad8a-6130-4ec0-bffc-9ebfad4dee39', TRUE, 0, 0, 200, 200);

-- Add mart imported reports for testing
INSERT INTO "martImportedReports" ("personUuid", "reportUuid", "success", "createdAt", "errors") VALUES
('87fdbc6a-3109-4e11-9702-a894d6ca31ef', '59be259b-30b9-4d04-9e21-e8ceb58cbe9c', TRUE, CURRENT_TIMESTAMP, NULL);

-- Update the link-text indexes
REFRESH MATERIALIZED VIEW CONCURRENTLY "mv_lts_attachments";
-- authorizationGroups currently have no links
Expand Down
5 changes: 5 additions & 0 deletions src/main/java/mil/dds/anet/AnetObjectEngine.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import mil.dds.anet.database.EntityAvatarDao;
import mil.dds.anet.database.JobHistoryDao;
import mil.dds.anet.database.LocationDao;
import mil.dds.anet.database.MartImportedReportDao;
import mil.dds.anet.database.NoteDao;
import mil.dds.anet.database.OrganizationDao;
import mil.dds.anet.database.PersonDao;
Expand Down Expand Up @@ -148,6 +149,10 @@ public EntityAvatarDao getEntityAvatarDao() {
return ApplicationContextProvider.getBean(EntityAvatarDao.class);
}

public MartImportedReportDao getMartImportedReportDao() {
return ApplicationContextProvider.getBean(MartImportedReportDao.class);
}

public CompletableFuture<Boolean> canUserApproveStep(GraphQLContext context, String userUuid,
String approvalStepUuid, String advisorOrgUuid) {
return new UuidFetcher<ApprovalStep>()
Expand Down
Loading
Loading