Skip to content

Commit

Permalink
Merge pull request #4933 from kobotoolbox/nlp-addons-task-624-change-…
Browse files Browse the repository at this point in the history
…displayed-language

[TASK-624] Allow users to select between translated labels and XML names for displayed form questions in NLP UI
  • Loading branch information
magicznyleszek authored Jul 1, 2024
2 parents f7569d9 + a832255 commit b6e5a4e
Show file tree
Hide file tree
Showing 9 changed files with 158 additions and 34 deletions.
9 changes: 8 additions & 1 deletion jsapp/js/assetUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,9 @@ export function getOrganizationDisplayString(asset: AssetResponse | ProjectViewA
* Returns the index of language or null if not found.
*/
export function getLanguageIndex(asset: AssetResponse, langString: string) {
let foundIndex = null;
// Return -1 instead of null as that would allow
// `getQuestionOrChoiceDisplayName` to defualt to xml names.
let foundIndex = -1;

if (
Array.isArray(asset.summary?.languages) &&
Expand Down Expand Up @@ -224,6 +226,11 @@ export function getQuestionOrChoiceDisplayName(
}

if (questionOrChoice.label && Array.isArray(questionOrChoice.label)) {
// If the user hasn't made translations yet for a form language show
// the xml names instead of blank.
if (questionOrChoice.label[translationIndex] === null) {
return getRowName(questionOrChoice);
}
return questionOrChoice.label[translationIndex];
} else if (questionOrChoice.label && !Array.isArray(questionOrChoice.label)) {
// in rare cases the label could be a string
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export default function ProcessingSidebar(props: ProcessingSidebarProps) {
)}

{displays.includes(StaticDisplays.Data) && (
<SidebarSubmissionData assetContent={props.asset.content} />
<SidebarSubmissionData asset={props.asset} />
)}

{displays.length === 0 && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@
}
}

.options {
margin: 0 !important;
overflow-y: auto;
// We need to win the specificity here
ul.options.options {
margin: 0;
}

.questionList {
Expand Down
37 changes: 33 additions & 4 deletions jsapp/js/components/processing/sidebar/sidebarDisplaySettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,21 @@ import ToggleSwitch from 'js/components/common/toggleSwitch';
import Button from 'js/components/common/button';
import {AsyncLanguageDisplayLabel} from 'js/components/languages/languagesUtils';
import type {LanguageCode} from 'js/components/languages/languagesStore';
import type {AssetContent} from 'js/dataInterface';
import {getActiveTab} from 'js/components/processing/routes.utils';
import styles from './sidebarDisplaySettings.module.scss';
import MultiCheckbox from 'js/components/common/multiCheckbox';
import type {MultiCheckboxItem} from 'js/components/common/multiCheckbox';
import cx from 'classnames';
import KoboSelect from 'js/components/common/koboSelect';

export default function SidebarDisplaySettings() {
const [store] = useState(() => singleProcessingStore);
const [isModalOpen, setIsModalOpen] = useState<boolean>(false);
const [labelLanguage, setLabelLanguage] = useState<LanguageCode | string>(
store.getCurrentlyDisplayedLanguage()
);

const displayedLanguageList = store.getDisplayedLanguagesList();

const activeTab = getActiveTab();

Expand Down Expand Up @@ -159,7 +164,13 @@ export default function SidebarDisplaySettings() {
}}
size='medium'
>
<KoboModalHeader>{t('Customize display settings')}</KoboModalHeader>
<KoboModalHeader
onRequestCloseByX={() => {
setSelectedDisplays(store.getDisplays(activeTab));
setSelectedFields(getInitialFields());
setIsModalOpen(false);
}}
>{t('Customize display settings')}</KoboModalHeader>

<KoboModalContent>
<p className={styles.description}>
Expand All @@ -169,6 +180,22 @@ export default function SidebarDisplaySettings() {
</p>

<ul className={styles.options}>
<li className={styles.display}>
<KoboSelect
label={t('Display labels or XML values?')}
name='displayedLanguage'
type='outline'
size='s'
options={displayedLanguageList}
selectedOption={labelLanguage}
onChange={(languageCode) => {
if (languageCode) {
setLabelLanguage(languageCode);
}
}}
/>
</li>

{availableDisplays.map((entry) => {
const isEnabled = selectedDisplays.includes(entry);

Expand Down Expand Up @@ -234,7 +261,7 @@ export default function SidebarDisplaySettings() {
<KoboModalFooter alignment='center'>
{/* This button resets the displays for current tab. */}
<Button
label={<strong>{t('Reset')}</strong>}
label={t('Reset')}
type='frame'
color='light-blue'
size='m'
Expand All @@ -245,19 +272,21 @@ export default function SidebarDisplaySettings() {
// when the modal is closed.
resetFieldsSelection();
setSelectedDisplays(store.getDisplays(activeTab));
setLabelLanguage(store.getCurrentlyDisplayedLanguage());
setIsModalOpen(false);
}}
/>

{/* Applies current selection of displays to the sidebar. */}
<Button
label={<strong>{t('Apply selection')}</strong>}
label={t('Apply selection')}
type='full'
color='light-blue'
size='m'
onClick={() => {
applyFieldsSelection();
store.setDisplays(activeTab, selectedDisplays);
store.setCurrentlyDisplayedLanguage(labelLanguage);
setIsModalOpen(false);
}}
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import React, {useState} from 'react';
import SubmissionDataList from 'js/components/submissions/submissionDataList';
import singleProcessingStore from 'js/components/processing/singleProcessingStore';
import type {AssetContent} from 'js/dataInterface';
import type {AssetContent, AssetResponse} from 'js/dataInterface';
import {META_QUESTION_TYPES, ADDITIONAL_SUBMISSION_PROPS} from 'js/constants';
import styles from './sidebarSubmissionData.module.scss';

interface SidebarSubmissionDataProps {
assetContent: AssetContent | undefined;
asset: AssetResponse;
}

export default function SidebarSubmissionData(
Expand All @@ -16,7 +16,7 @@ export default function SidebarSubmissionData(

const submissionData = store.getSubmissionData();

if (!props.assetContent) {
if (!props.asset.content) {
return null;
}

Expand All @@ -41,7 +41,7 @@ export default function SidebarSubmissionData(
<div className={styles.dataListBody}>
{submissionData && (
<SubmissionDataList
assetContent={props.assetContent}
asset={props.asset}
submissionData={submissionData}
hideQuestions={getQuestionsToHide()}
hideGroups
Expand Down
21 changes: 16 additions & 5 deletions jsapp/js/components/processing/singleProcessingHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import React from 'react';
import {QUESTION_TYPES, META_QUESTION_TYPES} from 'js/constants';
import type {AssetContent} from 'js/dataInterface';
import type {AssetContent, AssetResponse} from 'js/dataInterface';
import {
findRowByQpath,
getRowTypeIcon,
getTranslatedRowLabel,
getRowName,
getLanguageIndex,
} from 'js/assetUtils';
import {ROUTES} from 'js/router/routerConstants';
import Button from 'js/components/common/button';
Expand All @@ -22,7 +23,7 @@ import classNames from 'classnames';
interface SingleProcessingHeaderProps extends WithRouterProps {
submissionEditId: string;
assetUid: string;
assetContent: AssetContent;
asset: AssetResponse;
}

interface SingleProcessingHeaderState {
Expand Down Expand Up @@ -86,9 +87,19 @@ class SingleProcessingHeader extends React.Component<
getQuestionSelectorOptions() {
const options: KoboSelectOption[] = [];
const editIds = singleProcessingStore.getSubmissionsEditIds();
const assetContent = this.props.asset.content;
const languageIndex = getLanguageIndex(
this.props.asset,
singleProcessingStore.getCurrentlyDisplayedLanguage()
);

if (!assetContent) {
return [];
}

if (editIds) {
Object.keys(editIds).forEach((qpath) => {
const questionData = findRowByQpath(this.props.assetContent, qpath);
const questionData = findRowByQpath(assetContent, qpath);
// At this point we want to find out whether the question has at least
// one editId (i.e. there is at least one transcriptable response to
// the question). Otherwise there's no point in having the question as
Expand All @@ -107,8 +118,8 @@ class SingleProcessingHeader extends React.Component<
const rowName = getRowName(questionData);
const translatedLabel = getTranslatedRowLabel(
rowName,
this.props.assetContent.survey,
0
assetContent.survey,
languageIndex
);
options.push({
value: qpath,
Expand Down
2 changes: 1 addition & 1 deletion jsapp/js/components/processing/singleProcessingRoute.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ export default class SingleProcessingRoute extends React.Component<
<SingleProcessingHeader
submissionEditId={this.props.params.submissionEditId}
assetUid={this.props.params.uid}
assetContent={this.state.asset.content}
asset={this.state.asset}
/>
</section>

Expand Down
51 changes: 50 additions & 1 deletion jsapp/js/components/processing/singleProcessingStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
getRowName,
getRowNameByQpath,
getFlatQuestionsList,
getLanguageIndex,
} from 'js/assetUtils';
import type {SurveyFlatPaths} from 'js/assetUtils';
import assetStore from 'js/assetStore';
Expand All @@ -34,6 +35,7 @@ import {
getCurrentProcessingRouteParts,
ProcessingTab,
} from 'js/components/processing/routes.utils';
import type {KoboSelectOption} from 'js/components/common/koboSelect';
import {getExponentialDelayTime} from 'jsapp/js/utils';
import envStore from 'jsapp/js/envStore';

Expand Down Expand Up @@ -127,6 +129,7 @@ interface SingleProcessingStoreData {
isFetchingData: boolean;
isPollingForTranscript: boolean;
hiddenSidebarQuestions: string[];
currentlyDisplayedLanguage: LanguageCode | string;
exponentialBackoffCount: number;
}

Expand Down Expand Up @@ -158,6 +161,7 @@ class SingleProcessingStore extends Reflux.Store {
isFetchingData: false,
isPollingForTranscript: false,
hiddenSidebarQuestions: [],
currentlyDisplayedLanguage: this.getInitialDisplayedLanguage(),
exponentialBackoffCount: 1,
};

Expand All @@ -171,6 +175,7 @@ class SingleProcessingStore extends Reflux.Store {
this.data.translationDraft = undefined;
this.data.source = undefined;
this.data.isPristine = true;
this.data.currentlyDisplayedLanguage = this.getInitialDisplayedLanguage();
this.data.exponentialBackoffCount = 1;
}

Expand Down Expand Up @@ -968,6 +973,41 @@ class SingleProcessingStore extends Reflux.Store {
);
}

getDisplayedLanguagesList(): KoboSelectOption[] {
const languagesList = [];

languagesList.push({label: t('XML names'), value: 'xml_names'});
const asset = assetStore.getAsset(this.currentAssetUid);
const baseLabel = t('Labels');

if (asset?.summary?.languages && asset?.summary?.languages.length > 0) {
asset.summary.languages.forEach((language) => {
let label = baseLabel;
if (language !== null) {
label += ` - ${language}`;
}
languagesList.push({label: label, value: language});
});
} else {
languagesList.push({label: baseLabel, value: ''});
}

return languagesList;
}

getInitialDisplayedLanguage() {
const asset = assetStore.getAsset(this.currentAssetUid);
if (asset?.summary?.languages && asset?.summary?.languages[0]) {
return asset?.summary?.languages[0];
} else {
return '';
}
}

getCurrentlyDisplayedLanguage() {
return this.data.currentlyDisplayedLanguage;
}

getInitialDisplays(): SidebarDisplays {
return {
transcript: DefaultDisplays.get(ProcessingTab.Transcript) || [],
Expand Down Expand Up @@ -1000,7 +1040,10 @@ class SingleProcessingStore extends Reflux.Store {
const asset = assetStore.getAsset(this.currentAssetUid);

if (asset?.content?.survey) {
const questionsList = getFlatQuestionsList(asset.content.survey, 0)
const questionsList = getFlatQuestionsList(
asset.content.survey,
getLanguageIndex(asset, this.data.currentlyDisplayedLanguage)
)
.filter((question) => !(question.name === this.currentQuestionName))
.map((question) => {
// We make an object to show the question label to the user but use the
Expand Down Expand Up @@ -1071,6 +1114,12 @@ class SingleProcessingStore extends Reflux.Store {

this.trigger(this.data);
}

setCurrentlyDisplayedLanguage(language: LanguageCode) {
this.data.currentlyDisplayedLanguage = language;

this.trigger(this.data);
}
}

/**
Expand Down
Loading

0 comments on commit b6e5a4e

Please sign in to comment.