Skip to content

Commit

Permalink
feat: release 2023-03-28 (#1593)
Browse files Browse the repository at this point in the history
* feat: add user csv export (#1560)

* feat: listing export (#1548)

* feat: starting listing export

* fix: wip building listing query

* fix: csv support

* fix: adding column definitions wip

* fix: wip address query

* fix: updates to fix query

* fix: wip listing selects

* fix: reformatted testing output

* fix: completed select statements

* fix: exporter service wip

* fix: a11y features formatting

* fix: wip adding to exporter

* fix: wip lottery info formatting

* fix: wip date formatting

* fix: listings exporter service fields (95%)

* fix: complete units csv building

* fix: functioning zip download

* fix: req.user type casting

* fix: refactor to config approach

* fix: various csv bugs

* fix: debugging column values

* fix: user access array

* fix: clean up

* fix: clean up pt 2

* fix: ui content and error handling

* fix: ui content and error handling actually

* fix: cleanup + cypress test

* fix: cypress test and naming clean up

* fix: unit test draft

* fix: error handling fix

* fix: removed unused seed

* fix: functional cypress and wip unit tests

* fix: one functional unit test

* fix: functional unit tests

* fix: wip error test

* fix: error message test

* fix: wip module mocking

* fix: generateAsnyc readded to test

* fix: controller cleanup

* fix: functional success message testing

* fix: testing cleanup

* fix: further testing and comment cleanup

* fix: toast timeout

* fix: clean up formatting

* fix: column cleanup

* fix: console log cleanup

* fix: rebase cleanup

* fix: wip bug fixes

* fix: wip data bug resolutions

* fix: unit bug fixes

* fix: formatting contd

* fix: clean up

* fix: button focus issue

* fix: testing repository error

* fix: include csv exporter service in test

* fix: refined testing fix

* fix: button color correction

* fix: align sizing and export language

---------

Co-authored-by: Yazeed Loonat <[email protected]>

* fix: listings export clean up (#1590)

* fix: local import

* fix: unit tests fix

* fix: add aria label to min/max rent fields (#1582)

* fix: remove the due date question (#1584)

* 1525/user access issue (#1592)

* fix: include admin who are not partners

* fix: correct user seed

* fix: remove admin from permissions column

* fix: testing cleanup

* fix: change test location

* fix: add undefined check

* fix: add aria label to table (#1585)

* fix: add aria label to table

* fix: add aria labels to detail tables

* fix: unit labeling on detailed page

* fix: add aria-label to grouped table

* fix: add descriptive alt tags to listings images (#1587)

* fix: add descriptive alt tags to listings images

* fix: add empty value to alt text

* fix: remove redundant photo string

* fix: remove redundant image string from translations

* fix: change min/max rent inputs from number to text with only numbers (#1599)

* fix: resolve bugs from bash (#1597)

* fix: resolve bugs from bash

* fix: paper app helper

* fix: refine helper

* fix: user export fixes (#1600)

* fix: user export fixes

* fix: fix tests

* fix: add cypress downloads to gitignore file

* fix: release scrub (#1604)

* fix: align csv error messages

* fix: timezone temporary approach

* fix: align application export

* fix: formatting case consistency

* fix: missing UTC

* feat: focus title on modal open (#1558)

* feat: focus title on modal open

* feat: bring back id to h1 in modal header

* fix: add a11y statement (#1601)

* fix: added english a11y statement

* fix: formatting feedback

* fix: spanish added

* fix: add ar and bn

* fix: remove unused div and styling

---------

Co-authored-by: ColinBuyck <[email protected]>
Co-authored-by: Yazeed Loonat <[email protected]>
Co-authored-by: Krzysztof Zięcina <[email protected]>
Co-authored-by: Emily Jablonski <[email protected]>
  • Loading branch information
5 people authored Mar 28, 2023
1 parent 8051ae7 commit b6fcd32
Show file tree
Hide file tree
Showing 59 changed files with 2,169 additions and 335 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ storybook-static
# Cypress test output videos
**/cypress/videos
**/cypress/screenshots
**/cypress/downloads

# Complied Typescript
dist
Expand Down
96 changes: 49 additions & 47 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,65 +1,67 @@
language: node_js
node_js:
- 14
- 14
cache:
yarn: true
services:
- redis-server
- redis-server
before_install:
- sudo sed -i -e '/local.*peer/s/postgres/all/' -e 's/peer\|md5/trust/g' /etc/postgresql/*/main/pg_hba.conf
- sudo systemctl restart postgresql@11-main
- sleep 1
- sudo sed -i -e '/local.*peer/s/postgres/all/' -e 's/peer\|md5/trust/g' /etc/postgresql/*/main/pg_hba.conf
- sudo systemctl restart postgresql@11-main
- sleep 1
before_script:
- cp sites/public/.env.template sites/public/.env
- cp sites/partners/.env.template sites/partners/.env
- cp backend/core/.env.template backend/core/.env
- cp sites/public/.env.template sites/public/.env
- cp sites/partners/.env.template sites/partners/.env
- cp backend/core/.env.template backend/core/.env
jobs:
include:
- script: yarn build:app:public
name: Build public site
- script: yarn build:app:partners
name: Build partners site
- script: yarn test:backend:core:testdbsetup && yarn test:backend:core
name: Backend unit tests
- script: yarn test:e2e:backend:core
name: Backend e2e tests
- script: yarn test:app:public:unit
name: Public site unit tests
- stage: longer tests
name: Partners site Cypress tests
script:
- yarn cypress install
- cd backend/core
- yarn db:reseed:detroit
- yarn nest start &
- cd ../../sites/partners
- yarn build
- yarn start -p 3001 &
- yarn wait-on "http-get://localhost:3001" && yarn cypress run
- kill $(jobs -p) || true
- stage: longer tests
name: Public site Cypress tests
script:
- yarn cypress install
- yarn db:reseed
- cd backend/core
- yarn nest start &
- cd ../../sites/public
- yarn build
- yarn start -p 3000 &
- yarn wait-on "http-get://localhost:3000" && yarn cypress run
- kill $(jobs -p) || true
- script: yarn build:app:public
name: Build public site
- script: yarn build:app:partners
name: Build partners site
- script: yarn test:backend:core:testdbsetup && yarn test:backend:core
name: Backend unit tests
- script: yarn test:e2e:backend:core
name: Backend e2e tests
- script: yarn test:app:public:unit
name: Public site unit tests
- script: yarn test:app:partners:unit
name: Partners site unit tests
- stage: longer tests
name: Partners site Cypress tests
script:
- yarn cypress install
- cd backend/core
- yarn db:reseed:detroit
- yarn nest start &
- cd ../../sites/partners
- yarn build
- yarn start -p 3001 &
- yarn wait-on "http-get://localhost:3001" && yarn cypress run
- kill $(jobs -p) || true
- stage: longer tests
name: Public site Cypress tests
script:
- yarn cypress install
- yarn db:reseed
- cd backend/core
- yarn nest start &
- cd ../../sites/public
- yarn build
- yarn start -p 3000 &
- yarn wait-on "http-get://localhost:3000" && yarn cypress run
- kill $(jobs -p) || true
addons:
postgresql: '11'
postgresql: "11"
apt:
packages:
- postgresql-11
- postgresql-client-11
- libgconf-2-4
- postgresql-11
- postgresql-client-11
- libgconf-2-4
env:
global: PGPORT=5433
PGUSER=travis
TEST_DATABASE_URL=postgres://localhost:5433/bloom_test
REDIS_TLS_URL=redis://127.0.0.1:6379/0
NEW_RELIC_ENABLED=false
NEW_RELIC_LOG_ENABLED=false
NEW_RELIC_LOG_ENABLED=false
1 change: 1 addition & 0 deletions backend/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
"ioredis": "^5.2.3",
"joi": "^17.3.0",
"jwt-simple": "^0.5.6",
"jszip": "^3.10.1",
"lodash": "^4.17.21",
"mapbox": "^1.0.0-beta10",
"nanoid": "^3.1.12",
Expand Down
2 changes: 0 additions & 2 deletions backend/core/src/applications/applications.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import { ApplicationDto } from "./dto/application.dto"
import { ValidationsGroupsEnum } from "../shared/types/validations-groups-enum"
import { defaultValidationPipeOptions } from "../shared/default-validation-pipe-options"
import { applicationPreferenceApiExtraModels } from "./types/application-preference-api-extra-models"
import { ListingsService } from "../listings/listings.service"
import { ApplicationCsvExporterService } from "./services/application-csv-exporter.service"
import { ApplicationsService } from "./services/applications.service"
import { ActivityLogInterceptor } from "../activity-log/interceptors/activity-log.interceptor"
Expand Down Expand Up @@ -49,7 +48,6 @@ import { IdDto } from "../shared/dto/id.dto"
export class ApplicationsController {
constructor(
private readonly applicationsService: ApplicationsService,
private readonly listingsService: ListingsService,
private readonly applicationCsvExporter: ApplicationCsvExporterService
) {}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,8 +148,8 @@ export class ApplicationCsvExporterService {
app.application_submission_type === "electronical"
? "electronic"
: app.application_submission_type,
"Application Submission Date": dayjs(app.application_submission_date).format(
"MM-DD-YYYY h:mm:ssA"
"Application Submission Date (UTC)": dayjs(app.application_submission_date).format(
"MM-DD-YYYY hh:mm:ssA"
),
"Primary Applicant First Name": app.applicant_first_name,
"Primary Applicant Middle Name": app.applicant_middle_name,
Expand Down
4 changes: 4 additions & 0 deletions backend/core/src/auth/auth.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import { UserPreferencesController } from "./controllers/user-preferences.contro
import { UserPreferencesService } from "./services/user-preferences.services"
import { UserPreferences } from "./entities/user-preferences.entity"
import { UserRepository } from "./repositories/user-repository"
import { UserCsvExporterService } from "./services/user-csv-exporter.service"
import { CsvBuilder } from "../applications/services/csv-builder.service"

@Module({
imports: [
Expand Down Expand Up @@ -62,6 +64,8 @@ import { UserRepository } from "./repositories/user-repository"
PasswordService,
SmsMfaService,
UserPreferencesService,
CsvBuilder,
UserCsvExporterService,
],
exports: [AuthzService, AuthService, UserService, UserPreferencesService],
controllers: [AuthController, UserController, UserProfileController, UserPreferencesController],
Expand Down
2 changes: 2 additions & 0 deletions backend/core/src/auth/controllers/user.controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { UserService } from "../services/user.service"
import { AuthzService } from "../services/authz.service"
import { ActivityLogService } from "../../activity-log/services/activity-log.service"
import { EmailService } from "../../email/email.service"
import { UserCsvExporterService } from "../services/user-csv-exporter.service"

// Cypress brings in Chai types for the global expect, but we want to use jest
// expect here so we need to re-declare it.
Expand All @@ -22,6 +23,7 @@ describe("User Controller", () => {
{ provide: AuthService, useValue: {} },
{ provide: AuthzService, useValue: {} },
{ provide: UserService, useValue: {} },
{ provide: UserCsvExporterService, useValue: {} },
{ provide: EmailService, useValue: {} },
{ provide: ActivityLogService, useValue: {} },
],
Expand Down
29 changes: 28 additions & 1 deletion backend/core/src/auth/controllers/user.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
Controller,
Delete,
Get,
Header,
Param,
Post,
Put,
Expand Down Expand Up @@ -45,14 +46,19 @@ import { DefaultAuthGuard } from "../guards/default.guard"
import { UserProfileAuthzGuard } from "../guards/user-profile-authz.guard"
import { ActivityLogInterceptor } from "../../activity-log/interceptors/activity-log.interceptor"
import { IdDto } from "../../shared/dto/id.dto"
import { UserCsvExporterService } from "../services/user-csv-exporter.service"
import { Compare } from "../../shared/dto/filter.dto"

@Controller("user")
@ApiBearerAuth()
@ApiTags("user")
@ResourceType("user")
@UsePipes(new ValidationPipe(defaultValidationPipeOptions))
export class UserController {
constructor(private readonly userService: UserService) {}
constructor(
private readonly userService: UserService,
private readonly userCsvExporter: UserCsvExporterService
) {}

@Get()
@UseGuards(DefaultAuthGuard, UserProfileAuthzGuard)
Expand Down Expand Up @@ -158,6 +164,27 @@ export class UserController {
)
}

@Get("/csv")
@UseGuards(OptionalAuthGuard, AuthzGuard)
@ApiOperation({ summary: "List users in CSV", operationId: "listAsCsv" })
@Header("Content-Type", "text/csv")
async listAsCsv(@Request() req: ExpressRequest): Promise<string> {
const users = await this.userService.list(
{
page: 1,
limit: 300,
filter: [
{
isPortalUser: true,
$comparison: Compare["="],
},
],
},
new AuthContext(req.user as User)
)
return this.userCsvExporter.exportFromObject(users)
}

@Post("/invite")
@UseGuards(OptionalAuthGuard, AuthzGuard)
@ApiOperation({ summary: "Invite user", operationId: "invite" })
Expand Down
36 changes: 36 additions & 0 deletions backend/core/src/auth/services/user-csv-exporter.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Injectable, Scope } from "@nestjs/common"
import dayjs from "dayjs"
import { Pagination } from "nestjs-typeorm-paginate"
import { CsvBuilder } from "../../applications/services/csv-builder.service"
import { User } from "../entities/user.entity"

@Injectable({ scope: Scope.REQUEST })
export class UserCsvExporterService {
constructor(private readonly csvBuilder: CsvBuilder) {}

exportFromObject(users: Pagination<User>): string {
const userObj = users.items.reduce((obj, user) => {
const status = []
if (user.roles?.isAdmin) {
status.push("Administrator")
}
if (user.roles?.isPartner) {
status.push("Partner")
}
obj[user.id] = {
"First Name": user.firstName,
"Last Name": user.lastName,
Email: user.email,
Role: status.join(", "),
"Date Created (UTC)": dayjs(user.createdAt).format("MM-DD-YYYY hh:mmA"),
Status: user.confirmedAt ? "Confirmed" : "Unconfirmed",
"Listing Names":
user.leasingAgentInListings?.map((listing) => listing.name).join(", ") || "",
"Listing Ids": user.leasingAgentInListings?.map((listing) => listing.id).join(", ") || "",
"Last Logged In (UTC)": dayjs(user.lastLoginAt).format("MM-DD-YYYY hh:mmA"),
}
return obj
}, {})
return this.csvBuilder.buildFromIdIndex(userObj)
}
}
98 changes: 98 additions & 0 deletions backend/core/src/listings/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import dayjs from "dayjs"
import { MinMax } from "../../types"
import { UnitGroupAmiLevelDto } from "../../src/units-summary/dto/unit-group-ami-level.dto"
import { PaperApplication } from "../../src/paper-applications/entities/paper-application.entity"

export const isDefined = (item: number | string): boolean => {
return item !== null && item !== undefined && item !== ""
}

export const cloudinaryPdfFromId = (publicId: string): string => {
if (isDefined(publicId)) {
const cloudName = process.env.cloudinaryCloudName || process.env.CLOUDINARY_CLOUD_NAME
return `https://res.cloudinary.com/${cloudName}/image/upload/${publicId}.pdf`
} else return ""
}

export const formatDate = (rawDate: string, format: string): string => {
if (isDefined(rawDate)) {
return dayjs(rawDate).format(format)
} else return ""
}

export const getPaperAppUrls = (paperApps: PaperApplication[]) => {
if (!paperApps || paperApps?.length === 0) return ""
const urlArr = paperApps.map((paperApplication) =>
cloudinaryPdfFromId(paperApplication.file?.fileId)
)
const formattedResults = urlArr.join(", ")
return formattedResults
}

export const getRentTypes = (amiLevels: UnitGroupAmiLevelDto[]): string => {
if (!amiLevels || amiLevels?.length === 0) return ""
const uniqueTypes = []
amiLevels?.forEach((elem) => {
if (!uniqueTypes.includes(elem.monthlyRentDeterminationType))
uniqueTypes.push(elem.monthlyRentDeterminationType)
})
const formattedResults = uniqueTypes.map((elem) => convertToTitleCase(elem)).join(", ")
return formattedResults
}

export const formatYesNo = (value: boolean | null): string => {
if (value === null || typeof value == "undefined") return ""
else if (value) return "Yes"
else return "No"
}

export const formatStatus = {
active: "Public",
pending: "Draft",
}

export const formatBedroom = {
oneBdrm: "1 BR",
twoBdrm: "2 BR",
threeBdrm: "3 BR",
fourBdrm: "4 BR",
fiveBdrm: "5 BR",
studio: "Studio",
}

export const formatCurrency = (value: string): string => {
return value ? `$${value}` : ""
}

export const convertToTitleCase = (value: string): string => {
if (!isDefined(value)) return ""
const spacedValue = value.replace(/([A-Z])/g, (match) => ` ${match}`)
const result = spacedValue.charAt(0).toUpperCase() + spacedValue.slice(1)
return result
}

export const formatRange = (
min: string | number,
max: string | number,
prefix: string,
postfix: string
): string => {
if (!isDefined(min) && !isDefined(max)) return ""
if (min == max || !isDefined(max)) return `${prefix}${min}${postfix}`
if (!isDefined(min)) return `${prefix}${max}${postfix}`
return `${prefix}${min}${postfix} - ${prefix}${max}${postfix}`
}

export function formatRentRange(rent: MinMax, percent: MinMax): string {
let toReturn = ""
if (rent) {
toReturn += formatRange(rent.min, rent.max, "", "")
}
if (rent && percent) {
toReturn += ", "
}
if (percent) {
toReturn += formatRange(percent.min, percent.max, "", "%")
}
return toReturn
}
Loading

0 comments on commit b6fcd32

Please sign in to comment.