Skip to content

Commit

Permalink
1254 Create Artifact Record as part of Project Creation
Browse files Browse the repository at this point in the history
 - Added artifact creation endpoint and returning artifact id from server to client
 - Create a form through which users may submit and attach documents (client)
 - Added validation for required submission of project documents
Co-authored-by: horatio <[email protected]>
Co-authored-by: tangoyankee <[email protected]>
  • Loading branch information
TangoYankee authored and horatiorosa committed Dec 3, 2024
1 parent 37cdcb0 commit 68b681e
Show file tree
Hide file tree
Showing 20 changed files with 492 additions and 59 deletions.
5 changes: 3 additions & 2 deletions .github/workflows/production.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ jobs:
deploy-server:
name: 🚀 Deploy server
needs: test-client
environment:
environment:
name: production
url: https://applicants-api.nycplanningdigital.com
runs-on: ubuntu-latest
Expand Down Expand Up @@ -75,6 +75,7 @@ jobs:
HD_PAYMENT_IP_RANGE: ${{ secrets.PAYMENT_IP_RANGE }}
HD_PAYMENT_STEP1_URL: ${{ secrets.PAYMENT_STEP1_URL }}
HD_RER_FILETYPE_UUID: ${{ secrets.RER_FILETYPE_UUID }}
HD_LETTER_FILETYPE_UUID: ${{ secrets.LETTER_FILETYPE_UUID }}
HD_SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
HD_SHAREPOINT_CLIENT_ID: ${{ secrets.SHAREPOINT_CLIENT_ID }}
HD_SHAREPOINT_CLIENT_SECRET: ${{ secrets.SHAREPOINT_CLIENT_SECRET }}
Expand Down Expand Up @@ -107,7 +108,7 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: 12.x
- name: Install application dependencies
- name: Install application dependencies
working-directory: client
run: yarn install --immutable --immutable-cache --check-cache
- name: Build client
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/qa.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ jobs:
HD_PAYMENT_IP_RANGE: ${{ secrets.PAYMENT_IP_RANGE }}
HD_PAYMENT_STEP1_URL: ${{ secrets.PAYMENT_STEP1_URL }}
HD_RER_FILETYPE_UUID: ${{ secrets.RER_FILETYPE_UUID }}
HD_LETTER_FILETYPE_UUID: ${{ secrets.LETTER_FILETYPE_UUID }}
HD_SHAREPOINT_CLIENT_ID: ${{ secrets.SHAREPOINT_CLIENT_ID }}
HD_SHAREPOINT_CLIENT_SECRET: ${{ secrets.SHAREPOINT_CLIENT_SECRET }}
HD_SHAREPOINT_CRM_SITE: ${{ secrets.SHAREPOINT_CRM_SITE }}
Expand Down Expand Up @@ -102,7 +103,7 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: 12.x
- name: Install application dependencies
- name: Install application dependencies
working-directory: client
run: yarn install --immutable --immutable-cache --check-cache
- name: Build client
Expand All @@ -119,4 +120,3 @@ jobs:
--site ${{secrets.NETLIFY_SITE_ID}} \
--auth ${{secrets.NETLIFY_AUTH_TOKEN}} \
--message "${{ github.event.head_commit.message }}"
3 changes: 2 additions & 1 deletion .github/workflows/staging.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ jobs:
HD_NYCID_TOKEN_SECRET: ${{ secrets.NYCID_TOKEN_SECRET }}
HD_PAPERTRAIL_API_TOKEN: ${{ secrets.PAPERTRAIL_API_TOKEN }}
HD_RER_FILETYPE_UUID: ${{ secrets.RER_FILETYPE_UUID }}
HD_LETTER_FILETYPE_UUID: ${{ secrets.LETTER_FILETYPE_UUID }}
HD_SHAREPOINT_CLIENT_ID: ${{ secrets.SHAREPOINT_CLIENT_ID }}
HD_SHAREPOINT_CLIENT_SECRET: ${{ secrets.SHAREPOINT_CLIENT_SECRET }}
HD_SHAREPOINT_CRM_SITE: ${{ secrets.SHAREPOINT_CRM_SITE }}
Expand Down Expand Up @@ -94,7 +95,7 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: 12.x
- name: Install application dependencies
- name: Install application dependencies
working-directory: client
run: yarn install
- name: Build client
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
@attribute='documents'
@validation={{@form.errors.documents.validation}}
/>

<Packages::Attachments
@package={{@form.data}}
@fileManager={{@model.fileManager}}
Expand Down
12 changes: 9 additions & 3 deletions client/app/components/projects/new.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,17 @@
@selectedApplicantType={{this.selectedApplicantType}}
@applicantOptions={{this.applicantOptions}}
/>

<Projects::ProjectsNewAddContacts
@form={{saveableProjectsNewForm}} />

<Projects::ProjectsNewProjectDescription
@form={{saveableProjectsNewForm}} />

<Projects::ProjectsNewAttachedDocuments
@artifact={{saveableProjectsNewForm.data}}
@form={{saveableProjectsNewForm}}
@model={{@package}} />
</div>
<div class="cell large-4 sticky-sidebar">
<saveableProjectsNewForm.PageNav>
Expand All @@ -48,9 +55,8 @@
Are you sure?
</p>
<p>
Before submitting, ensure that your answers are accurate and complete, and that necessary attachments (including
the
signature form) have been uploaded.
Before submitting, ensure that your answers are accurate and complete, and that necessary attachments have been uploaded. If NYC Planning does not receive enough accurate information to provide guidance, the Lead Planner will notify you and request that this form be resubmitted with necessary materials, corrections, or
clarifications.
</p>
</saveableProjectsNewForm.ConfirmationModal>
</div>
Expand Down
17 changes: 16 additions & 1 deletion client/app/components/projects/new.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { inject as service } from '@ember/service';
import SubmittableProjectsNewForm from '../../validations/submittable-projects-new-form';
import { optionset } from '../../helpers/optionset';
import config from '../../config/environment';
import validateFileUpload from '../../validators/validate-file-presence';

export default class ProjectsNewFormComponent extends Component {
validations = {
Expand Down Expand Up @@ -42,6 +43,17 @@ export default class ProjectsNewFormComponent extends Component {

const contactInputs = [primaryContactInput, applicantInput];

const validationResult = validateFileUpload(
{
message: 'Please upload at least one file before submitting.',
},
)('documents', this.args.package.documents);

if (validationResult !== true) {
this.errorMessage = validationResult;
return;
}

try {
const contactPromises = contactInputs.map((contact) => this.store.queryRecord('contact', {
email: contact.email,
Expand Down Expand Up @@ -96,13 +108,16 @@ export default class ProjectsNewFormComponent extends Component {
dcpApplicanttype: this.args.package.applicantType.code,
dcpProjectbrief: this.args.package.projectBrief,
_dcpApplicantadministratorCustomerValue:
verifiedPrimaryContact.id,
verifiedPrimaryContact.id,
_dcpApplicantCustomerValue: verifiedApplicant.id,
},
},
}),
});
const { data: project } = await response.json();

this.args.package.saveAttachedFiles(project.attributes['dcp-artifactsid']);

this.router.transitionTo('project', project.id);
} catch {
/* eslint-disable-next-line no-console */
Expand Down
23 changes: 23 additions & 0 deletions client/app/components/projects/projects-new-attached-documents.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{{#let @form as |form|}}
<@form.Section @title='Attached Documents'>
<p>
Please attach the required items list on the
<Ui::ExternalLink @href="https://www.nyc.gov/assets/planning/download/pdf/applicants/applicant-portal/interest_checklist.pdf">
Informational Interest Meeting checklist
</Ui::ExternalLink> &nbsp;in one PDF document. The maximum
size for a document is 50MB.
</p>

<SaveableForm::FieldValidationMessage
@attribute='documents'
@validation={{@form.errors.documents.validation}} />

<Projects::ProjectsNewAttachments
@package={{@form.data}}
@artifact={{@artifact}}
@fileManager={{@model.fileManager}}
@attribute="documents"
data-test-section='attachments' />
</@form.Section>

{{/let}}
133 changes: 133 additions & 0 deletions client/app/components/projects/projects-new-attachments.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
<div ...attributes>
<fieldset class="fieldset">
<Ui::Legend>
Attachments
<Ui::RequiredAsterisk />
</Ui::Legend>

<ul class="no-bullet">
{{#each @fileManager.existingFiles as |file idx|}}
<li class="slide-in-bottom">
<div class="grid-x">
<div class="cell auto">
<a
href={{concat (get-env-variable 'host') '/documents' file.serverRelativeUrl}}
target="_blank"
rel="noopener noreferrer"
data-test-document-name={{idx}}
>
{{file.name}}
</a>
</div>
<div class="cell shrink medium-padding-left">
<small>
{{file.timeCreated}}
</small>
</div>
<div class="cell shrink medium-padding-left">
<button
type="button"
class="text-red-dark"
{{on "click" (fn this.markFileForDeletion file)}}
data-test-delete-file-button={{idx}}
>
<FaIcon @icon="times" @prefix="fas" @fixedWidth={{true}} />
</button>
</div>
</div>
</li>
{{/each}}
</ul>

{{#if (or @fileManager.filesToDelete @fileManager.filesToUpload.files)}}
{{#if @fileManager.existingFiles}}
<hr>
{{/if}}

<h6 class="medium-margin-bottom slide-in-top">
To be
{{if @fileManager.filesToUpload.files 'uploaded'}}
{{if (and @fileManager.filesToDelete @fileManager.filesToUpload.files) '/'}}
{{if @fileManager.filesToDelete 'deleted'}}
when you save the project:
</h6>
{{/if}}

<ul class="no-bullet">
{{#each @fileManager.filesToDelete as |file idx|}}
<li class="slide-in-top">
<div class="grid-x">
<div class="cell auto">
<b
data-test-document-to-be-deleted-name={{idx}}
>
{{file.name}}
</b>
</div>
<div class="cell shrink medium-padding-left">
<small>
TO BE DELETED
</small>
</div>
<div class="cell shrink medium-padding-left">
<button
type="button"
{{on "click" (fn this.unmarkFileForDeletion file)}}
data-test-undo-delete-file-button={{idx}}
>
<FaIcon @icon="undo" @prefix="fas" @fixedWidth={{true}} />
</button>
</div>
</div>
</li>
{{/each}}
</ul>

<ul class="no-bullet">
{{#each @fileManager.filesToUpload.files as |file idx|}}
<li class="slide-in-top">
<div class="grid-x">
<div class="cell auto">
<b
data-test-document-to-be-uploaded-name={{idx}}
>
{{file.name}}
</b>
</div>
<div class="cell shrink medium-padding-left">
<small>
TO BE ADDED
</small>
</div>
<div class="cell shrink medium-padding-left">
<button
type="button"
class="text-red-dark"
{{on "click" (fn this.deselectFileForUpload file)}}
data-test-deselect-file-button={{idx}}
>
<FaIcon @icon="times" @prefix="fas" @fixedWidth={{true}} />
</button>
</div>
</div>
</li>
{{/each}}
</ul>

<FileUpload
id={{concat "FileUploader" @artifact.id}}
@name={{concat "artifact" @artifact.id}}
@accept='*/*'
@multiple={{true}}
@onfileadd={{this.trackFileForUpload}}
class="button secondary expanded"
>
<FaIcon @icon="upload" @prefix="fas" />
Choose Files
</FileUpload>

<p class="text-small tiny-margin-bottom text-dark-gray">
The size limit for each file is 50 MB. You can upload up to 1 GB of files.
</p>
</fieldset>
</div>
63 changes: 63 additions & 0 deletions client/app/components/projects/projects-new-attachments.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import Component from '@glimmer/component';
import { action } from '@ember/object';
import validateFileUpload from '../../validators/validate-file-presence';

/**
* This component wires a fileManager to the attachments UI.
* @param {Artifact Model} artifact
*/
export default class ProjectsNewAttachmentsComponent extends Component {
get fileManager() {
// should be an instance of FileManager
return this.args.fileManager;
}

@action
markFileForDeletion(file) {
this.fileManager.markFileForDeletion(file);

this.args.package.documents = this.fileManager.existingFiles;
}

@action
unmarkFileForDeletion(file) {
this.fileManager.unMarkFileForDeletion(file);
}

// This action doesn't perform any file selection.
// That part is automatically handled by the
// ember-file-upload addon.
// Here we manually increment the number of files to
// upload to update the fileManager isDirty state.
@action
trackFileForUpload() {
this.fileManager.trackFileForUpload();
this.args.package.documents = [
...this.args.package.documents,
...this.fileManager.filesToUpload.files,
];
}

@action
deselectFileForUpload(file) {
this.fileManager.deselectFileForUpload(file);

this.args.package.documents = this.args.package.documents.filter(
(document) => document !== file,
);
}

@action
validateFilePresence() {
const validationResult = validateFileUpload({ message: 'One or more document uploads is required.' })(
'documents',
this.fileManager.filesToUpload.files,
);

if (validationResult !== true) {
this.errorMessage = validationResult;
} else {
this.errorMessage = null;
}
}
}
22 changes: 11 additions & 11 deletions client/app/components/projects/projects-new-project-description.hbs
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
{{#let @form as |form|}}
<form.Section @title="Project Description">
<Ui::Question @required={{true}} as |Q|>
<Q.Label>
Please replace information in the brackets to the best of your ability.
</Q.Label>
<Ui::Question @required={{true}} as |Q|>
<Q.Label>
Please replace information in the brackets to the best of your ability.
</Q.Label>

<form.Field
@attribute="projectBrief"
@type="text-area"
id={{Q.questionId}}
@maxlength='500'
value={{"A [action(s)] [ZR#'s for ZR, ZS, ZA] to facilitate a [new] [# of max stories], [total zsf, (# DU's)], [use] development, including [sf for each use, sf open space], is being sought by [public/private] [applicant] at [address] in [neighborhood], [Community District], [Borough]."}} />
</Ui::Question>
<form.Field
@attribute="projectBrief"
@type="text-area"
id={{Q.questionId}}
@maxlength='500'
value={{"A [action(s)] [ZR#'s for ZR, ZS, ZA] to facilitate a [new] [# of max stories], [total zsf, (# DU's)], [use] development, including [sf for each use, sf open space], is being sought by [public/private] [applicant] at [address] in [neighborhood], [Community District], [Borough]."}} />
</Ui::Question>
</form.Section>
{{/let}}
Loading

0 comments on commit 68b681e

Please sign in to comment.