Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
25ee5da
Merge pull request #4227 from LiteFarmOrg/main
SayakaOno Jun 25, 2026
44802a0
LF-5365 Add types field to API tsconfig
SayakaOno Jun 26, 2026
b677e2c
LF-5360 Generalize the TAPE survey flow into a reusable multi-survey …
litefarm-pr-bot Jun 26, 2026
49b10a3
Merge pull request #4228 from LiteFarmOrg/LF-5365/Fix_missing_Jest_ty…
kathyavini Jun 26, 2026
6cb95c5
LF-5360 Move survey catalog and country/version gating to frontend co…
kathyavini Jun 26, 2026
fcfa378
LF-5360 Return 200 null instead of 404 for a survey not yet completed
kathyavini Jun 26, 2026
707bdba
LF-5360 Restore removed patch flow; add tests to better document flow…
kathyavini Jun 26, 2026
7ecd6c8
LF-5360 Clean up comments
kathyavini Jun 26, 2026
1e1e2a7
LF-5360 Make survey_step nullable since non-TAPE will not include it
kathyavini Jun 26, 2026
678b944
LF-5360 Point to TAPE keys to prevent duplication and translator work
kathyavini Jun 26, 2026
fad71f2
LF-5360 Display 'Completed' instead of 'Completed -- See your results…
kathyavini Jun 26, 2026
943e58a
LF-5360 Fix survey.tsx title; no TSX syntax and want to indicate it h…
kathyavini Jun 26, 2026
365dae0
Merge pull request #4229 from LiteFarmOrg/LF-5360-generalize-the-tape…
kathyavini Jun 26, 2026
1a66e43
LF-5361 Add Cathi-Gao project to survey configuration
kathyavini Jun 26, 2026
a2c6bc0
Merge pull request #4230 from LiteFarmOrg/LF-5361-add-the-kenya-surve…
kathyavini Jun 26, 2026
2cdad31
chore: attempt to fix chromatic run
atrevino89 Jun 29, 2026
e5a4723
chore: pin chromatic action version
atrevino89 Jun 30, 2026
3bc7358
Merge pull request #4231 from LiteFarmOrg/chore/atrevino/LF-4779-chro…
kathyavini Jun 30, 2026
1a435b9
LF-5366 Create new action and update actionType for resetting farmSta…
kathyavini Jun 30, 2026
b240ba7
LF-5366 Rename action to resetFarmStateReducer for clarity; trim comm…
kathyavini Jun 30, 2026
1ed0ceb
LF-5367 Update SurveyJS and survey-react-ui version
kathyavini Jun 30, 2026
d6ee743
LF-5367 Pin exact version
kathyavini Jun 30, 2026
e724c78
Merge pull request #4234 from LiteFarmOrg/LF-5366-survey-draft-persis…
SayakaOno Jul 2, 2026
71f677e
Merge pull request #4236 from LiteFarmOrg/LF-5367-survey-crashes-afte…
SayakaOno Jul 2, 2026
28d1142
New Crowdin translations by GitHub Action
crowdin-bot Jul 2, 2026
ce214fb
New Crowdin translations by GitHub Action
crowdin-bot Jul 2, 2026
6e44f24
New Crowdin translations by GitHub Action
crowdin-bot Jul 2, 2026
11e6b4b
New Crowdin translations by GitHub Action
crowdin-bot Jul 2, 2026
3eb8c0e
New Crowdin translations by GitHub Action
crowdin-bot Jul 2, 2026
88274f4
LF-5371 Update Ensemble Irrigation Prescription report URL to new format
kathyavini Jul 2, 2026
6cd5ff8
Clean untranslated and unused strings
kathyavini Jul 2, 2026
ff0daf9
Revert changes other than km addition
kathyavini Jul 2, 2026
c96ff4d
Revert changes other than adding km
kathyavini Jul 2, 2026
93308e3
Merge pull request #4239 from LiteFarmOrg/LF-5371-update-ensemble-irr…
SayakaOno Jul 2, 2026
1ad8fc2
Revert changes other than adding Khmer
kathyavini Jul 2, 2026
55bfbc3
Restore everything other than Khmer to integration version
kathyavini Jul 2, 2026
0f689aa
Merge pull request #3576 from LiteFarmOrg/l10n_crowdin_translations_i…
kathyavini Jul 2, 2026
02b8f80
Merge pull request #3579 from LiteFarmOrg/l10n_crowdin_translations_i…
kathyavini Jul 2, 2026
0c6a0b3
Merge pull request #3645 from LiteFarmOrg/l10n_crowdin_translations_i…
kathyavini Jul 2, 2026
76cc18f
Merge pull request #3718 from LiteFarmOrg/l10n_crowdin_translations_i…
kathyavini Jul 2, 2026
b187932
Merge pull request #4238 from LiteFarmOrg/l10n_crowdin_translations_i…
kathyavini Jul 2, 2026
67bb3dc
New Crowdin translations by GitHub Action
crowdin-bot Jul 3, 2026
9e5c5a0
Clean incomplete translations
kathyavini Jul 3, 2026
d36b496
Merge pull request #4241 from LiteFarmOrg/l10n_crowdin_translations_i…
kathyavini Jul 3, 2026
712e66b
LF-5370 Replace history.push with history.replace so the survey can't…
kathyavini Jul 3, 2026
3b8fbc3
LF-5370 Make comment more precise
kathyavini Jul 3, 2026
0757759
Update release version to 3.12.1
kathyavini Jul 3, 2026
39106fa
Merge pull request #4246 from LiteFarmOrg/LF-5370-completed-survey-ca…
SayakaOno Jul 3, 2026
1d51a43
Merge pull request #4247 from LiteFarmOrg/patch/3.12.1-version-numbers
SayakaOno Jul 3, 2026
ff06241
LF-5375 Accept paginated or unpaginated data from Ensemble, and add t…
kathyavini Jul 3, 2026
3c0d69b
LF-5375 Use prescription ID rather than array order to select the lat…
kathyavini Jul 3, 2026
d260847
LF-5375 Clean up handling of non-paginated responses
kathyavini Jul 3, 2026
a91ffcc
LF-5375 Update timeNotification tests to mock new shape
kathyavini Jul 3, 2026
f96eb42
LF-5375 Use mockImplementation instead of call ordering to return rig…
kathyavini Jul 3, 2026
311f7f4
Merge pull request #4248 from LiteFarmOrg/LF-5375-handle-pagination-o…
kathyavini Jul 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 12 additions & 9 deletions .github/workflows/chromatic.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,25 @@ on:
jobs:
chromatic-deployment:
runs-on: ubuntu-latest
container: node:22.21
defaults:
run:
working-directory: packages/webapp
steps:
- name: Checkout repo
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
fetch-depth: 0
- uses: actions/setup-node@v6
with:
node-version: 22.21
- name: Setup pnpm
uses: pnpm/action-setup@v4
uses: pnpm/action-setup@v6
with:
package_json_file: "packages/webapp/package.json"
package_json_file: packages/webapp/package.json
- name: Install dependencies
run: pnpm install
- name: Publish to Chromatic
env:
NODE_OPTIONS: "--max-old-space-size=8192"
run: ./node_modules/.bin/chromatic --project-token ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
run: pnpm install --frozen-lockfile
- name: Run Chromatic
uses: chromaui/action@98132da0fca2211fb41b3af7b49ce85176007851 # v17.8.0
with:
projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
workingDir: packages/webapp
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* Copyright 2026 LiteFarm.org
* This file is part of LiteFarm.
*
* LiteFarm is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LiteFarm is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details, see <https://www.gnu.org/licenses/>.
*/

/**
* Generalizes the bespoke TAPE survey storage into a reusable, multi-survey model:
* - Renames `tape_survey` to `survey_response` and adds a `survey_key` column identifying which survey each row answers
* - Makes `survey_step` nullable, as it is TAPE-specific and not used by every survey
* - Repurposes the TAPE permissions to generic survey_response permissions (add/get/edit).
*
* @param { import("knex").Knex } knex
* @returns { Promise<void> }
*/
export const up = async function (knex) {
await knex.schema.renameTable('tape_survey', 'survey_response');

await knex.schema.alterTable('survey_response', function (table) {
table.string('survey_key').nullable();
});

// Every existing row is a TAPE response.
await knex('survey_response').update({ survey_key: 'tape' });
await knex.schema.alterTable('survey_response', function (table) {
table.string('survey_key').notNullable().alter();
});

await knex.schema.alterTable('survey_response', function (table) {
table.string('survey_step').nullable().alter();
});

// Repurpose the TAPE permissions (names only)
await knex('permissions')
.where({ permission_id: 185 })
.update({ name: 'add:survey_response', description: 'add survey_response' });
await knex('permissions')
.where({ permission_id: 186 })
.update({ name: 'get:survey_response', description: 'get survey_response' });
await knex('permissions')
.where({ permission_id: 187 })
.update({ name: 'edit:survey_response', description: 'edit survey_response' });
};

/**
* @param { import("knex").Knex } knex
* @returns { Promise<void> }
*/
export const down = async function (knex) {
await knex('permissions')
.where({ permission_id: 185 })
.update({ name: 'add:tape_survey', description: 'add tape_survey' });
await knex('permissions')
.where({ permission_id: 186 })
.update({ name: 'get:tape_survey', description: 'get tape_survey' });
await knex('permissions')
.where({ permission_id: 187 })
.update({ name: 'edit:tape_survey', description: 'edit tape_survey' });

await knex.schema.alterTable('survey_response', function (table) {
table.string('survey_step').notNullable().alter();
});
await knex.schema.alterTable('survey_response', function (table) {
table.dropColumn('survey_key');
});
await knex.schema.renameTable('survey_response', 'tape_survey');
};
4 changes: 2 additions & 2 deletions packages/api/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/api/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "litefarm-api",
"version": "3.12.0",
"version": "3.12.1",
"description": "LiteFarm API server",
"main": "./api/src/server.js",
"type": "module",
Expand Down
149 changes: 149 additions & 0 deletions packages/api/src/controllers/surveyResponseController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
/*
* Copyright 2026 LiteFarm.org
* This file is part of LiteFarm.
*
* LiteFarm is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LiteFarm is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details, see <https://www.gnu.org/licenses/>.
*/

import { Response } from 'express';
import { LiteFarmRequest } from '../types.js';
import SurveyResponseModel from '../models/surveyResponseModel.js';

interface SurveyResponseData {
survey_version: string;
project_id: string;
survey_step?: string;
[key: string]: unknown;
}

interface CreateSurveyResponseReqBody {
survey_key: string;
survey_response: SurveyResponseData;
}

interface LatestSurveyResponseQuery {
survey_key?: string;
}

interface UpdateSurveyResponseParams {
submission_id: string;
}

interface UpdateSurveyResponseReqBody {
survey_response: SurveyResponseData;
}

type InsertableQuery = { insert: (data: Record<string, unknown>) => Promise<unknown> };

const surveyResponseController = {
createSurveyResponse() {
return async (
req: LiteFarmRequest<unknown, unknown, unknown, CreateSurveyResponseReqBody>,
res: Response,
) => {
try {
const { farm_id } = req.headers;
const user_id = req.auth?.user_id;
const { survey_key, survey_response } = req.body;
if (!survey_key) {
return res.status(400).json({ error: 'survey_key is required' });
}
const { survey_version, project_id, survey_step } = survey_response;

const insertQuery = SurveyResponseModel.query().context({
user_id,
}) as unknown as InsertableQuery;
await insertQuery.insert({
farm_id,
survey_key,
survey_response,
survey_version,
project_id,
survey_step,
});

return res.status(201).send();
} catch (error) {
console.error(error);
return res.status(500).json({ error });
}
};
},

getLatestSurveyResponse() {
return async (req: LiteFarmRequest<LatestSurveyResponseQuery>, res: Response) => {
try {
const { farm_id } = req.headers;
const { survey_key } = req.query;
if (!survey_key) {
return res.status(400).json({ error: 'survey_key is required' });
}
// Find the latest survey response of this kind for the farm
const result = await SurveyResponseModel.query()
.where({ farm_id, survey_key })
.orderBy('created_at', 'desc')
.first();
return res.status(200).send(result ?? null);
} catch (error) {
console.error(error);
return res.status(500).json({ error });
}
};
},

// Note: Not currently called from frontend
updateSurveyResponse() {
return async (
req: LiteFarmRequest<
unknown,
UpdateSurveyResponseParams,
unknown,
UpdateSurveyResponseReqBody
>,
res: Response,
) => {
try {
const { farm_id } = req.headers;
const user_id = req.auth?.user_id;
const { submission_id } = req.params;
const { survey_response } = req.body;
const { survey_version, project_id, survey_step } = survey_response;

const existing = (await SurveyResponseModel.query().findOne({ submission_id })) as
| { survey_key: string }
| undefined;
if (!existing) {
return res.status(404).json({ error: 'Survey response not found' });
}

const insertQuery = SurveyResponseModel.query().context({
user_id,
}) as unknown as InsertableQuery;
await insertQuery.insert({
submission_id,
farm_id,
survey_key: existing.survey_key,
survey_response,
survey_version,
project_id,
survey_step,
});

return res.status(204).send();
} catch (error) {
console.error(error);
return res.status(500).json({ error });
}
};
},
};

export default surveyResponseController;
87 changes: 0 additions & 87 deletions packages/api/src/controllers/tapeSurveyController.js

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,10 @@ const timeNotificationController = {
);

for (const locationId in prescriptionsByLocation) {
const latestPrescription = prescriptionsByLocation[locationId].at(-1);
// Select the latest prescription explicitly rather than relying on the order Ensemble returns.
const latestPrescription = prescriptionsByLocation[locationId].reduce((latest, current) =>
current.id > latest.id ? current : latest,
);
const { id: irrigation_prescription_id, recommended_start_date } = latestPrescription;

const previousNotification = await NotificationModel.query()
Expand Down
Loading
Loading