freealberta/influence/RESPONSE_WALL_FIXES.md

182 lines
7.6 KiB
Markdown

# Response Wall Bug Fixes
## Issues Identified and Fixed
### 1. **TypeError: responses.filter is not a function**
**Error Location**: `app/controllers/responses.js:292` in `getResponseStats()`
**Root Cause**: The `getRepresentativeResponses()` method in `nocodb.js` was returning the raw NocoDB API response object `{list: [...], pageInfo: {...}}` instead of an array. The controller code expected an array and tried to call `.filter()` on an object.
**Fix Applied**: Modified `getRepresentativeResponses()` to extract the `list` array from the response and normalize each item:
```javascript
async getRepresentativeResponses(params = {}) {
if (!this.tableIds.representativeResponses) {
throw new Error('Representative responses table not configured');
}
const result = await this.getAll(this.tableIds.representativeResponses, params);
// NocoDB returns {list: [...]} or {pageInfo: {...}, list: [...]}
const list = result.list || [];
return list.map(item => this.normalizeResponse(item));
}
```
### 2. **TypeError: responses.map is not a function**
**Error Location**: `app/controllers/responses.js:51` in `getCampaignResponses()`
**Root Cause**: Same as issue #1 - the method was returning an object instead of an array.
**Fix Applied**: Same fix as above ensures an array is always returned.
### 3. **Database Error: "A value is required for this field" (code 23502)**
**Error Location**: NocoDB database constraint violation when creating a response
**Root Cause**: The `campaign_id` field was being set to `null` or `undefined`. Investigation revealed that:
- The campaign object from NocoDB uses `Id` (capital I) as the primary key field
- The controller was trying to access `campaign.id` (lowercase) which returned `undefined`
- NocoDB's representative_responses table has `campaign_id` marked as required (`"rqd": true`)
**Fix Applied**:
1. Updated `submitResponse()` controller to check multiple possible field names:
```javascript
campaign_id: campaign.Id || campaign.id || campaign['Campaign ID'],
```
2. Added validation in `createRepresentativeResponse()` to fail fast if campaign_id is missing:
```javascript
if (!responseData.campaign_id) {
throw new Error('Campaign ID is required for creating a response');
}
```
3. Added debug logging to track the campaign_id value:
```javascript
console.log('Submitting response with campaign_id:', newResponse.campaign_id, 'from campaign:', campaign);
```
### 4. **Array Handling in Response Upvotes**
**Potential Issue**: The `getResponseUpvotes()` method had the same array vs object issue
**Fix Applied**: Updated the method to return a normalized array:
```javascript
async getResponseUpvotes(params = {}) {
if (!this.tableIds.responseUpvotes) {
throw new Error('Response upvotes table not configured');
}
const result = await this.getAll(this.tableIds.responseUpvotes, params);
// NocoDB returns {list: [...]} or {pageInfo: {...}, list: [...]}
const list = result.list || [];
return list.map(item => this.normalizeUpvote(item));
}
```
## Files Modified
1. **app/services/nocodb.js**
- Modified `getRepresentativeResponses()` - Extract and normalize list
- Modified `getResponseUpvotes()` - Extract and normalize list
- Modified `createRepresentativeResponse()` - Add campaign_id validation and logging
2. **app/controllers/responses.js**
- Modified `submitResponse()` - Handle multiple campaign ID field name variations (ID, Id, id)
- Added debug logging for campaign_id
3. **app/public/js/admin.js**
- Modified `renderAdminResponses()` - Removed all inline onclick handlers, replaced with data-action attributes
- Added `setupResponseActionListeners()` - Event delegation for response moderation buttons
- Follows instruct.md guidelines: "No inline event handlers. Always use addEventListener in JS files."
4. **app/public/js/api-client.js**
- Added `put()` method for HTTP PUT requests
- Added `patch()` method for HTTP PATCH requests
- Added `delete()` method for HTTP DELETE requests
- These methods were missing and causing errors in admin panel operations
## Testing
After applying these fixes, test the following:
1. **Load Response Wall** - Visit `http://localhost:3333/response-wall.html?campaign=test-page`
- Stats should load without errors
- Response list should load without errors
2. **Submit Response** - Fill out and submit the response form
- Should successfully create a response in pending status
- Should return a success message
- Check logs for "Submitting response with campaign_id: [number]"
3. **Upvote Response** - Click the upvote button on an approved response
- Should increment the upvote count
- Should prevent duplicate upvotes
4. **Admin Moderation** - Visit `http://localhost:3333/admin.html` → Response Moderation tab
- Should see pending responses
- Should be able to approve/reject responses
## Deployment
The application container has been restarted with:
```bash
docker compose restart app
```
All fixes are now live and ready for testing.
## Root Cause Analysis
The main issue was a misunderstanding of NocoDB's API response structure:
- **Expected**: Array of records directly
- **Actual**: Object with `{list: [records], pageInfo: {...}}`
This is a common pattern in REST APIs for pagination support. The fix ensures all service methods return properly normalized arrays for consistent usage throughout the application.
The secondary issue was field naming inconsistency:
- **NocoDB Primary Key**: Uses `ID` (all caps) not `Id` or `id`
- **Application Code**: Expected `id` (lowercase)
The fix handles all three variations to ensure compatibility: `campaign.ID || campaign.Id || campaign.id`
### 5. **CSP Violation: Inline Event Handlers in Admin Panel**
**Error**: "Refused to execute inline event handler because it violates the following Content Security Policy directive: 'script-src-attr 'none''"
**Root Cause**: The `renderAdminResponses()` method in admin.js was using inline `onclick` handlers like:
```javascript
<button onclick="adminPanel.approveResponse(${response.id})">
```
This violates the development guidelines in instruct.md which explicitly state: **"No inline event handlers. Always use addEventListener in JS files."**
**Fix Applied**:
- Replaced all inline onclick handlers with data-action attributes
- Created `setupResponseActionListeners()` method using event delegation
- All buttons now use pattern: `data-action="approve-response" data-response-id="${response.id}"`
- Event delegation listens on container and routes actions based on data attributes
### 6. **Missing HTTP Methods in API Client**
**Error**: "TypeError: window.apiClient.patch is not a function"
**Root Cause**: The APIClient class in api-client.js only had `get()` and `post()` methods. Admin panel operations needed `patch()`, `put()`, and `delete()` methods for updating and deleting responses.
**Fix Applied**: Added three new methods to APIClient:
```javascript
async put(endpoint, data) { ... }
async patch(endpoint, data) { ... }
async delete(endpoint) { ... }
```
## Prevention
To prevent similar issues in the future:
1. **Type Safety**: Consider adding TypeScript or JSDoc type annotations
2. **Consistent Normalization**: Always normalize data at the service layer
3. **Field Name Standards**: Document NocoDB field naming conventions
4. **Validation**: Add validation for required fields early in the request flow
5. **Logging**: Continue adding debug logging for data transformations
## Related Documentation
- See `RESPONSE_WALL_USAGE.md` for usage instructions
- See `RESPONSE_WALL_IMPLEMENTATION.md` for feature implementation details
- See `scripts/build-nocodb.sh` for database schema definitions