]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-14495 Add the .NET tutorial for gitlab CI
authorMathieu Suen <mathieu.suen@sonarsource.com>
Mon, 1 Mar 2021 09:26:21 +0000 (10:26 +0100)
committersonartech <sonartech@sonarsource.com>
Wed, 3 Mar 2021 20:12:52 +0000 (20:12 +0000)
13 files changed:
server/sonar-web/src/main/js/components/tutorials/gitlabci/GitLabCITutorial.tsx
server/sonar-web/src/main/js/components/tutorials/gitlabci/ProjectKeyStep.tsx
server/sonar-web/src/main/js/components/tutorials/gitlabci/YmlFileStep.tsx
server/sonar-web/src/main/js/components/tutorials/gitlabci/__tests__/ProjectKeyStep-test.tsx
server/sonar-web/src/main/js/components/tutorials/gitlabci/__tests__/YmlFileStep-test.tsx
server/sonar-web/src/main/js/components/tutorials/gitlabci/__tests__/__snapshots__/GitLabCITutorial-test.tsx.snap
server/sonar-web/src/main/js/components/tutorials/gitlabci/__tests__/__snapshots__/ProjectKeyStep-test.tsx.snap
server/sonar-web/src/main/js/components/tutorials/gitlabci/__tests__/__snapshots__/YmlFileStep-test.tsx.snap
server/sonar-web/src/main/js/components/tutorials/gitlabci/commands/PipeCommand.tsx
server/sonar-web/src/main/js/components/tutorials/gitlabci/commands/__tests__/PipeCommand-test.tsx
server/sonar-web/src/main/js/components/tutorials/gitlabci/commands/__tests__/__snapshots__/PipeCommand-test.tsx.snap
server/sonar-web/src/main/js/components/tutorials/gitlabci/types.ts
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index 0d53961bb59f4564afffd52fcc27f82ee15eb89a..0afca163c58f6bdc967f0ffca872665cc2dc3a94 100644 (file)
@@ -26,7 +26,7 @@ import {
 } from '../../../types/alm-settings';
 import EnvironmentVariablesStep from './EnvironmentVariablesStep';
 import ProjectKeyStep from './ProjectKeyStep';
-import { BuildTools } from './types';
+import { GitlabBuildTools } from './types';
 import YmlFileStep from './YmlFileStep';
 
 export enum Steps {
@@ -46,7 +46,7 @@ export default function GitLabCITutorial(props: GitLabCITutorialProps) {
   const { baseUrl, component, currentUser, projectBinding } = props;
 
   const [step, setStep] = React.useState(Steps.PROJECT_KEY);
-  const [buildTool, setBuildTool] = React.useState<BuildTools | undefined>();
+  const [buildTool, setBuildTool] = React.useState<GitlabBuildTools | undefined>();
 
   // Failsafe; should never happen.
   if (!isProjectGitLabBindingResponse(projectBinding)) {
@@ -81,7 +81,7 @@ export default function GitLabCITutorial(props: GitLabCITutorialProps) {
         open={step === Steps.ENV_VARIABLES}
       />
 
-      <YmlFileStep buildTool={buildTool} open={step === Steps.YML} />
+      <YmlFileStep buildTool={buildTool} open={step === Steps.YML} projectKey={component.key} />
     </>
   );
 }
index b7fdef1d98a9e609d98a11c1800067aa973a625e..fabcc7432a2f0f20bb643a6bc0ace4a6d5449787 100644 (file)
@@ -25,16 +25,17 @@ import { translate } from 'sonar-ui-common/helpers/l10n';
 import CodeSnippet from '../../common/CodeSnippet';
 import RenderOptions from '../components/RenderOptions';
 import Step from '../components/Step';
-import { BuildTools } from './types';
+import { BuildTools } from '../types';
+import { GitlabBuildTools, GITLAB_BUILDTOOLS_LIST } from './types';
 
 export interface ProjectKeyStepProps {
-  buildTool?: BuildTools;
+  buildTool?: GitlabBuildTools;
   component: T.Component;
   finished: boolean;
   onDone: () => void;
   onOpen: () => void;
   open: boolean;
-  setBuildTool: (tool: BuildTools) => void;
+  setBuildTool: (tool: GitlabBuildTools) => void;
 }
 
 const mavenSnippet = (key: string) => `<properties>
@@ -72,6 +73,13 @@ const filenameForBuildTool = {
 export default function ProjectKeyStep(props: ProjectKeyStepProps) {
   const { buildTool, component, finished, open } = props;
 
+  const buildToolSelect = (value: GitlabBuildTools) => {
+    props.setBuildTool(value);
+    if (value === BuildTools.DotNet) {
+      props.onDone();
+    }
+  };
+
   const renderForm = () => (
     <div className="boxed-group-inner">
       <ol className="list-styled">
@@ -80,12 +88,12 @@ export default function ProjectKeyStep(props: ProjectKeyStepProps) {
           <RenderOptions
             checked={buildTool}
             name="buildtool"
-            onCheck={value => props.setBuildTool(value as BuildTools)}
+            onCheck={buildToolSelect}
             optionLabelKey="onboarding.build"
-            options={Object.values(BuildTools)}
+            options={GITLAB_BUILDTOOLS_LIST}
           />
         </li>
-        {buildTool !== undefined && (
+        {buildTool !== undefined && buildTool !== BuildTools.DotNet && (
           <li className="abs-width-600">
             <FormattedMessage
               defaultMessage={translate(
index cb4126ee72e7f91137ab855fea7cc3e2b55cb74b..e745c7b9bb2147a5eceaba08e8cb591d585748bb 100644 (file)
@@ -25,15 +25,21 @@ import { translate } from 'sonar-ui-common/helpers/l10n';
 import { withAppState } from '../../hoc/withAppState';
 import Step from '../components/Step';
 import PipeCommand from './commands/PipeCommand';
-import { BuildTools } from './types';
+import { GitlabBuildTools } from './types';
 
 export interface YmlFileStepProps {
   appState: T.AppState;
-  buildTool?: BuildTools;
+  buildTool?: GitlabBuildTools;
   open: boolean;
+  projectKey: string;
 }
 
-export function YmlFileStep({ appState: { branchesEnabled }, buildTool, open }: YmlFileStepProps) {
+export function YmlFileStep({
+  appState: { branchesEnabled },
+  buildTool,
+  open,
+  projectKey
+}: YmlFileStepProps) {
   const renderForm = () => (
     <div className="boxed-group-inner">
       <div className="flex-columns">
@@ -61,7 +67,11 @@ export function YmlFileStep({ appState: { branchesEnabled }, buildTool, open }:
               </div>
 
               <div className="big-spacer-bottom">
-                <PipeCommand buildTool={buildTool} branchesEnabled={branchesEnabled} />
+                <PipeCommand
+                  buildTool={buildTool}
+                  branchesEnabled={branchesEnabled}
+                  projectKey={projectKey}
+                />
               </div>
 
               <p className="little-spacer-bottom">
index a8328d9f2c7a181150319cf9ad854e45756d1221..5518d425202aafd12eca776c629c8647bcd49223 100644 (file)
@@ -22,8 +22,9 @@ import * as React from 'react';
 import { mockComponent } from '../../../../helpers/testMocks';
 import RenderOptions from '../../components/RenderOptions';
 import { renderStepContent } from '../../jenkins/test-utils';
+import { BuildTools } from '../../types';
 import ProjectKeyStep, { ProjectKeyStepProps } from '../ProjectKeyStep';
-import { BuildTools } from '../types';
+import { GITLAB_BUILDTOOLS_LIST } from '../types';
 
 it('should render correctly', () => {
   const wrapper = shallowRender();
@@ -31,7 +32,7 @@ it('should render correctly', () => {
   expect(renderStepContent(wrapper)).toMatchSnapshot('initial content');
 });
 
-it.each([[BuildTools.Maven], [BuildTools.Gradle], [BuildTools.Other]])(
+it.each(GITLAB_BUILDTOOLS_LIST.map(tool => [tool]))(
   'should render correctly for build tool %s',
   buildTool => {
     expect(renderStepContent(shallowRender({ buildTool }))).toMatchSnapshot();
index 6d24ad7873eb1f93680e3f4e44eb4d275d4d70ae..db93a32aabf9a745c673424249f704f03ecafedf 100644 (file)
@@ -21,7 +21,7 @@ import { shallow } from 'enzyme';
 import * as React from 'react';
 import { mockAppState } from '../../../../helpers/testMocks';
 import { renderStepContent } from '../../jenkins/test-utils';
-import { BuildTools } from '../types';
+import { GITLAB_BUILDTOOLS_LIST } from '../types';
 import { YmlFileStep, YmlFileStepProps } from '../YmlFileStep';
 
 it('should render correctly', () => {
@@ -30,7 +30,7 @@ it('should render correctly', () => {
   expect(renderStepContent(wrapper)).toMatchSnapshot('initial content');
 });
 
-it.each([[BuildTools.Maven], [BuildTools.Gradle], [BuildTools.Other]])(
+it.each(GITLAB_BUILDTOOLS_LIST.map(tool => [tool]))(
   'should render correctly for build tool %s',
   buildTool => {
     expect(renderStepContent(shallowRender({ buildTool }))).toMatchSnapshot();
@@ -39,6 +39,11 @@ it.each([[BuildTools.Maven], [BuildTools.Gradle], [BuildTools.Other]])(
 
 function shallowRender(props: Partial<YmlFileStepProps> = {}) {
   return shallow<YmlFileStepProps>(
-    <YmlFileStep appState={mockAppState({ branchesEnabled: true })} open={true} {...props} />
+    <YmlFileStep
+      appState={mockAppState({ branchesEnabled: true })}
+      open={true}
+      projectKey="test"
+      {...props}
+    />
   );
 }
index e7e458b4f47f74b3f8053f6bf5f3ad6a5c1341c6..02b85fac3946fdfa55376fe8c996744cf6d696ce 100644 (file)
@@ -80,6 +80,7 @@ exports[`should render correctly 1`] = `
   />
   <Connect(withAppState(YmlFileStep))
     open={false}
+    projectKey="my-project"
   />
 </Fragment>
 `;
index b1e3b8e57c28bb053e33ddeb073a93d40445fb1c..a291f1e798a0544602deca0c2012deec26003a9c 100644 (file)
@@ -1,5 +1,38 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
+exports[`should render correctly for build tool dotnet 1`] = `
+<div
+  className="boxed-group-inner"
+>
+  <ol
+    className="list-styled"
+  >
+    <li>
+      onboarding.build
+      <RenderOptions
+        checked="dotnet"
+        name="buildtool"
+        onCheck={[Function]}
+        optionLabelKey="onboarding.build"
+        options={
+          Array [
+            "maven",
+            "gradle",
+            "dotnet",
+            "other",
+          ]
+        }
+      />
+    </li>
+  </ol>
+  <Button
+    onClick={[MockFunction]}
+  >
+    continue
+  </Button>
+</div>
+`;
+
 exports[`should render correctly for build tool gradle 1`] = `
 <div
   className="boxed-group-inner"
@@ -18,6 +51,7 @@ exports[`should render correctly for build tool gradle 1`] = `
           Array [
             "maven",
             "gradle",
+            "dotnet",
             "other",
           ]
         }
@@ -85,6 +119,7 @@ exports[`should render correctly for build tool maven 1`] = `
           Array [
             "maven",
             "gradle",
+            "dotnet",
             "other",
           ]
         }
@@ -146,6 +181,7 @@ exports[`should render correctly for build tool other 1`] = `
           Array [
             "maven",
             "gradle",
+            "dotnet",
             "other",
           ]
         }
@@ -216,6 +252,7 @@ exports[`should render correctly: initial content 1`] = `
           Array [
             "maven",
             "gradle",
+            "dotnet",
             "other",
           ]
         }
index 2fed2d0975bb347ba8079c31f69b892f23962eae..857fc801663bc5a1615672c8b322d7c68a7c2459 100644 (file)
@@ -1,5 +1,117 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
+exports[`should render correctly for build tool dotnet 1`] = `
+<div
+  className="boxed-group-inner"
+>
+  <div
+    className="flex-columns"
+  >
+    <div
+      className="flex-column-full"
+    >
+      <React.Fragment>
+        <div
+          className="big-spacer-bottom"
+        >
+          <FormattedMessage
+            defaultMessage="onboarding.tutorial.with.gitlab_ci.yml.description"
+            id="onboarding.tutorial.with.gitlab_ci.yml.description"
+            values={
+              Object {
+                "filename": <React.Fragment>
+                  <code
+                    className="rule"
+                  >
+                    onboarding.tutorial.with.gitlab_ci.yml.filename
+                  </code>
+                  <ClipboardIconButton
+                    className="little-spacer-left"
+                    copyValue="onboarding.tutorial.with.gitlab_ci.yml.filename"
+                  />
+                </React.Fragment>,
+              }
+            }
+          />
+        </div>
+        <div
+          className="big-spacer-bottom"
+        >
+          <PipeCommand
+            branchesEnabled={true}
+            buildTool="dotnet"
+            projectKey="test"
+          />
+        </div>
+        <p
+          className="little-spacer-bottom"
+        >
+          onboarding.tutorial.with.gitlab_ci.yml.baseconfig
+        </p>
+        <p
+          className="huge-spacer-bottom"
+        >
+          onboarding.tutorial.with.gitlab_ci.yml.existing
+        </p>
+        <hr
+          className="no-horizontal-margins"
+        />
+        <div>
+          <p
+            className="big-spacer-bottom"
+          >
+            <strong>
+              onboarding.tutorial.with.gitlab_ci.yml.done
+               
+            </strong>
+             
+            <FormattedMessage
+              defaultMessage="onboarding.tutorial.with.gitlab_ci.yml.done.description"
+              id="onboarding.tutorial.with.gitlab_ci.yml.done.description"
+              values={
+                Object {
+                  "link": "onboarding.tutorial.with.gitlab_ci.yml.done.description.link",
+                }
+              }
+            />
+          </p>
+          <p
+            className="big-spacer-bottom"
+          >
+            <strong>
+              onboarding.tutorial.with.gitlab_ci.yml.done.then-what
+            </strong>
+             
+            onboarding.tutorial.with.gitlab_ci.yml.done.then-what.description
+          </p>
+          <p
+            className="big-spacer-bottom"
+          >
+            <FormattedMessage
+              defaultMessage="onboarding.tutorial.with.gitlab_ci.yml.done.links.title"
+              id="onboarding.tutorial.with.gitlab_ci.yml.done.links.title"
+              values={
+                Object {
+                  "links": <Link
+                    onlyActiveOnIndex={false}
+                    rel="noopener noreferrer"
+                    style={Object {}}
+                    target="_blank"
+                    to="/documentation/user-guide/quality-gates/"
+                  >
+                    onboarding.tutorial.with.gitlab_ci.yml.done.links.QG
+                  </Link>,
+                }
+              }
+            />
+          </p>
+        </div>
+      </React.Fragment>
+    </div>
+  </div>
+</div>
+`;
+
 exports[`should render correctly for build tool gradle 1`] = `
 <div
   className="boxed-group-inner"
@@ -40,6 +152,7 @@ exports[`should render correctly for build tool gradle 1`] = `
           <PipeCommand
             branchesEnabled={true}
             buildTool="gradle"
+            projectKey="test"
           />
         </div>
         <p
@@ -151,6 +264,7 @@ exports[`should render correctly for build tool maven 1`] = `
           <PipeCommand
             branchesEnabled={true}
             buildTool="maven"
+            projectKey="test"
           />
         </div>
         <p
@@ -262,6 +376,7 @@ exports[`should render correctly for build tool other 1`] = `
           <PipeCommand
             branchesEnabled={true}
             buildTool="other"
+            projectKey="test"
           />
         </div>
         <p
index e3b14660cff8e33c02730a0e78c3be2a8216f24d..1044efaa1ac56800972c8a68ac6fc0e2cf7bf01e 100644 (file)
  */
 import * as React from 'react';
 import CodeSnippet from '../../../common/CodeSnippet';
-import { BuildTools } from '../types';
+import { BuildTools } from '../../types';
+import { GitlabBuildTools } from '../types';
 
 export interface PipeCommandProps {
   branchesEnabled?: boolean;
-  buildTool: BuildTools;
+  buildTool: GitlabBuildTools;
+  projectKey: string;
 }
 
 const BUILD_TOOL_SPECIFIC = {
-  [BuildTools.Gradle]: { image: 'gradle:jre11-slim', script: 'gradle sonarqube' },
+  [BuildTools.Gradle]: { image: 'gradle:jre11-slim', script: () => 'gradle sonarqube' },
   [BuildTools.Maven]: {
     image: 'maven:3.6.3-jdk-11',
-    script: `
+    script: () => `
     - mvn verify sonar:sonar`
   },
+  [BuildTools.DotNet]: {
+    image: 'mcr.microsoft.com/dotnet/core/sdk:latest',
+    script: (projectKey: string) => `
+      - "apt-get update"
+      - "apt-get install --yes openjdk-11-jre"
+      - "dotnet tool install --global dotnet-sonarscanner"
+      - "export PATH=\\"$PATH:$HOME/.dotnet/tools\\""
+      - "dotnet sonarscanner begin /k:\\"${projectKey}\\" /d:sonar.login=\\"$SONAR_TOKEN\\" /d:\\"sonar.host.url=$SONAR_HOST_URL\\" "
+      - "dotnet build"
+      - "dotnet sonarscanner end /d:sonar.login=\\"$SONAR_TOKEN\\""`
+  },
   [BuildTools.Other]: {
     image: `
     name: sonarsource/sonar-scanner-cli:latest
     entrypoint: [""]`,
-    script: `
+    script: () => `
     - sonar-scanner`
   }
 };
 
-export default function PipeCommand({ branchesEnabled, buildTool }: PipeCommandProps) {
+export default function PipeCommand({ projectKey, branchesEnabled, buildTool }: PipeCommandProps) {
   const onlyBlock = branchesEnabled
     ? `- merge_requests
     - master
@@ -60,7 +73,7 @@ export default function PipeCommand({ branchesEnabled, buildTool }: PipeCommandP
     key: "\${CI_JOB_NAME}"
     paths:
       - .sonar/cache
-  script: ${script}
+  script: ${script(projectKey)}
   allow_failure: true
   only:
     ${onlyBlock}
index 8e2c93671828609b232ac071e28a01b361fdefd1..abc594b60f5cdef9ea2b68cf40e2661ae453d9e2 100644 (file)
  */
 import { shallow } from 'enzyme';
 import * as React from 'react';
-import { BuildTools } from '../../types';
+import { GITLAB_BUILDTOOLS_LIST } from '../../types';
 import PipeCommand from '../PipeCommand';
 
-it.each([[BuildTools.Gradle], [BuildTools.Maven], [BuildTools.Other]])(
-  'should render correctly for %s',
-  buildTool => {
-    expect(shallow(<PipeCommand buildTool={buildTool} branchesEnabled={true} />)).toMatchSnapshot(
-      'branches enabled'
-    );
-    expect(shallow(<PipeCommand buildTool={buildTool} branchesEnabled={true} />)).toMatchSnapshot(
-      'branches not enabled'
-    );
-  }
-);
+it.each(GITLAB_BUILDTOOLS_LIST.map(tool => [tool]))('should render correctly for %s', buildTool => {
+  expect(
+    shallow(<PipeCommand buildTool={buildTool} branchesEnabled={true} projectKey="test" />)
+  ).toMatchSnapshot('branches enabled');
+  expect(
+    shallow(<PipeCommand buildTool={buildTool} branchesEnabled={true} projectKey="test" />)
+  ).toMatchSnapshot('branches not enabled');
+});
index 019eef78564e1e251be5094dd75f194598eeffc4..c7e7a6cbcc75e849f56f90b7fdb43a963d8c72da 100644 (file)
@@ -1,5 +1,61 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
+exports[`should render correctly for dotnet: branches enabled 1`] = `
+<CodeSnippet
+  snippet="sonarqube-check:
+  image: mcr.microsoft.com/dotnet/core/sdk:latest
+  variables:
+    SONAR_USER_HOME: \\"\${CI_PROJECT_DIR}/.sonar\\"  # Defines the location of the analysis task cache
+    GIT_DEPTH: \\"0\\"  # Tells git to fetch all the branches of the project, required by the analysis task
+  cache:
+    key: \\"\${CI_JOB_NAME}\\"
+    paths:
+      - .sonar/cache
+  script: 
+      - \\"apt-get update\\"
+      - \\"apt-get install --yes openjdk-11-jre\\"
+      - \\"dotnet tool install --global dotnet-sonarscanner\\"
+      - \\"export PATH=\\\\\\"$PATH:$HOME/.dotnet/tools\\\\\\"\\"
+      - \\"dotnet sonarscanner begin /k:\\\\\\"test\\\\\\" /d:sonar.login=\\\\\\"$SONAR_TOKEN\\\\\\" /d:\\\\\\"sonar.host.url=$SONAR_HOST_URL\\\\\\" \\"
+      - \\"dotnet build\\"
+      - \\"dotnet sonarscanner end /d:sonar.login=\\\\\\"$SONAR_TOKEN\\\\\\"\\"
+  allow_failure: true
+  only:
+    - merge_requests
+    - master
+    - develop
+"
+/>
+`;
+
+exports[`should render correctly for dotnet: branches not enabled 1`] = `
+<CodeSnippet
+  snippet="sonarqube-check:
+  image: mcr.microsoft.com/dotnet/core/sdk:latest
+  variables:
+    SONAR_USER_HOME: \\"\${CI_PROJECT_DIR}/.sonar\\"  # Defines the location of the analysis task cache
+    GIT_DEPTH: \\"0\\"  # Tells git to fetch all the branches of the project, required by the analysis task
+  cache:
+    key: \\"\${CI_JOB_NAME}\\"
+    paths:
+      - .sonar/cache
+  script: 
+      - \\"apt-get update\\"
+      - \\"apt-get install --yes openjdk-11-jre\\"
+      - \\"dotnet tool install --global dotnet-sonarscanner\\"
+      - \\"export PATH=\\\\\\"$PATH:$HOME/.dotnet/tools\\\\\\"\\"
+      - \\"dotnet sonarscanner begin /k:\\\\\\"test\\\\\\" /d:sonar.login=\\\\\\"$SONAR_TOKEN\\\\\\" /d:\\\\\\"sonar.host.url=$SONAR_HOST_URL\\\\\\" \\"
+      - \\"dotnet build\\"
+      - \\"dotnet sonarscanner end /d:sonar.login=\\\\\\"$SONAR_TOKEN\\\\\\"\\"
+  allow_failure: true
+  only:
+    - merge_requests
+    - master
+    - develop
+"
+/>
+`;
+
 exports[`should render correctly for gradle: branches enabled 1`] = `
 <CodeSnippet
   snippet="sonarqube-check:
index 12d802a15b96140f5235f30fcb386c3eda5d3216..5f67d6386b5dc1dded957af0fcdea69a4bf80ece 100644 (file)
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
-export enum BuildTools {
-  Maven = 'maven',
-  Gradle = 'gradle',
-  // MSBuild = 'msbuild', // Not yet supported
-  Other = 'other'
-}
+
+import { BuildTools } from '../types';
+
+export type GitlabBuildTools =
+  | BuildTools.Maven
+  | BuildTools.Gradle
+  | BuildTools.DotNet
+  | BuildTools.Other;
+
+export const GITLAB_BUILDTOOLS_LIST: GitlabBuildTools[] = [
+  BuildTools.Maven,
+  BuildTools.Gradle,
+  BuildTools.DotNet,
+  BuildTools.Other
+];
index b576336b9e1f741e87ab9531fe0e2e40111df536..829fc92b5e5287ad5285022ed0750425eef390d0 100644 (file)
@@ -3396,6 +3396,8 @@ onboarding.tutorial.with.gitlab_ci.project_key.title=Set your project key
 onboarding.tutorial.with.gitlab_ci.project_key.maven.step2=Add the following to your {file} file:
 onboarding.tutorial.with.gitlab_ci.project_key.gradle.step2=Add the following to your {file} file:
 onboarding.tutorial.with.gitlab_ci.project_key.other.step2=Create a {file} file in your repository and paste the following code:
+onboarding.tutorial.with.gitlab_ci.project_key.dotnet.step2=Create a {file} file in your repository and paste the following code:
+
 
 onboarding.tutorial.with.gitlab_ci.env_variables.title=Add environment variables
 onboarding.tutorial.with.gitlab_ci.env_variables.enter_field_value=In the {field} field, enter {value} {extra}