diff --git a/.github/workflows/production.yml b/.github/workflows/production.yml
index d85a680f..f47bed15 100644
--- a/.github/workflows/production.yml
+++ b/.github/workflows/production.yml
@@ -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
@@ -36,6 +36,8 @@ jobs:
- uses: actions/checkout@v4
with:
sparse-checkout: server
+ - name: 'Install Heroku CLI'
+ run: curl https://cli-assets.heroku.com/install.sh | sh
- uses: akhileshns/heroku-deploy@v3.13.15
name: Deploy server to Heroku
with:
@@ -75,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 }}
@@ -107,7 +110,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
diff --git a/.github/workflows/qa.yml b/.github/workflows/qa.yml
index b6c79aad..59c6860d 100644
--- a/.github/workflows/qa.yml
+++ b/.github/workflows/qa.yml
@@ -36,6 +36,8 @@ jobs:
- uses: actions/checkout@v4
with:
sparse-checkout: server
+ - name: 'Install Heroku CLI'
+ run: curl https://cli-assets.heroku.com/install.sh | sh
- uses: akhileshns/heroku-deploy@v3.13.15
name: Deploy server to Heroku
with:
@@ -72,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 }}
@@ -102,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
@@ -119,4 +122,3 @@ jobs:
--site ${{secrets.NETLIFY_SITE_ID}} \
--auth ${{secrets.NETLIFY_AUTH_TOKEN}} \
--message "${{ github.event.head_commit.message }}"
-
diff --git a/.github/workflows/staging.yml b/.github/workflows/staging.yml
index b2326ace..7a2c58ba 100644
--- a/.github/workflows/staging.yml
+++ b/.github/workflows/staging.yml
@@ -40,6 +40,8 @@ jobs:
with:
ref: 'main'
sparse-checkout: server
+ - name: 'Install Heroku CLI'
+ run: curl https://cli-assets.heroku.com/install.sh | sh
- uses: akhileshns/heroku-deploy@v3.13.15
name: Deploy server to Heroku
with:
@@ -63,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 }}
@@ -94,7 +97,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
diff --git a/client/app/components/packages/pas-form/attached-documents.hbs b/client/app/components/packages/pas-form/attached-documents.hbs
index ab10012c..4c3020d7 100644
--- a/client/app/components/packages/pas-form/attached-documents.hbs
+++ b/client/app/components/packages/pas-form/attached-documents.hbs
@@ -7,7 +7,6 @@
@attribute='documents'
@validation={{@form.errors.documents.validation}}
/>
-
+
+
+
+
diff --git a/client/app/components/projects/new.js b/client/app/components/projects/new.js
index 5ab960b9..032b8c91 100644
--- a/client/app/components/projects/new.js
+++ b/client/app/components/projects/new.js
@@ -1,15 +1,25 @@
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;
+
+ @tracked
+ isSubmitting = false;
+
+ requestCounter = 0;
+
@service
router;
@@ -26,6 +36,13 @@ export default class ProjectsNewFormComponent extends Component {
@action
async submitProject() {
+ this.submissionError = false;
+ if (this.isSubmitting) return;
+ this.isSubmitting = true;
+ const requestStartTime = Date.now();
+ this.requestCounter++;
+ console.log(`LOGGER: [Total Requests Made in the client controller] ${this.requestCounter}`);
+
const primaryContactInput = {
first: this.args.package.primaryContactFirstName,
last: this.args.package.primaryContactLastName,
@@ -42,6 +59,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,
@@ -96,17 +124,31 @@ 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 requestEndTime = Date.now();
+ console.debug(`LOGGER: POST request in the client controller to took ${requestEndTime - requestStartTime} ms`);
+ console.debug('response in client controller: ', response, 'response status: ', response.status);
+
+ 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 {
+ this.submissionError = true;
/* eslint-disable-next-line no-console */
console.error('Error while creating project');
+ } finally {
+ this.isSubmitting = false;
}
}
}
diff --git a/client/app/components/projects/projects-new-attached-documents.hbs b/client/app/components/projects/projects-new-attached-documents.hbs
new file mode 100644
index 00000000..60642d84
--- /dev/null
+++ b/client/app/components/projects/projects-new-attached-documents.hbs
@@ -0,0 +1,23 @@
+{{#let @form as |form|}}
+<@form.Section @title='Attached Documents'>
+
+ Please attach the required items listed on the
+
+ Informational Interest Meeting Checklist
+ in at least one PDF document. The maximum
+ size for a document is 50MB.
+
+
+
+
+
+@form.Section>
+
+{{/let}}
diff --git a/client/app/components/projects/projects-new-attachments.hbs b/client/app/components/projects/projects-new-attachments.hbs
new file mode 100644
index 00000000..f771f631
--- /dev/null
+++ b/client/app/components/projects/projects-new-attachments.hbs
@@ -0,0 +1,133 @@
+
+
+
+ Attachments
+
+
+
+
+ {{#each @fileManager.existingFiles as |file idx|}}
+
+
+
+
+
+ {{file.timeCreated}}
+
+
+
+
+
+
+
+
+
+ {{/each}}
+
+
+ {{#if (or @fileManager.filesToDelete @fileManager.filesToUpload.files)}}
+ {{#if @fileManager.existingFiles}}
+
+ {{/if}}
+
+
+ To be
+ {{if @fileManager.filesToUpload.files 'uploaded'}}
+ {{if (and @fileManager.filesToDelete @fileManager.filesToUpload.files) '/'}}
+ {{if @fileManager.filesToDelete 'deleted'}}
+ when you save the project:
+
+ {{/if}}
+
+
+ {{#each @fileManager.filesToDelete as |file idx|}}
+
+
+
+
+ {{file.name}}
+
+
+
+
+ TO BE DELETED
+
+
+
+
+
+
+
+
+
+ {{/each}}
+
+
+
+ {{#each @fileManager.filesToUpload.files as |file idx|}}
+
+
+
+
+ {{file.name}}
+
+
+
+
+ TO BE ADDED
+
+
+
+
+
+
+
+
+
+ {{/each}}
+
+
+
+
+ Choose Files
+
+
+
+ The size limit for each file is 50 MB. You can upload up to 1 GB of files.
+
+
+
diff --git a/client/app/components/projects/projects-new-attachments.js b/client/app/components/projects/projects-new-attachments.js
new file mode 100644
index 00000000..5e81ff89
--- /dev/null
+++ b/client/app/components/projects/projects-new-attachments.js
@@ -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;
+ }
+ }
+}
diff --git a/client/app/components/projects/projects-new-project-description.hbs b/client/app/components/projects/projects-new-project-description.hbs
index ecd8a503..f2a33b31 100644
--- a/client/app/components/projects/projects-new-project-description.hbs
+++ b/client/app/components/projects/projects-new-project-description.hbs
@@ -1,16 +1,16 @@
{{#let @form as |form|}}
-
-
-
- Please replace information in the brackets to the best of your ability.
-
+
+
+
+ Please replace information in the brackets to the best of your ability.
+
-
-
+
+
{{/let}}
diff --git a/client/app/models/artifact.js b/client/app/models/artifact.js
index f6baabaf..56667272 100644
--- a/client/app/models/artifact.js
+++ b/client/app/models/artifact.js
@@ -33,6 +33,9 @@ export default class ArtifactModel extends Model {
@belongsTo('project', { async: false })
project;
+ @belongsTo('project-new', { async: false })
+ projectNew
+
@attr()
dcpName;
diff --git a/client/app/models/project-new.js b/client/app/models/project-new.js
new file mode 100644
index 00000000..d49936a4
--- /dev/null
+++ b/client/app/models/project-new.js
@@ -0,0 +1,124 @@
+import Model, { attr, belongsTo } from '@ember-data/model';
+import { inject as service } from '@ember/service';
+import { tracked } from '@glimmer/tracking';
+import FileManager from '../services/file-manager';
+
+export default class ProjectNew extends Model {
+ createFileQueue() {
+ if (this.fileManager) {
+ this.fileManager.existingFiles = this.documents;
+ } else {
+ const fileQueue = this.fileQueue.create(`artifact${this.id}`);
+
+ this.fileManager = new FileManager(
+ this.id,
+ 'artifact',
+ this.documents,
+ [],
+ fileQueue,
+ this.session,
+ );
+ }
+ }
+
+ // Since file upload doesn't perform requests through
+ // an Ember Model save() process, it doesn't automatically
+ // hydrate the package.adapterError property. When an error occurs
+ // during upload we have to manually hydrate a custom error property
+ // to trigger the error box displayed to the user.
+ @tracked
+ fileUploadErrors = null;
+
+ @service
+ session;
+
+ @service
+ fileQueue;
+
+ @belongsTo('projects', { async: false })
+ projects;
+
+ @attr('string', {
+ defaultValue: '',
+ })
+ projectName;
+
+ @attr('string', {
+ defaultValue: '',
+ })
+ borough;
+
+ @attr('string', {
+ defaultValue: '',
+ })
+ applicantType;
+
+ @attr('string', {
+ defaultValue: '',
+ })
+ primaryContactFirstName;
+
+ @attr('string', {
+ defaultValue: '',
+ })
+ primaryContactLastName;
+
+ @attr('string', {
+ defaultValue: '',
+ })
+ primaryContactEmail;
+
+ @attr('string', {
+ defaultValue: '',
+ })
+ primaryContactPhone;
+
+ @attr('string', {
+ defaultValue: '',
+ })
+ applicantFirstName;
+
+ @attr('string', {
+ defaultValue: '',
+ })
+ applicantLastName;
+
+ @attr('string', {
+ defaultValue: '',
+ })
+ applicantEmail;
+
+ @attr('string', {
+ defaultValue: '',
+ })
+ applicantPhone;
+
+ @attr('string', {
+ defaultValue: '',
+ })
+ projectBrief;
+
+ @attr('string', {
+ defaultValue: () => [],
+ })
+ documents;
+
+ async saveAttachedFiles(instanceId) {
+ try {
+ await this.fileManager.save(instanceId);
+ } catch (e) {
+ console.log('Error saving files: ', e); // eslint-disable-line no-console
+
+ // See comment on the tracked fileUploadError property
+ // definition above.
+ this.fileUploadErrors = [
+ {
+ code: 'UPLOAD_DOC_FAILED',
+ title: 'Failed to upload documents',
+ detail:
+ 'An error occured while uploading your documents. Please refresh and retry.',
+ },
+ ];
+ }
+ }
+}
diff --git a/client/app/routes/projects/new.js b/client/app/routes/projects/new.js
index 731aa2d8..f49105d6 100644
--- a/client/app/routes/projects/new.js
+++ b/client/app/routes/projects/new.js
@@ -1,25 +1,21 @@
import Route from '@ember/routing/route';
import AuthenticatedRouteMixin from 'ember-simple-auth/mixins/authenticated-route-mixin';
+import { inject as service } from '@ember/service';
export default class ProjectsNewRoute extends Route.extend(
AuthenticatedRouteMixin,
) {
authenticationRoute = '/';
+ @service
+ store
+
async model() {
- return {
- projectName: '',
- borough: '',
- applicantType: '',
- primaryContactFirstName: '',
- primaryContactLastName: '',
- primaryContactEmail: '',
- primaryContactPhone: '',
- applicantFirstName: '',
- applicantLastName: '',
- applicantEmail: '',
- applicantPhone: '',
- projectBrief: '',
- };
+ const projectsNewForm = this.store.createRecord('project-new');
+
+ projectsNewForm.id = `new${self.crypto.randomUUID()}`;
+ projectsNewForm.createFileQueue();
+
+ return projectsNewForm;
}
}
diff --git a/client/app/services/file-manager.js b/client/app/services/file-manager.js
index c516b586..e27c748a 100644
--- a/client/app/services/file-manager.js
+++ b/client/app/services/file-manager.js
@@ -12,7 +12,10 @@ export default class FileManager {
filesToUpload, // EmberFileUpload Queue Object
session,
) {
- console.assert(entityType === 'package' || entityType === 'artifact', "entityType must be 'package' or 'artifact'");
+ console.assert(
+ entityType === 'package' || entityType === 'artifact',
+ "entityType must be 'package' or 'artifact'",
+ );
this.recordId = recordId;
this.entityType = entityType;
@@ -61,18 +64,24 @@ export default class FileManager {
this.numFilesToUpload -= 1;
}
- async uploadFiles() {
+ async uploadFiles(instanceId = this.recordId) {
for (let i = 0; i < this.filesToUpload.files.length; i += 1) {
- await this.filesToUpload.files[i].upload(`${ENV.host}/documents/${this.entityType}`, { // eslint-disable-line
- fileKey: 'file',
- headers: {
- Authorization: `Bearer ${this.session.data.authenticated.access_token}`,
+ // eslint-disable-next-line no-await-in-loop
+ await this.filesToUpload.files[i].upload(
+ `${ENV.host}/documents/${this.entityType}`,
+ {
+ // eslint-disable-line
+ fileKey: 'file',
+ headers: {
+ Authorization: `Bearer ${this.session.data.authenticated.access_token}`,
+ },
+ data: {
+ instanceId,
+ entityName:
+ this.entityType === 'artifact' ? 'dcp_artifacts' : 'dcp_package',
+ },
},
- data: {
- instanceId: this.recordId,
- entityName: this.entityType === 'artifact' ? 'dcp_artifacts' : 'dcp_package',
- },
- });
+ );
}
}
@@ -82,21 +91,24 @@ export default class FileManager {
// TODO: If this is not possible, rework this to be a
// POST request to a differently named endpoint, like
// deleteDocument
- return Promise.all(this.filesToDelete.map((file) => fetch(
- `${ENV.host}/documents?serverRelativeUrl=${file.serverRelativeUrl}`, {
- method: 'DELETE',
- credentials: 'include',
- headers: {
- 'Content-Type': 'application/json',
- Authorization: `Bearer ${this.session.data.authenticated.access_token}`,
+ return Promise.all(
+ this.filesToDelete.map((file) => fetch(
+ `${ENV.host}/documents?serverRelativeUrl=${file.serverRelativeUrl}`,
+ {
+ method: 'DELETE',
+ credentials: 'include',
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: `Bearer ${this.session.data.authenticated.access_token}`,
+ },
},
- },
- )));
+ )),
+ );
}
- async save() {
+ async save(instanceId) {
// See TODO at top of this file.
- await this.uploadFiles();
+ await this.uploadFiles(instanceId);
await this.deleteFiles();
diff --git a/client/app/validations/submittable-projects-new-form.js b/client/app/validations/submittable-projects-new-form.js
index 14603a18..498af0ad 100644
--- a/client/app/validations/submittable-projects-new-form.js
+++ b/client/app/validations/submittable-projects-new-form.js
@@ -3,6 +3,7 @@ import {
validateLength,
validatePresence,
} from 'ember-changeset-validations/validators';
+import validateFileUpload from '../validators/validate-file-presence';
export default {
primaryContactFirstName: [
@@ -137,4 +138,9 @@ export default {
message: 'This field is required',
}),
],
+ documents: [
+ validateFileUpload({
+ message: 'One or more document uploads is required.',
+ }),
+ ],
};
diff --git a/client/app/validators/validate-file-presence.js b/client/app/validators/validate-file-presence.js
new file mode 100644
index 00000000..293e41a0
--- /dev/null
+++ b/client/app/validators/validate-file-presence.js
@@ -0,0 +1,9 @@
+export default function validateFileUpload(options = {}) {
+ return (key, newValue) => {
+ if (!newValue || newValue.length === 0) {
+ return options.message || `${key} one or more document uploads is required.`;
+ }
+
+ return true;
+ };
+}
diff --git a/server/src/artifacts/artifacts.service.ts b/server/src/artifacts/artifacts.service.ts
index 5d732ff1..d5d2fda9 100644
--- a/server/src/artifacts/artifacts.service.ts
+++ b/server/src/artifacts/artifacts.service.ts
@@ -7,6 +7,7 @@ import { ConfigService } from '../config/config.service';
@Injectable()
export class ArtifactService {
rerFiletypeUuid = '';
+ letterFiletypeUuid= '';
constructor(
private readonly crmService: CrmService,
@@ -14,6 +15,7 @@ export class ArtifactService {
private readonly config: ConfigService,
) {
this.rerFiletypeUuid = this.config.get('RER_FILETYPE_UUID');
+ this.letterFiletypeUuid = this.config.get('LETTER_FILETYPE_UUID');
}
public async createEquityReport(projectId: string) {
@@ -45,6 +47,35 @@ export class ArtifactService {
return newArtifact;
}
+ public async createProjectInitiationArtifacts(projectId: string) {
+ let newArtifact = null;
+
+ try {
+ newArtifact = this.crmService.create('dcp_artifactses', {
+ dcp_name: `Project Initiation`,
+ dcp_isdcpinternal: false,
+ dcp_filecreator: 717170000, // Applicant
+ dcp_filecategory: 717170006, // Other
+ dcp_visibility: 717170002, // Applicant Only
+ 'dcp_applicantfiletype@odata.bind': `/dcp_filetypes(${this.letterFiletypeUuid})`,
+ ...(projectId
+ ? { 'dcp_project@odata.bind': `/dcp_projects(${projectId})` }
+ : {}),
+ });
+ } catch (e) {
+ throw new HttpException(
+ {
+ code: 'CREATE_PROJECT_ARTIFACT_ERROR',
+ title: `Unable to create Project Initiation Artifact dcp_artifactses entity for project with UUID ${projectId}`,
+ detail: e,
+ },
+ HttpStatus.INTERNAL_SERVER_ERROR,
+ );
+ }
+
+ return newArtifact;
+ }
+
async getArtifactSharepointDocuments(relativeUrl: string, dcp_name: string) {
if (relativeUrl) {
try {
diff --git a/server/src/crm/crm.service.ts b/server/src/crm/crm.service.ts
index 5f0f0d8e..8ef9aba5 100644
--- a/server/src/crm/crm.service.ts
+++ b/server/src/crm/crm.service.ts
@@ -19,6 +19,7 @@ export class CrmService {
crmUrlPath = '';
crmHost = '';
host = '';
+ requestCounter = 0;
constructor(private readonly config: ConfigService) {
ADAL.ADAL_CONFIG = {
@@ -62,7 +63,21 @@ export class CrmService {
}
async create(query, data, headers = {}) {
- return this._create(query, data, headers);
+ try {
+ console.debug(
+ "LOGGER CRM create query", query,
+ "LOGGER CRM create data", data,
+ "LOGGER CRM create headers", headers,
+ );
+ const requestStartTime = Date.now();
+ const response = this._create(query, data, headers);
+ const requestEndTime = Date.now();
+ console.debug(`LOGGER: LOGGER CRM create request took ${requestEndTime - requestStartTime} ms`);
+ return response;
+
+ } catch (e) {
+ console.debug(`LOGGER error in CRM create: ${e}`);
+ }
}
async update(entity, guid, data, headers = {}) {
diff --git a/server/src/projects/projects.controller.ts b/server/src/projects/projects.controller.ts
index 40e2d0ae..ffcc65a3 100644
--- a/server/src/projects/projects.controller.ts
+++ b/server/src/projects/projects.controller.ts
@@ -38,6 +38,7 @@ import { INVOICE_ATTRS } from '../invoices/invoices.attrs';
'team-members',
'contacts',
'milestones',
+ 'dcp_artifactsid',
],
packages: {
ref: 'dcp_packageid',
@@ -82,6 +83,7 @@ import { INVOICE_ATTRS } from '../invoices/invoices.attrs';
@Controller('projects')
export class ProjectsController {
CRM_IMPOSTER_ID = '';
+ requestCounter = 0;
constructor(
private readonly projectsService: ProjectsService,
@@ -131,6 +133,7 @@ export class ProjectsController {
@Post('/')
async createProject(@Body() body) {
+ const requestStartTime = Date.now();
const allowedAttrs = pick(body, PROJECT_ATTRS) as {
dcp_projectname: string;
dcp_borough: string;
@@ -148,6 +151,10 @@ export class ProjectsController {
HttpStatus.NOT_FOUND,
);
}
+ const requestEndTime = Date.now();
+ this.requestCounter++;
+ console.log(`LOGGER: [Total Requests Made in the controller] ${this.requestCounter}`)
+ console.debug(`LOGGER: POST request in the controller to took ${requestEndTime - requestStartTime} ms`);
return await this.projectsService.create(allowedAttrs);
}
diff --git a/server/src/projects/projects.module.ts b/server/src/projects/projects.module.ts
index 97e4eb17..6614eaf9 100644
--- a/server/src/projects/projects.module.ts
+++ b/server/src/projects/projects.module.ts
@@ -6,10 +6,18 @@ import { ConfigModule } from '../config/config.module';
import { AuthModule } from '../auth/auth.module';
import { ProjectsController } from './projects.controller';
import { ProjectApplicantController } from './project-applicants/project-applicant.controller';
+import { ArtifactService } from '../artifacts/artifacts.service';
+import { SharepointModule } from '../sharepoint/sharepoint.module';
@Module({
- imports: [CrmModule, ConfigModule, ContactModule, AuthModule],
- providers: [ProjectsService],
+ imports: [
+ CrmModule,
+ SharepointModule,
+ ConfigModule,
+ ContactModule,
+ AuthModule,
+ ],
+ providers: [ProjectsService, ArtifactService],
exports: [ProjectsService],
controllers: [ProjectsController, ProjectApplicantController],
})
diff --git a/server/src/projects/projects.service.ts b/server/src/projects/projects.service.ts
index d1364fdd..86e86678 100644
--- a/server/src/projects/projects.service.ts
+++ b/server/src/projects/projects.service.ts
@@ -4,11 +4,13 @@ import { NycidService } from '../contact/nycid/nycid.service';
import { CrmService } from '../crm/crm.service';
import { overwriteCodesWithLabels } from '../_utils/overwrite-codes-with-labels';
import { MILESTONE_ATTRS, MILESTONE_NON_DATE_ATTRS } from './projects.attrs';
+import { ArtifactService } from '../artifacts/artifacts.service';
const APPLICANT_ACTIVE_STATUS_CODE = 1;
const PROJECT_ACTIVE_STATE_CODE = 0;
const PROJECT_VISIBILITY_APPLICANT_ONLY = 717170002;
const PROJECT_VISIBILITY_GENERAL_PUBLIC = 717170003;
+let requestCounter = 0;
const PACKAGE_VISIBILITY = {
APPLICANT_ONLY: 717170002,
@@ -52,6 +54,7 @@ export class ProjectsService {
constructor(
private readonly crmService: CrmService,
private readonly nycidService: NycidService,
+ private readonly artifactService: ArtifactService,
) {}
public async findManyByContactId(contactId: string) {
@@ -110,6 +113,8 @@ export class ProjectsService {
_dcp_applicantadministrator_customer_value: string;
}) {
try {
+ const requestStartTime = Date.now();
+
const data = {
dcp_projectname: attributes.dcp_projectname,
dcp_borough: attributes.dcp_borough,
@@ -118,15 +123,28 @@ export class ProjectsService {
'dcp_applicant_customer_contact@odata.bind': `/contacts(${attributes._dcp_applicant_customer_value})`,
'dcp_applicantadministrator_customer_contact@odata.bind': `/contacts(${attributes._dcp_applicantadministrator_customer_value})`,
};
- const { dcp_projectid } = await this.crmService.create(
- 'dcp_projects',
- data,
- );
+
+ const project = await this.crmService.create('dcp_projects', data);
+
+ const dcpProjectId = project['dcp_projectid'];
+ if (dcpProjectId === undefined) throw new Error('Failed to create project');
+
+ const artifact =
+ await this.artifactService.createProjectInitiationArtifacts(dcpProjectId);
+
+ const dcpArtifactsId = artifact['dcp_artifactsid'];
+ if (dcpArtifactsId === undefined) throw new Error('Failed to create artifact for project');
+
+ const requestEndTime = Date.now();
+ console.debug(`LOGGER: POST (service) request in the service to took ${requestEndTime - requestStartTime} ms`);
+ requestCounter++;
+ console.log(`LOGGER: [Total Requests Made in the service] ${requestCounter}`);
return {
- dcp_projectid,
+ dcp_projectid: dcpProjectId,
+ dcp_artifactsid: dcpArtifactsId,
};
} catch (e) {
- console.debug('error creating project', e);
+ console.error('(service) error creating project', e);
throw new HttpException(
'Unable to create project',
HttpStatus.INTERNAL_SERVER_ERROR,