Skip to content

Commit

Permalink
Merge pull request #483 from guardian/jsh/read-and-write-lt-core-rules
Browse files Browse the repository at this point in the history
Read and write core LT rules
  • Loading branch information
jonathonherbert authored Nov 11, 2024
2 parents 6f475da + 61a390e commit 4ab23a8
Show file tree
Hide file tree
Showing 9 changed files with 192 additions and 40 deletions.
50 changes: 35 additions & 15 deletions apps/rule-manager/app/db/DbRuleDraft.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import scalikejdbc._

import java.time.OffsetDateTime
import scala.util.{Failure, Success, Try}
import utils.StringHelpers
import service.RuleManager.RuleType

case class DbRuleDraft(
id: Option[Int],
Expand Down Expand Up @@ -280,7 +282,8 @@ object DbRuleDraft extends SQLSyntaxSupport[DbRuleDraft] {
val orderByClause = {
val maybeSimilarityCol = maybeWord.map { _ => SQLSyntax.createUnsafely(similarityCol) }
val orderStmts = maybeSimilarityCol.toList ++ sortBy.map { sortByStr =>
val col = rd.column(sortByStr.slice(1, sortByStr.length))
val colName = StringHelpers.camelToSnakeCase(sortByStr.slice(1, sortByStr.length))
val col = rd.column(colName)
sortByStr.slice(0, 1) match {
case "+" => sqls"$col ASC"
case "-" => sqls"$col DESC"
Expand Down Expand Up @@ -377,29 +380,44 @@ object DbRuleDraft extends SQLSyntaxSupport[DbRuleDraft] {
notes: Option[String] = None,
forceRedRule: Option[Boolean] = None,
advisoryRule: Option[Boolean] = None,
externalId: Option[String] = None,
user: String,
isArchived: Boolean = false,
tags: List[Int] = List.empty
)(implicit session: DBSession = autoSession): Try[DbRuleDraft] = {
val latestRuleOrder = getLatestRuleOrder()

val externalIdCol = externalId
.orElse {
ruleType match {
case RuleType.languageToolCore => Some("CHANGE_ME")
case _ => None
}
}
.map((column.externalId -> _))
.toList

val columnUpdates = List(
column.ruleType -> ruleType,
column.pattern -> pattern,
column.replacement -> replacement,
column.category -> category,
column.description -> description,
column.ignore -> ignore,
column.notes -> notes,
column.forceRedRule -> forceRedRule,
column.advisoryRule -> advisoryRule,
column.createdBy -> user,
column.updatedBy -> user,
column.isArchived -> isArchived,
column.ruleOrder -> (latestRuleOrder + 1)
) ++ externalIdCol

val id = withSQL {
insert
.into(DbRuleDraft)
.namedValues(
column.ruleType -> ruleType,
column.pattern -> pattern,
column.replacement -> replacement,
column.category -> category,
column.description -> description,
column.ignore -> ignore,
column.notes -> notes,
column.forceRedRule -> forceRedRule,
column.advisoryRule -> advisoryRule,
column.createdBy -> user,
column.updatedBy -> user,
column.isArchived -> isArchived,
column.ruleOrder -> (latestRuleOrder + 1)
columnUpdates: _*
)
}.updateAndReturnGeneratedKey().apply().toInt

Expand Down Expand Up @@ -429,6 +447,7 @@ object DbRuleDraft extends SQLSyntaxSupport[DbRuleDraft] {
description = formRule.description,
ignore = formRule.ignore,
notes = formRule.notes,
externalId = formRule.externalId,
forceRedRule = formRule.forceRedRule,
advisoryRule = formRule.advisoryRule,
user = user
Expand All @@ -451,7 +470,8 @@ object DbRuleDraft extends SQLSyntaxSupport[DbRuleDraft] {
category = formRule.category,
tags = formRule.tags,
description = formRule.description,
advisoryRule = formRule.advisoryRule
advisoryRule = formRule.advisoryRule,
externalId = formRule.externalId.orElse(existingRule.externalId)
)
)
updatedRule match {
Expand Down
6 changes: 4 additions & 2 deletions apps/rule-manager/app/model/CreateRuleForm.scala
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ object CreateRuleForm {
"ignore" -> boolean,
"notes" -> optional(text()),
"forceRedRule" -> optional(boolean),
"advisoryRule" -> optional(boolean)
"advisoryRule" -> optional(boolean),
"externalId" -> optional(text())
)(CreateRuleForm.apply)(CreateRuleForm.unapply)
)
}
Expand All @@ -52,5 +53,6 @@ case class CreateRuleForm(
ignore: Boolean,
notes: Option[String] = None,
forceRedRule: Option[Boolean] = None,
advisoryRule: Option[Boolean] = None
advisoryRule: Option[Boolean] = None,
externalId: Option[String] = None
)
6 changes: 4 additions & 2 deletions apps/rule-manager/app/model/UpdateRuleForm.scala
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ object UpdateRuleForm {
"category" -> optional(text()),
"tags" -> list(number()),
"description" -> optional(text()),
"advisoryRule" -> optional(boolean)
"advisoryRule" -> optional(boolean),
"externalId" -> optional(text())
)(UpdateRuleForm.apply)(UpdateRuleForm.unapply)
)
}
Expand All @@ -47,5 +48,6 @@ case class UpdateRuleForm(
category: Option[String] = None,
tags: List[Int],
description: Option[String] = None,
advisoryRule: Option[Boolean] = None
advisoryRule: Option[Boolean] = None,
externalId: Option[String] = None
)
10 changes: 10 additions & 0 deletions apps/rule-manager/app/utils/StringHelpers.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package utils

object StringHelpers {
def camelToSnakeCase(name: String) = "[A-Z\\d]".r.replaceAllIn(
name,
{ m =>
"_" + m.group(0).toLowerCase()
}
)
}
70 changes: 50 additions & 20 deletions apps/rule-manager/client/src/ts/components/RuleContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,17 @@ export const ruleTypeOptions: RuleTypeOption[] = [
id: 'regex',
label: 'Regex',
},
{
id: 'dictionary',
label: 'Dictionary',
},
{
id: 'languageToolXML',
label: 'LanguageTool',
},
{
id: 'dictionary',
label: 'Dictionary',
id: 'languageToolCore',
label: 'LT built-in',
},
];

Expand Down Expand Up @@ -68,7 +72,14 @@ export const RuleContent = ({
};

const patternErrors = getErrorPropsForField('pattern', validationErrors);
const isDictionaryRule = ruleFormData.ruleType === 'dictionary';
const idErrors = getErrorPropsForField('externalId', validationErrors);
const { ruleType } = ruleFormData;
const displayDescription = ruleType !== 'dictionary';
const displayPattern =
ruleType !== 'dictionary' && ruleType !== 'languageToolCore';
const displayReplacement =
ruleType !== 'dictionary' && ruleType !== 'languageToolCore';
const displayExternalId = ruleType === 'languageToolCore';

return (
<RuleFormSection
Expand All @@ -85,7 +96,7 @@ export const RuleContent = ({
>
<LineBreak />
<EuiFlexItem>
{!isDictionaryRule && (
{displayDescription && (
<EuiFormRow
label={
<div>
Expand All @@ -111,7 +122,6 @@ export const RuleContent = ({
}
helpText="What will the users see in Composer?"
fullWidth={true}
isDisabled={isDictionaryRule}
>
{!showMarkdownPreview ? (
<EuiTextArea
Expand Down Expand Up @@ -149,30 +159,50 @@ export const RuleContent = ({
display: flex;
gap: 1rem;
align-items: flex-end;
white-space: nowrap;
`}
/>
<EuiSpacer size="s" />
<EuiFormRow
label={<Label text="Pattern" required={true} />}
fullWidth={true}
{...patternErrors}
>
<TextField
value={ruleFormData.pattern || ''}
onChange={(_) =>
partiallyUpdateRuleData({ pattern: _.target.value })
}
required={true}
{displayExternalId && (
<EuiFormRow
label={<Label text="LanguageTool ID" required={true} />}
helpText="The ID of the built-in LanguageTool rule"
fullWidth={true}
{...idErrors}
>
<TextField
value={ruleFormData.externalId || ''}
onChange={(_) =>
partiallyUpdateRuleData({ externalId: _.target.value })
}
required={true}
fullWidth={true}
{...patternErrors}
/>
</EuiFormRow>
)}
{displayPattern && (
<EuiFormRow
label={<Label text="Pattern" required={true} />}
fullWidth={true}
{...patternErrors}
/>
</EuiFormRow>
{!isDictionaryRule && (
>
<TextField
value={ruleFormData.pattern || ''}
onChange={(_) =>
partiallyUpdateRuleData({ pattern: _.target.value })
}
required={true}
fullWidth={true}
{...patternErrors}
/>
</EuiFormRow>
)}
{displayReplacement && (
<EuiFormRow
label="Replacement"
helpText="What is the ideal term as per the house style?"
fullWidth={true}
isDisabled={isDictionaryRule}
>
<EuiFieldText
value={ruleFormData.replacement || ''}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export type BaseRule = {
updatedBy: string;
updatedAt: string;
id?: number;
externalId?: string;
isArchived: boolean;
isPublished: boolean;
hasUnpublishedChanges: boolean;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { TagsContext } from '../context/tags';
import { EuiDataGridToolBarVisibilityOptions } from '@elastic/eui/src/components/datagrid/data_grid_types';
import { useEuiFontSize } from '@elastic/eui/src/global_styling/mixins/_typography';
import { useNavigate } from 'react-router-dom';
import { ruleTypeOptions } from '../RuleContent';

type EditRuleButtonProps = {
editIsEnabled: boolean;
Expand Down Expand Up @@ -121,6 +122,12 @@ const TestRule = ({
};

const columns: EuiDataGridColumn[] = [
{
id: 'ruleType',
display: 'Rule type',
isSortable: true,
initialWidth: 120,
},
{
id: 'description',
display: 'Description',
Expand Down Expand Up @@ -338,7 +345,15 @@ export const PaginatedRulesTable = ({
if (!rule || isLoading) {
return <EuiSkeletonText />;
}
return getRuleAtRowIndex(rowIndex)[columnId as keyof BaseRule] || '';

const value =
getRuleAtRowIndex(rowIndex)[columnId as keyof BaseRule] || '';

if (columnId === 'ruleType') {
return ruleTypeOptions.find(({ id }) => id === value)?.label ?? '';
}

return value;
},
[ruleData, isLoading],
);
Expand Down
7 changes: 7 additions & 0 deletions apps/rule-manager/conf/evolutions/default/16.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
-- !Ups

UPDATE rules_draft SET description = external_id WHERE rule_type = 'languageToolCore'

-- !Downs

UPDATE rules_draft SET description = '' WHERE rule_type = 'languageToolCore'
Loading

0 comments on commit 4ab23a8

Please sign in to comment.