Lesson 1
Managing Product Reviews and Data Aggregation in TypeScript
Introduction

Hello, and welcome to the first lesson in this course! We are going to dive into the world of managing product reviews and applying data aggregation in practice. We will start with a relatively simple Starter Task to set up our base and then gradually build up to a more complex solution involving data aggregation. Let's jump in!

Starter Task: Methods and Their Definitions

For our starter task, we will lay the foundation by implementing basic operations for managing product reviews. These are the methods we will need to implement, with TypeScript-specific type annotations:

  • addReview(productId: string, reviewId: string, reviewText: string, rating: number): boolean — This method adds a review to the product specified by productId. If a review with reviewId already exists, it updates the existing review. The review contains a flagged field, which indicates whether the review is marked as inappropriate (by default, this field is set to false). Returns true if the review was added or updated successfully, false otherwise.

  • getReview(productId: string, reviewId: string): { text: string; rating: number; flagged: boolean } | null — Returns the review details (reviewText, rating, and flagged fields) for the review specified by reviewId under the given productId. If the review or product does not exist, it returns null.

  • deleteReview(productId: string, reviewId: string): boolean — Deletes the review specified by reviewId under the given productId. Returns true if the review was deleted, false otherwise.

Starter Task Implementation

Let's look at the code that implements these functionalities in TypeScript:

TypeScript
1type Review = { 2 text: string; 3 rating: number; 4 flagged: boolean; 5}; 6 7class ReviewManager { 8 private products: Record<string, Record<string, Review>> = {}; 9 10 addReview(productId: string, reviewId: string, reviewText: string, rating: number): boolean { 11 if (rating < 1 || rating > 5) { 12 return false; 13 } 14 if (!this.products[productId]) { 15 this.products[productId] = {}; 16 } 17 this.products[productId][reviewId] = { text: reviewText, rating: rating, flagged: false }; 18 return true; 19 } 20 21 getReview(productId: string, reviewId: string): { text: string; rating: number; flagged: boolean } | null { 22 if (this.products[productId] && this.products[productId][reviewId]) { 23 const review = this.products[productId][reviewId]; 24 return { text: review.text, rating: review.rating, flagged: review.flagged }; 25 } 26 return null; 27 } 28 29 deleteReview(productId: string, reviewId: string): boolean { 30 if (this.products[productId] && this.products[productId][reviewId]) { 31 delete this.products[productId][reviewId]; 32 if (Object.keys(this.products[productId]).length === 0) { 33 delete this.products[productId]; 34 } 35 return true; 36 } 37 return false; 38 } 39} 40 41// Instantiate the ReviewManager 42const reviewManager = new ReviewManager(); 43 44// Adding some reviews 45reviewManager.addReview("p1", "r1", "Great product!", 5); 46reviewManager.addReview("p1", "r2", "Not bad", 3); 47 48// Testing getReview method 49console.log(reviewManager.getReview("p1", "r1")); // Expected: { text: "Great product!", rating: 5, flagged: false } 50console.log(reviewManager.getReview("p1", "r3")); // Expected: null 51 52// Testing deleteReview method 53console.log(reviewManager.deleteReview("p1", "r2")); // Expected: true 54console.log(reviewManager.getReview("p1", "r2")); // Expected: null

This code establishes the foundational methods for managing product reviews within a ReviewManager class. The addReview method allows adding or updating reviews with valid ratings between 1 and 5, creating a product entry if it doesn’t exist. The getReview method retrieves details for a specific product and review, returning null if either doesn’t exist. The deleteReview method removes a review and deletes the product entry if no reviews remain.

The Record type, used for the products property, is a TypeScript utility that defines an object structure with specific key-value mappings. Here, Record<string, Record<string, Review>> maps a productId (outer string key) to another object, which maps a reviewId (inner string key) to its corresponding Review. This ensures a structured and type-safe representation of products and their associated reviews, simplifying the management of nested data.

Now, let's extend this with new features.

New Task: Advanced Functions and Data Aggregation

With our basic review management system in place, we will now introduce new methods to handle more complex operations, such as flagging inappropriate reviews and aggregating review data for a specific product.

Here are the new methods we will add:

  • flagReview(productId: string, reviewId: string): boolean — This method flags a specific review as inappropriate for a given product. Returns true if the review was successfully flagged, false otherwise.

  • aggregateReviews(productId: string): AggregatedReviewData | null — This method aggregates review data for a given product, providing statistics such as the total number of reviews, the number of flagged reviews, average rating, and review texts excluding flagged ones. If the product does not have any reviews or does not exist, it returns null.

Implementation, Step 1: Adding the 'flagReview' Method

First, let's add functionality to flag a review using TypeScript:

TypeScript
1class ReviewManager { 2 // Existing methods remain unchanged... 3 4 flagReview(productId: string, reviewId: string): boolean { 5 if (this.products[productId] && this.products[productId][reviewId]) { 6 this.products[productId][reviewId].flagged = true; 7 return true; 8 } 9 return false; 10 } 11} 12 13// Instantiate the ReviewManager 14const reviewManager = new ReviewManager(); 15 16// Adding some reviews 17reviewManager.addReview("p1", "r1", "Great product!", 5); 18reviewManager.addReview("p1", "r2", "Not bad", 3); 19reviewManager.addReview("p1", "r3", "Terrible", 1); 20 21// Flagging a review 22reviewManager.flagReview("p1", "r3"); 23 24// Testing flagReview method 25console.log(reviewManager.getReview("p1", "r3")); // Expected: { text: "Terrible", rating: 1, flagged: true }

In this step, the flagReview method uses TypeScript to ensure proper typing of parameters and return types. This method marks a review as inappropriate by setting its flagged property to true. It incorporates checks to confirm the existence of both the product and review before modification, preventing errors from accessing non-existent entities. Successful flagging returns true, providing immediate feedback, while failure to flag due to missing entities results in a false return, ensuring reliable operation and communication with the method caller.

Step 2: Adding the 'aggregateReviews' Method

Next, we will implement the method to aggregate reviews using TypeScript:

TypeScript
1type AggregatedReviewData = { 2 totalReviews: number; 3 flaggedReviews: number; 4 averageRating: number; 5 reviewTexts: string[]; 6}; 7 8class ReviewManager { 9 // Existing methods remain unchanged... 10 11 aggregateReviews(productId: string): AggregatedReviewData | null { 12 if (!this.products[productId] || Object.keys(this.products[productId]).length === 0) { 13 return null; 14 } 15 16 let totalReviews = Object.keys(this.products[productId]).length; 17 let flaggedReviews = 0; 18 let totalRating = 0; 19 let reviewTexts: string[] = []; 20 21 for (let reviewId in this.products[productId]) { 22 const review = this.products[productId][reviewId]; 23 if (review.flagged) { 24 flaggedReviews += 1; 25 } else { 26 totalRating += review.rating; 27 reviewTexts.push(review.text); 28 } 29 } 30 31 let averageRating = flaggedReviews === totalReviews ? 0 : totalRating / (totalReviews - flaggedReviews); 32 33 return { 34 totalReviews: totalReviews, 35 flaggedReviews: flaggedReviews, 36 averageRating: averageRating, 37 reviewTexts: reviewTexts, 38 }; 39 } 40} 41 42// Instantiate the ReviewManager 43const reviewManager = new ReviewManager(); 44 45reviewManager.addReview("p1", "r1", "Great product!", 5); 46reviewManager.addReview("p1", "r2", "Not bad", 3); 47reviewManager.addReview("p1", "r3", "Terrible", 1); 48 49// Flagging a review 50reviewManager.flagReview("p1", "r3"); 51 52// Testing the aggregation method 53console.log(reviewManager.aggregateReviews("p1")); 54// Expected: 55// { 56// totalReviews: 3, 57// flaggedReviews: 1, 58// averageRating: 4, 59// reviewTexts: ["Great product!", "Not bad"] 60// }

In this step, the aggregateReviews method is added to the ReviewManager class. This method aggregates review data for a given product by calculating various statistics, such as the total number of reviews, the number of flagged reviews, the average rating excluding flagged reviews, and a list of review texts that are not flagged. The method first ensures the product exists and contains reviews. It then iterates through the reviews to collect the necessary data, considering only non-flagged reviews for the average rating and review texts. If all reviews are flagged, the average rating defaults to zero. This aggregation provides a comprehensive overview of a product’s review status, useful for both users and administrators.

Conclusion

Great job! Today, you have learned how to manage product reviews and apply data aggregation in a TypeScript environment. We started with basic operations for adding, retrieving, and deleting reviews. Then, we extended our functionality to include flagging reviews and aggregating review data. This gradual build-up, with strong typing provided by TypeScript, demonstrates how to enhance features incrementally and handle more complex data aggregation tasks efficiently.

Feel free to practice solving similar challenges to strengthen your skills further. Keep coding, and see you in the next lesson!

Enjoy this lesson? Now it's time to practice with Cosmo!
Practice is how you turn knowledge into actual skills.