Skip to content

Commit

Permalink
Show Live Check Results (#1114)
Browse files Browse the repository at this point in the history
  • Loading branch information
rolfheij-sil authored Sep 9, 2024
2 parents df652fe + 4819457 commit 3f4b63d
Show file tree
Hide file tree
Showing 15 changed files with 1,582 additions and 1,569 deletions.
1 change: 0 additions & 1 deletion extensions/src/hello-world/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,6 @@ const helloWorldProjectWebViewProvider: IWebViewProviderWithType = {
`${this.webViewType} provider received request to provide a ${savedWebView.webViewType} web view`,
);

// We know that the projectId (if present in the state) will be a string.
const projectId = getWebViewOptions.projectId || savedWebView.projectId || undefined;
return {
title: projectId
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,14 @@
"%project_settings_platformScripture_nonRepeatableWords_label%": "Non-repeatable Words",
"%project_settings_platformScripture_nonRepeatableWords_description%": "List of words that are not allowed to be repeated in this project.",
"%mainMenu_toggleIncludeMyParatext9Projects%": "Toggle Include My Paratext 9 Projects",
"%webView_platformScripture_tools%": "Tools",
"%webView_platformScripture_showCheckResults%": "Show Check Results...",
"%webView_characterInventory_title%": "Character Inventory",
"%webView_repeatedWordsInventory_title%": "Repeated Words Inventory",
"%webView_configureChecks_title%": "Configure Checks - {projectName}",
"%webView_checkResultsList_title%": "Check Results - {projectName}",
"%webView_configureChecks_title%": "Configure Checks: {projectName}",
"%webView_checkResultsList_title%": "Check Results ({resultsCount}): {projectName}",
"%webView_checkResultsList_unspecifiedCheckId%": "Unspecified check ID",
"%webView_checkResultsList_unspecifiedCheckDescription%": "Unspecified check description",
"%webView_inventory_all%": "All items",
"%webView_inventory_approved%": "Approved items",
"%webView_inventory_unapproved%": "Unapproved items",
Expand Down
40 changes: 30 additions & 10 deletions extensions/src/platform-scripture/contributions/menus.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,34 @@
}
]
},
"defaultWebViewTopMenu": {
"columns": {},
"groups": {},
"items": []
},
"defaultWebViewContextMenu": {
"groups": {},
"items": []
},
"webViewMenus": {}
"webViewMenus": {
"platformScripture.configureChecks": {
"topMenu": {
"columns": {
"platformScripture.tools": {
"label": "%webView_platformScripture_tools%",
"order": 1
}
},
"groups": {
"platformScripture.checks": {
"column": "platformScripture.tools",
"order": 1
}
},
"items": [
{
"label": "%webView_platformScripture_showCheckResults%",
"group": "platformScripture.checks",
"order": 1,
"command": "platformScripture.showCheckResults"
}
]
},
"contextMenu": {
"groups": {},
"items": []
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
SavedWebViewDefinition,
WebViewDefinition,
} from '@papi/core';
import { formatReplacementString } from 'platform-bible-utils';
import checkingResultsListWebView from './checking-results-list.web-view?inline';
import checkingResultsListStyles from './checking-results-list.web-view.scss?inline';

Expand All @@ -27,25 +28,25 @@ export default class CheckResultsWebViewProvider implements IWebViewProvider {
);

// We know that the projectId (if present in the state) will be a string.
let projectId =
getWebViewOptions.projectId ||
// eslint-disable-next-line no-type-assertion/no-type-assertion
(savedWebView.state?.projectId as string) ||
undefined;

let title = await papi.localization.getLocalizedString({
localizeKey: '%webView_checkResultsList_title%',
});
let projectId = getWebViewOptions.projectId || savedWebView.projectId || undefined;

let projectName: string | undefined;

if (projectId) {
const pdp = await papi.projectDataProviders.get('platform.base', projectId);
projectName = (await pdp.getSetting('platform.name')) ?? projectId;

title += ` - ${projectName}`;
}

const title = formatReplacementString(
await papi.localization.getLocalizedString({
localizeKey: '%webView_checkResultsList_title%',
}),
{
resultsCount: '0',
projectName,
},
);

if (!projectId && globalThis.isNoisyDevModeEnabled) {
logger.debug(`${title} web view did not get a project passed in. Choosing a random one...`);

Expand All @@ -60,13 +61,12 @@ export default class CheckResultsWebViewProvider implements IWebViewProvider {
logger.info(`${title} web view opening with ${projectId}`);

return {
title,
...savedWebView,
title,
projectId,
content: checkingResultsListWebView,
styles: checkingResultsListStyles,
state: {
projectName,
projectId,
...savedWebView.state,
webViewType: this.webViewType,
},
Expand Down
155 changes: 124 additions & 31 deletions extensions/src/platform-scripture/src/checking-results-list.web-view.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { WebViewProps } from '@papi/core';
import { Button, Label, ResultsSet, ScriptureResultsViewer } from 'platform-bible-react';
import { useState, useCallback, useEffect, useMemo } from 'react';
import { badLeftoversCheck, engineProblemsCheck } from './testing/test-scripture-checks';
import { Label, ResultsSet, ScriptureResultsViewer, usePromise } from 'platform-bible-react';
import { useCallback, useEffect, useMemo } from 'react';
import { useData, useLocalizedStrings } from '@papi/frontend/react';
import { CheckRunnerCheckDetails, CheckRunResult } from 'platform-scripture';
import { Canon } from '@sillsdev/scripture';
import { formatReplacementString, LanguageStrings } from 'platform-bible-utils';
import papi, { logger } from '@papi/frontend';

const getLabel = (
projectName: string | undefined,
Expand All @@ -23,46 +27,135 @@ const getLabel = (
return result;
};

global.webViewComponent = function CheckingResultsListWebView({ useWebViewState }: WebViewProps) {
const [projectName] = useWebViewState('projectName', 'Dummy project');
const parseResults = (
checkResults: CheckRunResult[],
availableChecks: CheckRunnerCheckDetails[],
projectId: string | undefined,
localizedStrings: LanguageStrings,
): ResultsSet[] => {
const unspecifiedCheckId = localizedStrings['%webView_checkResultsList_unspecifiedCheckId%'];
const unspecifiedCheckDescription =
localizedStrings['%webView_checkResultsList_unspecifiedCheckDescription%'];

// This is stub code to get some dummy checking results.
// TODO (#994): Replace this with calls to get actual check results and subscribe to updates.
const onRerun = useCallback(() => {
badLeftoversCheck.reRun();
engineProblemsCheck.reRun();
}, []);
const resultsSets: ResultsSet[] = [];

const sources = useMemo(() => [badLeftoversCheck, engineProblemsCheck], []);
const checkResultsForProject = checkResults.filter(
(checkResult) => checkResult.projectId === projectId,
);
checkResultsForProject.forEach((checkResult) => {
let resultsSet = resultsSets.find((newSource) => newSource.source.id === checkResult.checkId);
if (!resultsSet) {
const checkId = checkResult.checkId ?? unspecifiedCheckId;
const checkDescription =
availableChecks.find((check) => check.checkId === checkResult.checkId)?.checkDescription ??
unspecifiedCheckDescription;
resultsSet = {
source: {
id: checkId,
displayName: checkDescription,
},
data: [],
};
resultsSets.push(resultsSet);
}
resultsSet.data.push({
detail: checkResult.messageFormatString,
start:
'verseRef' in checkResult.start
? {
bookNum: Canon.bookIdToNumber(checkResult.start.verseRef.book),
chapterNum: checkResult.start.verseRef.chapterNum,
verseNum: checkResult.start.verseRef.verseNum,
jsonPath: '',
offset: checkResult.start.offset,
}
: {
bookNum: 0,
chapterNum: 0,
verseNum: 0,
jsonPath: checkResult.start.jsonPath,
offset: checkResult.start.offset,
},
});
});
return resultsSets;
};

const [lastUpdateTimestamp, setLastUpdateTimestamp] = useState<string | undefined>(undefined);
const [currentSources, setCurrentSources] = useState(sources);
global.webViewComponent = function CheckingResultsListWebView({
projectId,
updateWebViewDefinition,
}: WebViewProps) {
const [checkResults] = useData('platformScripture.checkAggregator').CheckResults(undefined, []);
const [availableChecks] = useData('platformScripture.checkAggregator').AvailableChecks(
undefined,
[],
);

const [localizedStrings] = useLocalizedStrings(
useMemo(
() => [
'%webView_checkResultsList_unspecifiedCheckId%',
'%webView_checkResultsList_unspecifiedCheckDescription%',
],
[],
),
);

const viewableResults = useMemo(
() => parseResults(checkResults, availableChecks, projectId, localizedStrings),
[availableChecks, checkResults, localizedStrings, projectId],
);

const [projectName] = usePromise(
useCallback(async () => {
if (!projectId) return '';
const pdp = await papi.projectDataProviders.get('platform.base', projectId);
const name = await pdp.getSetting('platform.name');
return name;
}, [projectId]),
useMemo(() => '', []),
);

const label = useMemo(
() => getLabel(projectName, new Date().toLocaleString(), viewableResults),
[projectName, viewableResults],
);

useEffect(() => {
setCurrentSources(sources);
}, [sources]);
let promiseIsCurrent = true;
const updateTitle = async () => {
try {
const newTitle = formatReplacementString(
await papi.localization.getLocalizedString({
localizeKey: '%webView_checkResultsList_title%',
}),
{
resultsCount: checkResults.filter((checkResult) => checkResult.projectId === projectId)
.length,
projectName,
},
);

const handleResultsUpdated = useCallback(() => {
const currentTimestamp = new Date().toLocaleString();
setLastUpdateTimestamp(currentTimestamp);
}, []);
if (promiseIsCurrent)
updateWebViewDefinition({
title: newTitle,
});
} catch (error) {
logger.error('Error updating Check Results title! Reason:', error);
}
};

const reRunChecks = useCallback(() => {
if (onRerun) {
onRerun();
// Since onRerun modifies the sources directly, we need to trigger a state update
setCurrentSources([...sources]);
handleResultsUpdated();
}
}, [onRerun, sources, handleResultsUpdated]);
updateTitle();

const label = getLabel(projectName, lastUpdateTimestamp, sources);
return () => {
promiseIsCurrent = false;
};
}, [updateWebViewDefinition, checkResults.length, projectName, checkResults, projectId]);

return (
<div className="checking-results-list">
<Button onClick={reRunChecks}>Rerun</Button>
{label && <Label className="checking-results-list-label">{label}</Label>}
<ScriptureResultsViewer sources={currentSources} />
<ScriptureResultsViewer sources={viewableResults} />
</div>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,7 @@ export default class ConfigureChecksWebViewProvider implements IWebViewProvider
savedWebView: SavedWebViewDefinition,
getWebViewOptions: ConfigureChecksWebViewOptions,
): Promise<WebViewDefinition | undefined> {
// We know that the projectId (if present in the state) will be a string.
const projectId =
getWebViewOptions.projectId ||
// eslint-disable-next-line no-type-assertion/no-type-assertion
(savedWebView.state?.projectId as string) ||
undefined;
const projectId = getWebViewOptions.projectId || savedWebView.projectId || undefined;

let projectName: string | undefined;

Expand All @@ -46,13 +41,13 @@ export default class ConfigureChecksWebViewProvider implements IWebViewProvider
);

return {
title,
...savedWebView,
title,
projectId,
content: configureChecksWebView,
styles: configureChecksWebViewStyles,
state: {
...savedWebView.state,
projectId,
},
};
}
Expand Down
Loading

0 comments on commit 3f4b63d

Please sign in to comment.