Skip to content

Commit

Permalink
1254 Create Artifact Record with 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]>
  • Loading branch information
TangoYankee and horatiorosa committed Jan 8, 2025
1 parent fe9fbbf commit 1cea764
Show file tree
Hide file tree
Showing 20 changed files with 533 additions and 72 deletions.
1 change: 1 addition & 0 deletions .github/workflows/production.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,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
4 changes: 2 additions & 2 deletions .github/workflows/qa.yml
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,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 @@ -104,7 +105,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 @@ -121,4 +122,3 @@ jobs:
--site ${{secrets.NETLIFY_SITE_ID}} \
--auth ${{secrets.NETLIFY_AUTH_TOKEN}} \
--message "${{ github.event.head_commit.message }}"
1 change: 1 addition & 0 deletions .github/workflows/staging.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,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
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
30 changes: 20 additions & 10 deletions client/app/components/projects/new.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -30,28 +30,38 @@
@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>
<saveableProjectsNewForm.SubmitButton @isEnabled={{saveableProjectsNewForm.isSubmittable}} data-test-save-button />
</saveableProjectsNewForm.PageNav>
<Messages::Assistance class="large-margin-top" />
<saveableProjectsNewForm.ConfirmationModal @action={{component saveableProjectsNewForm.SubmitButton
isEnabled=saveableProjectsNewForm.isSubmittable onClick=this.submitProject class="no-margin" }}
<saveableProjectsNewForm.ConfirmationModal @action={{component saveableProjectsNewForm.SubmitButton onClick=this.submitProject class="no-margin" }}
disabled={{or (not saveableProjectsNewForm.isSubmittable) this.submissionError}}
@continueButtonTitle="Continue Editing" data-test-confirm-submit-button={{true}}>
<h6>Confirm New Project Submission</h6>
<p class="header-large medium-margin-top small-margin-bottom">
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.
</p>
{{#if this.submissionError}}
<p class="header-large medium-marin-top small-margin-bottom">Error while creating project. Lorem Ipsum.....</p>
{{else}}
<p class="header-large medium-margin-top small-margin-bottom">
Are you sure?
</p>
<p>
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>
{{/if}}
</saveableProjectsNewForm.ConfirmationModal>
</div>
</div>
Expand Down
31 changes: 27 additions & 4 deletions client/app/components/projects/new.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import Component from '@glimmer/component';
import { action } from '@ember/object';
import { tracked } from '@glimmer/tracking';
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 = {
SubmittableProjectsNewForm,
};

@tracked submissionError = false;

@service
router;

Expand All @@ -26,6 +30,7 @@ export default class ProjectsNewFormComponent extends Component {

@action
async submitProject() {
this.submissionError = false;
const primaryContactInput = {
first: this.args.package.primaryContactFirstName,
last: this.args.package.primaryContactLastName,
Expand All @@ -42,6 +47,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,17 +112,24 @@ 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.router.transitionTo('project', project.id);

const artifactsId = project.attributes['dcp-artifactsid'];
if (artifactsId === undefined) {
throw new Error('failed to create project with artifact');
}

await this.args.package.saveAttachedFiles(artifactsId);

this.router.transitionTo('projects');
} catch {
/* eslint-disable-next-line no-console */
console.error('Error while creating project');
this.submissionError = true;
}
}
}
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 listed 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 at least 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;
}
}
}
Loading

0 comments on commit 1cea764

Please sign in to comment.