From 7c4d03591d21dd025fd6328050afce749d8214f5 Mon Sep 17 00:00:00 2001 From: Jiangfan Li Date: Thu, 17 Oct 2024 18:38:15 +1100 Subject: [PATCH 1/6] Built the input field and the selectable dropdown area to make the name of the student searchable. --- package-lock.json | 2 +- .../question-submission-form.component.html | 356 +++++++++--------- 2 files changed, 186 insertions(+), 172 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6267ea605d9..a36f50a287f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "teammates", + "name": "Assignment4_5", "lockfileVersion": 3, "requires": true, "packages": { diff --git a/src/web/app/components/question-submission-form/question-submission-form.component.html b/src/web/app/components/question-submission-form/question-submission-form.component.html index e2d079b9840..c020c07f3f1 100644 --- a/src/web/app/components/question-submission-form/question-submission-form.component.html +++ b/src/web/app/components/question-submission-form/question-submission-form.component.html @@ -70,181 +70,195 @@

Question {{ model.questionNumber }}: {{ mode {{ getRecipientName(recipientSubmissionFormModel.recipientIdentifier) }} ({{ model.recipientType | recipientTypeName:model.giverType }})
- -
- ({{ model.recipientType | recipientTypeName: model.giverType }}) -
-
- -
- - - - - - - - - - - - - -
-
-
-
-

Only the following persons can see your comments:

-
    -
  • You can see your own feedback in the results page later on.
  • - -
  • - {{ visibilityType | visibilityEntityName:model.recipientType:model.numberOfEntitiesToGiveFeedbackToSetting:model.customNumberOfEntitiesToGiveFeedbackTo }} {{ visibilityStateMachine.getVisibilityControlUnderVisibilityType(visibilityType) | visibilityCapability }} -
  • + +
    + + + + +
    -
-
-
- - -
- -
- -
-
- - -
-
-
-
- -
- +
+ ({{ model.recipientType | recipientTypeName: model.giverType }}) +
+ + +
+ + + + + + + + + + + + + +
-
- - - -
+
+
+
+

Only the following persons can see your comments:

+
    +
  • You can see your own feedback in the results page later on.
  • + +
  • + {{ visibilityType | visibilityEntityName:model.recipientType:model.numberOfEntitiesToGiveFeedbackToSetting:model.customNumberOfEntitiesToGiveFeedbackTo }} {{ visibilityStateMachine.getVisibilityControlUnderVisibilityType(visibilityType) | visibilityCapability }} +
  • +
    +
  • No-one can see your responses
  • +
+
+
+
+
+ + +
+ +
+ +
+
+ + +
+
+
+
+ + + - +
+ + + +
-
-
- - -
-
- - + + +
+
+ + +
+
+ + From 998a08822fb40c75709a2e980c9ec41f5d04b133 Mon Sep 17 00:00:00 2001 From: Andy Chih Date: Thu, 17 Oct 2024 20:26:11 +1100 Subject: [PATCH 2/6] Add searchName array and triggerRecipientIdentifierChange method to manage recipient search functionality --- .../question-submission-form.component.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/web/app/components/question-submission-form/question-submission-form.component.ts b/src/web/app/components/question-submission-form/question-submission-form.component.ts index 59d83d25227..0ca8ebb6bc6 100644 --- a/src/web/app/components/question-submission-form/question-submission-form.component.ts +++ b/src/web/app/components/question-submission-form/question-submission-form.component.ts @@ -58,6 +58,8 @@ export class QuestionSubmissionFormComponent implements DoCheck { isMCQDropDownEnabled: boolean = false; isSaved: boolean = false; hasResponseChanged: boolean = false; + // Initial a new array to store names + searchName: string[] = []; @Input() formMode: QuestionSubmissionFormMode = QuestionSubmissionFormMode.FIXED_RECIPIENT; @@ -94,6 +96,9 @@ export class QuestionSubmissionFormComponent implements DoCheck { this.model.isTabExpandedForRecipients.set(recipient.recipientIdentifier, true); }); this.hasResponseChanged = Array.from(this.model.hasResponseChangedForRecipients.values()).some((value) => value); + // Initialize the searchName array with empty strings, matching the length of recipientSubmissionForms. + this.searchName = Array.from({ length: this.model.recipientSubmissionForms.length }, () => ""); + } @Input() @@ -360,6 +365,14 @@ export class QuestionSubmissionFormComponent implements DoCheck { (recipientSubmissionFormModel: FeedbackResponseRecipientSubmissionFormModel) => recipientSubmissionFormModel.recipientIdentifier === recipient.recipientIdentifier); } + + // Method to handle changes in the recipient identifier. + triggerRecipientIdentifierChange(index: number, data: any): void { + if (this.searchName[index] !== undefined) { + this.searchName[index] = ""; + } + this.triggerRecipientSubmissionFormChange(index, 'recipientIdentifier', data); +} /** * Triggers the change of the recipient submission form. From ccdfea01be601464a76f7450df213d1257428a5a Mon Sep 17 00:00:00 2001 From: Andy Chih Date: Thu, 17 Oct 2024 23:45:48 +1100 Subject: [PATCH 3/6] Refactored the filterRecipientsBySearchText method, and added a trigger to select the chang of input --- .../question-submission-form.component.ts | 35 ++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/src/web/app/components/question-submission-form/question-submission-form.component.ts b/src/web/app/components/question-submission-form/question-submission-form.component.ts index 0ca8ebb6bc6..628cc54724e 100644 --- a/src/web/app/components/question-submission-form/question-submission-form.component.ts +++ b/src/web/app/components/question-submission-form/question-submission-form.component.ts @@ -325,6 +325,34 @@ export class QuestionSubmissionFormComponent implements DoCheck { this.updateSubmissionFormIndexes(); } +/** + * Filters the recipients based on the provided search text. + * + * This method searches through the list of recipients and returns those that match + * the search criteria. The search is case-insensitive and supports both full names + * and individual name parts. If the search text contains a space, it matches the + * entire name; otherwise, it searches for any part of the name. + * + * @param {string} searchText - The text to search for within recipient names. If empty or only whitespace, all recipients are returned. + * @param {FeedbackResponseRecipient[]} recipients - The list of recipients to filter. + * @returns {FeedbackResponseRecipient[]} - A filtered list of recipients that match the search criteria and are marked as selected. + */ + filterRecipientsBySearchText(searchText: string, recipients: FeedbackResponseRecipient[]): FeedbackResponseRecipient[] { + if (!searchText) return recipients; + const searchName = searchText.trim().toLowerCase(); + if (searchName.length === 0) return recipients; + + const isSpaceIncluded = searchName.includes(' '); + return recipients.filter((r) => { + if (!this.isRecipientSelected(r)) return false; + + const recipientName = r.recipientName.toLowerCase(); + return isSpaceIncluded + ? recipientName.includes(searchName) + : recipientName.split(' ').some((s) => s.includes(searchName)); + }); +} + private sortRecipientsBySectionTeam(): void { if (this.recipientLabelType === FeedbackRecipientLabelType.INCLUDE_SECTION) { this.model.recipientList.sort((firstRecipient, secondRecipient) => { @@ -365,7 +393,7 @@ export class QuestionSubmissionFormComponent implements DoCheck { (recipientSubmissionFormModel: FeedbackResponseRecipientSubmissionFormModel) => recipientSubmissionFormModel.recipientIdentifier === recipient.recipientIdentifier); } - + // Method to handle changes in the recipient identifier. triggerRecipientIdentifierChange(index: number, data: any): void { if (this.searchName[index] !== undefined) { @@ -374,6 +402,11 @@ export class QuestionSubmissionFormComponent implements DoCheck { this.triggerRecipientSubmissionFormChange(index, 'recipientIdentifier', data); } + // Trigger to change the text input + triggerSelectInputChange(index: number): void { + this.model.recipientSubmissionForms[index].recipientIdentifier = ''; + } + /** * Triggers the change of the recipient submission form. */ From 74ba457dfb0545ed2e01837a370d5d38074a7947 Mon Sep 17 00:00:00 2001 From: u7721487 Date: Fri, 18 Oct 2024 00:15:45 +1100 Subject: [PATCH 4/6] Added unit tests for isRecipientSelected and filterRecipientsBySearchText, improved method documentation. --- ...question-submission-form.component.spec.ts | 108 ++++++++++++++++-- 1 file changed, 100 insertions(+), 8 deletions(-) diff --git a/src/web/app/components/question-submission-form/question-submission-form.component.spec.ts b/src/web/app/components/question-submission-form/question-submission-form.component.spec.ts index 1ff82d95cac..8fa8e1d9fe7 100644 --- a/src/web/app/components/question-submission-form/question-submission-form.component.spec.ts +++ b/src/web/app/components/question-submission-form/question-submission-form.component.spec.ts @@ -457,7 +457,21 @@ describe('QuestionSubmissionFormComponent', () => { expect(component.shouldTabExpand()).toBeFalsy(); }); - + /** + * Tests the isRecipientSelected method to check if it can correctly identify whether + * a given recipient is selected from the list of recipient submission forms. + * This test covers both existing and non-existing recipients. + * + * @description + * 1. Creates two recipients, one with a valid identifier present in the list of + * recipient submission forms, and another that does not exist in the list. + * 2. Calls the isRecipientSelected method and verifies that it returns true for + * the existing recipient and false for the non-existent one. + * + * @scenario + * - A recipient exists in the submission forms list: should return true. + * - A recipient does not exist in the list: should return false. + */ it('isRecipientSelected: should return true if FeedbackResponseRecipient exists in recipientSubmissionForms', () => { const feedbackResponseRecipientIdentifier = 'test-identifer'; const feedbackResponseRecipient = @@ -470,19 +484,97 @@ describe('QuestionSubmissionFormComponent', () => { expect(component.isRecipientSelected(feedbackResponseRecipient)).toBeTruthy(); }); - it('isRecipientSelected: should return false if FeedbackResponseRecipient does not' - + 'exist in recipientSubmissionForms', () => { + it('isRecipientSelected: should handle both existing and non-existing recipients correctly', () => { const feedbackResponseRecipientIdentifier = 'test-identifer'; - const feedbackResponseRecipient = - feedbackResponseRecipientBuilder.recipientIdentifier(feedbackResponseRecipientIdentifier).build(); + const feedbackResponseRecipient = feedbackResponseRecipientBuilder + .recipientIdentifier(feedbackResponseRecipientIdentifier) + .recipientName('test-name') // Adding recipient's name to ensure clarity + .build(); + + // Adding test scenario for non-existent recipient + const nonExistentRecipient = feedbackResponseRecipientBuilder + .recipientIdentifier('nonexistent-id') + .recipientName('nonexistent-name') + .build(); + component.model.recipientSubmissionForms = [ - recipientSubmissionFormBuilder.recipientIdentifier('testid1').build(), - recipientSubmissionFormBuilder.recipientIdentifier('testid2').build(), + recipientSubmissionFormBuilder + .recipientIdentifier(feedbackResponseRecipientIdentifier) + .build(), + recipientSubmissionFormBuilder.recipientIdentifier('testid').build(), ]; - expect(component.isRecipientSelected(feedbackResponseRecipient)).toBeFalsy(); + // Test for an existing recipient + expect(component.isRecipientSelected(feedbackResponseRecipient)).toBeTruthy(); + // Test for a non-existent recipient + expect(component.isRecipientSelected(nonExistentRecipient)).toBeFalsy(); + }); + /** + * Tests the filterRecipientsBySearchText method to ensure that recipients are filtered + * correctly based on the search text. The search should be case-insensitive and able + * to match either full names or parts of names. Additionally, it should only return + * recipients that are selected via the isRecipientSelected method. + * + * @description + * 1. Sets up a list of recipients with different names to test various scenarios. + * 2. Calls the filterRecipientsBySearchText method with different search inputs + * (full name match, partial match, case-insensitive match, and empty/whitespace). + * 3. Verifies the output against the expected filtered recipients. + * + * @scenario + * - No search text or only spaces: returns all recipients. + * - Search for 'Jane': only Jane Doe should be returned. + * - Search for 'J': should return James, John, and Jane. + * - Search for 'Brown': should return Alice Brown. + * - Case-insensitive match on 'bob': should return Bob Black. + * - Partial search 'gre': should return Dave Grey and Edgar Green. + * - Search for 'Doe': should return James Doe and Jane Doe. + * - Multiple partial match on 'al': should return Alice and Charlie. + */ + it('filterRecipientsBySearchText: should return correct filtered names', () => { + // Setting up test data for recipients + const james = { recipientIdentifier: 'jamesDoe', recipientName: 'James Doe' }; + const john = { recipientIdentifier: 'johnSmith', recipientName: 'John Smith' }; + const jane = { recipientIdentifier: 'janeDoe', recipientName: 'Jane Doe' }; + const alice = { recipientIdentifier: 'aliceBrown', recipientName: 'Alice Brown' }; + const bob = { recipientIdentifier: 'bobBlack', recipientName: 'Bob Black' }; + const charlie = { recipientIdentifier: 'charlieWhite', recipientName: 'Charlie White' }; + const dave = { recipientIdentifier: 'daveGrey', recipientName: 'Dave Grey' }; + const edgar = { recipientIdentifier: 'edgarGreen', recipientName: 'Edgar Green' }; + const francis = { recipientIdentifier: 'francisBlue', recipientName: 'Francis Blue' }; + + const recipients = [james, john, jane, alice, bob, charlie, dave, edgar, francis]; + + // Test case when search text is an empty string, should return all recipients + expect(component.filterRecipientsBySearchText('', recipients)).toStrictEqual(recipients); + + // Test case when search text contains only spaces, should return all recipients + expect(component.filterRecipientsBySearchText(' ', recipients)).toStrictEqual(recipients); + + // Test case for filtering by the name 'Jane', expecting only Jane Doe + expect(component.filterRecipientsBySearchText('Jane', recipients)).toStrictEqual([jane]); + + // Test case for filtering recipients by the letter 'J', should return James, John, Jane + expect(component.filterRecipientsBySearchText('J', recipients)).toStrictEqual([james, john, jane]); + + // Test case for filtering by the string 'Brown', should return Alice Brown + expect(component.filterRecipientsBySearchText('Brown', recipients)).toStrictEqual([alice]); + + // Test case for a case-insensitive match on 'bob', should return Bob Black + expect(component.filterRecipientsBySearchText('bob', recipients)).toStrictEqual([bob]); + + // Test case for partial search 'gre', should return both Dave Grey and Edgar Green + expect(component.filterRecipientsBySearchText('gre', recipients)).toStrictEqual([dave, edgar]); + + // Test case for filtering using the last name 'Doe', should return James Doe and Jane Doe + expect(component.filterRecipientsBySearchText('Doe', recipients)).toStrictEqual([james, jane]); + + // Test case for filtering by multiple partial matches 'al', should return Alice and Charlie + expect(component.filterRecipientsBySearchText('al', recipients)).toStrictEqual([alice, charlie]); }); + + it('triggerDeleteCommentEvent: should emit the correct index to deleteCommentEvent', () => { let emittedIndex: number | undefined; testEventEmission(component.deleteCommentEvent, (index) => { emittedIndex = index; }); From ca8c5061172dc793d7df2053b042cb1a8a15b84e Mon Sep 17 00:00:00 2001 From: Jiangfan Li Date: Sat, 19 Oct 2024 14:09:56 +1100 Subject: [PATCH 5/6] Modified the error names. --- .../question-submission-form.component.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/web/app/components/question-submission-form/question-submission-form.component.html b/src/web/app/components/question-submission-form/question-submission-form.component.html index c020c07f3f1..e6071eaf07f 100644 --- a/src/web/app/components/question-submission-form/question-submission-form.component.html +++ b/src/web/app/components/question-submission-form/question-submission-form.component.html @@ -76,11 +76,11 @@

Question {{ model.questionNumber }}: {{ mode + [ngClass]="filterRecipientsBySearchText(searchName[i],model.recipientList).length === 0 ? 'no-match' : ''" /> From b24ef295e36d80f99d8650c3e42dac74915fe592 Mon Sep 17 00:00:00 2001 From: Andy Chih Date: Wed, 23 Oct 2024 20:09:54 +1100 Subject: [PATCH 6/6] Add Triggers to fix the bugs --- .../question-submission-form.component.ts | 51 ++++++++++++++----- 1 file changed, 37 insertions(+), 14 deletions(-) diff --git a/src/web/app/components/question-submission-form/question-submission-form.component.ts b/src/web/app/components/question-submission-form/question-submission-form.component.ts index 628cc54724e..4e53b775cdc 100644 --- a/src/web/app/components/question-submission-form/question-submission-form.component.ts +++ b/src/web/app/components/question-submission-form/question-submission-form.component.ts @@ -60,6 +60,7 @@ export class QuestionSubmissionFormComponent implements DoCheck { hasResponseChanged: boolean = false; // Initial a new array to store names searchName: string[] = []; + feedbackRecipients: Array = []; @Input() formMode: QuestionSubmissionFormMode = QuestionSubmissionFormMode.FIXED_RECIPIENT; @@ -97,7 +98,8 @@ export class QuestionSubmissionFormComponent implements DoCheck { }); this.hasResponseChanged = Array.from(this.model.hasResponseChangedForRecipients.values()).some((value) => value); // Initialize the searchName array with empty strings, matching the length of recipientSubmissionForms. - this.searchName = Array.from({ length: this.model.recipientSubmissionForms.length }, () => ""); + this.searchName = new Array(this.model.recipientSubmissionForms.length).fill(''); + this.feedbackRecipients = new Array(this.model.recipientSubmissionForms.length).fill(null); } @@ -342,16 +344,13 @@ export class QuestionSubmissionFormComponent implements DoCheck { const searchName = searchText.trim().toLowerCase(); if (searchName.length === 0) return recipients; - const isSpaceIncluded = searchName.includes(' '); - return recipients.filter((r) => { - if (!this.isRecipientSelected(r)) return false; - - const recipientName = r.recipientName.toLowerCase(); - return isSpaceIncluded - ? recipientName.includes(searchName) - : recipientName.split(' ').some((s) => s.includes(searchName)); - }); -} + if (searchName.includes(' ')) { + return recipients.filter((r) => !this.isRecipientSelected(r) + && r.recipientName.toLowerCase().includes(searchName)); + } + return recipients.filter((r) => !this.isRecipientSelected(r) + && r.recipientName.split(' ').some((s) => s.toLowerCase().includes(searchName))); + } private sortRecipientsBySectionTeam(): void { if (this.recipientLabelType === FeedbackRecipientLabelType.INCLUDE_SECTION) { @@ -396,15 +395,38 @@ export class QuestionSubmissionFormComponent implements DoCheck { // Method to handle changes in the recipient identifier. triggerRecipientIdentifierChange(index: number, data: any): void { - if (this.searchName[index] !== undefined) { - this.searchName[index] = ""; - } + this.searchName[index] = ''; this.triggerRecipientSubmissionFormChange(index, 'recipientIdentifier', data); } +updateSearchNameTextByShowSection(): void { + this.searchName = this.searchName.map( + (s, i) => { + return this.feedbackRecipients[i] === null ? s : this.getSelectionOptionLabel(this.feedbackRecipients[i]!); + }, + ); +} + +triggerSelectInputFocus(index: number): void { + if (this.feedbackRecipients[index] !== null) { + this.searchName[index] = ''; + this.model.recipientSubmissionForms[index].recipientIdentifier = ''; + this.feedbackRecipients[index] = null; + } +} + + // Trigger to change the text input triggerSelectInputChange(index: number): void { this.model.recipientSubmissionForms[index].recipientIdentifier = ''; + this.feedbackRecipients[index] = null; + } + + triggerRecipientOptionSelect(index: number, recipient: FeedbackResponseRecipient, event: any): void { + event.target.blur(); + this.searchName[index] = this.getSelectionOptionLabel(recipient); + this.feedbackRecipients[index] = recipient; + this.triggerRecipientSubmissionFormChange(index, 'recipientIdentifier', recipient.recipientIdentifier); } /** @@ -569,6 +591,7 @@ export class QuestionSubmissionFormComponent implements DoCheck { this.isSectionTeamShown = false; this.sortRecipientsByName(); } + this.updateSearchNameTextByShowSection(); } /**