Skip to content

Commit

Permalink
[DataCleanup] Implement changes, completed for ReviewEntries (#2743)
Browse files Browse the repository at this point in the history
  • Loading branch information
imnasnainaec authored and jmgrady committed Dec 5, 2023
1 parent d69ed66 commit 26e9677
Show file tree
Hide file tree
Showing 76 changed files with 1,234 additions and 456 deletions.
28 changes: 28 additions & 0 deletions Backend.Tests/Controllers/WordControllerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,34 @@ public async Task TestIsFrontierNonemptyMissingProject()
Assert.That(result, Is.InstanceOf<NotFoundObjectResult>());
}

[Test]
public async Task TestIsInFrontier()
{
var wordNotInFrontier = await _wordRepo.Add(Util.RandomWord(_projId));
var falseResult = (ObjectResult)await _wordController.IsInFrontier(_projId, wordNotInFrontier.Id);
Assert.That(falseResult.Value, Is.False);

var wordInFrontier = await _wordRepo.AddFrontier(Util.RandomWord(_projId));
var trueResult = (ObjectResult)await _wordController.IsInFrontier(_projId, wordInFrontier.Id);
Assert.That(trueResult.Value, Is.True);
}

[Test]
public async Task TestIsInFrontierNoPermission()
{
var wordInFrontier = await _wordRepo.AddFrontier(Util.RandomWord(_projId));
_wordController.ControllerContext.HttpContext = PermissionServiceMock.UnauthorizedHttpContext();
var result = await _wordController.IsInFrontier(_projId, wordInFrontier.Id);
Assert.That(result, Is.InstanceOf<ForbidResult>());
}

[Test]
public async Task TestIsInFrontierMissingProject()
{
var result = await _wordController.IsInFrontier(MissingId, "anything");
Assert.That(result, Is.InstanceOf<NotFoundObjectResult>());
}

[Test]
public async Task TestGetFrontier()
{
Expand Down
17 changes: 17 additions & 0 deletions Backend/Controllers/WordController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,23 @@ public async Task<IActionResult> GetProjectFrontierWords(string projectId)
return Ok(await _wordRepo.GetFrontier(projectId));
}

/// <summary> Checks if Frontier has <see cref="Word"/> in specified <see cref="Project"/>. </summary>
[HttpGet("isinfrontier/{wordId}", Name = "IsInFrontier")]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(bool))]
public async Task<IActionResult> IsInFrontier(string projectId, string wordId)
{
if (!await _permissionService.HasProjectPermission(HttpContext, Permission.WordEntry, projectId))
{
return Forbid();
}
var project = await _projRepo.GetProject(projectId);
if (project is null)
{
return NotFound(projectId);
}
return Ok(await _wordRepo.IsInFrontier(projectId, wordId));
}

/// <summary>
/// Checks if a <see cref="Word"/> is a duplicate--i.e., are its primary text fields
/// (Vernacular, Gloss text, Definition text) contained in a frontier entry?
Expand Down
15 changes: 15 additions & 0 deletions public/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,14 @@
"toolbar": {
"search": "Search"
}
},
"completed": {
"number": "Number of entries edited: "
},
"undo": {
"undo": "Undo Edit",
"undoDialog": "Undo this edit?",
"undoDisabled": "Undo Unavailable"
}
},
"charInventory": {
Expand Down Expand Up @@ -482,5 +490,12 @@
"Other": "Other",
"Unspecified": "Unspecified"
}
},
"wordCard": {
"senseCount": "Senses: {{ val }}",
"wordId": "Id: {{ val }}",
"wordModified": "Modified: {{ val }}",
"domainAdded": "Added: {{ val }}",
"user": "By user: {{ val }}"
}
}
132 changes: 132 additions & 0 deletions src/api/api/word-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,55 @@ export const WordApiAxiosParamCreator = function (
options: localVarRequestOptions,
};
},
/**
*
* @param {string} projectId
* @param {string} wordId
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
isInFrontier: async (
projectId: string,
wordId: string,
options: any = {}
): Promise<RequestArgs> => {
// verify required parameter 'projectId' is not null or undefined
assertParamExists("isInFrontier", "projectId", projectId);
// verify required parameter 'wordId' is not null or undefined
assertParamExists("isInFrontier", "wordId", wordId);
const localVarPath =
`/v1/projects/{projectId}/words/isinfrontier/{wordId}`
.replace(`{${"projectId"}}`, encodeURIComponent(String(projectId)))
.replace(`{${"wordId"}}`, encodeURIComponent(String(wordId)));
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
let baseOptions;
if (configuration) {
baseOptions = configuration.baseOptions;
}

const localVarRequestOptions = {
method: "GET",
...baseOptions,
...options,
};
const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any;

setSearchParams(localVarUrlObj, localVarQueryParameter, options.query);
let headersFromBaseOptions =
baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {
...localVarHeaderParameter,
...headersFromBaseOptions,
...options.headers,
};

return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
/**
*
* @param {string} projectId
Expand Down Expand Up @@ -692,6 +741,32 @@ export const WordApiFp = function (configuration?: Configuration) {
configuration
);
},
/**
*
* @param {string} projectId
* @param {string} wordId
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async isInFrontier(
projectId: string,
wordId: string,
options?: any
): Promise<
(axios?: AxiosInstance, basePath?: string) => AxiosPromise<boolean>
> {
const localVarAxiosArgs = await localVarAxiosParamCreator.isInFrontier(
projectId,
wordId,
options
);
return createRequestFunction(
localVarAxiosArgs,
globalAxios,
BASE_PATH,
configuration
);
},
/**
*
* @param {string} projectId
Expand Down Expand Up @@ -870,6 +945,22 @@ export const WordApiFactory = function (
.isFrontierNonempty(projectId, options)
.then((request) => request(axios, basePath));
},
/**
*
* @param {string} projectId
* @param {string} wordId
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
isInFrontier(
projectId: string,
wordId: string,
options?: any
): AxiosPromise<boolean> {
return localVarFp
.isInFrontier(projectId, wordId, options)
.then((request) => request(axios, basePath));
},
/**
*
* @param {string} projectId
Expand Down Expand Up @@ -1035,6 +1126,27 @@ export interface WordApiIsFrontierNonemptyRequest {
readonly projectId: string;
}

/**
* Request parameters for isInFrontier operation in WordApi.
* @export
* @interface WordApiIsInFrontierRequest
*/
export interface WordApiIsInFrontierRequest {
/**
*
* @type {string}
* @memberof WordApiIsInFrontier
*/
readonly projectId: string;

/**
*
* @type {string}
* @memberof WordApiIsInFrontier
*/
readonly wordId: string;
}

/**
* Request parameters for updateDuplicate operation in WordApi.
* @export
Expand Down Expand Up @@ -1215,6 +1327,26 @@ export class WordApi extends BaseAPI {
.then((request) => request(this.axios, this.basePath));
}

/**
*
* @param {WordApiIsInFrontierRequest} requestParameters Request parameters.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof WordApi
*/
public isInFrontier(
requestParameters: WordApiIsInFrontierRequest,
options?: any
) {
return WordApiFp(this.configuration)
.isInFrontier(
requestParameters.projectId,
requestParameters.wordId,
options
)
.then((request) => request(this.axios, this.basePath));
}

/**
*
* @param {WordApiUpdateDuplicateRequest} requestParameters Request parameters.
Expand Down
9 changes: 9 additions & 0 deletions src/backend/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -707,6 +707,15 @@ export async function isFrontierNonempty(projectId?: string): Promise<boolean> {
return (await wordApi.isFrontierNonempty(params, defaultOptions())).data;
}

export async function isInFrontier(
wordId: string,
projectId?: string
): Promise<boolean> {
projectId = projectId || LocalStorage.getProjectId();
const params = { projectId, wordId };
return (await wordApi.isInFrontier(params, defaultOptions())).data;
}

export async function updateDuplicate(
dupId: string,
word: Word
Expand Down
2 changes: 1 addition & 1 deletion src/components/App/DefaultState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { defaultState as pronunciationsState } from "components/Pronunciations/R
import { defaultState as treeViewState } from "components/TreeView/Redux/TreeViewReduxTypes";
import { defaultState as characterInventoryState } from "goals/CharacterInventory/Redux/CharacterInventoryReduxTypes";
import { defaultState as mergeDuplicateGoal } from "goals/MergeDuplicates/Redux/MergeDupsReducer";
import { defaultState as reviewEntriesState } from "goals/ReviewEntries/ReviewEntriesComponent/Redux/ReviewEntriesReduxTypes";
import { defaultState as reviewEntriesState } from "goals/ReviewEntries/Redux/ReviewEntriesReduxTypes";
import { defaultState as analyticsState } from "types/Redux/analyticsReduxTypes";

export const defaultState = {
Expand Down
4 changes: 2 additions & 2 deletions src/components/Buttons/FlagButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { themeColors } from "types/theme";

interface FlagButtonProps {
flag: Flag;
buttonId: string;
buttonId?: string;
updateFlag?: (flag: Flag) => void;
}

Expand Down Expand Up @@ -56,7 +56,7 @@ export default function FlagButton(props: FlagButtonProps): ReactElement {
onClick={
props.updateFlag ? () => setOpen(true) : active ? () => {} : undefined
}
buttonId={props.buttonId}
buttonId={props.buttonId ?? "flag-button"}
side="top"
/>
{props.updateFlag && (
Expand Down
61 changes: 61 additions & 0 deletions src/components/Buttons/UndoButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { Button, Grid } from "@mui/material";
import { ReactElement, useEffect, useState } from "react";
import { useTranslation } from "react-i18next";

import { CancelConfirmDialog } from "components/Dialogs";

interface UndoButtonProps {
buttonIdEnabled?: string;
buttonIdCancel?: string;
buttonIdConfirm?: string;
textIdDialog: string;
textIdDisabled: string;
textIdEnabled: string;
isUndoAllowed: () => Promise<boolean>;
undo: () => Promise<void>;
}

export default function UndoButton(props: UndoButtonProps): ReactElement {
const isUndoAllowed = props.isUndoAllowed;

const [isUndoEnabled, setUndoEnabled] = useState(false);
const [undoDialogOpen, setUndoDialogOpen] = useState(false);

const { t } = useTranslation();

useEffect(() => {
if (!undoDialogOpen) {
isUndoAllowed().then(setUndoEnabled);
}
}, [isUndoAllowed, undoDialogOpen]);

return (
<Grid container direction="column" justifyContent="center">
{isUndoEnabled ? (
<div>
<Button
variant="outlined"
id={props.buttonIdEnabled}
onClick={() => setUndoDialogOpen(true)}
>
{t(props.textIdEnabled)}
</Button>
<CancelConfirmDialog
open={undoDialogOpen}
textId={props.textIdDialog}
handleCancel={() => setUndoDialogOpen(false)}
handleConfirm={() =>
props.undo().then(() => setUndoDialogOpen(false))
}
buttonIdCancel={props.buttonIdCancel}
buttonIdConfirm={props.buttonIdConfirm}
/>
</div>
) : (
<div>
<Button disabled>{t(props.textIdDisabled)}</Button>
</div>
)}
</Grid>
);
}
2 changes: 2 additions & 0 deletions src/components/Buttons/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import IconButtonWithTooltip from "components/Buttons/IconButtonWithTooltip";
import LoadingButton from "components/Buttons/LoadingButton";
import LoadingDoneButton from "components/Buttons/LoadingDoneButton";
import PartOfSpeechButton from "components/Buttons/PartOfSpeechButton";
import UndoButton from "components/Buttons/UndoButton";

export {
FileInputButton,
Expand All @@ -12,4 +13,5 @@ export {
LoadingButton,
LoadingDoneButton,
PartOfSpeechButton,
UndoButton,
};
Loading

0 comments on commit 26e9677

Please sign in to comment.