]> source.dussan.org Git - sonarqube.git/commitdiff
MMF-1420 Ease management of Embedded Docs navigation (#699)
authorPascal Mugnier <pascal.mugnier@sonarsource.com>
Wed, 19 Sep 2018 12:03:27 +0000 (14:03 +0200)
committerSonarTech <sonartech@sonarsource.com>
Wed, 19 Sep 2018 18:20:55 +0000 (20:20 +0200)
105 files changed:
server/sonar-docs/README.md
server/sonar-docs/gatsby-node.js
server/sonar-docs/package.json
server/sonar-docs/src/EmbedDocsSuggestions.json
server/sonar-docs/src/__tests__/BrokenLinkSafetyNet.test.js [new file with mode: 0644]
server/sonar-docs/src/layouts/components/CategoryLink.js
server/sonar-docs/src/layouts/components/ExternalLink.js [new file with mode: 0644]
server/sonar-docs/src/layouts/components/Footer.js
server/sonar-docs/src/layouts/components/HeadingAnchor.js [new file with mode: 0644]
server/sonar-docs/src/layouts/components/HeadingsLink.js
server/sonar-docs/src/layouts/components/Search.js
server/sonar-docs/src/layouts/components/SearchEntryResult.js
server/sonar-docs/src/layouts/components/Sidebar.js
server/sonar-docs/src/layouts/components/icons/DetachIcon.js [new file with mode: 0644]
server/sonar-docs/src/layouts/index.js
server/sonar-docs/src/layouts/utils.js
server/sonar-docs/src/pages/404.md [new file with mode: 0644]
server/sonar-docs/src/pages/analysis/background-tasks.md
server/sonar-docs/src/pages/analysis/generic-issue.md [new file with mode: 0644]
server/sonar-docs/src/pages/analysis/generic-test.md [new file with mode: 0644]
server/sonar-docs/src/pages/analysis/generic_issue.md [deleted file]
server/sonar-docs/src/pages/analysis/generic_test.md [deleted file]
server/sonar-docs/src/pages/analysis/index.md [deleted file]
server/sonar-docs/src/pages/analysis/overview.md [new file with mode: 0644]
server/sonar-docs/src/pages/analysis/pull-request.md
server/sonar-docs/src/pages/analysis/scm-integration.md [new file with mode: 0644]
server/sonar-docs/src/pages/analysis/scm_integration.md [deleted file]
server/sonar-docs/src/pages/analyze-a-project.md [deleted file]
server/sonar-docs/src/pages/branches/branches-faq.md
server/sonar-docs/src/pages/branches/index.md [deleted file]
server/sonar-docs/src/pages/branches/long-lived-branches.md
server/sonar-docs/src/pages/branches/overview.md [new file with mode: 0644]
server/sonar-docs/src/pages/branches/short-lived-branches.md
server/sonar-docs/src/pages/custom-measures.md [deleted file]
server/sonar-docs/src/pages/fixing-the-water-leak.md [deleted file]
server/sonar-docs/src/pages/housekeeping.md [deleted file]
server/sonar-docs/src/pages/index.md
server/sonar-docs/src/pages/instance-administration/custom-measures.md [new file with mode: 0644]
server/sonar-docs/src/pages/instance-administration/housekeeping.md [new file with mode: 0644]
server/sonar-docs/src/pages/instance-administration/look-and-feel.md [new file with mode: 0644]
server/sonar-docs/src/pages/instance-administration/quality-profiles.md [new file with mode: 0644]
server/sonar-docs/src/pages/integrations/bitbucketcloud.md [deleted file]
server/sonar-docs/src/pages/integrations/github.md [deleted file]
server/sonar-docs/src/pages/integrations/index.md [deleted file]
server/sonar-docs/src/pages/integrations/vsts.md [deleted file]
server/sonar-docs/src/pages/keyboard-shortcuts.md [deleted file]
server/sonar-docs/src/pages/look-and-feel.md [deleted file]
server/sonar-docs/src/pages/metric-definitions.md [deleted file]
server/sonar-docs/src/pages/organizations/index.md [deleted file]
server/sonar-docs/src/pages/organizations/manage-team.md [deleted file]
server/sonar-docs/src/pages/organizations/organization-visibility.md [deleted file]
server/sonar-docs/src/pages/privacy.md [deleted file]
server/sonar-docs/src/pages/project-administration/webhooks.md [new file with mode: 0644]
server/sonar-docs/src/pages/quality-gates.md [deleted file]
server/sonar-docs/src/pages/quality-profiles.md [deleted file]
server/sonar-docs/src/pages/security-reports.md [deleted file]
server/sonar-docs/src/pages/security.md [deleted file]
server/sonar-docs/src/pages/sonarcloud-pricing.md [deleted file]
server/sonar-docs/src/pages/sonarcloud/analyze-a-project.md [new file with mode: 0644]
server/sonar-docs/src/pages/sonarcloud/integrations/bitbucketcloud.md [new file with mode: 0644]
server/sonar-docs/src/pages/sonarcloud/integrations/github.md [new file with mode: 0644]
server/sonar-docs/src/pages/sonarcloud/integrations/vsts.md [new file with mode: 0644]
server/sonar-docs/src/pages/sonarcloud/organizations/index.md [new file with mode: 0644]
server/sonar-docs/src/pages/sonarcloud/organizations/manage-team.md [new file with mode: 0644]
server/sonar-docs/src/pages/sonarcloud/organizations/organization-visibility.md [new file with mode: 0644]
server/sonar-docs/src/pages/sonarcloud/privacy.md [new file with mode: 0644]
server/sonar-docs/src/pages/sonarcloud/security.md [new file with mode: 0644]
server/sonar-docs/src/pages/sonarcloud/sonarcloud-pricing.md [new file with mode: 0644]
server/sonar-docs/src/pages/user-account.md [deleted file]
server/sonar-docs/src/pages/user-guide/fixing-the-water-leak.md [new file with mode: 0644]
server/sonar-docs/src/pages/user-guide/keyboard-shortcuts.md [new file with mode: 0644]
server/sonar-docs/src/pages/user-guide/metric-definitions.md [new file with mode: 0644]
server/sonar-docs/src/pages/user-guide/quality-gates.md [new file with mode: 0644]
server/sonar-docs/src/pages/user-guide/security-reports.md [new file with mode: 0644]
server/sonar-docs/src/pages/user-guide/user-account.md [new file with mode: 0644]
server/sonar-docs/src/pages/webhooks.md [deleted file]
server/sonar-docs/src/templates/page.js
server/sonar-docs/static/README.md [new file with mode: 0644]
server/sonar-docs/static/SonarCloudNavigationTree.json [new file with mode: 0644]
server/sonar-docs/static/SonarQubeNavigationTree.json [new file with mode: 0644]
server/sonar-docs/static/StaticNavigationTree.json [new file with mode: 0644]
server/sonar-docs/yarn.lock
server/sonar-web/src/main/js/apps/documentation/components/App.tsx
server/sonar-web/src/main/js/apps/documentation/components/Menu.tsx
server/sonar-web/src/main/js/apps/documentation/components/MenuBlock.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/documentation/components/MenuExternalLink.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/documentation/components/MenuItem.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/documentation/components/SearchResultEntry.tsx
server/sonar-web/src/main/js/apps/documentation/components/SearchResults.tsx
server/sonar-web/src/main/js/apps/documentation/components/Sidebar.tsx
server/sonar-web/src/main/js/apps/documentation/components/__tests__/Menu-test.tsx
server/sonar-web/src/main/js/apps/documentation/components/__tests__/MenuBlock-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/documentation/components/__tests__/SearchResultEntry-test.tsx
server/sonar-web/src/main/js/apps/documentation/components/__tests__/SearchResults-test.tsx
server/sonar-web/src/main/js/apps/documentation/components/__tests__/Sidebar-test.tsx
server/sonar-web/src/main/js/apps/documentation/components/__tests__/__snapshots__/Menu-test.tsx.snap
server/sonar-web/src/main/js/apps/documentation/components/__tests__/__snapshots__/MenuBlock-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/documentation/components/__tests__/__snapshots__/SearchResultEntry-test.tsx.snap
server/sonar-web/src/main/js/apps/documentation/components/__tests__/__snapshots__/SearchResults-test.tsx.snap
server/sonar-web/src/main/js/apps/documentation/components/__tests__/__snapshots__/Sidebar-test.tsx.snap
server/sonar-web/src/main/js/apps/documentation/components/__tests__/pages-test.ts [deleted file]
server/sonar-web/src/main/js/apps/documentation/pages.ts
server/sonar-web/src/main/js/apps/documentation/utils.ts
server/sonar-web/src/main/js/components/docs/__tests__/DocMarkdownBlock-test.tsx
server/sonar-web/src/main/js/helpers/markdown.js

index 05af399cff841be7d8b4c4b4b7bc3026a6616f9c..aca7bd3b15d8922a9b700024ec6efa3d0d9a306b 100644 (file)
@@ -62,30 +62,56 @@ cd sonar-enterprise/server/sonar-docs
 yarn develop
 ```
 
+## Testing
+As documentation writers there are two ways it is possible for us to break the SonarQube build
+* malformed markup
+* broken links
+
+Even without spinning up servers, you can double-check that your changes won't break the build.
+**Test everything**
+You can run all the tests, and make sure that both your markup is well-formed and your links are correct by running the build script:
+```
+cd sonar-enterprise/
+./build.sh -x test -x obfuscate
+```
+**Test links only** 
+If you only want to double-check your links changes, you can
+```
+cd sonar-enterprise/server/sonar-docs
+yarn jest
+```
+
+This will run the broken link test and stop at the first broken link it finds. Continue running this iteratively until it passes.
+
+## Navigation trees
+Controlling the navigation trees of the tree instances is covered in [static/README.md](static)
+
+
 ## Writing docs
 
+### URLs
+All urls _must_ end with a trailing slash (`/`).
+
 ### Header
 
 Each documentation file should contain a header at the top of the file delimited by "---" top and bottom. The header holds file metadata:
 
 * The `title` tag defines the title of the page for the index
-* The `order` tag defines the order of the page for the index. (Floats are interpreted correctly)
-* The `scope` tag defines to which product the doc applies. Omit `scope` to have a file show up everywhere:
-  * “sonarqube” - visible only for SonarQube and the static website
-  * “sonarcloud” - visible only for SonarCloud
-  * "static" - visible only on the static website
+* The `url` tag is required and defines the path at which to publish the page. Reminder: end this with a trailing slash.
 
 Ex.:
 
 ```
 ---
 title: Demo page
-order: 0
-scope: static
+url: /sonarcloud-pricing
 ---
 ```
 
-Metadata tags can appear in any order, but by convention, `title` should come first.
+** Metadata conventions**
+* Metadata tags can appear in any order, but by convention, `title` should come first.
+* The `url` tag is optional, but by convention, should be specified to both make the publish path explicit and avoid problems potentially caused by moving or renaming files.
+
 
 ### Includes
 
@@ -98,15 +124,12 @@ Basic syntax: `@include tooltips/quality-gates/quality-gate`
 
 With special comments you can mark a page or a part of the content to be displayed only on SonarCloud, SonarQube or the static documentation website.
 
-To display a page only in a certain context use the frontmatter option:
-
-```md
----
-scope: sonarcloud (or sonarqube, or static)
----
+To drop in "SonarQube" or "SonarCloud" as appropriate, use:
+```
+{instance}
 ```
 
-To display/hide a part of the content use special comments:
+To display/hide some other part of the content, use special comments:
 
 ```md
 <!-- sonarcloud -->
@@ -128,7 +151,7 @@ this content is displayed only in the static website
 <!-- /static -->
 ```
 
-You can also use inline comments:
+You can also use these comments inline:
 
 ```md
 this content is displayed on <!-- sonarcloud -->SonarCloud<!-- /sonarcloud --><!-- sonarqube -->SonarQube<!-- /sonarqube -->
index 0c39ec3c7ba71849bd8dda97584792f521e51bc0..4564fe524919a75102a5328c425af88b064d4f0e 100644 (file)
@@ -42,7 +42,7 @@ exports.createPages = ({ graphql, boundActionCreators }) => {
           edges {
             node {
               frontmatter {
-                scope
+                url
               }
               headings {
                 depth
@@ -57,16 +57,14 @@ exports.createPages = ({ graphql, boundActionCreators }) => {
       }
     `).then(result => {
       result.data.allMarkdownRemark.edges.forEach(({ node }) => {
-        if (node.frontmatter.scope !== 'sonarcloud') {
-          createPage({
-            path: node.fields.slug,
-            component: path.resolve('./src/templates/page.js'),
-            context: {
-              // Data passed to context is available in page queries as GraphQL variables.
-              slug: node.fields.slug
-            }
-          });
-        }
+        createPage({
+          path: node.frontmatter.url || node.fields.slug,
+          component: path.resolve('./src/templates/page.js'),
+          context: {
+            // Data passed to context is available in page queries as GraphQL variables.
+            slug: node.fields.slug
+          }
+        });
       });
       resolve();
     });
index b94d672d5f2651a939263b1d87760c66e0ba1b5d..15091a0dc94bcd4f585751c6585017328cbb4473 100644 (file)
     "typography": "^0.16.16"
   },
   "scripts": {
-    "build": "gatsby build --prefix-paths",
+    "build": "yarn jest && gatsby build --prefix-paths",
     "develop": "gatsby develop",
     "start": "serve public/",
     "format": "prettier --write 'src/**/*.{md,js}'"
   },
   "devDependencies": {
-    "prettier": "^1.12.0"
+    "glob-promise": "3.4.0",
+    "jest": "23.6.0",
+    "prettier": "^1.12.0",
+    "remark": "9.0.0",
+    "unist-util-visit": "1.4.0"
   },
   "prettier": {
     "jsxBracketSameLine": true,
     "printWidth": 100,
     "singleQuote": true
+  },
+  "jest": {
+    "moduleFileExtensions": [
+      "js"
+    ],
+    "testPathIgnorePatterns": [
+      "<rootDir>/node_modules",
+      "<rootDir>/.cache"
+    ],
+    "testRegex": "(/__tests__/.*|\\-test)\\.(ts|tsx|js)$"
   }
 }
index 996ecb4c49bc964b4096b52df365977e9721ec96..45beba34d09d7c4f3cae08e18596d3938b953291 100644 (file)
   "api_documentation": [],
   "background_tasks": [
     {
-      "link": "/documentation/analysis/background-tasks",
+      "link": "/documentation/analysis/background-tasks/",
       "text": "About Background Tasks"
     }
   ],
   "code": [],
   "coding_rules": [
     {
-      "link": "/documentation/quality-profiles",
+      "link": "/documentation/quality-profiles/",
       "text": "Quality Profiles"
     },
     {
-      "link": "/documentation/keyboard-shortcuts",
+      "link": "/documentation/keyboard-shortcuts/",
       "text": "Keyboard Shortcuts"
     }
   ],
   "component_measures": [
     {
-      "link": "/documentation/fixing-the-water-leak",
+      "link": "/documentation/fixing-the-water-leak/",
       "text": "Fixing the Water Leak"
     },
     {
-      "link":"/documentation/metric-definitions",
-      "text":"Metric Definitions"
+      "link": "/documentation/metric-definitions/",
+      "text": "Metric Definitions"
     },
     {
-      "link": "/documentation/keyboard-shortcuts",
+      "link": "/documentation/keyboard-shortcuts/",
       "text": "Keyboard Shortcuts"
     }
   ],
   "custom_measures": [
     {
-      "link": "/documentation/custom-measures",
+      "link": "/documentation/custom-measures/",
       "text": "About Custom Measures"
     }
   ],
   "custom_metrics": [
     {
-      "link": "/documentation/custom-measures",
+      "link": "/documentation/custom-measures/",
       "text": "Custom Measures"
     }
   ],
   "extension_billing": [
     {
-      "link": "/documentation/sonarcloud-pricing",
+      "link": "/documentation/sonarcloud-pricing/",
       "text": "Pricing",
       "scope": "sonarcloud"
     }
   ],
   "global_permissions": [
     {
-      "link": "/documentation/organizations/manage-team",
+      "link": "/documentation/organizations/manage-team/",
       "text": "Manage a Team",
       "scope": "sonarcloud"
     }
   ],
   "issues": [
     {
-      "link": "/documentation/keyboard-shortcuts",
+      "link": "/documentation/keyboard-shortcuts/",
       "text": "Keyboard Shortcuts"
     }
   ],
   "marketplace": [],
   "organization_members": [
     {
-      "link": "/documentation/organizations/manage-team",
+      "link": "/documentation/organizations/manage-team/",
       "text": "Manage a Team",
       "scope": "sonarcloud"
     }
   ],
   "organization_projects": [
     {
-      "link": "/documentation/organizations/manage-team",
+      "link": "/documentation/organizations/manage-team/",
       "text": "Manage a Team",
       "scope": "sonarcloud"
     }
   ],
   "organization_space": [
     {
-      "link": "/documentation/organizations/index",
+      "link": "/documentation/organizations/index/",
       "text": "Organizations",
       "scope": "sonarcloud"
     }
   ],
   "overview": [
     {
-      "link": "/documentation/fixing-the-water-leak",
+      "link": "/documentation/fixing-the-water-leak/",
       "text": "Fixing the Water Leak"
     },
     {
-      "link": "/documentation/branches/index",
+      "link": "/documentation/branches/index/",
       "text": "Branches Overview"
     },
     {
-      "link": "/documentation/analysis/pull-request",
+      "link": "/documentation/analysis/pull-request/",
       "text": "Analyzing Pull Requests"
     }
   ],
   "permission_templates": [],
-  "profiles": [ 
+  "profiles": [
     {
-      "link": "/documentation/quality-profiles",
+      "link": "/documentation/quality-profiles/",
       "text": "Quality Profiles"
     }
   ],
   "project_activity": [],
   "project_quality_gate": [
     {
-      "link": "/documentation/fixing-the-water-leak",
+      "link": "/documentation/fixing-the-water-leak/",
       "text": "Fixing the Water Leak"
     }
   ],
   "project_quality_profiles": [
     {
-      "link": "/documentation/quality-profiles",
+      "link": "/documentation/quality-profiles/",
       "text": "About Quality Profiles"
     }
   ],
   "projects_management": [
     {
-      "link": "/documentation/analyze-a-project",
+      "link": "/documentation/analyze-a-project/",
       "text": "Analyze a Project",
       "scope": "sonarcloud"
     }
   ],
   "projects": [
     {
-      "link": "/documentation/analyze-a-project",
+      "link": "/documentation/analyze-a-project/",
       "text": "Analyze a Project",
       "scope": "sonarcloud"
     }
   ],
   "quality_gates": [
     {
-      "link": "/documentation/fixing-the-water-leak",
+      "link": "/documentation/fixing-the-water-leak/",
       "text": "Fixing the Water Leak"
     }
   ],
   "quality_profiles": [
     {
-      "link": "/documentation/quality-profiles",
+      "link": "/documentation/quality-profiles/",
       "text": "Quality Profiles"
     }
   ],
   "security_reports": [
     {
-      "link": "/documentation/security-reports",
+      "link": "/documentation/security-reports/",
       "text": "About Security Reports"
     }
   ],
   "system_info": [],
   "user_groups": [
     {
-      "link": "/documentation/organizations/manage-team",
+      "link": "/documentation/organizations/manage-team/",
       "text": "Manage a Team",
       "scope": "sonarcloud"
     }
   "users": [],
   "webhooks": [
     {
-      "link": "/documentation/webhooks",
+      "link": "/documentation/webhooks/",
       "text": "About Webhooks"
     }
   ]
diff --git a/server/sonar-docs/src/__tests__/BrokenLinkSafetyNet.test.js b/server/sonar-docs/src/__tests__/BrokenLinkSafetyNet.test.js
new file mode 100644 (file)
index 0000000..5eafbfa
--- /dev/null
@@ -0,0 +1,178 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+const remark = require('remark');
+const fs = require('fs');
+const path = require('path');
+const glob = require('glob-promise');
+const visit = require('unist-util-visit');
+
+it('should not have any broken link', async () => {
+  const root = path.resolve(__dirname + '/..');
+  const files = await glob(root + '/pages/**/*.md')
+    .then(files => files.map(file => file.substr(root.length + 1)))
+    .then(files =>
+      files.map(file => ({
+        path: file.slice(0, -3),
+        content: handleIncludes(fs.readFileSync(root + '/' + file, 'utf8'), root)
+      }))
+    );
+
+  const parsedFiles = files.map(file => {
+    return { ...separateFrontMatter(file.content), path: file.path };
+  });
+
+  const trees = [
+    'SonarCloudNavigationTree.json',
+    'SonarQubeNavigationTree.json',
+    'StaticNavigationTree.json'
+  ];
+  trees.forEach(file => {
+    const tree = JSON.parse(fs.readFileSync(root + '/../static/' + file, 'utf8'));
+    tree.forEach(leaf => {
+      if (typeof leaf === 'object') {
+        if (leaf.children) {
+          leaf.children.forEach(child => {
+            // Check children markdown file path validity
+            const result = urlExists(parsedFiles, child);
+            if (!result) {
+              // Display custom error message
+              console.log('[', child, '] is not a valid link, in ', file);
+            }
+            expect(result).toBeTruthy();
+          });
+        }
+      } else {
+        // Check markdown file path validity
+        const result = urlExists(parsedFiles, leaf);
+        if (!result) {
+          console.log('[', leaf, '] is not a valid link, in ', file);
+        }
+        expect(result).toBeTruthy();
+      }
+    });
+  });
+
+  // Check if all url tag in frontmatter are valid and uniques
+  let urlLists = [];
+  parsedFiles.map(file => {
+    let result = file.frontmatter.url;
+    if (!result) {
+      console.log('[', file.path, '] has no url metadata');
+    }
+    expect(result).toBeTruthy();
+
+    result = file.frontmatter.url.startsWith('/');
+    if (!result) {
+      console.log('[', file.path, '] should starts with a slash  ', file.frontmatter.url);
+    }
+    expect(result).toBeTruthy();
+
+    result = file.frontmatter.url.endsWith('/');
+    if (!result) {
+      console.log('[', file.path, '] should ends with a slash  ', file.frontmatter.url);
+    }
+    expect(result).toBeTruthy();
+
+    result = !urlLists.includes(file.frontmatter.url);
+    if (!result) {
+      console.log('[', file.path, '] has an url that is not unique  ', file.frontmatter.url);
+    }
+    expect(result).toBeTruthy();
+
+    urlLists = [...urlLists, file.frontmatter.url];
+  });
+
+  parsedFiles.map(file => {
+    const ast = remark().parse(file.content);
+    visit(ast, node => {
+      if (node.type === 'image' && !node.url.startsWith('http')) {
+        // Check image path validity
+        const result = fs.existsSync(root + '/' + node.url);
+        if (!result) {
+          console.log('[', node.url, '] is not a valid image path, in ', file.path + '.md');
+        }
+        expect(result).toBeTruthy();
+      } else if (
+        node.type === 'link' &&
+        !node.url.startsWith('http') &&
+        !node.url.startsWith('/#')
+      ) {
+        // Check markdown file path validity
+        const result = urlExists(parsedFiles, node.url);
+        if (!result) {
+          console.log('[', node.url, '] is not a valid link, in ', file.path + '.md');
+        }
+        expect(result).toBeTruthy();
+      }
+    });
+  });
+  expect(true).toBeTruthy();
+});
+
+function urlExists(files, url) {
+  return files.find(f => f.frontmatter.url === url) !== undefined;
+}
+
+function handleIncludes(content, root) {
+  return content.replace(/@include (.+)/, (match, p) => {
+    const filePath = path.join(root, '..', `${p}.md`);
+    return fs.readFileSync(filePath, 'utf8');
+  });
+}
+
+function getFrontMatterPosition(lines) {
+  let firstLine;
+  let lastLine;
+  for (let i = 0; i < lines.length; i++) {
+    const line = lines[i];
+    if (line.trim() === '---') {
+      if (firstLine === undefined) {
+        firstLine = i;
+      } else {
+        lastLine = i;
+        break;
+      }
+    }
+  }
+  return lastLine !== undefined ? { firstLine, lastLine } : undefined;
+}
+
+function parseFrontMatter(lines) {
+  const data = {};
+  for (let i = 0; i < lines.length; i++) {
+    const tokens = lines[i].split(':').map(x => x.trim());
+    if (tokens.length === 2) {
+      data[tokens[0]] = tokens[1];
+    }
+  }
+  return data;
+}
+
+function separateFrontMatter(content) {
+  const lines = content.split('\n');
+  const position = getFrontMatterPosition(lines);
+  if (position) {
+    const frontmatter = parseFrontMatter(lines.slice(position.firstLine + 1, position.lastLine));
+    const content = lines.slice(position.lastLine + 1).join('\n');
+    return { frontmatter, content };
+  } else {
+    return { frontmatter: {}, content };
+  }
+}
index 2cfc743b1ec046d0ea8d418fd3ff271c7808fdf5..12e51b570ae9407564f18fbe4bb9aaf3cce2e0b7 100644 (file)
@@ -21,39 +21,59 @@ import * as React from 'react';
 import Link from 'gatsby-link';
 import SubpageLink from './SubpageLink';
 import HeadingsLink from './HeadingsLink';
-import { sortNodes } from '../utils';
 import ChevronDownIcon from './icons/ChevronDownIcon';
 import ChevronUpIcon from './icons/ChevronUpIcon';
 
-export default function CategoryLink({ node, location, headers, onToggle }) {
-  const hasChild = node.pages && node.pages.length > 0;
-  const prefix = process.env.GATSBY_USE_PREFIX === '1' ? '/' + process.env.GATSBY_DOCS_VERSION : '';
-  const { slug } = node.fields;
-  const isCurrentPage = location.pathname === prefix + slug;
-  const open = location.pathname.startsWith(prefix + slug);
-  return (
-    <div>
-      <h2 className={isCurrentPage || open ? 'active' : ''}>
-        <Link to={slug} title={node.frontmatter.title}>
-          {hasChild && open && <ChevronUpIcon />}
-          {hasChild && !open && <ChevronDownIcon />}
-          {node.frontmatter.title}
-        </Link>
-      </h2>
-      {isCurrentPage && <HeadingsLink headers={headers} />}
-      {hasChild &&
-        open && (
-          <div className="sub-menu">
-            {sortNodes(node.pages).map(page => (
-              <SubpageLink
-                key={page.fields.slug}
-                headers={headers}
-                displayHeading={location.pathname === prefix + page.fields.slug}
-                node={page}
-              />
-            ))}
-          </div>
-        )}
-    </div>
-  );
+export default class CategoryLink extends React.PureComponent {
+  constructor(props) {
+    super(props);
+    this.state = { open: props.open };
+  }
+
+  toggle = event => {
+    event.preventDefault();
+    event.stopPropagation();
+    this.props.onToggle(this.props.title);
+  };
+
+  render() {
+    const { node, location, headers, children, title, open } = this.props;
+    const prefix =
+      process.env.GATSBY_USE_PREFIX === '1' ? '/' + process.env.GATSBY_DOCS_VERSION : '';
+    const url = node ? node.frontmatter.url || node.fields.slug : '';
+    const isCurrentPage = location.pathname === prefix + url;
+    return (
+      <div>
+        <h2 className={isCurrentPage || open ? 'active' : ''}>
+          {node ? (
+            <Link to={url} title={node.frontmatter.title}>
+              {node.frontmatter.title}
+            </Link>
+          ) : (
+            <a href="#" onClick={this.toggle}>
+              {open ? <ChevronUpIcon /> : <ChevronDownIcon />}
+              {title}
+            </a>
+          )}
+        </h2>
+        {isCurrentPage && <HeadingsLink headers={headers} />}
+        {children &&
+          open && (
+            <div className="sub-menu">
+              {children.map(page => {
+                const url = page.frontmatter.url || page.fields.slug;
+                return (
+                  <SubpageLink
+                    displayHeading={location.pathname === prefix + url}
+                    headers={headers}
+                    key={url}
+                    node={page}
+                  />
+                );
+              })}
+            </div>
+          )}
+      </div>
+    );
+  }
 }
diff --git a/server/sonar-docs/src/layouts/components/ExternalLink.js b/server/sonar-docs/src/layouts/components/ExternalLink.js
new file mode 100644 (file)
index 0000000..516f1d0
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import DetachIcon from './icons/DetachIcon';
+
+export function ExternalLink({ external, title }) {
+  return (
+    <div>
+      <h2>
+        <a href={external} target="_blank">
+          <DetachIcon />
+          {title}
+        </a>
+      </h2>
+    </div>
+  );
+}
index e807022e79b26f1e8cc6e65b52c88e03d9792797..83e72f5c56f395d266c0ca294ff3391bf304eaa7 100644 (file)
@@ -23,10 +23,10 @@ export default function Footer() {
   return (
     <div className="page-footer">
       <a
+        href="https://creativecommons.org/licenses/by-nc/3.0/us/"
         rel="noopener noreferrer"
         target="_blank"
-        title="Creative Commons License"
-        href="https://creativecommons.org/licenses/by-nc/3.0/us/">
+        title="Creative Commons License">
         <img
           alt="Creative Commons License"
           src="https://licensebuttons.net/l/by-nc/3.0/us/88x31.png"
@@ -35,9 +35,9 @@ export default function Footer() {
       © 2008-2017, SonarSource S.A, Switzerland. Except where otherwise noted, content in this space
       is licensed under a{' '}
       <a
+        href="https://creativecommons.org/licenses/by-nc/3.0/us/"
         rel="noopener noreferrer"
-        target="_blank"
-        href="https://creativecommons.org/licenses/by-nc/3.0/us/">
+        target="_blank">
         Creative Commons Attribution-NonCommercial 3.0 United States License.
       </a>{' '}
       SONARQUBE is a trademark of SonarSource SA. All other trademarks and copyrights are the
diff --git a/server/sonar-docs/src/layouts/components/HeadingAnchor.js b/server/sonar-docs/src/layouts/components/HeadingAnchor.js
new file mode 100644 (file)
index 0000000..a831007
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+
+export default function HeadingAnchor(props) {
+  const onClick = event => {
+    event.stopPropagation();
+    event.preventDefault();
+    props.clickHandler(props.index);
+  };
+  return (
+    <li>
+      <a className={props.active ? 'active' : ''} href={'#header-' + props.index} onClick={onClick}>
+        {props.value}
+      </a>
+    </li>
+  );
+}
index 6abd72ceee33504ca4818b4ac9e403406749f38c..a012a04a78f8ca3f9fc8d7ba32e5b8ca7fff4990 100644 (file)
@@ -18,6 +18,7 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import * as React from 'react';
+import HeadingAnchor from './HeadingAnchor';
 
 export default class HeadingsLink extends React.Component {
   skipScrollingHandler = false;
@@ -51,7 +52,7 @@ export default class HeadingsLink extends React.Component {
   highlightHeading = scrollTop => {
     let headingIndex = 0;
     for (let i = 0; i < this.state.headers.length; i++) {
-      if (document.querySelector('#header-' + (i + 1)).offsetTop > scrollTop + 40) {
+      if (document.querySelector('#header-' + (i + 1)).offsetTop > scrollTop + 200) {
         break;
       }
       headingIndex = i;
@@ -71,8 +72,8 @@ export default class HeadingsLink extends React.Component {
       node.classList.add('targetted-heading');
       if (scrollTo) {
         this.skipScrollingHandler = true;
-        window.scrollTo(0, node.offsetTop - 30);
-        this.highlightHeading(node.offsetTop - 30);
+        window.scrollTo(0, node.offsetTop - 200);
+        this.highlightHeading(node.offsetTop - 200);
       }
     }
   };
@@ -87,12 +88,8 @@ export default class HeadingsLink extends React.Component {
     this.highlightHeading(scrollTop);
   };
 
-  clickHandler = target => {
-    return event => {
-      event.stopPropagation();
-      event.preventDefault();
-      this.markH2(target, true);
-    };
+  clickHandler = index => {
+    this.markH2(index, true);
   };
 
   render() {
@@ -106,14 +103,13 @@ export default class HeadingsLink extends React.Component {
         <ul>
           {headers.map((header, index) => {
             return (
-              <li key={index + 1}>
-                <a
-                  onClick={this.clickHandler(index + 1)}
-                  href={'#header-' + (index + 1)}
-                  className={this.state.activeIndex === index ? 'active' : ''}>
-                  {header.value}
-                </a>
-              </li>
+              <HeadingAnchor
+                active={this.state.activeIndex === index}
+                clickHandler={this.clickHandler}
+                key={index}
+                index={index + 1}
+                value={header.value}
+              />
             );
           })}
         </ul>
index 05b31278f4e1ce5f6965352b3dff1cedbad7485b..1562ff0484e35a82e7a5f6cf1db8699f0a6e3a88 100644 (file)
@@ -18,8 +18,9 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import React, { Component } from 'react';
-import lunr, { LunrIndex } from 'lunr';
+import lunr from 'lunr';
 import ClearIcon from './icons/ClearIcon';
+import { getUrlsList } from '../utils';
 
 // Search component
 export default class Search extends Component {
@@ -36,13 +37,17 @@ export default class Search extends Component {
 
       this.metadataWhitelist = ['position'];
 
-      props.pages.forEach(page =>
-        this.add({
-          id: page.id,
-          title: page.frontmatter.title,
-          text: page.html.replace(/<(?:.|\n)*?>/gm, '').replace(/&#x3C;(?:.|\n)*?>/gm, '')
-        })
-      );
+      props.pages
+        .filter(page =>
+          getUrlsList(props.navigation).includes(page.frontmatter.url || page.fields.slug)
+        )
+        .forEach(page =>
+          this.add({
+            id: page.id,
+            text: page.html.replace(/<(?:.|\n)*?>/gm, '').replace(/&#x3C;(?:.|\n)*?>/gm, ''),
+            title: page.frontmatter.title
+          })
+        );
     });
   }
 
@@ -67,9 +72,9 @@ export default class Search extends Component {
       return {
         page: {
           id: page.id,
-          slug: page.fields.slug,
+          text: page.html.replace(/<(?:.|\n)*?>/gm, '').replace(/&#x3C;(?:.|\n)*?>/gm, ''),
           title: page.frontmatter.title,
-          text: page.html.replace(/<(?:.|\n)*?>/gm, '').replace(/&#x3C;(?:.|\n)*?>/gm, '')
+          url: page.frontmatter.url || page.fields.slug
         },
         highlights,
         longestTerm
index bfd0daba0ca15353c7240e370551c94a4882cc70..c145669167451063699944b34d9cf4c1c12cfcc1 100644 (file)
@@ -23,7 +23,7 @@ import { highlightMarks, cutWords } from '../utils';
 
 export default function SearchResultEntry({ active, result }) {
   return (
-    <Link className={active ? 'active search-result' : 'search-result'} to={result.page.slug}>
+    <Link className={active ? 'active search-result' : 'search-result'} to={result.page.url}>
       <SearchResultTitle result={result} />
       <SearchResultText result={result} />
     </Link>
index a9cf8668d2900c6967564c0b222b3d55ad06729e..e6a7fa1408ed60a806bbb64553e9f14a8a9915b0 100644 (file)
  */
 import React from 'react';
 import Link from 'gatsby-link';
-import { fromPairs } from 'lodash';
-import { sortNodes } from '../utils';
 import CategoryLink from './CategoryLink';
 import VersionSelect from './VersionSelect';
 import Search from './Search';
 import SearchEntryResult from './SearchEntryResult';
+import NavigationTree from '../../../static/StaticNavigationTree.json';
+import { ExternalLink } from './ExternalLink';
 
 export default class Sidebar extends React.PureComponent {
-  state = { loaded: false, query: '', results: [], versions: [] };
+  constructor(props) {
+    super(props);
+    this.state = {
+      loaded: false,
+      openBlockTitle: this.getOpenBlockFromLocation(this.props.location),
+      query: '',
+      results: [],
+      versions: []
+    };
+  }
 
   componentDidMount() {
     this.loadVersions();
   }
 
+  componentDidUpdate(prevProps) {
+    if (this.props.location.pathname !== prevProps.location.pathname) {
+      this.setState({ openBlockTitle: this.getOpenBlockFromLocation(this.props.location) });
+    }
+  }
+
+  // A block is opened if the current page is set to one of his children
+  getOpenBlockFromLocation({ pathname }) {
+    const element = NavigationTree.find(
+      item =>
+        typeof item === 'object' &&
+        item.children &&
+        item.children.some(child => pathname.endsWith(child))
+    );
+    return element ? element.title : '';
+  }
+
   loadVersions() {
     fetch('/DocsVersions.json').then(response =>
       response.json().then(json => {
@@ -41,21 +67,43 @@ export default class Sidebar extends React.PureComponent {
     );
   }
 
-  getPagesHierarchy() {
-    const categories = sortNodes(
-      this.props.pages.filter(p => p.fields.slug.split('/').length === 3)
-    );
-    const pages = this.props.pages.filter(p => p.fields.slug.split('/').length > 3);
-    const categoriesObject = fromPairs(categories.map(c => [c.fields.slug, { ...c, pages: [] }]));
-    pages.forEach(page => {
-      const parentSlug = page.fields.slug
-        .split('/')
-        .slice(0, 2)
-        .join('/');
-      categoriesObject[parentSlug + '/'].pages.push(page);
+  getNodeFromUrl = url => {
+    return this.props.pages.find(p => p.fields.slug === url + '/' || p.frontmatter.url === url);
+  };
+
+  handleToggle = title => {
+    this.setState(state => ({ openBlockTitle: state.openBlockTitle === title ? '' : title }));
+  };
+
+  renderCategories = tree => {
+    return tree.map(item => {
+      if (typeof item === 'object') {
+        if (item.children) {
+          return (
+            <CategoryLink
+              children={item.children.map(child => this.getNodeFromUrl(child))}
+              headers={this.props.headers}
+              key={item.title}
+              location={this.props.location}
+              onToggle={this.handleToggle}
+              open={item.title === this.state.openBlockTitle}
+              title={item.title}
+            />
+          );
+        } else {
+          return <ExternalLink external={item.url} key={item.title} title={item.title} />;
+        }
+      }
+      return (
+        <CategoryLink
+          headers={this.props.headers}
+          key={item}
+          location={this.props.location}
+          node={this.getNodeFromUrl(item)}
+        />
+      );
     });
-    return categoriesObject;
-  }
+  };
 
   renderResults = () => {
     return (
@@ -79,7 +127,6 @@ export default class Sidebar extends React.PureComponent {
   };
 
   render() {
-    const nodes = this.getPagesHierarchy();
     const isOnCurrentVersion =
       this.state.versions.find(v => v.value === this.props.version) !== undefined;
     return (
@@ -89,9 +136,9 @@ export default class Sidebar extends React.PureComponent {
             <img
               alt="Continuous Code Quality"
               css={{ verticalAlign: 'top', margin: 0 }}
-              width="160"
               src="/images/SonarQubeIcon.svg"
               title="Continuous Code Quality"
+              width="160"
             />
           </Link>
           <VersionSelect
@@ -110,17 +157,13 @@ export default class Sidebar extends React.PureComponent {
             )}
         </div>
         <div className="page-indexes">
-          <Search pages={this.props.pages} onResultsChange={this.handleSearch} />
+          <Search
+            navigation={NavigationTree}
+            onResultsChange={this.handleSearch}
+            pages={this.props.pages}
+          />
           {this.state.query !== '' && this.renderResults()}
-          {this.state.query === '' &&
-            Object.keys(nodes).map(key => (
-              <CategoryLink
-                key={key}
-                headers={this.props.headers}
-                node={nodes[key]}
-                location={this.props.location}
-              />
-            ))}
+          {this.state.query === '' && this.renderCategories(NavigationTree)}
         </div>
       </div>
     );
diff --git a/server/sonar-docs/src/layouts/components/icons/DetachIcon.js b/server/sonar-docs/src/layouts/components/icons/DetachIcon.js
new file mode 100644 (file)
index 0000000..a485719
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import Icon from './Icon';
+
+export default function DetachIcon({ className, fill = 'currentColor', size }) {
+  return (
+    <Icon className={className} size={size}>
+      <path
+        d="M12 9.25v2.5A2.25 2.25 0 0 1 9.75 14h-6.5A2.25 2.25 0 0 1 1 11.75v-6.5A2.25 2.25 0 0 1 3.25 3h5.5c.14 0 .25.11.25.25v.5c0 .14-.11.25-.25.25h-5.5C2.562 4 2 4.563 2 5.25v6.5c0 .688.563 1.25 1.25 1.25h6.5c.688 0 1.25-.563 1.25-1.25v-2.5c0-.14.11-.25.25-.25h.5c.14 0 .25.11.25.25zm3-6.75v4c0 .273-.227.5-.5.5a.497.497 0 0 1-.352-.148l-1.375-1.375L7.68 10.57a.27.27 0 0 1-.18.078.27.27 0 0 1-.18-.078l-.89-.89a.27.27 0 0 1-.078-.18.27.27 0 0 1 .078-.18l5.093-5.093-1.375-1.375A.497.497 0 0 1 10 2.5c0-.273.227-.5.5-.5h4c.273 0 .5.227.5.5z"
+        style={{ fill }}
+      />
+    </Icon>
+  );
+}
index 2fc755141ced06eab4ad10956c179db689ee2b01..824955d0ae5ee3ab6da7f1815a346f50355f0b5e 100644 (file)
@@ -38,13 +38,7 @@ export default function Layout(props) {
                 location={props.location}
                 pages={props.data.allMarkdownRemark.edges
                   .map(e => e.node)
-                  .filter(n => !n.fields.slug.startsWith('/tooltips'))
-                  .filter(
-                    n =>
-                      !n.frontmatter.scope ||
-                      n.frontmatter.scope === 'sonarqube' ||
-                      n.frontmatter.scope === 'static'
-                  )}
+                  .filter(n => !n.fields.slug.startsWith('/tooltips'))}
                 searchIndex={props.data.siteSearchIndex}
                 version={version}
               />
@@ -95,8 +89,7 @@ export const query = graphql`
           }
           frontmatter {
             title
-            order
-            scope
+            url
           }
           fields {
             slug
index 9fdae25b2fdbdedcfd5922035fefba698d9665f4..40c2c59da23ff26a4bfe71b277e647f8ad28817b 100644 (file)
@@ -1,12 +1,26 @@
-import { sortBy } from 'lodash';
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import { sortBy, flatten } from 'lodash';
 
-export function sortNodes(nodes) {
-  return nodes.sort((a, b) => {
-    if (a.frontmatter.order) {
-      return b.frontmatter.order ? a.frontmatter.order - b.frontmatter.order : 1;
-    }
-    return a.frontmatter.title < b.frontmatter.title ? -1 : 1;
-  });
+export function getUrlsList(navigation) {
+  return flatten(navigation.map(item => item.children || [item]));
 }
 
 const WORDS = 6;
diff --git a/server/sonar-docs/src/pages/404.md b/server/sonar-docs/src/pages/404.md
new file mode 100644 (file)
index 0000000..d6a0f85
--- /dev/null
@@ -0,0 +1,8 @@
+---
+title: Page not found
+url: /404/
+---
+
+# Error
+
+This page does not exist
index 62157db41da838cba1b0d147cb3e01297e7bc565..c12fcfde131c898339ed1f234340af5ed7699627 100644 (file)
@@ -1,5 +1,6 @@
 ---
 title: Background Tasks
+url: /analysis/background-tasks/
 ---
 
 A Background Task can be:
diff --git a/server/sonar-docs/src/pages/analysis/generic-issue.md b/server/sonar-docs/src/pages/analysis/generic-issue.md
new file mode 100644 (file)
index 0000000..27ef557
--- /dev/null
@@ -0,0 +1,86 @@
+---
+title: Generic Issue Data
+url: /analysis/generic-issue/
+---
+
+SonarQube supports a generic import format for raising "external" issues in code. It is intended to allow you to import the issues from your favorite linter even if no plugin exists for it.
+
+External issues suffer from two important limitations:
+
+* they cannot be managed within SonarQube; for instance, there is no ability to mark them False Positive.
+* the activation of the rules that raise these issues cannot be managed within SonarQube. In fact, external rules are not visible in the Rules page or reflected in any Quality Profile.
+
+External issues and the rules that raise them must be managed in the configuration of your linter. 
+
+## Import 
+The analysis parameter `sonar.externalIssuesReportPaths` accepts a comma-delimited list of paths to reports.
+
+Each report must contain, at top-level, an array of `Issue` objects named `issues`.
+
+#### Issue fields:
+
+* `engineId` - string
+* `ruleId` - string
+* `primaryLocation` - Location object 
+* `type` - string. One of BUG, VULNERABILITY, CODE_SMELL
+* `severity` - string. One of BLOCKER, CRITICAL, MAJOR, MINOR, INFO
+* `effortMinutes` - integer, optional. Defaults to 0
+* `secondaryLocations` - array of Location objects, optional
+
+#### Location fields:
+
+* `message` - string
+* `filePath` - string
+* `textRange` - TextRange object, optional for secondary locations only
+
+#### TextRange fields:
+
+* `startLine` - integer. 1-indexed
+* `endLine` - integer, optional. 1-indexed
+* `startColumn` - integer, optional. 0-indexed
+* `endColumn` - integer, optional. 0-indexed
+
+## Example
+Here is an example of the expected format:
+
+       { "issues": [
+               {
+                 "engineId": "test",
+                 "ruleId": "rule1",
+                 "severity":"BLOCKER",
+                 "type":"CODE_SMELL",
+                 "primaryLocation": {
+                       "message": "fully-fleshed issue",
+                       "filePath": "sources/A.java",
+                       "textRange": {
+                         "startLine": 30,
+                         "endLine": 30,
+                         "startColumn": 9,
+                         "endColumn": 14
+                       }
+                 },
+                 "effortMinutes": 90,
+                 "secondaryLocations": [
+                       {
+                         "message": "cross-file 2ndary location",
+                         "filePath": "sources/B.java",
+                         "textRange": {
+                               "startLine": 10,
+                               "endLine": 10,
+                               "startColumn": 6,
+                               "endColumn": 38
+                         }
+                       }
+                 ]
+               },
+               {
+                 "engineId": "test",
+                 "ruleId": "rule2",
+                 "severity": "INFO",
+                 "type": "BUG",
+                 "primaryLocation": {
+                       "message": "minimal issue raised at file level",
+                       "filePath": "sources/Measure.java"
+                 }
+               }
+       ]}
diff --git a/server/sonar-docs/src/pages/analysis/generic-test.md b/server/sonar-docs/src/pages/analysis/generic-test.md
new file mode 100644 (file)
index 0000000..d7fa694
--- /dev/null
@@ -0,0 +1,97 @@
+---
+title: Generic Test Data
+url: /analysis/generic-test/
+---
+
+Out of the box, SonarQube supports generic formats for test coverage and test execution import. If your coverage engines' native output formats aren't supported by your language plugins, simply covert them to these formats.
+
+## Generic Coverage
+Report paths should be passed in a comma-delimited list to:
+
+ * `sonar.coverageReportPaths`
+
+The supported format is described by the `sonar-generic-coverage.xsd`:
+
+       <xs:schema>
+         <xs:element name="coverage">
+               <xs:complexType>
+                 <xs:sequence>
+                       <xs:element name="file" minOccurs="0" maxOccurs="unbounded">
+                         <xs:complexType>
+                               <xs:sequence>
+                                 <xs:element name="lineToCover" minOccurs="0" maxOccurs="unbounded">
+                                       <xs:complexType>
+                                         <xs:attribute name="lineNumber" type="xs:positiveInteger" use="required"/>
+                                         <xs:attribute name="covered" type="xs:boolean" use="required"/>
+                                         <xs:attribute name="branchesToCover" type="xs:nonNegativeInteger"/>
+                                         <xs:attribute name="coveredBranches" type="xs:nonNegativeInteger"/>
+                                       </xs:complexType>
+                                 </xs:element>
+                               </xs:sequence>
+                         <xs:attribute name="path" type="xs:string" use="required"/>
+                         </xs:complexType>
+                       </xs:element>
+                 </xs:sequence>
+                 <xs:attribute name="version" type="xs:positiveInteger" use="required"/>
+               </xs:complexType>
+         </xs:element>
+       </xs:schema>
+
+and looks like this:
+
+       <coverage version="1">
+         <file path="xources/hello/NoConditions.xoo">
+               <lineToCover lineNumber="6" covered="true"/>
+               <lineToCover lineNumber="7" covered="false"/>
+         </file>
+         <file path="xources/hello/WithConditions.xoo">
+               <lineToCover lineNumber="3" covered="true" branchesToCover="2" coveredBranches="1"/>
+         </file>
+       </coverage>
+
+The root node should be named `coverage`. Its version attribute should be set to `1`.
+
+Insert a `file` element for each file which can be covered by tests. Its `path` attribute can be either absolute or relative to the root of the module.
+Inside a `file` element, insert a `lineToCover` for each line which can be covered by unit tests. It can have the following attributes:
+* `lineNumber` (mandatory)
+* `covered` (mandatory): boolean value indicating whether tests actually hit that line
+* `branchesToCover` (optional): number of branches which can be covered
+* `coveredBranches` (optional): number of branches which are actually covered by tests
+
+## Generic Execution
+Report paths should be passed in a comma-delimited list to:
+
+* `sonar.testExecutionReportPaths`
+
+The supported format looks like this:
+
+       <testExecutions version="1">
+         <file path="testx/ClassOneTest.xoo">
+               <testCase name="test1" duration="5"/>
+               <testCase name="test2" duration="500">
+                 <skipped message="short message">other</skipped>
+               </testCase>
+               <testCase name="test3" duration="100">
+                 <failure message="short">stacktrace</failure>
+               </testCase>
+               <testCase name="test4" duration="500">
+                 <error message="short">stacktrace</error>
+               </testCase>
+         </file>
+       </testExecutions>
+       
+The root node should be named `testExecutions`. Its version attribute should be set to `1`.
+
+Insert a `file` element for each test file. Its `path` attribute can be either absolute or relative to the root of the module.
+
+**Note** unlike for coverage reports, the files present in the report must be test file names, not source code files covered by tests.
+
+Inside a `file` element, insert a `testCase` for each test run by unit tests. It can have the following attributes/children:
+
+* `testCase` (mandatory)
+  * `name` (mandatory): name of the test case
+  * `duration` (mandatory): long value in milliseconds
+  * `failure|error|skipped` (optional): if the test is not OK, report the cause with a message and a long description
+    * `message` (mandatory): short message describing the cause
+    * `stacktrace` (optional): long message containing details about `failure|error|skipped` status
diff --git a/server/sonar-docs/src/pages/analysis/generic_issue.md b/server/sonar-docs/src/pages/analysis/generic_issue.md
deleted file mode 100644 (file)
index 33c3ff4..0000000
+++ /dev/null
@@ -1,84 +0,0 @@
----
-title: Generic Issue Data
----
-
-SonarQube supports a generic import format for raising "external" issues in code. It is intended to allow you to import the issues from your favorite linter even if no plugin exists for it.
-
-External issues suffer from two important limitations:
-
-* they cannot be managed within SonarQube; for instance, there is no ability to mark them False Positive.
-* the activation of the rules that raise these issues cannot be managed within SonarQube. In fact, external rules are not visible in the Rules page or reflected in any Quality Profile.
-
-External issues and the rules that raise them must be managed in the configuration of your linter. 
-
-## Import
-The analysis parameter `sonar.externalIssuesReportPaths` accepts a comma-delimited list of paths to reports.
-
-Each report must contain, at top-level, an array of `Issue` objects named `issues`.
-
-#### Issue fields:
-
-* `engineId` - string
-* `ruleId` - string
-* `primaryLocation` - Location object 
-* `type` - string. One of BUG, VULNERABILITY, CODE_SMELL
-* `severity` - string. One of BLOCKER, CRITICAL, MAJOR, MINOR, INFO
-* `effortMinutes` - integer, optional. Defaults to 0
-* `secondaryLocations` - array of Location objects, optional
-
-#### Location fields:
-
-* `message` - string
-* `filePath` - string
-* `textRange` - TextRange object, optional for secondary locations only
-
-#### TextRange fields:
-
-* `startLine` - integer. 1-indexed
-* `endLine` - integer, optional. 1-indexed
-* `startColumn` - integer, optional. 0-indexed
-* `endColumn` - integer, optional. 0-indexed
-
-Here is an example of the expected format:
-
-       { "issues": [
-               {
-                 "engineId": "test",
-                 "ruleId": "rule1",
-                 "severity":"BLOCKER",
-                 "type":"CODE_SMELL",
-                 "primaryLocation": {
-                       "message": "fully-fleshed issue",
-                       "filePath": "sources/A.java",
-                       "textRange": {
-                         "startLine": 30,
-                         "endLine": 30,
-                         "startColumn": 9,
-                         "endColumn": 14
-                       }
-                 },
-                 "effortMinutes": 90,
-                 "secondaryLocations": [
-                       {
-                         "message": "cross-file 2ndary location",
-                         "filePath": "sources/B.java",
-                         "textRange": {
-                               "startLine": 10,
-                               "endLine": 10,
-                               "startColumn": 6,
-                               "endColumn": 38
-                         }
-                       }
-                 ]
-               },
-               {
-                 "engineId": "test",
-                 "ruleId": "rule2",
-                 "severity": "INFO",
-                 "type": "BUG",
-                 "primaryLocation": {
-                       "message": "minimal issue raised at file level",
-                       "filePath": "sources/Measure.java"
-                 }
-               }
-       ]}
diff --git a/server/sonar-docs/src/pages/analysis/generic_test.md b/server/sonar-docs/src/pages/analysis/generic_test.md
deleted file mode 100644 (file)
index 47a5850..0000000
+++ /dev/null
@@ -1,96 +0,0 @@
----
-title: Generic Test Data
----
-
-Out of the box, SonarQube supports generic formats for test coverage and test execution import. If your coverage engines' native output formats aren't supported by your language plugins, simply covert them to these formats.
-
-## Generic Coverage
-Report paths should be passed in a comma-delimited list to:
-
- * `sonar.coverageReportPaths`
-
-The supported format is described by the `sonar-generic-coverage.xsd`:
-
-       <xs:schema>
-         <xs:element name="coverage">
-               <xs:complexType>
-                 <xs:sequence>
-                       <xs:element name="file" minOccurs="0" maxOccurs="unbounded">
-                         <xs:complexType>
-                               <xs:sequence>
-                                 <xs:element name="lineToCover" minOccurs="0" maxOccurs="unbounded">
-                                       <xs:complexType>
-                                         <xs:attribute name="lineNumber" type="xs:positiveInteger" use="required"/>
-                                         <xs:attribute name="covered" type="xs:boolean" use="required"/>
-                                         <xs:attribute name="branchesToCover" type="xs:nonNegativeInteger"/>
-                                         <xs:attribute name="coveredBranches" type="xs:nonNegativeInteger"/>
-                                       </xs:complexType>
-                                 </xs:element>
-                               </xs:sequence>
-                         <xs:attribute name="path" type="xs:string" use="required"/>
-                         </xs:complexType>
-                       </xs:element>
-                 </xs:sequence>
-                 <xs:attribute name="version" type="xs:positiveInteger" use="required"/>
-               </xs:complexType>
-         </xs:element>
-       </xs:schema>
-
-and looks like this:
-
-       <coverage version="1">
-         <file path="xources/hello/NoConditions.xoo">
-               <lineToCover lineNumber="6" covered="true"/>
-               <lineToCover lineNumber="7" covered="false"/>
-         </file>
-         <file path="xources/hello/WithConditions.xoo">
-               <lineToCover lineNumber="3" covered="true" branchesToCover="2" coveredBranches="1"/>
-         </file>
-       </coverage>
-
-The root node should be named `coverage`. Its version attribute should be set to `1`.
-
-Insert a `file` element for each file which can be covered by tests. Its `path` attribute can be either absolute or relative to the root of the module.
-Inside a `file` element, insert a `lineToCover` for each line which can be covered by unit tests. It can have the following attributes:
-* `lineNumber` (mandatory)
-* `covered` (mandatory): boolean value indicating whether tests actually hit that line
-* `branchesToCover` (optional): number of branches which can be covered
-* `coveredBranches` (optional): number of branches which are actually covered by tests
-
-## Generic Execution
-Report paths should be passed in a comma-delimited list to:
-
-* `sonar.testExecutionReportPaths`
-
-The supported format looks like this:
-
-       <testExecutions version="1">
-         <file path="testx/ClassOneTest.xoo">
-               <testCase name="test1" duration="5"/>
-               <testCase name="test2" duration="500">
-                 <skipped message="short message">other</skipped>
-               </testCase>
-               <testCase name="test3" duration="100">
-                 <failure message="short">stacktrace</failure>
-               </testCase>
-               <testCase name="test4" duration="500">
-                 <error message="short">stacktrace</error>
-               </testCase>
-         </file>
-       </testExecutions>
-       
-The root node should be named `testExecutions`. Its version attribute should be set to `1`.
-
-Insert a `file` element for each test file. Its `path` attribute can be either absolute or relative to the root of the module.
-
-**Note** unlike for coverage reports, the files present in the report must be test file names, not source code files covered by tests.
-
-Inside a `file` element, insert a `testCase` for each test run by unit tests. It can have the following attributes/children:
-
-* `testCase` (mandatory)
-  * `name` (mandatory): name of the test case
-  * `duration` (mandatory): long value in milliseconds
-  * `failure|error|skipped` (optional): if the test is not OK, report the cause with a message and a long description
-    * `message` (mandatory): short message describing the cause
-    * `stacktrace` (optional): long message containing details about `failure|error|skipped` status
diff --git a/server/sonar-docs/src/pages/analysis/index.md b/server/sonar-docs/src/pages/analysis/index.md
deleted file mode 100644 (file)
index e6603fe..0000000
+++ /dev/null
@@ -1,50 +0,0 @@
----
-title: Analyzing Source Code
----
-
-Once the SonarQube platform has been installed, you're ready to install an analyzer and begin creating projects. To do that, you must install and configure the scanner that is most appropriate for your needs. Do you build with:
-
-<!-- sonarcloud -->
-* TravisCI for SonarCloud - [SonarCloud Travis addon](https://docs.travis-ci.com/user/sonarcloud/)
-<!-- /sonarcloud -->
-* Gradle - [SonarQube Scanner for Gradle](https://redirect.sonarsource.com/doc/gradle.html)
-* MSBuild - [SonarQube Scanner for MSBuild](https://redirect.sonarsource.com/doc/install-configure-scanner-msbuild.html)
-* Maven - use the [SonarQube Scanner for Maven](https://redirect.sonarsource.com/doc/install-configure-scanner-maven.html)
-* Jenkins - [SonarQube Scanner for Jenkins](https://redirect.sonarsource.com/plugins/jenkins.html)
-* VSTS / TFS - [SonarQube Extension for VSTS-TFS](https://redirect.sonarsource.com/doc/install-configure-scanner-tfs-ts.html)
-* Ant - [SonarQube Scanner for Ant](https://redirect.sonarsource.com/doc/install-configure-scanner-ant.html)
-* anything else (CLI) - [SonarQube Scanner](https://redirect.sonarsource.com/doc/install-configure-scanner.html)
-
-**Note** that we do not recommend running an antivirus scanner on the machine where a SonarQube analysis runs, it could result in unpredictable behavior.
-
-
-A project is created in the platform automatically on its first analysis. However, if you need to set some configuration on your project before its first analysis, you have the option of provisioning it via Administration options.
-
-## What does analysis produce? 
-SonarQube can perform analysis on 20+ different languages. The outcome of this analysis will be quality measures and issues (instances where coding rules were broken). However, what gets analyzed will vary depending on the language:
-
-* On all languages, "blame" data will automatically be imported from supported SCM providers. Git and SVN are supported automatically. Other providers require additional plugins.
-* On all languages, a static analysis of source code is performed (Java files, COBOL programs, etc.)
-* A static analysis of compiled code can be performed for certain languages (.class files in Java, .dll files in C#, etc.)
-* A dynamic analysis of code can be performed on certain languages.
-
-## Will all files be analyzed?
-By default, only files that are recognized by a language analyzer are loaded into the project during analysis. For example if your SonarQube instance had only SonarJava SonarJS on board, all .java and .js files would be loaded, but .xml files would be ignored.
-
-## What happens during analysis?
-During analysis, data is requested from the server, the files provided to the analysis are analyzed, and the resulting data is sent back to the server at the end in the form of a report, which is then analyzed asynchronously server-side.
-
-Analysis reports are queued, and processed sequentially, so it is quite possible that for a brief period after your analysis log shows completion, the updated values are not visible in your SonarQube project. However, you will be able to tell what's going on because an icon will be added on the project homepage to the right of the project name. Mouse over it for more detail (and links if you're logged in with the proper permissions).
-
-![background task processing in progress.](/images/backgroundTaskProcessingInProgress.jpeg)
-
-
-The icon goes away once processing is complete, but if analysis report processing fails for some reason, the icon changes:
-
-![background task processing failed.](/images/backgroundTaskProcessingFailedIcon.jpeg)
-
-
-## F.A.Q.
-
-**Q.** Analysis errors out with `java.lang.OutOfMemoryError: GC overhead limit exceeded`. What do I do?  
-**A.** This means your project is too large or too intricate for the scanner to analyze with the default memory allocation. To fix this you'll want to allocate a larger heap (using `-Xmx[numeric value here]`) to the process running the analysis. Some CI engines may give you an input to specify the necessary values, for instance if you're using a Maven Build Step in a Jenkins job to run analysis. Otherwise, use Java Options to set a higher value. Note that details of setting Java Options are omitted here because they vary depending on the environment.
diff --git a/server/sonar-docs/src/pages/analysis/overview.md b/server/sonar-docs/src/pages/analysis/overview.md
new file mode 100644 (file)
index 0000000..1dc4166
--- /dev/null
@@ -0,0 +1,51 @@
+---
+title: Overview
+url: /analysis/overview/
+---
+
+Once the SonarQube platform has been installed, you're ready to install an analyzer and begin creating projects. To do that, you must install and configure the scanner that is most appropriate for your needs. Do you build with:
+
+<!-- sonarcloud -->
+* TravisCI for SonarCloud - [SonarCloud Travis addon](https://docs.travis-ci.com/user/sonarcloud/)
+<!-- /sonarcloud -->
+* Gradle - [SonarQube Scanner for Gradle](https://redirect.sonarsource.com/doc/gradle.html)
+* MSBuild - [SonarQube Scanner for MSBuild](https://redirect.sonarsource.com/doc/install-configure-scanner-msbuild.html)
+* Maven - use the [SonarQube Scanner for Maven](https://redirect.sonarsource.com/doc/install-configure-scanner-maven.html)
+* Jenkins - [SonarQube Scanner for Jenkins](https://redirect.sonarsource.com/plugins/jenkins.html)
+* VSTS / TFS - [SonarQube Extension for VSTS-TFS](https://redirect.sonarsource.com/doc/install-configure-scanner-tfs-ts.html)
+* Ant - [SonarQube Scanner for Ant](https://redirect.sonarsource.com/doc/install-configure-scanner-ant.html)
+* anything else (CLI) - [SonarQube Scanner](https://redirect.sonarsource.com/doc/install-configure-scanner.html)
+
+**Note** that we do not recommend running an antivirus scanner on the machine where a SonarQube analysis runs, it could result in unpredictable behavior.
+
+
+A project is created in the platform automatically on its first analysis. However, if you need to set some configuration on your project before its first analysis, you have the option of provisioning it via Administration options.
+
+## What does analysis produce? 
+SonarQube can perform analysis on 20+ different languages. The outcome of this analysis will be quality measures and issues (instances where coding rules were broken). However, what gets analyzed will vary depending on the language:
+
+* On all languages, "blame" data will automatically be imported from supported SCM providers. Git and SVN are supported automatically. Other providers require additional plugins.
+* On all languages, a static analysis of source code is performed (Java files, COBOL programs, etc.)
+* A static analysis of compiled code can be performed for certain languages (.class files in Java, .dll files in C#, etc.)
+* A dynamic analysis of code can be performed on certain languages.
+
+## Will all files be analyzed?
+By default, only files that are recognized by a language analyzer are loaded into the project during analysis. For example if your SonarQube instance had only SonarJava SonarJS on board, all .java and .js files would be loaded, but .xml files would be ignored.
+
+## What happens during analysis?
+During analysis, data is requested from the server, the files provided to the analysis are analyzed, and the resulting data is sent back to the server at the end in the form of a report, which is then analyzed asynchronously server-side.
+
+Analysis reports are queued, and processed sequentially, so it is quite possible that for a brief period after your analysis log shows completion, the updated values are not visible in your SonarQube project. However, you will be able to tell what's going on because an icon will be added on the project homepage to the right of the project name. Mouse over it for more detail (and links if you're logged in with the proper permissions).
+
+![background task processing in progress.](/images/backgroundTaskProcessingInProgress.jpeg)
+
+
+The icon goes away once processing is complete, but if analysis report processing fails for some reason, the icon changes:
+
+![background task processing failed.](/images/backgroundTaskProcessingFailedIcon.jpeg)
+
+
+## F.A.Q.
+
+**Q.** Analysis errors out with `java.lang.OutOfMemoryError: GC overhead limit exceeded`. What do I do?  
+**A.** This means your project is too large or too intricate for the scanner to analyze with the default memory allocation. To fix this you'll want to allocate a larger heap (using `-Xmx[numeric value here]`) to the process running the analysis. Some CI engines may give you an input to specify the necessary values, for instance if you're using a Maven Build Step in a Jenkins job to run analysis. Otherwise, use Java Options to set a higher value. Note that details of setting Java Options are omitted here because they vary depending on the environment.
index 497b5ffb83023dbc42cfc17911ef2bd6cc4261b4..4e17d90c01d64e482fb49298453bd0e50d7de811 100644 (file)
@@ -1,5 +1,6 @@
 ---
 title: Pull Request Analysis
+url: /analysis/pull-request/
 ---
 
 <!-- sonarqube -->
@@ -24,7 +25,7 @@ PR analyses on SonarQube are deleted automatically after 30 days with no analysi
 
 <!-- sonarcloud -->
 ## Integrations for GitHub, Bitbucket Cloud and VSTS
-If your repositories are hosted on GitHub, Bitbucket Cloud or VSTS, check out first the dedicated ["Integrations" pages](/integrations/index). Chances are that you do not need to read this page further since those integrations handle the configuration and analysis parameters for you.
+If your repositories are hosted on GitHub, Bitbucket Cloud or VSTS, check out first the dedicated Integrations for: [BitBucketCloud](/integrations/bitbucketcloud/), [GitHub](/integrations/github/), and [VSTS](/integrations/vsts/). Chances are that you do not need to read this page further since those integrations handle the configuration and analysis parameters for you.
 <!-- /sonarcloud -->
 
 ## Analysis Parameters
diff --git a/server/sonar-docs/src/pages/analysis/scm-integration.md b/server/sonar-docs/src/pages/analysis/scm-integration.md
new file mode 100644 (file)
index 0000000..78a498f
--- /dev/null
@@ -0,0 +1,16 @@
+---
+title: SCM Integration
+url: /analysis/scm-integration/
+---
+
+Collecting SCM data during code analysis can unlock a number of SonarQube features:
+
+* Automatic Issue Assignment
+* code annotation (blame data) in the Code Viewer
+* SCM-driven detection of new code (to help with [Fixing the Water Leak](/user-guide/fixing-the-water-leak/)). Without SCM data, SonarQube determines new code using analysis dates (to timestamp modification of lines).
+
+### Turning it on/off
+SCM integration requires support for your individual SCM provider. Git and SVN are supported by default. <!-- sonarqube -->For other SCM providers, see the Marketplace.<!-- /sonarqube -->
+
+If need be, you can toggle it off at global/project level via administration settings.
+
diff --git a/server/sonar-docs/src/pages/analysis/scm_integration.md b/server/sonar-docs/src/pages/analysis/scm_integration.md
deleted file mode 100644 (file)
index 8771b78..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
----
-title: SCM Integration
----
-
-Collecting SCM data during code analysis can unlock a number of SonarQube features:
-
-* Automatic Issue Assignment
-* code annotation (blame data) in the Code Viewer
-* SCM-driven detection of new code (to help with [Fixing the Water Leak](/fixing-the-water-leak)). Without SCM data, SonarQube determines new code using analysis dates (to timestamp modification of lines).
-
-### Turning it on/off
-SCM integration requires support for your individual SCM provider. Git and SVN are supported by default. <!-- sonarqube -->For other SCM providers, see the Marketplace.<!-- /sonarqube -->
-
-If need be, you can toggle it off at global/project level via administration settings.
-
diff --git a/server/sonar-docs/src/pages/analyze-a-project.md b/server/sonar-docs/src/pages/analyze-a-project.md
deleted file mode 100644 (file)
index 07f425a..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
----
-title: Analyze a Project
-scope: sonarcloud
----
-
-## Prepare your organization
-
-A project must belong to an [organization](/organizations/index). Create one if you intend to collaborate with your team mates, or use your personal organization for test purposes.
-
-[[info]]
-| ** Important note for private code:** Newly created organizations and personal organizations are under a free plan by default. This means projects analyzed on these organizations are public by default: the code will be browsable by anyone. If you want private projects, you should [upgrade your organization to a paid plan](/sonarcloud-pricing) in the "Administration > Billing" page of your organization.
-
-Find the key of your organization, you will need it at later stages. It can be found on the top right corner of your organization's header.
-
-## Run analysis
-
-SonarCloud currently does not trigger analyses automatically - this feature will come in a near future. Currently, it's up to you to launch them inside your
-existing CI scripts.
-
-Depending on which cloud solution you are using for your developments, you can rely on dedicated integrations to help you:
-
-* VSTS: [read our dedicated documentation](/integrations/vsts)
-* Bitbucket Cloud: [read our dedicated documentation](/integrations/bitbucketcloud)
-* GitHub: [read our dedicated documentation](/integrations/github)
-
-If you are not using those solutions, you will have to find out what command to execute to run the analysis. Our [tutorial](/#sonarcloud#/onboarding) will help you on this.
index e5925192786f2862c8a5e3f0f2fea39483c1a0b5..304db0a2fdd1624fe03a578518ab4bebf0d62b0c 100644 (file)
@@ -1,6 +1,6 @@
 ---
 title: Frequently Asked Branches Questions
-order: 99
+url: /branches/branches-faq/
 ---
 
 <!-- sonarqube -->
@@ -10,32 +10,32 @@ _Branch analysis is available as part of [Developer Edition](https://redirect.so
 <!-- /sonarqube -->
 
 
-**Q:** How long are branches retained?  
-**A:** Long-lived branches are retained until you delete them manually (**Administration > Branches**).
+## How long are branches retained?  
+Long-lived branches are retained until you delete them manually (**Administration > Branches**).
 Short-lived branches are deleted automatically after 30 days with no analysis.
 This can be updated in **Configuration > General > Number of days before purging inactive short living branches**.
 
-**Q:** Do I need to have my project stored in an SCM such as Git or SVN to use this feature?  
-**A:** No, you don't need to be connected to a SCM. But if you use Git or SVN we can better track the new files on short-lived branches and so better report new issues on the files that really changed during the life of the short-lived branch.
+## Does my project need to be stored in an SCM like Git or SVN?  
+No, you don't need to be connected to a SCM. But if you use Git or SVN we can better track the new files on short-lived branches and so better report new issues on the files that really changed during the life of the short-lived branch.
 
-**Q:** If I flag an Issue as "Won't Fix" or "False-Positive", will it be replicated as such when merging my short-lived branch into the Master?  
-**A:** Yes. Each time there is an analysis of a long-lived branch, we look at the issues on the short-lived branches and try to synchronize them with the newly raised issues on the long-lived branch. In case you made some changes on the issues (false-positive, won't fix), these changes will be reported on the long-lived branch.
+## What if I mark an Issue "Won't Fix" or "False-Positive" in a branch?
+It be replicated as such when merging my short-lived branch into the Master. Each time there is an analysis of a long-lived branch, we look at the issues on the short-lived branches and try to synchronize them with the newly raised issues on the long-lived branch. In case you made some changes on the issues (false-positive, won't fix), these changes will be reported on the long-lived branch.
 
-**Q:** Can I still use `sonar.branch`?  
-**A:** `sonar.branch` is deprecated. You can still use it but it will behave the same way it always has: a separate project will be created. We encourage you to smoothly migrate your users to the new parameter `sonar.branch.name`.
+## Can I still use 'sonar.branch'?  
+`sonar.branch` is deprecated. You can still use it but it will behave the same way it always has: a separate project will be created. We encourage you to smoothly migrate your users to the new parameter `sonar.branch.name`.
 Please note you cannot use `sonar.branch` together with `sonar.branch.name`.
 
-**Q:** Can I manually delete a branch?  
-**A:** This can be achieved by going into the Administration menu at Project's level, then Branches.
+## Can I manually delete a branch?  
+This can be achieved by going into the Administration menu at Project's level, then Branches.
 
-**Q:** How do I control the lifespan of a short-lived branch?  
-**A:** As a global admin, you can set the parameter sonar.dbcleaner.daysBeforeDeletingInactiveShortLivingBranches to control how many days you want to keep an inactive short-lived branch.
+## How do I control the lifespan of a short-lived branch?  
+As a global admin, you can set the parameter `sonar.dbcleaner.daysBeforeDeletingInactiveShortLivingBranches` to control how many days you want to keep an inactive short-lived branch.
 
-**Q:** Does the payload of the Webhook contain extra information related to Branches?  
-**A:** Yes, an extra node called "branch" is added to the payload.
+## Does the payload of the Webhook include branch information?  
+Yes, an extra node called "branch" is added to the payload.
 
-**Q:** When are Webhooks called?  
-**A:** When the computation of the background task is done for a given branch but also when an issue is updated on a short-lived branch.
+## When are Webhooks called?  
+When the computation of the background task is done for a given branch but also when an issue is updated on a short-lived branch.
 
-**Q:** What is the impact on my LOCs consumption vs my license?  
-**A:** The LOC of your largest branch are counted toward your license limit. All other branches are ignored.  
+## What is the impact on my LOCs consumption vs my license?  
+The LOC of your largest branch are counted toward your license limit. All other branches are ignored.  
diff --git a/server/sonar-docs/src/pages/branches/index.md b/server/sonar-docs/src/pages/branches/index.md
deleted file mode 100644 (file)
index ffe24b0..0000000
+++ /dev/null
@@ -1,81 +0,0 @@
----
-title: Branches
----
-
-<!-- sonarqube -->
-_Branch analysis is available as part of [Developer Edition](https://redirect.sonarsource.com/editions/developer.html)_
-<!-- /sonarqube -->
-
-## Table of Contents
-
-
-Branch analysis allows you to
-
-* analyze long-lived branches
-* analyze short-lived branches
-* notify external systems when the status of a short-lived branch is impacted
-
-## Branch Types
-
-### Short-lived
-
-Short-Lived
-This corresponds to Pull/Merge Requests or Feature Branches. This kind of branch:
-
-* will disappear quickly
-* will be merged rapidly to prevent integration issues
-* is developed for a given version, so the version does not change,
-  and there is no way to set the New Code period; everything that has been changed in the branch is new code
-* tracks all the new issues related to the code that changed on it.
-
-![conceptual illustration of short-lived branches.](/images/short-lived-branch-concept.png)
-
-For more, see [Short-lived Branches](/branches/short-lived-branches)
-
-### Long-lived
-
-This corresponds to "Maintenance" Branches that will house several release versions.
-This kind of branch will:
-
-* last for a long time
-* inevitably diverge more and more from the other branches
-* house several release versions, each of which must pass the quality gate
-  to go to production not be expected to be merged into another branch
-
-![conceptual illustration of long-lived branches.](/images/long-lived-branch-concept.png)
-
-For more, see [Long-lived Branches](/branches/long-lived-branches)
-
-### Master / Main Branch
-
-This is the default, and typically corresponds to what's being developed for
-your next release. This is usually known within a development team as
-"master" or "head", and is what is analyzed when no specific branch parameters
-are provided. It is labeled "Main Branch" and defaults to the name "master",
-but can be renamed from within the interface. When you are not using Developer Edition, this is the only branch you see.
-
-## Analysis
-
-A branch is created when the `sonar.branch.name` parameter is passed during analysis.
-
-| Parameter Name        | Description                                                                                                                                                                                                                                                             |
-| --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `sonar.branch.name`   | Name of the branch (visible in the UI)                                                                                                                                                                                                                                  |
-| `sonar.branch.target` | Name of the branch where you intend to merge your short-lived branch at the end of its life. If left blank, this defaults to the master branch. It can also be used while initializing a long-lived branch to sync the issues from a branch other than the Main Branch. |
-
-### Git History
-
-By default, TravisCI only fetches the last 50 git commits. You must use `git fetch --unshallow` to get the full history. If you don't, new issues may not be assigned to the correct developer.
-
-### Configuring the Branch type
-
-A regular expression is used to determine whether a branch is treated as long-lived or short-lived. By default, branches that have names starting with either "branch" or "release" will be treated as long-lived.
-
-This can be updated globally in **Configuration > General > Detection** of long-lived branches or at project's level in the **Admininstration > Branches**.
-
-Once a branch type has been set, it cannot be changed. Explicitly, you cannot transform a long-lived to short-lived branch, or vice-versa.
-
-## See also
-* [Short-lived Branches](short-lived-branches)
-* [Long-lived Branches](long-lived-branches)
-* [Frequently Asked Questions](branches-faq)
index b806d2b6a21fc790f95dc189489957b0b04e97da..1d0aae67c47311925b1f02ff64a32dc5aa508723 100644 (file)
@@ -1,5 +1,6 @@
 ---
 title: Long-lived Branches
+url: /branches/long-lived-branches/
 ---
 
 <!-- sonarqube -->
diff --git a/server/sonar-docs/src/pages/branches/overview.md b/server/sonar-docs/src/pages/branches/overview.md
new file mode 100644 (file)
index 0000000..c73cb5f
--- /dev/null
@@ -0,0 +1,82 @@
+---
+title: Overview
+url: /branches/overview/
+---
+
+<!-- sonarqube -->
+_Branch analysis is available as part of [Developer Edition](https://redirect.sonarsource.com/editions/developer.html)_
+<!-- /sonarqube -->
+
+## Table of Contents
+
+
+Branch analysis allows you to
+
+* analyze long-lived branches
+* analyze short-lived branches
+* notify external systems when the status of a short-lived branch is impacted
+
+## Branch Types
+
+### Short-lived
+
+Short-Lived
+This corresponds to Pull/Merge Requests or Feature Branches. This kind of branch:
+
+* will disappear quickly
+* will be merged rapidly to prevent integration issues
+* is developed for a given version, so the version does not change,
+  and there is no way to set the New Code period; everything that has been changed in the branch is new code
+* tracks all the new issues related to the code that changed on it.
+
+![conceptual illustration of short-lived branches.](/images/short-lived-branch-concept.png)
+
+For more, see [Short-lived Branches](/branches/short-lived-branches/)
+
+### Long-lived
+
+This corresponds to "Maintenance" Branches that will house several release versions.
+This kind of branch will:
+
+* last for a long time
+* inevitably diverge more and more from the other branches
+* house several release versions, each of which must pass the quality gate
+  to go to production not be expected to be merged into another branch
+
+![conceptual illustration of long-lived branches.](/images/long-lived-branch-concept.png)
+
+For more, see [Long-lived Branches](/branches/long-lived-branches/)
+
+### Master / Main Branch
+
+This is the default, and typically corresponds to what's being developed for
+your next release. This is usually known within a development team as
+"master" or "head", and is what is analyzed when no specific branch parameters
+are provided. It is labeled "Main Branch" and defaults to the name "master",
+but can be renamed from within the interface. When you are not using Developer Edition, this is the only branch you see.
+
+## Analysis
+
+A branch is created when the `sonar.branch.name` parameter is passed during analysis.
+
+| Parameter Name        | Description                                                                                                                                                                                                                                                             |
+| --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `sonar.branch.name`   | Name of the branch (visible in the UI)                                                                                                                                                                                                                                  |
+| `sonar.branch.target` | Name of the branch where you intend to merge your short-lived branch at the end of its life. If left blank, this defaults to the master branch. It can also be used while initializing a long-lived branch to sync the issues from a branch other than the Main Branch. |
+
+### Git History
+
+By default, TravisCI only fetches the last 50 git commits. You must use `git fetch --unshallow` to get the full history. If you don't, new issues may not be assigned to the correct developer.
+
+### Configuring the Branch type
+
+A regular expression is used to determine whether a branch is treated as long-lived or short-lived. By default, branches that have names starting with either "branch" or "release" will be treated as long-lived.
+
+This can be updated globally in **Configuration > General > Detection** of long-lived branches or at project's level in the **Admininstration > Branches**.
+
+Once a branch type has been set, it cannot be changed. Explicitly, you cannot transform a long-lived to short-lived branch, or vice-versa.
+
+## See also
+* [Short-lived Branches](/branches/short-lived-branches/)
+* [Long-lived Branches](/branches/long-lived-branches/)
+* [Frequently Asked Questions](/branches/branches-faq/)
index 8a937009f643603e078847a849b65ae7087af260..53e27a263b65dc8f6e6596a2253151d901c0d9a5 100644 (file)
@@ -1,5 +1,6 @@
 ---
 title: Short-lived Branches
+url: /branches/short-lived-branches/
 ---
 
 <!-- sonarqube -->
diff --git a/server/sonar-docs/src/pages/custom-measures.md b/server/sonar-docs/src/pages/custom-measures.md
deleted file mode 100644 (file)
index 2c3979b..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
----
-title: Custom Measures
-scope: sonarqube
----
-
-SonarQube collects a maximum of measures in an automated manner but there are some measures for which this is not possible, such as when: the information is not available for collection, the measure is computed by a human, and so on. Whatever the reason, SonarQube provides a service to inject those measures manually and allow you to benefit from other services: the Manual Measures service. The manual measures entered will be picked during the next analysis of the project and thereafter treated as "normal" measures.
-
-## Managing Custom Metrics
-As with measures that are collected automatically, manual measures are the values collected in each analsis for manual metrics. Therefore, the first thing to do is create the metric you want to save your measure against. In order to do so, log in as a system administrator and go to **[Administration > Configuration > Custom Metrics](/#sonarqube-admin#/admin/custom_metrics)**, where the interface will guide you in creating the Metric you need. 
-
-## Managing Custom Measures
-Custom measures can be entered at project level. To add a measure, sign in as a project administrator, navigate to the desired project and choose **Administration > Custom Measures**, where you will find a table with the latest measure value entered for each metric. 
-
-Values entered in this interface are "Pending", and will not be visible outside this administrative interface until the next analysis. 
-
diff --git a/server/sonar-docs/src/pages/fixing-the-water-leak.md b/server/sonar-docs/src/pages/fixing-the-water-leak.md
deleted file mode 100644 (file)
index 6158b2f..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
----
-title: Fixing the Water Leak
----
-
-## What is the Water Leak
-
-Imagine you come home one day to find a puddle of water on the kitchen floor. As you watch, the puddle slowly gets larger.
-
-Do you reach for the mop? Or do you try to find the source and fix it? The choice is obvious, right? You find the source of the leak!
-
-So why do anything different with code quality? When you analyze an application with <!-- sonarqube -->SonarQube<!-- /sonarqube --><!-- sonarcloud -->SonarCloud<!-- /sonarcloud --> and realize that it has a lot of technical debt, the knee-jerk reaction is generally to start remediating – either that or put together a remediation plan. This is like mopping the floor once a day while ignoring the source of the water.
-
-Typically in this traditional approach, just before release a periodic code quality audit result in findings the developers should act on before releasing. This approach might work in the short term, especially with strong management backing, but it consistently fails in the mid to long run, because:
-
-* The code review comes too late in the process, and no stakeholder is keen to get the problems fixed; everyone wants the new version to ship.
-* Developers typically push back on the recommendations made by an external team that doesn't know the context of the project. And by the way the code under review is obsolete already.
-* There is a clear lack of ownership for code quality with this approach. Who owns quality? No one!
-* What gets reviewed is the entire application before it goes to production and it is obviously not possible to apply the same criteria to all applications. A negotiation will happen for each project, which will drain all credibility from the process
-
-Instead, why not apply the same simple logic you use at home to the way you manage code quality? Fixing the leak means putting the focus on the “new” code, i.e. the code that was added or changed since the last release. Then things get much easier:
-
-* The [Quality Gate](/quality-gates) can be run every day, and passing it is achievable. There are no surprises at release time.
-* It's pretty difficult for developers to push back on problems they introduced the previous day. Instead, they're generally happy to fix the problems while the code is still fresh.
-* There is a clear ownership of code quality
-* The criteria for go/no-go are consistent across applications, and are shared among teams. Indeed new code is new code, regardless of which application it is done in
-* The cost is insignificant because it is part of the development process
-
-As a bonus, the code that gets changed the most has the highest maintainability, and the code that doesn't get changed has the lowest, which makes a lot of sense. Because of the nature of software, and the fact that we keep making changes to it, the debt will naturally be reduced. Where it isn’t is where it doesn't need to be.
-
-## How to do it
-
-<!-- sonarqube -->SonarQube<!-- /sonarqube --><!-- sonarcloud -->SonarCloud<!-- /sonarcloud --> offers two main tools to help you find your leaks:
-
-* New Code metrics show the variance in your measures between the current code and a specific point you choose in its history, typically the `previous_version`
-* New Code is primarily detected based on SCM "blame" data starting from the first analysis within your New Code Period (formerly the "Leak Period"), with fallback mechanisms when needed. See [SCM integration](/analysis/scm-integration) for more details.
-* [Quality Gates](/quality-gates) allow you to set boolean thresholds against which your code is measured. Use them with differential metrics to ensure that your code quality moves in the right direction over time.
diff --git a/server/sonar-docs/src/pages/housekeeping.md b/server/sonar-docs/src/pages/housekeeping.md
deleted file mode 100644 (file)
index e2b3333..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
----
-title: Housekeeping
----
-
-When you run a new analysis of your project, some data that was previously available is cleaned out of the database. For example the source code of the previous analysis, measures at directory and file levels, and so on are automatically removed at the end of a new analysis. Additionally, some old analysis snapshots are also removed.
-
-Why? Well, it's useful to analyze a project frequently to see how its quality evolves. It is also useful to be able to see the trends over weeks, months, years. But when you look back in time, you don't really need the same level of detail as you do for the project's current state. To save space and to improve overall performance, the Database Cleaner deletes some rows in the database. Here is its default configuration:
-
-* For each project:
-  * only one snapshot per day is kept after 1 day. Snapshots marked by an event are not deleted.
-  * only one snapshot per week is kept after 1 month. Snapshots marked by an event are not deleted.
-  * only one snapshot per month is kept after 1 year. Snapshots marked by an event are not deleted.
-  * only snapshots with version events are kept after 2 years. Snapshots without events or with only other event types are deleted.
-  * **all snapshots** older than 5 years are deleted, including snapshots marked by an event. 
-* All closed issues more than 30 days old are deleted
-* History at package/directory level is removed
-
-These settings can be changed at [Administration > General > Database Cleaner](/#sonarqube-admin#/admin/settings).
index cfaf315e74dffa01209e7dc3cff02a12389321e2..240e76889dab6b9c2ee0b16b9c84088ab71eb938 100644 (file)
@@ -1,18 +1,23 @@
 ---
 title: Documentation
+url: /
 ---
+
 <!-- sonarqube -->
+
 [SonarQube](http://www.sonarqube.org/)® software (previously called Sonar) is an open source quality management platform, dedicated to continuously analyze and measure technical quality, from project portfolio to method. If you wish to extend the SonarQube platform with open source plugins, have a look at our [plugin library](https://docs.sonarqube.org/display/PLUG/Plugin+Library).
 
 ## I write code
 
-* [Fixing the Water Leak](/fixing-the-water-leak)
-* [Quality Gates](/quality-gates)
-* [Quality Profiles](/quality-profiles)
-<!-- /sonarqube -->
+* [Fixing the Water Leak](/user-guide/fixing-the-water-leak/)
+* [Quality Gates](/user-guide/quality-gates/)
+* [Quality Profiles](/instance-administration/quality-profiles/)
+  <!-- /sonarqube -->
 
 <!-- sonarcloud -->
+
 SonarCloud is the leading product for Continuous Code Quality online, totally free for open-source projects. It supports all major programming languages, including Java, C#, JavaScript, TypeScript, C/C++ and many more. If your code is closed source, SonarCloud also offers a paid plan to run private analyses.
 
-SonarCloud offers end-to-end integrations for teams leveraging [GitHub](/integrations/github), [VSTS](/integrations/vsts), or [Bitbucket Cloud](/integrations/bitbucketcloud) in their development processes.
+SonarCloud offers end-to-end integrations for teams leveraging [GitHub](/integrations/github/), [VSTS](/integrations/vsts/), or [Bitbucket Cloud](/integrations/bitbucketcloud/) in their development processes.
+
 <!-- /sonarcloud -->
diff --git a/server/sonar-docs/src/pages/instance-administration/custom-measures.md b/server/sonar-docs/src/pages/instance-administration/custom-measures.md
new file mode 100644 (file)
index 0000000..8867f06
--- /dev/null
@@ -0,0 +1,15 @@
+---
+title: Custom Measures
+url: /instance-administration/custom-measures/
+---
+
+SonarQube collects a maximum of measures in an automated manner but there are some measures for which this is not possible, such as when: the information is not available for collection, the measure is computed by a human, and so on. Whatever the reason, SonarQube provides a service to inject those measures manually and allow you to benefit from other services: the Manual Measures service. The manual measures entered will be picked during the next analysis of the project and thereafter treated as "normal" measures.
+
+## Managing Custom Metrics
+As with measures that are collected automatically, manual measures are the values collected in each analsis for manual metrics. Therefore, the first thing to do is create the metric you want to save your measure against. In order to do so, log in as a system administrator and go to **[Administration > Configuration > Custom Metrics](/#sonarqube-admin#/admin/custom_metrics)**, where the interface will guide you in creating the Metric you need. 
+
+## Managing Custom Measures
+Custom measures can be entered at project level. To add a measure, sign in as a project administrator, navigate to the desired project and choose **Administration > Custom Measures**, where you will find a table with the latest measure value entered for each metric. 
+
+Values entered in this interface are "Pending", and will not be visible outside this administrative interface until the next analysis. 
+
diff --git a/server/sonar-docs/src/pages/instance-administration/housekeeping.md b/server/sonar-docs/src/pages/instance-administration/housekeeping.md
new file mode 100644 (file)
index 0000000..bd11f3f
--- /dev/null
@@ -0,0 +1,19 @@
+---
+title: Housekeeping
+url: /instance-administration/housekeeping/
+---
+
+When you run a new analysis of your project, some data that was previously available is cleaned out of the database. For example the source code of the previous analysis, measures at directory and file levels, and so on are automatically removed at the end of a new analysis. Additionally, some old analysis snapshots are also removed.
+
+Why? Well, it's useful to analyze a project frequently to see how its quality evolves. It is also useful to be able to see the trends over weeks, months, years. But when you look back in time, you don't really need the same level of detail as you do for the project's current state. To save space and to improve overall performance, the Database Cleaner deletes some rows in the database. Here is its default configuration:
+
+* For each project:
+  * only one snapshot per day is kept after 1 day. Snapshots marked by an event are not deleted.
+  * only one snapshot per week is kept after 1 month. Snapshots marked by an event are not deleted.
+  * only one snapshot per month is kept after 1 year. Snapshots marked by an event are not deleted.
+  * only snapshots with version events are kept after 2 years. Snapshots without events or with only other event types are deleted.
+  * **all snapshots** older than 5 years are deleted, including snapshots marked by an event. 
+* All closed issues more than 30 days old are deleted
+* History at package/directory level is removed
+
+These settings can be changed at [Administration > General > Database Cleaner](/#sonarqube-admin#/admin/settings).
diff --git a/server/sonar-docs/src/pages/instance-administration/look-and-feel.md b/server/sonar-docs/src/pages/instance-administration/look-and-feel.md
new file mode 100644 (file)
index 0000000..1008e54
--- /dev/null
@@ -0,0 +1,13 @@
+---
+title: Look and Feel
+url: /instance-administration/look-and-feel/
+---
+
+## Home logo
+You can set your own "home" logo in **[Administration > General > Look & Feel](/#sonarqube-admin#/admin/settings)**. Simply provide an image URL and width. Ideally, the width will scale the height to 30 pixels. This logo will be used in both the menu bar and on the About page.
+
+## Content of the "About" page
+You also have the ability to add content to the About page, which anonymous users land on by default: **[Administration > General > Look & Feel](/#sonarqube-admin#/admin/settings)**.
+
+## Gravatar
+Gravatar support is enabled by default, using gravatar.com. You can configure a different server or disable the feature altogether. When enabled, gravatars show up next to most uses of the user name.
diff --git a/server/sonar-docs/src/pages/instance-administration/quality-profiles.md b/server/sonar-docs/src/pages/instance-administration/quality-profiles.md
new file mode 100644 (file)
index 0000000..db513e7
--- /dev/null
@@ -0,0 +1,80 @@
+---
+title: Quality Profiles
+url: /instance-administration/quality-profiles/
+---
+
+## Overview
+
+The Quality Profiles service is central to SonarQube, since it is where you define your requirements by defining sets of **rules** (ex: Methods should not have a Cognitive Complexity greater than 15).
+
+Ideally, all projects will be measured with the same profile for any given language, but that's not always practical. For instance, you may find that:
+
+* The technological implementation differs from one application to another (for example, different coding rules may apply when building threaded or non-threaded Java applications).
+* You want to ensure stronger requirements on some of your applications (internal frameworks for example).
+* Etc.
+
+Which is why you can define as many quality profiles as you wish even though it is recommended to have as few Quality Profiles as possible to ensure consistency across the projects in your company. To manage quality profiles, go to <!-- sonarqube -->[**Quality Profiles**](/#sonarqube#/profiles)<!-- /sonarqube --><!-- sonarcloud -->**Quality Profiles** page of your organization<!-- /sonarcloud -->, where you'll find profiles grouped by language.
+
+Each language plugin comes with a predefined, built-in profile (usually called "Sonar way") so that you can get started very quickly with SonarQube analyses. This is why as soon as you install a new language plugin, at least one quality profile will be available for you. Each language must have a default profile (marked with the Default tag). Projects that are not explicitly associated with a specific profile will be analyzed using the language's default profile.
+
+When starting from a new installation, it's tempting to use Sonar way as your default profile because it contains all the rules that are generally applicable to most projects. But as a best practice, you should create a new profile (you can populate it by copying the contents of Sonar way) and use it instead. Why? First because Sonar way profiles aren't editable, so you won't be able to customize it to your needs. Also, that lets you treat Sonar way as a baseline against which you can track your own profile as you make changes to it (and you will). Plus, Sonar way is typically updated with each new version of the plugin to add rules and sometimes adjust rule severities. Any profile that inherits from the built-in Sonar Way will de-facto be automatically updated at the same time.
+
+## How do I...
+
+### Delegate the management of Quality Profiles to someone else?
+
+By default, only users with the "Administer Quality Profiles" permission can edit Quality Profiles. But in large organizations, it may not be desirable to grant permissions to change all the Quality Profiles without distinction. That's why you can also grant users/groups the permission to edit an individual Quality Profile so that, for instance, the management of the Swift profile can be delegated to a group of Swift experts, and the same for COBOL, ...
+
+This delegation of permission can only be performed by someone who already has the "Administer Quality Profiles" permission or individual edit rights on the profile to which additional permissions should be granted. The interface to grant individual permissions is available on the profile detail page.
+
+### Copy the rules from one profile to another?
+
+Many times people want to work from a profile that's based on a built-in profile without actually using the built-in profile. The easiest thing to do in this case is to go to the original profile, we'll call it _Source_, in **Quality Profiles**. From there, click through on the total number of rules in _Source_ to land on the **Rules** page at a pre-narrowed search of _Source_'s rules. Use **Bulk Activate** to turn Source's rules on in your target profile.
+
+### Know what's changed in a profile?
+
+When SonarQube notices that an analysis was performed with a profile that is different in some way from the previous analysis, a Quality Profile event is added to the project's event log. To see the changes in a profile, navigate to the profile (**Quality Profiles > [ Profile Name ]**), and choose **Changelog**. This may help you understand how profile changes impact the issues raised in an analysis.
+
+Additionally, users with Quality Profile administration privileges are notified by email each time a built-in profile (one that is provided directly by an analyzer) is updated. These updates can only be caused by analyzer updates.
+
+### Copy a profile from one SonarQube instance to another?
+
+Use the **Back up** feature on the source instance to export the profile to an XML file. Use the **Restore Profile** feature on the target instance to import the file. Note that some [limitations](https://jira.sonarsource.com/browse/SONAR-5366) on this feature exist.
+
+![Restore Quality Profile](/images/restore-quality-profile.jpeg)
+
+### Apply a core set of rules plus additional rules to a project?
+
+Let's say your company has a minimum set of coding rules that all teams must follow, but you want to add rules that are specific to the in use technology in your project. Those rules are good for your team, but irrelevant or even misleading for others. This situation calls for inheritance. Set up a base profile, we'll call it _Root_ with your core set of rules. Then create a child profile, we'll call it _Sprout_. Once it's created, you can **Change parent** to inherit from _Root_, then add your missing rules.
+
+### Make sure my non-default profile is used on a project?
+
+One profile for each language is marked the default. Barring any other intervention, all projects that use that language will be analyzed with that profile. To have a project analyzed by a non-default profile instead, start from **Quality Profiles**, and click through on your target profile, then use the Projects part of the interface to manage which projects are explicitly assigned to the profile.
+
+### Make sure I've got all the relevant new rules in my profile?
+
+Each time a language plugin update is released, new rules are added, but they won't appear automatically in your profile unless you're using a built-in profile such as _Sonar way_.
+
+If you're not using a built-in profile, you can compare your profile to the built-in profile to see what new on-by-default rules you're missing.
+
+Another option is to go to the **Rules** space, and use the **Available Since** search facet to see what rules have been added to the platform since the day you upgraded the relevant plugin.
+
+And finally, the profile interface itself will help you be aware of rules added in a new plugin version in the **Latest New Rules** section on the right of the interface.
+
+### Compare two profiles?
+
+Starting from the **Quality Profiles** page, click through on one of the profiles you'd like to compare, then use the **Actions > Compare** interface to select the second profile and see the differences.
+
+### Make sure I don't have any deprecated rules in my profile?
+
+The **Deprecated Rules** section of the rules interface itself is your first warning that a profile contains deprecated rules. This pink-background section gives the total number of instances of deprecated rules that are currently active in profiles, and a breakdown of deprecated count per profile. A click-through here takes you to the **Rules** page to edit the profile in question.
+
+Alternately, you can perform a **Rules** search for the rules in a profile (either manually or by clicking-through from **Quality Profiles** page) and use the **Status** rule search facet to narrow the list to the ones that need attention.
+
+## Security
+
+The Quality Profiles service can be accessed by any user (even anonymous users). All users can view every aspect of a profile. That means anyone can see which rules are included in a profile, and which ones have been left out, see how a profile has changed over time, and compare the rules in any two profiles.
+
+To make rule profile changes (create, edit or delete) users must be granted the **Administer Quality Profiles and Gates** permission.
+
+A **project administrator** can choose which profiles his project is associated with. See Project Settings for more.
diff --git a/server/sonar-docs/src/pages/integrations/bitbucketcloud.md b/server/sonar-docs/src/pages/integrations/bitbucketcloud.md
deleted file mode 100644 (file)
index d547c0d..0000000
+++ /dev/null
@@ -1,79 +0,0 @@
----
-title: Integration with Bitbucket Cloud
-scope: sonarcloud
----
-
-## Authentication
-
-You can connect to SonarCloud using your Bitbucket Cloud account. On the [login page](/#sonarcloud#/sessions/new), just click on the "Log in with Bitbucket" button.
-
-## Install SonarCloud add-on for Bitbucket Cloud
-
-Our Bitbucket Cloud application allows users to automate the SonarCloud analysis with Pipelines. It also allows users to view their SonarCloud metrics directly on Bitbucket Cloud via our Code Quality widget and the decoration of pull-requests.
-
-In Bitbucket Cloud, go to your team's "Settings > Find integrations" page, search for "SonarCloud" in the "Code Quality" category and click on "Add" to install the application.
-
-## Analyzing with Pipelines
-
-SonarCloud integrates with Bitbucket Pipelines to make it easier to trigger analyses. Follow the steps:
-
-1.  On SonarCloud, once your project is created, follow the tutorial on the dashboard of the project. You can copy-paste the command line displayed at the end.
-
-2.  On Bitbucket Cloud, go to the "Settings > Pipelines > Environment variables" page of your team, and add a new SONAR_TOKEN variable that contains the value of the SonarCloud token (something like `9ad01c85336b265406fa6554a9a681a4b281135f`) which you created during the tutorial (and which is available inside the command line that you copy-pasted). **Make sure that you click on the "Lock" icon to encrypt and hide this token.**
-
-3.  Inside the `bitbucket-pipelines.yml` file of your repository, copy the command line provided by the tutorial and replace the actual token by its variable name. For example, for a Java Maven-based project, you should have something like:
-
-```
-script:
-  -mvn sonar:sonar -Dsonar.host.url=https://sonarcloud.io -Dsonar.organization=my-team-org -Dsonar.login=$SONAR_TOKEN
-```
-
-When this change on `bitbucket-pipelines.yml` is committed and pushed, Pipelines should automatically run a new build and therefore trigger the analysis of the repository. Shortly after, your project will appear on SonarCloud in your organization.
-
-4.  Once you see your project in SonarCloud, go to the Bitbucket Cloud "Settings > SonarCloud" page. If the dropdown is empty, find your project in the select box to link it.
-
-From now on, everytime Pipelines triggers a build, SonarCloud will:
-
-* Analyze every new branch that contains the change on the `bitbucket-pipelines.yml` file.
-* Analyze and decorate every pull request based on such a branch.
-
-## Quality widget
-
-SonarCloud provides a widget that shows the current quality metrics of your project directly on the repository's Overview page on Bitbucket Cloud.
-
-If you have configured the analysis with Pipelines as described above, you will see this widget on the "Overview" page of your repository.
-
-If you haven't configured the analysis with Pipelines (maybe because you are using another CI tool), follow these few steps:
-
-1.  Go to the repository where you want to display the widget. On the "Overview" page, you should see an empty SonarCloud widget. Click on the link inside the empty widget or just go directly to your repository's "Settings > SonarCloud Settings".
-
-2.  If you're not already logged in on SonarCloud, do it by using the options provided there. You can log in with Bitbucket Cloud by clicking on the blue button, or click on "More options" to log in with GitHub or VSTS.
-
-3.  Once you're logged in on SonarCloud, you should see a dropdown allowing you to choose one of the projects you administer. Just choose the one you want to link to this Bitbucket repository and click "Save".
-
-4.  You can now go back to your repository's Overview page on Bitbucket and see the widget with all current SonarCloud metrics displayed.
-
-If you want to hide this widget (e.g. because your repository is not analyzed on SonarCloud), you can go to the "Settings > SonarCloud" page of your repository and check "Hide repository overview widget".
-
-## FAQ
-
-**Do you have a sample project on Bitbucket Cloud?**
-For the time being, you can take a look at this very simple JS project: [Sample project analysed on SonarCloud](https://bitbucket.org/bellingard/fab)
-
-**Pipelines can't find sonar-scanner**
-If you want to analyze a non-Java project (JS, TS, PHP, Python, Go, ...), you will need to download and install the [Scanner CLI](https://redirect.sonarsource.com/doc/install-configure-scanner.html) during the execution of your build prior to the actual code scan. You have two options:
-
-* You can download it (with curl for instance) from the links available on the documentation page and unpack it (preferably in a cached folder for later reuse).
-* On Node environments, you can rely on a [community NPM module](https://www.npmjs.com/package/sonarqube-scanner) to install it globally and therefore make it available in the PATH.
-
-**I don't see the any quality information whereas I configured everything**
-Make sure that your browser is not using some extensions like AdBlocks. They tend to break the integration of third-party applications in BitBucket Cloud.
-
-## Upcoming features and improvements
-
-There are various areas in which you can expect new features and improvements:
-
-* Tighter integration with Pipelines (less parameters to pass on the CLI, availability of the scanner, ...)
-* Pull request decoration with inline comments to show the issues within the PR
-* Better and easier team onboarding
-* Automatic analysis (i.e. no need to configure anything from Pipelines)
diff --git a/server/sonar-docs/src/pages/integrations/github.md b/server/sonar-docs/src/pages/integrations/github.md
deleted file mode 100644 (file)
index 1203429..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
----
-title: Integration with GitHub
-scope: sonarcloud
----
-
-## Authentication
-
-You can connect to SonarCloud using your GitHub account. On the [login page](/#sonarcloud#/sessions/new), just click on the "Log in with GitHub" button.
-
-## Trigger analyses
-
-SonarCloud currently does not trigger analyses automatically. It's up to you to launch them inside your
-existing CI scripts. Please follow the [tutorial](/#sonarcloud#/onboarding) to get started.
-
-### Using Travis CI?
-
-If you are using Travis CI, the SonarCloud Travis Add-on will make it easier to activate analyses: 
-* Read the [guide to integrate with Travis CI](https://docs.travis-ci.com/user/sonarcloud/)
-* Check out the [various sample projects](https://github.com/SonarSource/sonarcloud_examples) (Java, TypeScript, C/C++, Go, ... etc) that are analyzed on SonarCloud on a frequent basis
-
-## Activating pull request decoration
-
-To have your pull requests decorated by SonarCloud in GitHub, you need to [install the SonarCloud application](https://github.com/apps/sonarcloud) on your GitHub organization(s).
-
-Once installed, there is nothing more to do if you are using the Travis Add-on. In any other case, you will need
-to pass the following properties in your script during the analysis:
-
-```
-sonar.pullrequest.base=master
-sonar.pullrequest.branch=feature/my-new-feature
-sonar.pullrequest.key=5
-sonar.pullrequest.provider=GitHub
-sonar.pullrequest.github.repository=my-company/my-repo
-```
diff --git a/server/sonar-docs/src/pages/integrations/index.md b/server/sonar-docs/src/pages/integrations/index.md
deleted file mode 100644 (file)
index 01fcb49..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
----
-title: Integrations
-scope: sonarcloud
----
-
-SonarCloud integrates with the following cloud services to help developers get the most out of their code:
-
-* [Integration with GitHub](/integrations/github)
-* [Integration with Bitbucket Cloud](/integrations/bitbucketcloud)
-* [Integration with VSTS](/integrations/vsts)
diff --git a/server/sonar-docs/src/pages/integrations/vsts.md b/server/sonar-docs/src/pages/integrations/vsts.md
deleted file mode 100644 (file)
index de94cd7..0000000
+++ /dev/null
@@ -1,38 +0,0 @@
----
-title: Integration with VSTS
-scope: sonarcloud
----
-
-
-## Authentication
-
-You can connect to SonarCloud using your VSTS account. On the [login page](/#sonarcloud#/sessions/new), just click on the "Log in with VSTS" button.
-
-[[warning]]
-| Only work and school VSTS accounts are authorized to login on SonarCloud.
-
-## Install the SonarCloud VSTS extension
-
-The SonarCloud VSTS extension brings everything you need to have your projects analyzed on SonarCloud 
-very quickly:
-* Integration with the Build definitions to easily trigger the analysis
-* Pull request decoration to get quick feedback on the code changes
-* Widget to have the overview quality of your projects inside VSTS dashboards
-
-Install [SonarCloud extension for VSTS](https://marketplace.visualstudio.com/items?itemName=SonarSource.sonarcloud)by clicking on the "Get it free" button.
-
-Then follow the comprehensive [Microsoft lab on how to integrate VSTS with SonarCloud](https://aka.ms/sonarcloudlab).
-
-## Quality Gate Status widget 
-
-You can monitor the Quality Gate status of your projects directly in your VSTS dashboard. Follow these simple steps to configure your widget:
-
-1. Once the VSTS extension is installed and your project has been successfully analyzed, go to one of your VSTS dashboards (or create one). Click on the pen icon in the bottom right corner of the screen, and then on the "+" icon to add a widget. 
-
-2. In the list of widgets, select the "Code Quality" one and then click on the "Add" button. An empty widget is added to your dashboard. 
-
-3. You can then click on the widget's cogwheel icon to configure it.
-
-    * **For public projects:** you can simply select your project from the dropdown. A searchbar inside the dropdown will help you find it easily. Just select it and click on the "Save" button.
-
-    * **For private projects:** you'll have to log in using the links provided under the dropdown. Once logged in, your private projects will appear in the dropdown. Select the one you are interested in, and click on "Save".
diff --git a/server/sonar-docs/src/pages/keyboard-shortcuts.md b/server/sonar-docs/src/pages/keyboard-shortcuts.md
deleted file mode 100644 (file)
index 178ccff..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
----
-title: Keyboard Shortcuts
-order: 99
----
-
-## Global
-
-| Shortcut | Action          |
-| -------- | --------------- |
-| `s`      | open search bar |
-| `?`      | open help       |
-
-## Issues Page
-
-| Shortcut         | Action                                        |
-| ---------------- | --------------------------------------------- |
-| `↑` `↓`          | navigate between issues                       |
-| `→`              | go from the list of issues to the source code |
-| `←`              | return back to the list                       |
-| `alt` + `↑` `↓`  | to navigate issue locations                   |
-| `alt` + `←` `→`  | to switch flows                               |
-| `f`              | do an issue transition                        |
-| `a`              | assign issue                                  |
-| `m`              | assign issue to the current user              |
-| `i`              | change severity of issue                      |
-| `c`              | comment issue                                 |
-| `ctrl` + `enter` | submit comment                                |
-| `t`              | change tags of issue                          |
-
-## Measures Page
-
-| Shortcut | Action                                        |
-| -------- | --------------------------------------------- |
-| `↑` `↓`  | select files                                  |
-| `→`      | open file                                     |
-| `←`      | return back to the list                       |
-| `j` `k`  | switch between files                          |
-
-## Rules Page
-
-| Shortcut | Action                                        |
-| -------- | --------------------------------------------- |
-| `↑` `↓`  | navigate between rules                        |
-| `→`      | go from the list of rules to the rule details |
-| `←`      | return back to the list                       |
diff --git a/server/sonar-docs/src/pages/look-and-feel.md b/server/sonar-docs/src/pages/look-and-feel.md
deleted file mode 100644 (file)
index d74542f..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
----
-title: Look and Feel
-scope: sonarqube
----
-
-## Home logo
-You can set your own "home" logo in **[Administration > General > Look & Feel](/#sonarqube-admin#/admin/settings)**. Simply provide an image URL and width. Ideally, the width will scale the height to 30 pixels. This logo will be used in both the menu bar and on the About page.
-
-## Content of the "About" page
-You also have the ability to add content to the About page, which anonymous users land on by default: **[Administration > General > Look & Feel](/#sonarqube-admin#/admin/settings)**.
-
-## Gravatar
-Gravatar support is enabled by default, using gravatar.com. You can configure a different server or disable the feature altogether. When enabled, gravatars show up next to most uses of the user name.
diff --git a/server/sonar-docs/src/pages/metric-definitions.md b/server/sonar-docs/src/pages/metric-definitions.md
deleted file mode 100644 (file)
index 737b3e6..0000000
+++ /dev/null
@@ -1,336 +0,0 @@
----
-title: Metric Definitions
----
-
-## Table of Contents
-
-   
-## Complexity
-**Complexity** (`complexity`)  
-It is the Cyclomatic Complexity calculated based on the number of paths through the code. Whenever the control flow of a function splits, the complexity counter gets incremented by one. Each function has a minimum complexity of 1. This calculation varies slightly by language because keywords and functionalities do.
-
-[[collapse]]
-| ## Language-specific details
-| Language | Notes
-| ---|---
-| ABAP | The following keywords increase the complexity by one: `AND`, `CATCH`, `CONTINUE`, `DO`, `ELSEIF`, `IF`, `LOOP`, `LOOPAT`, `OR`, `PROVIDE`, `SELECT…ENDSELECT`, `TRY`, `WHEN`, `WHILE`
-| C/C++/Objective-C | The complexity gets incremented by one for: function definitions, `while`, `do while`, `for`, `throw` statements, `switch`, `case`, `default`, `&&` operator, `||` operator, `?` ternary operator, `catch`, `break`, `continue`, `goto`.
-| COBOL | The following commands increase the complexity by one (except when they are used in a copybook): `ALSO`, `ALTER`, `AND`, `DEPENDING`, `END_OF_PAGE`, `ENTRY`, `EOP`, `EXCEPTION`, `EXIT`, `GOBACK`, `CONTINUE`, `IF`, `INVALID`, `OR`, `OVERFLOW`, `SIZE`, `STOP`, `TIMES`, `UNTIL`, `USE`, `VARYING`, `WHEN`, `EXEC CICS HANDLE`, `EXEC CICS LINK`, `EXEC CICS XCTL`, `EXEC CICS RETURN`
-| Java | Keywords incrementing the complexity: `if`, `for`, `while`, `case`, `catch`, `throw`, `&&`, `||`, `?`
-| JavaScript, PHP | Complexity is incremented by one for each: function (i.e non-abstract and non-anonymous constructors, functions, procedures or methods), `if`, short-circuit (AKA lazy) logical conjunction (`&&`), short-circuit (AKA lazy) logical disjunction (`||`), ternary conditional expressions, loop, `case` clause of a `switch` statement, `throw` and `catch` statement, `go to` statement (only for PHP)
-| PL/I | The following keywords increase the complexity by one: `PROC`, `PROCEDURE`, `GOTO`, `GO TO`, `DO`, `IF`, `WHEN`, `|`, `!`, `|=`, `!=`, `&`, `&=`
-| PL/SQL | The complexity gets incremented by one for: the main PL/SQL anonymous block (not inner ones), create procedure, create trigger, procedure_definition, basic loop statement, when_clause_statement (the “when” of simple_case_statement and searched_case_statement), continue_statement, cursor_for_loop_statement, continue_exit_when_clause (The “WHEN” part of the continue and exit statements), exception_handler (every individual “WHEN”), exit_statement, for_loop_statement, forall_statement, if_statement, elsif_clause, raise_statement, return_statement, while_loop_statement, and_expression (“and” reserved word used within PL/SQL expressions), or_expression (“or” reserved word used within PL/SQL expressions), when_clause_expression (the “when” of simple_case_expression and searched_case_expression)
-| VB.NET | The complexity gets incremented by one for: method or constructor declaration (Sub, Function), `AndAlso`, `Case`, `Continue`, `End`, `Error`, `Exit`, `If`, `Loop`, `On Error`, `GoTo`, `OrElse`, `Resume`, `Stop`, `Throw`, `Try`.
-
-**Cognitive Complexity** (`cognitive_complexity`)  
-How hard it is to understand the code's control flow. See [the Cognitive Complexity White Paper](https://www.sonarsource.com/resources/white-papers/cognitive-complexity.html) for a complete description of the mathematical model applied to compute this measure.
-
----
-## Duplications
-**Duplicated blocks** (`duplicated_blocks`)  
-Number of duplicated blocks of lines.
-
-[[collapse]]
-| ## Language-specific details
-| For a block of code to be considered as duplicated:
-|
-| Non-Java projects:  
-| * There should be at least 100 successive and duplicated tokens.
-| * Those tokens should be spread at least on:
-| * 30 lines of code for COBOL
-| * 20 lines of code for ABAP
-| * 10 lines of code for other languages
-|
-|Java projects:  
-| There should be at least 10 successive and duplicated statements whatever the number of tokens and lines. Differences in indentation and in string literals are ignored while detecting duplications.
-**Duplicated files** (`duplicated_files`)  
-Number of files involved in duplications.
-
-**Duplicated lines** (`duplicated_lines`)  
-Number of lines involved in duplications.
-
-**Duplicated lines (%)** (`duplicated_lines_density`)  
-= `duplicated_lines` / `lines` * 100
-
----
-## Issues
-**New issues** (`new_violations`)  
-Number of issues raised for the first time in the New Code period.
-
-**New xxx issues** (`new_xxx_violations`)  
-Number of issues of the specified severity raised for the first time in the New Code period, where xxx is one of: `blocker`, `critical`, `major`, `minor`, `info`.
-
-**Issues** (`violations`)  
-Total count of issues in all states.
-
-**xxx issues** (`xxx_issues`)  
-Total count of issues of the specified severity, where xxx is one of: `blocker`, `critical`, `major`, `minor`, `info`.
-
-**False positive issues** (`false_positive_issues`)  
-Total count of issues marked False Positive
-
-**Open issues** (`open_issues`)  
-Total count of issues in the Open state.
-
-**Confirmed issues** (`confirmed_issues`)  
-Total count of issues in the Confirmed state.
-
-**Reopened issues** (`reopened_issues`)  
-Total count of issues in the Reopened state
-
----
-## Maintainability
-**Code Smells** (`code_smells`)   
-Total count of Code Smell issues.
-
-**New Code Smells** (`new_code_smells`)  
-Total count of Code Smell issues raised for the first time in the New Code period.
-
-**Maintainability Rating** (`sqale_rating`)  
-(Formerly the SQALE rating.)
-Rating given to your project related to the value of your Technical Debt Ratio. The default Maintainability Rating grid is:
-
-A=0-0.05, B=0.06-0.1, C=0.11-0.20, D=0.21-0.5, E=0.51-1
-
-The Maintainability Rating scale can be alternately stated by saying that if the outstanding remediation cost is:
-
-* <=5% of the time that has already gone into the application, the rating is A
-* between 6 to 10% the rating is a B
-* between 11 to 20% the rating is a C
-* between 21 to 50% the rating is a D
-* anything over 50% is an E
-
-**Technical Debt** (`sqale_index`)  
-Effort to fix all Code Smells. The measure is stored in minutes in the database. An 8-hour day is assumed when values are shown in days.
-
-**Technical Debt on New Code** (`new_technical_debt`)  
-Effort to fix all Code Smells raised for the first time in the New Code period.
-
-**Technical Debt Ratio** (`sqale_debt_ratio`)  
-Ratio between the cost to develop the software and the cost to fix it. The Technical Debt Ratio formula is:  
-       `Remediation cost / Development cost`  
-Which can be restated as:  
-       `Remediation cost / (Cost to develop 1 line of code * Number of lines of code)`  
-The value of the cost to develop a line of code is 0.06 days.
-
-**Technical Debt Ratio on New Code** (`new_sqale_debt_ratio`)  
-Ratio between the cost to develop the code changed in the New Code period and the cost of the issues linked to it.
-
----
-## Quality Gates
-**Quality Gate Status** (`alert_status`)  
-State of the Quality Gate associated to your Project. Possible values are : `ERROR`, `WARN`, `OK`
-
-**Quality Gate Details** (`quality_gate_details`)  
-For all the conditions of your Quality Gate, you know which condition is failing and which is not.
-
----
-## Reliability
-**Bugs** (`bugs`)  
-Number of bug issues.
-
-**New Bugs** (`new_bugs`)  
-Number of new bug issues.
-
-**Reliability Rating** (`reliability_rating`)  
-A = 0 Bugs  
-B = at least 1 Minor Bug  
-C = at least 1 Major Bug  
-D = at least 1 Critical Bug  
-E = at least 1 Blocker Bug  
-
-**Reliability remediation effort** (`reliability_remediation_effort`)  
-Effort to fix all bug issues. The measure is stored in minutes in the DB. An 8-hour day is assumed when values are shown in days.
-
-**Reliability remediation effort on new code** (`new_reliability_remediation_effort`)  
-Same as _Reliability remediation effort_ but on the code changed in the New Code period.
-
----
-## Security
-**Vulnerabilities** (`vulnerabilities`)  
-Number of vulnerability issues.
-
-**New Vulnerabilities** (`new_vulnerabilities`)  
-Number of new vulnerability issues.
-
-**Security Rating** (`security_rating`)  
-A = 0 Vulnerabilities  
-B = at least 1 Minor Vulnerability  
-C = at least 1 Major Vulnerability  
-D = at least 1 Critical Vulnerability  
-E = at least 1 Blocker Vulnerability  
-
-**Security remediation effort** (`security_remediation_effort`)  
-Effort to fix all vulnerability issues. The measure is stored in minutes in the DB. An 8-hour day is assumed when values are shown in days.
-
-**Security remedation effort on new code** (`new_security_remediation_effort`)  
-Same as _Security remediation effort_ but on the code changed in the New Code period.
-
----
-## Size
-**Classes** (`classes`)  
-Number of classes (including nested classes, interfaces, enums and annotations).
-
-**Comment lines** (`comment_lines`)  
-Number of lines containing either comment or commented-out code.
-
-Non-significant comment lines (empty comment lines, comment lines containing only special characters, etc.) do not increase the number of comment lines.
-
-The following piece of code contains 9 comment lines:
-```
-/**                                    +0 => empty comment line
- *                                     +0 => empty comment line
- * This is my documentation            +1 => significant comment
- * although I don't                    +1 => significant comment
- * have much                           +1 => significant comment
- * to say                              +1 => significant comment
- *                                     +0 => empty comment line
- ***************************           +0 => non-significant comment
- *                                     +0 => empty comment line
- * blabla...                           +1 => significant comment
- */                                    +0 => empty comment line
-  
-/**                                    +0 => empty comment line
- * public String foo() {               +1 => commented-out code
- *   System.out.println(message);      +1 => commented-out code
- *   return message;                   +1 => commented-out code
- * }                                   +1 => commented-out code
- */                                    +0 => empty comment line
- ```
-[[collapse]]
-| ## Language-specific details
-| Language | Note
-| ---|---
-| COBOL | Lines containing the following instructions are counted both as comments and lines of code: `AUTHOR`, `INSTALLATION`, `DATE-COMPILED`, `DATE-WRITTEN`, `SECURITY`.
-| Java | File headers are not counted as comment lines (becuase they usually define the license).
-
-**Comments (%)** (`comment_lines_density`)  
-Density of comment lines = Comment lines / (Lines of code + Comment lines) * 100
-
-With such a formula:
-* 50% means that the number of lines of code equals the number of comment lines  
-* 100% means that the file only contains comment lines  
-
-**Directories** (`directories`)  
-Number of directories.
-
-**Files** (`files`)  
-Number of files.
-
-**Lines** (`lines`)  
-Number of physical lines (number of carriage returns).
-
-**Lines of code** (`ncloc`)  
-Number of physical lines that contain at least one character which is neither a whitespace nor a tabulation nor part of a comment.
-[[collapse]]
-| ## Language-specific details
-| Language | Note
-| --- | ---
-| COBOL | Generated lines of code and pre-processing instructions (`SKIP1`, `SKIP2`, `SKIP3`, `COPY`, `EJECT`, `REPLACE`) are not counted as lines of code.
-
-**Lines of code per language** (`ncloc_language_distribution`)  
-Non Commenting Lines of Code Distributed By Language
-
-**Functions** (`functions`)  
-Number of functions. Depending on the language, a function is either a function or a method or a paragraph.
-[[collapse]]
-| ## Language-specific details
-| Language | Note
-| ---|---
-| COBOL | It is the number of paragraphs.
-| Java | Methods in anonymous classes are ignored.
-| VB.NET | Accesors are not considered to be methods.
-
-**Projects** (`projects`)  
-Number of projects in a Portfolio.
-
-**Statements** (`statements`)  
-Number of statements.
-
----
-## Tests
-**Condition coverage** (`branch_coverage`)  
-On each line of code containing some boolean expressions, the condition coverage simply answers the following question: 'Has each boolean expression been evaluated both to true and false?'. This is the density of possible conditions in flow control structures that have been followed during unit tests execution.
-
-`Condition coverage = (CT + CF) / (2*B)`   
-where  
-* CT = conditions that have been evaluated to 'true' at least once
-* CF = conditions that have been evaluated to 'false' at least once
-* B = total number of conditions
-
-**Condition coverage on new code** (`new_branch_coverage`)  
-Identical to Condition coverage but restricted to new / updated source code.
-
-**Condition coverage hits** (`branch_coverage_hits_data`)  
-List of covered conditions.
-
-**Conditions by line** (`conditions_by_line`)  
-Number of conditions by line.
-
-**Covered conditions by line** (`covered_conditions_by_line`)  
-Number of covered conditions by line.
-
-**Coverage** (`coverage`)  
-It is a mix of Line coverage and Condition coverage. Its goal is to provide an even more accurate answer to the following question: How much of the source code has been covered by the unit tests?
-
-`Coverage = (CT + CF + LC)/(2*B + EL)`  
-where  
-* CT = conditions that have been evaluated to 'true' at least once
-* CF = conditions that have been evaluated to 'false' at least once
-* LC = covered lines = lines_to_cover - uncovered_lines
-* B = total number of conditions
-* EL = total number of executable lines (`lines_to_cover`)
-
-**Coverage on new code** (`new_coverage`)  
-Identical to Coverage but restricted to new / updated source code.
-
-**Line coverage (`line_coverage`)  
-On a given line of code, Line coverage simply answers the following question: Has this line of code been executed during the execution of the unit tests?. It is the density of covered lines by unit tests:
-
-`Line coverage = LC / EL`  
-where
-* LC = covered lines (`lines_to_cover` - `uncovered_lines`)
-* EL = total number of executable lines (`lines_to_cover`)
-
-**Line coverage on new code** (`new_line_coverage`)  
-Identical to Line coverage but restricted to new / updated source code.
-
-**Line coverage hits** (`coverage_line_hits_data`)  
-List of covered lines.
-
-**Lines to cover** (`lines_to_cover`)  
-Number of lines of code which could be covered by unit tests (for example, blank lines or full comments lines are not considered as lines to cover).
-
-**Lines to cover on new code** (`new_lines_to_cover`)  
-Identical to Lines to cover but restricted to new / updated source code.
-
-**Skipped unit tests** (`skipped_tests`)  
-Number of skipped unit tests.
-
-**Uncovered conditions** (`uncovered_conditions`)  
-Number of conditions which are not covered by unit tests.
-
-**Uncovered conditions on new code** (`new_uncovered_conditions`)  
-Identical to Uncovered conditions but restricted to new / updated source code.
-
-**Uncovered lines** (`uncovered_lines`)  
-Number of lines of code which are not covered by unit tests.
-
-**Uncovered lines on new code** (`new_uncovered_lines`)  
-Identical to Uncovered lines but restricted to new / updated source code.
-
-**Unit tests** (`tests`)  
-Number of unit tests.
-
-**Unit tests duration** (`test_execution_time`)  
-Time required to execute all the unit tests.
-
-**Unit test errors** (`test_errors`)  
-Number of unit tests that have failed.
-
-**Unit test failures** (`test_failures`)  
-Number of unit tests that have failed with an unexpected exception.
-
-**Unit test success density (%)** (`test_success_density`)  
-`Test success density = (Unit tests - (Unit test errors + Unit test failures)) / Unit tests * 100`
diff --git a/server/sonar-docs/src/pages/organizations/index.md b/server/sonar-docs/src/pages/organizations/index.md
deleted file mode 100644 (file)
index 7ddec3f..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
----
-title: Organizations
-scope: sonarcloud
----
-
-## Overview
-
-An organization is a space where a team or a whole company can collaborate across many projects.
-
-An organization consists of:
-* Projects, on which users collaborate
-* [Members](/organizations/manage-team), who can have different persmissions on the projects
-* [Quality Profiles](/quality-profiles) and [Quality Gates](/quality-gates), which can be customized and shared accross projects
-
-There are 2 kind of organizations:
-* **Personal organizations**. Each account has a personal organization linked to it. This is typically where open-source developers host their personal projects. It is not possible to delete this kind of organization.
-* **Standard organization**. This is the kind of organization that users want to create for their companies or for their open-source communities. As soon as you want to collaborate, it is a good idea to create such an organization.
-
-Organizations can be on:
-* **Free plan**. This is the default plan. Every project in an organization on the free plan is public.
-* **Paid plan**. This plan unlocks the ability to have private projects. Go to the "Billing" page of your organization to upgrade it to the paid plan.
-
-Depending on which plan the organization is in, its [visibility](/organizations/organization-visibility) will change.
diff --git a/server/sonar-docs/src/pages/organizations/manage-team.md b/server/sonar-docs/src/pages/organizations/manage-team.md
deleted file mode 100644 (file)
index 53d650f..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
----
-title: Manage a Team
-scope: sonarcloud
----
-
-Members can collaborate on the projects in the organizations to which they belong. Depending on their permisssions within the organization, members can:
-* Analyse projects
-* Manage project settings (permissions, visibility, quality profiles, ...)
-* Update issues
-* Manage quality gates and quality profiles
-* Administer the organization itself
-
-## Adding Members
-
-Adding members is done on the "Members" page of the organization, and this can be done only by an administrator of 
-the organization.
-
-Adding a user as a member is possible only if that user has already signed up on SonarCloud. If the user never authenticated to
-the system, the administrator will simply not be able to find the user in the search modal window.
-
-## Granting permissions
-
-Once added, a user can be granted permissions to perform various operations in the organization. It is up to the 
-administrator who added the user to make sure that she gets the relevant permissions.
-
-Organization admins will prefer to create groups to manage permissions, and add new users to those
-groups through the "Members" page. With such an approach, they won't have to manage individal permissions at
-project level for instance.
-
-## Future evolutions
-
-Future versions of SonarCloud will make this onboarding process easier thanks to better integrations with GitHub, 
-Bitbucket Cloud and VSTS: users won't have to sign up prior to joining an organization, and their permissions will 
-be retrieved at best from the ones existing on the other systems.
diff --git a/server/sonar-docs/src/pages/organizations/organization-visibility.md b/server/sonar-docs/src/pages/organizations/organization-visibility.md
deleted file mode 100644 (file)
index c9e746d..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
----
-title: Organization Visibility
-scope: sonarcloud
----
-
-## Free plan organization
-
-Free plan organizations are public. This means that almost everything is visible to any user - even anonymous ones:
-
-* Projects
-* Issues
-* Quality Profiles
-* Quality Gates
-* Rules
-
-The following pages are restricted:
-
-* Members: to members of the organization
-* Administration pages: to administrators of the organization
-
-## Paid plan organization
-
-Paid plan organizations are private. This means that nothing is visible to non-members of the organization. In other words, you need to be a member of the organization to see:
-
-* Projects - which are private by default
-* Issues
-* Quality Profiles
-* Quality Gates
-* Rules
-* Members
-
-The administration pages are obviously also restricted to administrators of the organization.
-
-### Want to make one project public?
-
-If you are on a paid plan organization but want to make a project public (for instance because you are developing an open-source library), this is possible. You will have to manually make the project public in its "Administration > Permissions" page. Once done, you will notice the "Public" badge on the project.
-
-As soon as you have one public project, the following pages will become visible to any user:
-
-* Projects
-* Issues
-* Rules
-
-"Quality Profiles" and "Quality Gates" pages will remain restricted to members only - since you might not want to unveil some information used by your private projects.
diff --git a/server/sonar-docs/src/pages/privacy.md b/server/sonar-docs/src/pages/privacy.md
deleted file mode 100644 (file)
index 3e122cf..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
----
-title: Privacy
-scope: sonarcloud
----
-
-The privacy policy specifies how data collected on this website is used. Thank you for visiting our website and your interest in our services and products. As the protection of your personal data is an important concern for us, we will explain below what information we collect during your visit to our website, as they are processed and whether or how these may be used.
-
-## PERSONAL DATA
-
-Personal information is information about personal or material circumstances of an identified or identifiable natural person. This includes information such as your first and last name, your postal or residential address, telephone numbers and date of birth. Information that can not be directly related to your real identity – such as your favorite websites or the number of users of a page – are not considered as personal data.
-
-## COLLECTION AND PROCESSING OF PERSONAL DATA
-
-As the operator and creator of the website, we do not store personal data itself automatically. If you go to our website, the provider – where the web server is hosted – may temporarily store data for the purpose of system security such as the connection of the computer, the web pages you visit, the date and duration of the visit, data about the used browser software and operating system and the web page from which you visit us. In addition to that, personal information such as your name, address, phone number or e-mail will only be stored, if you have provided this information voluntarily, eg. as part of a registration, a survey, a contest, to carry out an order or contract or an information request.
-
-## USE AND DISCLOSURE OF PERSONAL DATA
-
-Personal data you provided may be used solely for the purpose of technical website administration and to fulfill your wishes and requirements, thus primarily to processing the order with you or to respond to your request. Only if you have previously given your consent or – if stipulated by legal regulations – you entered no objection, we use this data for product surveys and marketing purposes. We don’t share, sell or transfer your personal data to third parties, unless this is necessary for the purpose of the contract or unless you have explicitly consented. For example it may be necessary, that in case of an product order we share your address and order with our suppliers.
-
-## USE OF WEB ANALYSIS SOFTWARE
-
-To improve the structure and the data we offer on our website, we might use open source or proprietary web analysis software. Our evaluations will be based on summary or averaged information amalgamated for the large numbers of people visiting the vebsite. The data provided by won’t be matched with any individual’s data from other sources.
-
-Data collected might include IP, time and duration of the visit, what pages are visited, used browser and add-ons/plugins, search-engines and referrer. While statistic tools might use a “cookie” to distinguish between individual visitors, the collected data doesn’t allow to identify individuals.
-
-## SECURITY
-
-We take all the necessary technical and organisational security measures to protect your personal data from loss and misuse. Your data is stored in a secure operating environment that is not accessible to the public. If you communicate with us via e-mail, please note that the confidentiality of the information is not guaranteed. The contents of e-mails can be intercepted by third parties. In case of doubt we therefore recommend to send confidential information only by snail mail.
-
-## RIGHT OF ACCESS TO PERSONAL DATA
-
-Upon written request you will be informed by us what information we stored about you (such as name or address).
-
-## CONTACT
-
-If you have questions regarding the processing of personal data or in case of requests for information, suggestions or complaints, please [contact us](/#sonarcloud#/about/contact) directly.
diff --git a/server/sonar-docs/src/pages/project-administration/webhooks.md b/server/sonar-docs/src/pages/project-administration/webhooks.md
new file mode 100644 (file)
index 0000000..9605049
--- /dev/null
@@ -0,0 +1,119 @@
+---
+title: Webhooks
+url: /project-administration/webhooks/
+---
+
+Webhooks notify external services when a project analysis is complete. An HTTP POST request including a JSON payload is sent to each URL. URLs may be specified at both the project and global levels. Project-level specification does not replace global-level webhooks. All hooks at both levels are called.
+Plugins
+
+The HTTP(S) call:
+
+* is made regardless of the status of the Background Task
+* includes a JSON document as payload, using the POST method
+* has a content type of "application/json", with UTF-8 encoding
+
+## Configuration
+
+You can configure up to 10 webhooks in in **Administration > Webhooks**.
+
+An additional set of 10 webhooks can be configured at the global level in **Administration > Configuration > Webhooks**.
+
+If configured, all 20 will be executed.
+
+## Delivery and Payload
+
+### Delivery
+
+The Webhook administration console shows the result and timestamp of the most recent delivery of each webhook with the payload available via the list icon. Results and payloads of earlier deliveries are available from the tools menu to the right of each webhook
+
+Response records are purged after 30 days.
+
+The URL must respond within 10 seconds or the delivery is marked as failed.
+
+### Payload
+
+An HTTP header "X-SonarQube-Project" with the project key is sent to allow quick identification of the project involved
+
+The Payload is a JSON document which includes:
+
+* when the analysis was performed: see "analysedAt"
+* the identification of the project analyzed: see "project"
+* each Quality Gate criterion checked and its status: see "qualityGate"
+* the Quality Gate status of the project: see "qualityGate.status"
+* the status and the identifier of the Background Task : see "status" and "taskId"
+* user-specified properties: see "properties"
+
+#### Example
+
+```
+{
+    "analysedAt": "2016-11-18T10:46:28+0100",
+    "project": {
+        "key": "org.sonarqube:example",
+        "name": "Example"
+    },
+    "properties": {
+    },
+    "qualityGate": {
+        "conditions": [
+            {
+                "errorThreshold": "1",
+                "metric": "new_security_rating",
+                "onLeakPeriod": true,
+                "operator": "GREATER_THAN",
+                "status": "OK",
+                "value": "1"
+            },
+            {
+                "errorThreshold": "1",
+                "metric": "new_reliability_rating",
+                "onLeakPeriod": true,
+                "operator": "GREATER_THAN",
+                "status": "OK",
+                "value": "1"
+            },
+            {
+                "errorThreshold": "1",
+                "metric": "new_maintainability_rating",
+                "onLeakPeriod": true,
+                "operator": "GREATER_THAN",
+                "status": "OK",
+                "value": "1"
+            },
+            {
+                "errorThreshold": "80",
+                "metric": "new_coverage",
+                "onLeakPeriod": true,
+                "operator": "LESS_THAN",
+                "status": "NO_VALUE"
+            }
+        ],
+        "name": "SonarQube way",
+        "status": "OK"
+    },
+    "serverUrl": "http://localhost:9000",
+    "status": "SUCCESS",
+    "taskId": "AVh21JS2JepAEhwQ-b3u"
+}
+```
+
+## Additional parameters
+
+A basic authentication mechanism is supported by providing user/password in the URL of the Webhook such as `https://myLogin:myPassword@my_server/foo`.
+
+If you provide additional properties to your SonarQube Scanner using the pattern `sonar.analysis.*`, these properties will be automatically added to the section "properties" of the payload.
+
+For example these additional parameters:
+
+```
+sonar-scanner -Dsonar.analysis.scmRevision=628f5175ada0d685fd7164baa7c6382c1f25cab4 -Dsonar.analysis.buildNumber=12345
+```
+
+Would add this to the payload:
+
+```
+"properties": {
+  "sonar.analysis.scmRevision": "628f5175ada0d685fd7164baa7c6382c1f25cab4",
+  "sonar.analysis.buildNumber": "12345"
+}
+```
diff --git a/server/sonar-docs/src/pages/quality-gates.md b/server/sonar-docs/src/pages/quality-gates.md
deleted file mode 100644 (file)
index 9d1cdda..0000000
+++ /dev/null
@@ -1,73 +0,0 @@
----
-title: Quality Gates
----
-
-## Overview
-
-A quality gate is the best way to enforce a quality policy in your organization.
-It's there to answer ONE question: can I deliver my project to production today or not?
-
-In order to answer this question, you define a set of Boolean conditions based on measure thresholds against which projects are measured. For example:
-
-* No new blocker issues
-* Code coverage on new code greater than 80%
-* Etc.
-
-Ideally, all projects will be verified against the same quality gate, but that's not always practical. For instance, you may find that:
-
-* Technological implementation differs from one application to another (you might not require the same code coverage on new code for Web or Java applications).
-* You want to ensure stronger requirements on some of your applications (internal frameworks for example).
-* Etc.
-
-Which is why you can define as many quality gates as you wish. Quality Gates are defined and managed in the **[Quality Gates](/#sonarqube#/quality_gates)** page found in the top menu.
-
-## Use the Best Quality Gate Configuration
-
-The quality gate "Sonar way" is provided by SonarSource, activated by default and considered as built-in and so read-only. It represents our view of the best way to implement the [Fixing the Water Leak](/fixing-the-water-leak) concept. At each SonarQube release, we adjust automatically this default quality gate according to SonarQube's capabilities.
-
-Three metrics allow you to enforce a given Rating of Reliability, Security and Maintainability, not just overall but also on new code. These metrics are recommended and come as part of the default quality gate. We strongly advise you to adjust your own quality gates to use them to make feedback more clear to your developers looking at their quality gate on their project page.
-
-Don't forget also that quality gate conditions must use differential values. There is no point for example to check an absolute value such as : Number of Lines of Code is greater than 1000.
-
-### Recommended Quality Gate
-
-The `Sonar way` Built-in quality gate is recommended for most projects. If focuses on keeping new code clean, rather than spending a lot of effort remediating old code. Out of the box, it's already set as the default profile.
-
-## Quality Gate Status
-
-The current status is displayed prominently at the top of the Project Page:
-
-![Quality Gate Status](/images/quality-gate-status.jpeg)
-
-## Getting Notified When a Quality Gate Fails
-
-Thanks to the notification mechanism, users can be notified when a quality gate fails. To do so, subscribe to the **New quality gate status** notification either for all projects or a set of projects you're interested in.
-
-## Security
-
-Quality Gates can be accessed by any user (even anonymous users). All users can view every aspect of a quality gate.
-
-To make changes (create, edit or delete) users must be granted the **Administer Quality Profiles and Gates** permission.
-
-A **project administrator** can choose which quality gates his/her project is associated with. See Project Settings for more.
-
-## Defining Quality Gates
-
-To manage quality gates, go to **[Quality Gates](/#sonarqube#/quality_gates)** (top menu bar).
-
-Each Quality Gate condition is a combination of:
-
-* measure
-* period: **Value** (to date) or **New Code** (differential value over the New Code period)
-* comparison operator
-* warning value (optional)
-* error value (optional)
-
-For instance, a condition might be:
-
-* measure: Blocker issue
-* period: Value
-* comparison operator: >
-* error value: 0
-
-Which can be stated as: No blocker issues.
diff --git a/server/sonar-docs/src/pages/quality-profiles.md b/server/sonar-docs/src/pages/quality-profiles.md
deleted file mode 100644 (file)
index 1e9c03e..0000000
+++ /dev/null
@@ -1,79 +0,0 @@
----
-title: Quality Profiles
----
-
-## Overview
-
-The Quality Profiles service is central to SonarQube, since it is where you define your requirements by defining sets of **rules** (ex: Methods should not have a Cognitive Complexity greater than 15).
-
-Ideally, all projects will be measured with the same profile for any given language, but that's not always practical. For instance, you may find that:
-
-* The technological implementation differs from one application to another (for example, different coding rules may apply when building threaded or non-threaded Java applications).
-* You want to ensure stronger requirements on some of your applications (internal frameworks for example).
-* Etc.
-
-Which is why you can define as many quality profiles as you wish even though it is recommended to have as few Quality Profiles as possible to ensure consistency across the projects in your company. To manage quality profiles, go to <!-- sonarqube -->[**Quality Profiles**](/#sonarqube#/profiles)<!-- /sonarqube --><!-- sonarcloud -->**Quality Profiles** page of your organization<!-- /sonarcloud -->, where you'll find profiles grouped by language.
-
-Each language plugin comes with a predefined, built-in profile (usually called "Sonar way") so that you can get started very quickly with SonarQube analyses. This is why as soon as you install a new language plugin, at least one quality profile will be available for you. Each language must have a default profile (marked with the Default tag). Projects that are not explicitly associated with a specific profile will be analyzed using the language's default profile.
-
-When starting from a new installation, it's tempting to use Sonar way as your default profile because it contains all the rules that are generally applicable to most projects. But as a best practice, you should create a new profile (you can populate it by copying the contents of Sonar way) and use it instead. Why? First because Sonar way profiles aren't editable, so you won't be able to customize it to your needs. Also, that lets you treat Sonar way as a baseline against which you can track your own profile as you make changes to it (and you will). Plus, Sonar way is typically updated with each new version of the plugin to add rules and sometimes adjust rule severities. Any profile that inherits from the built-in Sonar Way will de-facto be automatically updated at the same time.
-
-## How do I...
-
-### Delegate the management of Quality Profiles to someone else?
-
-By default, only users with the "Administer Quality Profiles" permission can edit Quality Profiles. But in large organizations, it may not be desirable to grant permissions to change all the Quality Profiles without distinction. That's why you can also grant users/groups the permission to edit an individual Quality Profile so that, for instance, the management of the Swift profile can be delegated to a group of Swift experts, and the same for COBOL, ...
-
-This delegation of permission can only be performed by someone who already has the "Administer Quality Profiles" permission or individual edit rights on the profile to which additional permissions should be granted. The interface to grant individual permissions is available on the profile detail page.
-
-### Copy the rules from one profile to another?
-
-Many times people want to work from a profile that's based on a built-in profile without actually using the built-in profile. The easiest thing to do in this case is to go to the original profile, we'll call it _Source_, in **Quality Profiles**. From there, click through on the total number of rules in _Source_ to land on the **Rules** page at a pre-narrowed search of _Source_'s rules. Use **Bulk Activate** to turn Source's rules on in your target profile.
-
-### Know what's changed in a profile?
-
-When SonarQube notices that an analysis was performed with a profile that is different in some way from the previous analysis, a Quality Profile event is added to the project's event log. To see the changes in a profile, navigate to the profile (**Quality Profiles > [ Profile Name ]**), and choose **Changelog**. This may help you understand how profile changes impact the issues raised in an analysis.
-
-Additionally, users with Quality Profile administration privileges are notified by email each time a built-in profile (one that is provided directly by an analyzer) is updated. These updates can only be caused by analyzer updates.
-
-### Copy a profile from one SonarQube instance to another?
-
-Use the **Back up** feature on the source instance to export the profile to an XML file. Use the **Restore Profile** feature on the target instance to import the file. Note that some [limitations](https://jira.sonarsource.com/browse/SONAR-5366) on this feature exist.
-
-![Restore Quality Profile](/images/restore-quality-profile.jpeg)
-
-### Apply a core set of rules plus additional rules to a project?
-
-Let's say your company has a minimum set of coding rules that all teams must follow, but you want to add rules that are specific to the in use technology in your project. Those rules are good for your team, but irrelevant or even misleading for others. This situation calls for inheritance. Set up a base profile, we'll call it _Root_ with your core set of rules. Then create a child profile, we'll call it _Sprout_. Once it's created, you can **Change parent** to inherit from _Root_, then add your missing rules.
-
-### Make sure my non-default profile is used on a project?
-
-One profile for each language is marked the default. Barring any other intervention, all projects that use that language will be analyzed with that profile. To have a project analyzed by a non-default profile instead, start from **Quality Profiles**, and click through on your target profile, then use the Projects part of the interface to manage which projects are explicitly assigned to the profile.
-
-### Make sure I've got all the relevant new rules in my profile?
-
-Each time a language plugin update is released, new rules are added, but they won't appear automatically in your profile unless you're using a built-in profile such as _Sonar way_.
-
-If you're not using a built-in profile, you can compare your profile to the built-in profile to see what new on-by-default rules you're missing.
-
-Another option is to go to the **Rules** space, and use the **Available Since** search facet to see what rules have been added to the platform since the day you upgraded the relevant plugin.
-
-And finally, the profile interface itself will help you be aware of rules added in a new plugin version in the **Latest New Rules** section on the right of the interface.
-
-### Compare two profiles?
-
-Starting from the **Quality Profiles** page, click through on one of the profiles you'd like to compare, then use the **Actions > Compare** interface to select the second profile and see the differences.
-
-### Make sure I don't have any deprecated rules in my profile?
-
-The **Deprecated Rules** section of the rules interface itself is your first warning that a profile contains deprecated rules. This pink-background section gives the total number of instances of deprecated rules that are currently active in profiles, and a breakdown of deprecated count per profile. A click-through here takes you to the **Rules** page to edit the profile in question.
-
-Alternately, you can perform a **Rules** search for the rules in a profile (either manually or by clicking-through from **Quality Profiles** page) and use the **Status** rule search facet to narrow the list to the ones that need attention.
-
-## Security
-
-The Quality Profiles service can be accessed by any user (even anonymous users). All users can view every aspect of a profile. That means anyone can see which rules are included in a profile, and which ones have been left out, see how a profile has changed over time, and compare the rules in any two profiles.
-
-To make rule profile changes (create, edit or delete) users must be granted the **Administer Quality Profiles and Gates** permission.
-
-A **project administrator** can choose which profiles his project is associated with. See Project Settings for more.
diff --git a/server/sonar-docs/src/pages/security-reports.md b/server/sonar-docs/src/pages/security-reports.md
deleted file mode 100644 (file)
index 79d6372..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
----
-title: Security Reports
----
-
-## What do the Security Reports show?
-The Security Reports are designed to quickly give you the big picture on your application's security, with breakdowns of just where you stand in regard to each of the [OWASP Top 10](https://www.owasp.org/index.php/Top_10-2017_Top_10), and [SANS Top 25](https://www.sans.org/top25-software-errors) categories, and [CWE](http://cwe.mitre.org/)-specific details.
-The Security Reports are fed by the analyzers, which rely on the rules activated in your quality profiles to raise security issues. If there are no rules corresponding to a given OWASP category activated in your Quality Profile, you will get no issues linked to that specific category and the rating displayed will be A. That won't mean you are safe for that category, but that you need to activate more rules (assuming some exist).
-
-## What's the difference between a Hotspot and a Vulnerability?
-Vulnerabilities are points in the code which are open to attack.
-Security Hotspots are security-sensitive pieces of code that should be carefully reviewed by someone with a security auditor hat. This person can be:
-* a member of the development team who is more sensitive to security problems 
-* someone outside the development team contracted for the purpose of reviewing these Hotspots.
-
-The main goal of Security Hotspots is to help focus the efforts of the security auditors who manually review application source code. The second goal is to educate developers and to increase their security-awareness. 
-Having a Hotspot in your application does not mean there is a problem. What it does mean is that a human, preferably a security auditor/expert should look over the code to see if the sensitive piece of code is being used in the safest manner.
-
-## Why don't I see any Hotspots?
-They are three reasons you might not see any Hotspots:
-* it is possible you really have none of them because the code has been written without using any security-sensitive API. 
-* it is possible that Hotspot rules are available, but not yet activated in your Quality Profile, and so naturally no issues are raised
-* it is more likely that the analyzer for the langauge you're using does not yet offer Hotspot rules, and so it doesn't raise any Hotspots regardless of the quality of how many are actually there, but this last option will disappear over time.
-
-## Why don't I see any Vulnerabilities?
-You might not see any Vulnerabilities for more or less the same reasons as for Hotspots, but it may be more surprising for Vulnerabilities because you may see some Vulnerabilities reported in the Project homepage, while there are none in the Security Reports. This is because the language analyzer may not yet provide the "Security Standards" metadata required for issues to be visible on the Security Reports. This metadata is basically the link between a Rule (and its issues) and the "OWASP Top 10" or "SANS Top 25" categories. Without this link, there is no way to associate an already existing Vulnerability to the Security Standard categories and so to display security issues correctly in the reports. Every analyzer version released by SonarSource after July 2018 should feed the "Security Standards" and be compatible with the Hotspot issue type. 
-
-## I'm a developer. Should I care about Hotspots?
-Probably not. Hotspots, as such, aren't really actionable. They simply mark *potential* problems, so there's really nothing to do immediately on the code. That's why you don't receive notifications when Hotspot issues are raised, and why Hotspots aren't shown in the Issues page by default.
-
-## What if my Hotspot really marks a Vulnerability?
-If you look at the code where a Hotspot is raised and realize that there really is a problem, click on the current status (probably `Open`) to register that you've *Detect*ed a Vulnerability at that point in the code. Once you do, it will be converted to a Vulnerability, and the developer who last touched the line will receive "new issue" notifications (if she's signed up to get them).
-
-## What happens after my Hotspot becomes a Vulnerability?
-Once you've *Detect*ed that there really is a problem at a Hotspot location, it will be assigned to the appropriate developer, who will make a fix, and must then `Request Review` *via the UI*. That request moves the issue from Vulnerability back to Hotspot. From there, it's up to the security auditor to either `Accept` or `Reject` the fix. Accepting the fix will mark it `Won't Fix`, and rejecting it will turn it back into a Vulnerability, putting it back in the developer's queue.
-
-## What does it mean for a Hotspot to be marked "Won't Fix"?
-The `Won't Fix` designation is used to indicate that a Hotspot has been reviewed and there is no way, as of now, to exploit this piece of code to create an attack.
-
-
diff --git a/server/sonar-docs/src/pages/security.md b/server/sonar-docs/src/pages/security.md
deleted file mode 100644 (file)
index 1ec4590..0000000
+++ /dev/null
@@ -1,61 +0,0 @@
----
-title: SonarCloud Security
-scope: sonarcloud
----
-
-We know that your code is very important to you and your business. We also know that no one wants proven bugs or vulnerabilities found on their source code to be unveiled to third-parties. This is why we take security extremely seriously.
-
-## Hosting
-
-SonarCloud is hosted on Amazon AWS in Frankfurt. 
-
-## System security
-
-We keep system up to date, OS packages are updated at least weekly. SonarCloud is on its own AWS VPC. We have firewall at VPC and VM level.
-
-Except the Operations team, no SonarSource employee has access to the system, especially the database which stores source code and analysis results.
-
-The Operations team has access to the system through secured channels (SSH) only. 
-
-## Data security
-
-All the data is stored on a Postgres RDS instance which only the Operation has access to.
-
-Isolation of data per organization is ensured at software level, which secures access to source code to organization members only.
-
-The source code is not encrypted in the database, but the access to the database is restricted to SonarSource operations team and can be done only through a SSH tunnel.
-
-The DB is backed up everyday by Amazon RDS mechanism, with 7 days retention.
-
-## Software security
-
-The Web Application and Web APIs regularly pass penetration testing conducted by a an external company, specialized in cyber and application security, certified in accordance to ISO-27001 and which is also member of the OWASP.
-
-## Communications
-
-All communications are done over TLS 1.2:
-* Navigating in the Web application
-* Using WS APIs
-* Running analysis (by the scanners) from CI services and pushing analysis reports to SonarCloud
-
-## SonarCloud Webhook IPs
-
-SonarCloud performs webhook calls from the following list of IPs:
-```
-52.59.209.17
-52.59.246.1
-54.93.180.144
-18.194.44.125
-18.194.244.158
-18.195.64.198
-```
-
-## Authentication
-
-Primary authentication on the system is available only through OAuth authentication with GitHub, Bitbucket Cloud and Microsoft VSTS. As a consequence, users don’t have a password on SonarCloud, and are as protected as what they expect (especially with 2FA activated on those systems). 
-For WS API calls or source code analysis triggered from CI services, only revocable user tokens are accepted.
-
-## Payment
-
-When you subscribe to the paid plan on SonarCloud, your credit card information never transit through our system nor it gets stored on the server. It's handed off to [Braintree Payment Solutions](https://www.braintreepayments.com), a company dedicated to storing your sensitive data on [PCI-Compliant](http://en.wikipedia.org/wiki/Payment_Card_Industry_Data_Security_Standard) servers.
diff --git a/server/sonar-docs/src/pages/sonarcloud-pricing.md b/server/sonar-docs/src/pages/sonarcloud-pricing.md
deleted file mode 100644 (file)
index e38c41a..0000000
+++ /dev/null
@@ -1,89 +0,0 @@
----
-title: Pricing
-scope: sonarcloud
----
-
-Subscribing to a paid plan on SonarCloud allows you to analyze unlimited private projects. You can make your code visible by members of your organization only.
-
-## How is SonarCloud priced?
-
-SonarCloud is priced on a monthly basis per lines of code. You pay up front for a maximum number of lines of code to be analyzed in your organization.
-
-Find your max LOC below to see what it will cost you per month:
-
-| Up to lines of code  | Price per month in € |
-| ------------- |--------------:|
-| 100k          | 10            |
-| 250k          | 75            | 
-| 500k          | 150           |
-| 1M            | 250           |
-| 2M            | 500           |
-| 5M            | 1'500         |
-| 10M           | 3'000         |
-| 20M           | 4'000         |
-
-
-## What's the difference between the free and paid plans?
-
-2 options are available to start using SonarCloud: free and paid plans. Both plans let you benefit from all the features available on SonarCloud.
-
-*Free plan:*
-
-* For open source projects
-* Anyone can see your projects
-* You choose who can edit your projects
-* Unlimited lines of code (LOCs)
-
-*Paid plan:*
-
-* If you need (some or all) private projects
-* You choose who can see your private projects
-* You choose who can edit your projects
-* Priced by lines of private code
-
-## How do I activate the paid plan?
-
-You can activate the paid plan on the "Administration > Billing" page of your organization. 
-
-## What payment options are available?
-
-Payment is done online by credit card and will happen automatically every month, based on the plan you choose. 
-
-We also accept to receive a purchase order and a wire transfer payment, if ordering a yearly subscription for more than 1M LOCs. In this case, you need to contact us through the Contact form.
-
-## Can I try a private project on SonarCloud for free?
-
-Your first 14 days are on us. You just have to upgrade your organization to a paid plan, and fill your credit card information to get started. After your trial, if you love it you can continue using SonarCloud and you will be charged for the plan you selected when you first started your free trial. You can cancel anytime.
-
-## What is a Line of Code (LOC) on SonarCloud?
-
-LOCs are computed by summing up the LOCs of each project analyzed in SonarCloud. The LOCs used for a project are the LOCs found during the most recent analysis of this project.
-
-
-## How are Lines of Code (LOCs) counted towards billing?
-
-Only LOC from your private projects are counted toward your maximum number of LOCs. If your project contains branches, the counted LOCs are the ones of the biggest branch.
-
-The count is not related to how frequently the source code is analyzed. If your private project has a 6K LOCs and you analyze it 100 times in the month, this will be counted as 6K for the billing.
-
-If you are getting close to the threshold you will be notified to either upgrade your plan or reduce the number of LOCs in your projects.
-
-## When will I be invoiced?
-
-You will be invoiced once a month, the day of the month after your trial ends. For example if you start your free trial on January 1st, it will last till January 14th and you will be first billed on January 15th for your upcoming month, aka January 15th to February 15th.
-
-## How do I get invoices?
-
-You can download PDF invoices for every payment from the "Administration > Billing" page of your organization.
-
-If you want to get those invoices by email, please [contact us](/#sonarcloud#/about/contact).
-
-## Can I stop using the service?
-
-Yes, you can stop using SonarCloud anytime you want. You simply need to downgrade your organization to the free plan.
-
-## Still have more questions?
-
-Contact us [here](https://about.sonarcloud.io/contact).
-
-
diff --git a/server/sonar-docs/src/pages/sonarcloud/analyze-a-project.md b/server/sonar-docs/src/pages/sonarcloud/analyze-a-project.md
new file mode 100644 (file)
index 0000000..113f8d5
--- /dev/null
@@ -0,0 +1,26 @@
+---
+title: Analyze a Project
+url: /analyze-a-project/
+---
+
+## Prepare your organization
+
+A project must belong to an [organization](/organizations/overview/). Create one if you intend to collaborate with your team mates, or use your personal organization for test purposes.
+
+[[info]]
+| ** Important note for private code:** Newly created organizations and personal organizations are under a free plan by default. This means projects analyzed on these organizations are public by default: the code will be browsable by anyone. If you want private projects, you should [upgrade your organization to a paid plan](/sonarcloud-pricing/) in the "Administration > Billing" page of your organization.
+
+Find the key of your organization, you will need it at later stages. It can be found on the top right corner of your organization's header.
+
+## Run analysis
+
+SonarCloud currently does not trigger analyses automatically - this feature will come in a near future. Currently, it's up to you to launch them inside your
+existing CI scripts.
+
+Depending on which cloud solution you are using for your developments, you can rely on dedicated integrations to help you:
+
+* VSTS: [read our dedicated documentation](/integrations/vsts/)
+* Bitbucket Cloud: [read our dedicated documentation](/integrations/bitbucketcloud/)
+* GitHub: [read our dedicated documentation](/integrations/github/)
+
+If you are not using those solutions, you will have to find out what command to execute to run the analysis. Our [tutorial](/#sonarcloud#/onboarding) will help you on this.
diff --git a/server/sonar-docs/src/pages/sonarcloud/integrations/bitbucketcloud.md b/server/sonar-docs/src/pages/sonarcloud/integrations/bitbucketcloud.md
new file mode 100644 (file)
index 0000000..6006edc
--- /dev/null
@@ -0,0 +1,79 @@
+---
+title: Integration with Bitbucket Cloud
+url: /integrations/bitbucketcloud/
+---
+
+## Authentication
+
+You can connect to SonarCloud using your Bitbucket Cloud account. On the [login page](/#sonarcloud#/sessions/new), just click on the "Log in with Bitbucket" button.
+
+## Install SonarCloud add-on for Bitbucket Cloud
+
+Our Bitbucket Cloud application allows users to automate the SonarCloud analysis with Pipelines. It also allows users to view their SonarCloud metrics directly on Bitbucket Cloud via our Code Quality widget and the decoration of pull-requests.
+
+In Bitbucket Cloud, go to your team's "Settings > Find integrations" page, search for "SonarCloud" in the "Code Quality" category and click on "Add" to install the application.
+
+## Analyzing with Pipelines
+
+SonarCloud integrates with Bitbucket Pipelines to make it easier to trigger analyses. Follow the steps:
+
+1.  On SonarCloud, once your project is created, follow the tutorial on the dashboard of the project. You can copy-paste the command line displayed at the end.
+
+2.  On Bitbucket Cloud, go to the "Settings > Pipelines > Environment variables" page of your team, and add a new SONAR_TOKEN variable that contains the value of the SonarCloud token (something like `9ad01c85336b265406fa6554a9a681a4b281135f`) which you created during the tutorial (and which is available inside the command line that you copy-pasted). **Make sure that you click on the "Lock" icon to encrypt and hide this token.**
+
+3.  Inside the `bitbucket-pipelines.yml` file of your repository, copy the command line provided by the tutorial and replace the actual token by its variable name. For example, for a Java Maven-based project, you should have something like:
+
+```
+script:
+  -mvn sonar:sonar -Dsonar.host.url=https://sonarcloud.io -Dsonar.organization=my-team-org -Dsonar.login=$SONAR_TOKEN
+```
+
+When this change on `bitbucket-pipelines.yml` is committed and pushed, Pipelines should automatically run a new build and therefore trigger the analysis of the repository. Shortly after, your project will appear on SonarCloud in your organization.
+
+4.  Once you see your project in SonarCloud, go to the Bitbucket Cloud "Settings > SonarCloud" page. If the dropdown is empty, find your project in the select box to link it.
+
+From now on, everytime Pipelines triggers a build, SonarCloud will:
+
+* Analyze every new branch that contains the change on the `bitbucket-pipelines.yml` file.
+* Analyze and decorate every pull request based on such a branch.
+
+## Quality widget
+
+SonarCloud provides a widget that shows the current quality metrics of your project directly on the repository's Overview page on Bitbucket Cloud.
+
+If you have configured the analysis with Pipelines as described above, you will see this widget on the "Overview" page of your repository.
+
+If you haven't configured the analysis with Pipelines (maybe because you are using another CI tool), follow these few steps:
+
+1.  Go to the repository where you want to display the widget. On the "Overview" page, you should see an empty SonarCloud widget. Click on the link inside the empty widget or just go directly to your repository's "Settings > SonarCloud Settings".
+
+2.  If you're not already logged in on SonarCloud, do it by using the options provided there. You can log in with Bitbucket Cloud by clicking on the blue button, or click on "More options" to log in with GitHub or VSTS.
+
+3.  Once you're logged in on SonarCloud, you should see a dropdown allowing you to choose one of the projects you administer. Just choose the one you want to link to this Bitbucket repository and click "Save".
+
+4.  You can now go back to your repository's Overview page on Bitbucket and see the widget with all current SonarCloud metrics displayed.
+
+If you want to hide this widget (e.g. because your repository is not analyzed on SonarCloud), you can go to the "Settings > SonarCloud" page of your repository and check "Hide repository overview widget".
+
+## FAQ
+
+**Do you have a sample project on Bitbucket Cloud?**
+For the time being, you can take a look at this very simple JS project: [Sample project analysed on SonarCloud](https://bitbucket.org/bellingard/fab)
+
+**Pipelines can't find sonar-scanner**
+If you want to analyze a non-Java project (JS, TS, PHP, Python, Go, ...), you will need to download and install the [Scanner CLI](https://redirect.sonarsource.com/doc/install-configure-scanner.html) during the execution of your build prior to the actual code scan. You have two options:
+
+* You can download it (with curl for instance) from the links available on the documentation page and unpack it (preferably in a cached folder for later reuse).
+* On Node environments, you can rely on a [community NPM module](https://www.npmjs.com/package/sonarqube-scanner) to install it globally and therefore make it available in the PATH.
+
+**I don't see the any quality information whereas I configured everything**
+Make sure that your browser is not using some extensions like AdBlocks. They tend to break the integration of third-party applications in BitBucket Cloud.
+
+## Upcoming features and improvements
+
+There are various areas in which you can expect new features and improvements:
+
+* Tighter integration with Pipelines (less parameters to pass on the CLI, availability of the scanner, ...)
+* Pull request decoration with inline comments to show the issues within the PR
+* Better and easier team onboarding
+* Automatic analysis (i.e. no need to configure anything from Pipelines)
diff --git a/server/sonar-docs/src/pages/sonarcloud/integrations/github.md b/server/sonar-docs/src/pages/sonarcloud/integrations/github.md
new file mode 100644 (file)
index 0000000..c283c96
--- /dev/null
@@ -0,0 +1,34 @@
+---
+title: Integration with GitHub
+url: /integrations/github/
+---
+
+## Authentication
+
+You can connect to SonarCloud using your GitHub account. On the [login page](/#sonarcloud#/sessions/new), just click on the "Log in with GitHub" button.
+
+## Trigger analyses
+
+SonarCloud currently does not trigger analyses automatically. It's up to you to launch them inside your
+existing CI scripts. Please follow the [tutorial](/#sonarcloud#/onboarding) to get started.
+
+### Using Travis CI?
+
+If you are using Travis CI, the SonarCloud Travis Add-on will make it easier to activate analyses: 
+* Read the [guide to integrate with Travis CI](https://docs.travis-ci.com/user/sonarcloud/)
+* Check out the [various sample projects](https://github.com/SonarSource/sonarcloud_examples) (Java, TypeScript, C/C++, Go, ... etc) that are analyzed on SonarCloud on a frequent basis
+
+## Activating pull request decoration
+
+To have your pull requests decorated by SonarCloud in GitHub, you need to [install the SonarCloud application](https://github.com/apps/sonarcloud) on your GitHub organization(s).
+
+Once installed, there is nothing more to do if you are using the Travis Add-on. In any other case, you will need
+to pass the following properties in your script during the analysis:
+
+```
+sonar.pullrequest.base=master
+sonar.pullrequest.branch=feature/my-new-feature
+sonar.pullrequest.key=5
+sonar.pullrequest.provider=GitHub
+sonar.pullrequest.github.repository=my-company/my-repo
+```
diff --git a/server/sonar-docs/src/pages/sonarcloud/integrations/vsts.md b/server/sonar-docs/src/pages/sonarcloud/integrations/vsts.md
new file mode 100644 (file)
index 0000000..448affc
--- /dev/null
@@ -0,0 +1,38 @@
+---
+title: Integration with VSTS
+url: /integrations/vsts/
+---
+
+
+## Authentication
+
+You can connect to SonarCloud using your VSTS account. On the [login page](/#sonarcloud#/sessions/new), just click on the "Log in with VSTS" button.
+
+[[warning]]
+| Only work and school VSTS accounts are authorized to login on SonarCloud.
+
+## Install the SonarCloud VSTS extension
+
+The SonarCloud VSTS extension brings everything you need to have your projects analyzed on SonarCloud 
+very quickly:
+* Integration with the Build definitions to easily trigger the analysis
+* Pull request decoration to get quick feedback on the code changes
+* Widget to have the overview quality of your projects inside VSTS dashboards
+
+Install [SonarCloud extension for VSTS](https://marketplace.visualstudio.com/items?itemName=SonarSource.sonarcloud)by clicking on the "Get it free" button.
+
+Then follow the comprehensive [Microsoft lab on how to integrate VSTS with SonarCloud](https://aka.ms/sonarcloudlab).
+
+## Quality Gate Status widget 
+
+You can monitor the Quality Gate status of your projects directly in your VSTS dashboard. Follow these simple steps to configure your widget:
+
+1. Once the VSTS extension is installed and your project has been successfully analyzed, go to one of your VSTS dashboards (or create one). Click on the pen icon in the bottom right corner of the screen, and then on the "+" icon to add a widget. 
+
+2. In the list of widgets, select the "Code Quality" one and then click on the "Add" button. An empty widget is added to your dashboard. 
+
+3. You can then click on the widget's cogwheel icon to configure it.
+
+    * **For public projects:** you can simply select your project from the dropdown. A searchbar inside the dropdown will help you find it easily. Just select it and click on the "Save" button.
+
+    * **For private projects:** you'll have to log in using the links provided under the dropdown. Once logged in, your private projects will appear in the dropdown. Select the one you are interested in, and click on "Save".
diff --git a/server/sonar-docs/src/pages/sonarcloud/organizations/index.md b/server/sonar-docs/src/pages/sonarcloud/organizations/index.md
new file mode 100644 (file)
index 0000000..b872a83
--- /dev/null
@@ -0,0 +1,23 @@
+---
+title: Organizations
+url: /organizations/overview/
+---
+
+## Overview
+
+An organization is a space where a team or a whole company can collaborate across many projects.
+
+An organization consists of:
+* Projects, on which users collaborate
+* [Members](/organizations/manage-team/), who can have different persmissions on the projects
+* [Quality Profiles](/instance-administration/quality-profiles/) and [Quality Gates](/user-guide/quality-gates/), which can be customized and shared accross projects
+
+There are 2 kind of organizations:
+* **Personal organizations**. Each account has a personal organization linked to it. This is typically where open-source developers host their personal projects. It is not possible to delete this kind of organization.
+* **Standard organization**. This is the kind of organization that users want to create for their companies or for their open-source communities. As soon as you want to collaborate, it is a good idea to create such an organization.
+
+Organizations can be on:
+* **Free plan**. This is the default plan. Every project in an organization on the free plan is public.
+* **Paid plan**. This plan unlocks the ability to have private projects. Go to the "Billing" page of your organization to upgrade it to the paid plan.
+
+Depending on which plan the organization is in, its [visibility](/organizations/organization-visibility/) will change.
diff --git a/server/sonar-docs/src/pages/sonarcloud/organizations/manage-team.md b/server/sonar-docs/src/pages/sonarcloud/organizations/manage-team.md
new file mode 100644 (file)
index 0000000..8cbb162
--- /dev/null
@@ -0,0 +1,34 @@
+---
+title: Manage a Team
+url: /organizations/manage-team/
+---
+
+Members can collaborate on the projects in the organizations to which they belong. Depending on their permisssions within the organization, members can:
+* Analyse projects
+* Manage project settings (permissions, visibility, quality profiles, ...)
+* Update issues
+* Manage quality gates and quality profiles
+* Administer the organization itself
+
+## Adding Members
+
+Adding members is done on the "Members" page of the organization, and this can be done only by an administrator of 
+the organization.
+
+Adding a user as a member is possible only if that user has already signed up on SonarCloud. If the user never authenticated to
+the system, the administrator will simply not be able to find the user in the search modal window.
+
+## Granting permissions
+
+Once added, a user can be granted permissions to perform various operations in the organization. It is up to the 
+administrator who added the user to make sure that she gets the relevant permissions.
+
+Organization admins will prefer to create groups to manage permissions, and add new users to those
+groups through the "Members" page. With such an approach, they won't have to manage individal permissions at
+project level for instance.
+
+## Future evolutions
+
+Future versions of SonarCloud will make this onboarding process easier thanks to better integrations with GitHub, 
+Bitbucket Cloud and VSTS: users won't have to sign up prior to joining an organization, and their permissions will 
+be retrieved at best from the ones existing on the other systems.
diff --git a/server/sonar-docs/src/pages/sonarcloud/organizations/organization-visibility.md b/server/sonar-docs/src/pages/sonarcloud/organizations/organization-visibility.md
new file mode 100644 (file)
index 0000000..94c5605
--- /dev/null
@@ -0,0 +1,44 @@
+---
+title: Organization Visibility
+url: /organizations/organization-visibility/
+---
+
+## Free plan organization
+
+Free plan organizations are public. This means that almost everything is visible to any user - even anonymous ones:
+
+* Projects
+* Issues
+* Quality Profiles
+* Quality Gates
+* Rules
+
+The following pages are restricted:
+
+* Members: to members of the organization
+* Administration pages: to administrators of the organization
+
+## Paid plan organization
+
+Paid plan organizations are private. This means that nothing is visible to non-members of the organization. In other words, you need to be a member of the organization to see:
+
+* Projects - which are private by default
+* Issues
+* Quality Profiles
+* Quality Gates
+* Rules
+* Members
+
+The administration pages are obviously also restricted to administrators of the organization.
+
+### Want to make one project public?
+
+If you are on a paid plan organization but want to make a project public (for instance because you are developing an open-source library), this is possible. You will have to manually make the project public in its "Administration > Permissions" page. Once done, you will notice the "Public" badge on the project.
+
+As soon as you have one public project, the following pages will become visible to any user:
+
+* Projects
+* Issues
+* Rules
+
+"Quality Profiles" and "Quality Gates" pages will remain restricted to members only - since you might not want to unveil some information used by your private projects.
diff --git a/server/sonar-docs/src/pages/sonarcloud/privacy.md b/server/sonar-docs/src/pages/sonarcloud/privacy.md
new file mode 100644 (file)
index 0000000..a92e13c
--- /dev/null
@@ -0,0 +1,36 @@
+---
+title: Privacy
+url: /privacy/
+---
+
+The privacy policy specifies how data collected on this website is used. Thank you for visiting our website and your interest in our services and products. As the protection of your personal data is an important concern for us, we will explain below what information we collect during your visit to our website, as they are processed and whether or how these may be used.
+
+## PERSONAL DATA
+
+Personal information is information about personal or material circumstances of an identified or identifiable natural person. This includes information such as your first and last name, your postal or residential address, telephone numbers and date of birth. Information that can not be directly related to your real identity – such as your favorite websites or the number of users of a page – are not considered as personal data.
+
+## COLLECTION AND PROCESSING OF PERSONAL DATA
+
+As the operator and creator of the website, we do not store personal data itself automatically. If you go to our website, the provider – where the web server is hosted – may temporarily store data for the purpose of system security such as the connection of the computer, the web pages you visit, the date and duration of the visit, data about the used browser software and operating system and the web page from which you visit us. In addition to that, personal information such as your name, address, phone number or e-mail will only be stored, if you have provided this information voluntarily, eg. as part of a registration, a survey, a contest, to carry out an order or contract or an information request.
+
+## USE AND DISCLOSURE OF PERSONAL DATA
+
+Personal data you provided may be used solely for the purpose of technical website administration and to fulfill your wishes and requirements, thus primarily to processing the order with you or to respond to your request. Only if you have previously given your consent or – if stipulated by legal regulations – you entered no objection, we use this data for product surveys and marketing purposes. We don’t share, sell or transfer your personal data to third parties, unless this is necessary for the purpose of the contract or unless you have explicitly consented. For example it may be necessary, that in case of an product order we share your address and order with our suppliers.
+
+## USE OF WEB ANALYSIS SOFTWARE
+
+To improve the structure and the data we offer on our website, we might use open source or proprietary web analysis software. Our evaluations will be based on summary or averaged information amalgamated for the large numbers of people visiting the vebsite. The data provided by won’t be matched with any individual’s data from other sources.
+
+Data collected might include IP, time and duration of the visit, what pages are visited, used browser and add-ons/plugins, search-engines and referrer. While statistic tools might use a “cookie” to distinguish between individual visitors, the collected data doesn’t allow to identify individuals.
+
+## SECURITY
+
+We take all the necessary technical and organisational security measures to protect your personal data from loss and misuse. Your data is stored in a secure operating environment that is not accessible to the public. If you communicate with us via e-mail, please note that the confidentiality of the information is not guaranteed. The contents of e-mails can be intercepted by third parties. In case of doubt we therefore recommend to send confidential information only by snail mail.
+
+## RIGHT OF ACCESS TO PERSONAL DATA
+
+Upon written request you will be informed by us what information we stored about you (such as name or address).
+
+## CONTACT
+
+If you have questions regarding the processing of personal data or in case of requests for information, suggestions or complaints, please [contact us](/#sonarcloud#/about/contact) directly.
diff --git a/server/sonar-docs/src/pages/sonarcloud/security.md b/server/sonar-docs/src/pages/sonarcloud/security.md
new file mode 100644 (file)
index 0000000..8931503
--- /dev/null
@@ -0,0 +1,61 @@
+---
+title: SonarCloud Security
+url: /security/
+---
+
+We know that your code is very important to you and your business. We also know that no one wants proven bugs or vulnerabilities found on their source code to be unveiled to third-parties. This is why we take security extremely seriously.
+
+## Hosting
+
+SonarCloud is hosted on Amazon AWS in Frankfurt. 
+
+## System security
+
+We keep system up to date, OS packages are updated at least weekly. SonarCloud is on its own AWS VPC. We have firewall at VPC and VM level.
+
+Except the Operations team, no SonarSource employee has access to the system, especially the database which stores source code and analysis results.
+
+The Operations team has access to the system through secured channels (SSH) only. 
+
+## Data security
+
+All the data is stored on a Postgres RDS instance which only the Operation has access to.
+
+Isolation of data per organization is ensured at software level, which secures access to source code to organization members only.
+
+The source code is not encrypted in the database, but the access to the database is restricted to SonarSource operations team and can be done only through a SSH tunnel.
+
+The DB is backed up everyday by Amazon RDS mechanism, with 7 days retention.
+
+## Software security
+
+The Web Application and Web APIs regularly pass penetration testing conducted by a an external company, specialized in cyber and application security, certified in accordance to ISO-27001 and which is also member of the OWASP.
+
+## Communications
+
+All communications are done over TLS 1.2:
+* Navigating in the Web application
+* Using WS APIs
+* Running analysis (by the scanners) from CI services and pushing analysis reports to SonarCloud
+
+## SonarCloud Webhook IPs
+
+SonarCloud performs webhook calls from the following list of IPs:
+```
+52.59.209.17
+52.59.246.1
+54.93.180.144
+18.194.44.125
+18.194.244.158
+18.195.64.198
+```
+
+## Authentication
+
+Primary authentication on the system is available only through OAuth authentication with GitHub, Bitbucket Cloud and Microsoft VSTS. As a consequence, users don’t have a password on SonarCloud, and are as protected as what they expect (especially with 2FA activated on those systems). 
+For WS API calls or source code analysis triggered from CI services, only revocable user tokens are accepted.
+
+## Payment
+
+When you subscribe to the paid plan on SonarCloud, your credit card information never transit through our system nor it gets stored on the server. It's handed off to [Braintree Payment Solutions](https://www.braintreepayments.com), a company dedicated to storing your sensitive data on [PCI-Compliant](http://en.wikipedia.org/wiki/Payment_Card_Industry_Data_Security_Standard) servers.
diff --git a/server/sonar-docs/src/pages/sonarcloud/sonarcloud-pricing.md b/server/sonar-docs/src/pages/sonarcloud/sonarcloud-pricing.md
new file mode 100644 (file)
index 0000000..fd05674
--- /dev/null
@@ -0,0 +1,89 @@
+---
+title: Pricing
+url: /sonarcloud-pricing/
+---
+
+Subscribing to a paid plan on SonarCloud allows you to analyze unlimited private projects. You can make your code visible by members of your organization only.
+
+## How is SonarCloud priced?
+
+SonarCloud is priced on a monthly basis per lines of code. You pay up front for a maximum number of lines of code to be analyzed in your organization.
+
+Find your max LOC below to see what it will cost you per month:
+
+| Up to lines of code  | Price per month in € |
+| ------------- |--------------:|
+| 100k          | 10            |
+| 250k          | 75            | 
+| 500k          | 150           |
+| 1M            | 250           |
+| 2M            | 500           |
+| 5M            | 1'500         |
+| 10M           | 3'000         |
+| 20M           | 4'000         |
+
+
+## What's the difference between the free and paid plans?
+
+2 options are available to start using SonarCloud: free and paid plans. Both plans let you benefit from all the features available on SonarCloud.
+
+*Free plan:*
+
+* For open source projects
+* Anyone can see your projects
+* You choose who can edit your projects
+* Unlimited lines of code (LOCs)
+
+*Paid plan:*
+
+* If you need (some or all) private projects
+* You choose who can see your private projects
+* You choose who can edit your projects
+* Priced by lines of private code
+
+## How do I activate the paid plan?
+
+You can activate the paid plan on the "Administration > Billing" page of your organization. 
+
+## What payment options are available?
+
+Payment is done online by credit card and will happen automatically every month, based on the plan you choose. 
+
+We also accept to receive a purchase order and a wire transfer payment, if ordering a yearly subscription for more than 1M LOCs. In this case, you need to contact us through the Contact form.
+
+## Can I try a private project on SonarCloud for free?
+
+Your first 14 days are on us. You just have to upgrade your organization to a paid plan, and fill your credit card information to get started. After your trial, if you love it you can continue using SonarCloud and you will be charged for the plan you selected when you first started your free trial. You can cancel anytime.
+
+## What is a Line of Code (LOC) on SonarCloud?
+
+LOCs are computed by summing up the LOCs of each project analyzed in SonarCloud. The LOCs used for a project are the LOCs found during the most recent analysis of this project.
+
+
+## How are Lines of Code (LOCs) counted towards billing?
+
+Only LOC from your private projects are counted toward your maximum number of LOCs. If your project contains branches, the counted LOCs are the ones of the biggest branch.
+
+The count is not related to how frequently the source code is analyzed. If your private project has a 6K LOCs and you analyze it 100 times in the month, this will be counted as 6K for the billing.
+
+If you are getting close to the threshold you will be notified to either upgrade your plan or reduce the number of LOCs in your projects.
+
+## When will I be invoiced?
+
+You will be invoiced once a month, the day of the month after your trial ends. For example if you start your free trial on January 1st, it will last till January 14th and you will be first billed on January 15th for your upcoming month, aka January 15th to February 15th.
+
+## How do I get invoices?
+
+You can download PDF invoices for every payment from the "Administration > Billing" page of your organization.
+
+If you want to get those invoices by email, please [contact us](/#sonarcloud#/about/contact).
+
+## Can I stop using the service?
+
+Yes, you can stop using SonarCloud anytime you want. You simply need to downgrade your organization to the free plan.
+
+## Still have more questions?
+
+Contact us [here](https://about.sonarcloud.io/contact).
+
+
diff --git a/server/sonar-docs/src/pages/user-account.md b/server/sonar-docs/src/pages/user-account.md
deleted file mode 100644 (file)
index 43feacf..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
----
-title: User Account
----
-
-As a <!-- sonarqube -->SonarQube<!-- /sonarqube --><!-- sonarcloud -->SonarCloud<!-- /sonarcloud --> user you have your own space where you can see the things that are relevant to you:
-
-## Home Page
-
-It gives you a summary of:
-
-* your Groups
-* your SCM accounts
-
-## Security
-
-In addition to being able to change your password, if your instance is not using a 3rd party authentication mechanism such as LDAP or any OAuth provider (GitHub, Google Account, ...), you can manage your own [authentication tokens](/user-token).
-
-You can create as many Token as you want. Once a Token is created, you can use it to publish analysis to a project where you have the [execute analysis](/security/authorization) permission.
-
diff --git a/server/sonar-docs/src/pages/user-guide/fixing-the-water-leak.md b/server/sonar-docs/src/pages/user-guide/fixing-the-water-leak.md
new file mode 100644 (file)
index 0000000..be0a6b4
--- /dev/null
@@ -0,0 +1,37 @@
+---
+title: Fixing the Water Leak
+url: /user-guide/fixing-the-water-leak/
+---
+
+## What is the Water Leak
+
+Imagine you come home one day to find a puddle of water on the kitchen floor. As you watch, the puddle slowly gets larger.
+
+Do you reach for the mop? Or do you try to find the source and fix it? The choice is obvious, right? You find the source of the leak!
+
+So why do anything different with code quality? When you analyze an application with <!-- sonarqube -->SonarQube<!-- /sonarqube --><!-- sonarcloud -->SonarCloud<!-- /sonarcloud --> and realize that it has a lot of technical debt, the knee-jerk reaction is generally to start remediating – either that or put together a remediation plan. This is like mopping the floor once a day while ignoring the source of the water.
+
+Typically in this traditional approach, just before release a periodic code quality audit result in findings the developers should act on before releasing. This approach might work in the short term, especially with strong management backing, but it consistently fails in the mid to long run, because:
+
+* The code review comes too late in the process, and no stakeholder is keen to get the problems fixed; everyone wants the new version to ship.
+* Developers typically push back on the recommendations made by an external team that doesn't know the context of the project. And by the way the code under review is obsolete already.
+* There is a clear lack of ownership for code quality with this approach. Who owns quality? No one!
+* What gets reviewed is the entire application before it goes to production and it is obviously not possible to apply the same criteria to all applications. A negotiation will happen for each project, which will drain all credibility from the process
+
+Instead, why not apply the same simple logic you use at home to the way you manage code quality? Fixing the leak means putting the focus on the “new” code, i.e. the code that was added or changed since the last release. Then things get much easier:
+
+* The [Quality Gate](/user-guide/quality-gates/) can be run every day, and passing it is achievable. There are no surprises at release time.
+* It's pretty difficult for developers to push back on problems they introduced the previous day. Instead, they're generally happy to fix the problems while the code is still fresh.
+* There is a clear ownership of code quality
+* The criteria for go/no-go are consistent across applications, and are shared among teams. Indeed new code is new code, regardless of which application it is done in
+* The cost is insignificant because it is part of the development process
+
+As a bonus, the code that gets changed the most has the highest maintainability, and the code that doesn't get changed has the lowest, which makes a lot of sense. Because of the nature of software, and the fact that we keep making changes to it, the debt will naturally be reduced. Where it isn’t is where it doesn't need to be.
+
+## How to do it
+
+<!-- sonarqube -->SonarQube<!-- /sonarqube --><!-- sonarcloud -->SonarCloud<!-- /sonarcloud --> offers two main tools to help you find your leaks:
+
+* New Code metrics show the variance in your measures between the current code and a specific point you choose in its history, typically the `previous_version`
+* New Code is primarily detected based on SCM "blame" data starting from the first analysis within your New Code Period (formerly the "Leak Period"), with fallback mechanisms when needed. See [SCM integration](/analysis/scm-integration/) for more details.
+* [Quality Gates](/user-guide/quality-gates/) allow you to set boolean thresholds against which your code is measured. Use them with differential metrics to ensure that your code quality moves in the right direction over time.
diff --git a/server/sonar-docs/src/pages/user-guide/keyboard-shortcuts.md b/server/sonar-docs/src/pages/user-guide/keyboard-shortcuts.md
new file mode 100644 (file)
index 0000000..e6fa547
--- /dev/null
@@ -0,0 +1,45 @@
+---
+title: Keyboard Shortcuts
+url: /user-guide/keyboard-shortcuts/
+---
+
+## Global
+
+| Shortcut | Action          |
+| -------- | --------------- |
+| `s`      | open search bar |
+| `?`      | open help       |
+
+## Issues Page
+
+| Shortcut         | Action                                        |
+| ---------------- | --------------------------------------------- |
+| `↑` `↓`          | navigate between issues                       |
+| `→`              | go from the list of issues to the source code |
+| `←`              | return back to the list                       |
+| `alt` + `↑` `↓`  | to navigate issue locations                   |
+| `alt` + `←` `→`  | to switch flows                               |
+| `f`              | do an issue transition                        |
+| `a`              | assign issue                                  |
+| `m`              | assign issue to the current user              |
+| `i`              | change severity of issue                      |
+| `c`              | comment issue                                 |
+| `ctrl` + `enter` | submit comment                                |
+| `t`              | change tags of issue                          |
+
+## Measures Page
+
+| Shortcut | Action                                        |
+| -------- | --------------------------------------------- |
+| `↑` `↓`  | select files                                  |
+| `→`      | open file                                     |
+| `←`      | return back to the list                       |
+| `j` `k`  | switch between files                          |
+
+## Rules Page
+
+| Shortcut | Action                                        |
+| -------- | --------------------------------------------- |
+| `↑` `↓`  | navigate between rules                        |
+| `→`      | go from the list of rules to the rule details |
+| `←`      | return back to the list                       |
diff --git a/server/sonar-docs/src/pages/user-guide/metric-definitions.md b/server/sonar-docs/src/pages/user-guide/metric-definitions.md
new file mode 100644 (file)
index 0000000..9507636
--- /dev/null
@@ -0,0 +1,337 @@
+---
+title: Metric Definitions
+url: /user-guide/metric-definitions/
+---
+
+## Table of Contents
+
+   
+## Complexity
+**Complexity** (`complexity`)  
+It is the Cyclomatic Complexity calculated based on the number of paths through the code. Whenever the control flow of a function splits, the complexity counter gets incremented by one. Each function has a minimum complexity of 1. This calculation varies slightly by language because keywords and functionalities do.
+
+[[collapse]]
+| ## Language-specific details
+| Language | Notes
+| ---|---
+| ABAP | The following keywords increase the complexity by one: `AND`, `CATCH`, `CONTINUE`, `DO`, `ELSEIF`, `IF`, `LOOP`, `LOOPAT`, `OR`, `PROVIDE`, `SELECT…ENDSELECT`, `TRY`, `WHEN`, `WHILE`
+| C/C++/Objective-C | The complexity gets incremented by one for: function definitions, `while`, `do while`, `for`, `throw` statements, `switch`, `case`, `default`, `&&` operator, `||` operator, `?` ternary operator, `catch`, `break`, `continue`, `goto`.
+| COBOL | The following commands increase the complexity by one (except when they are used in a copybook): `ALSO`, `ALTER`, `AND`, `DEPENDING`, `END_OF_PAGE`, `ENTRY`, `EOP`, `EXCEPTION`, `EXIT`, `GOBACK`, `CONTINUE`, `IF`, `INVALID`, `OR`, `OVERFLOW`, `SIZE`, `STOP`, `TIMES`, `UNTIL`, `USE`, `VARYING`, `WHEN`, `EXEC CICS HANDLE`, `EXEC CICS LINK`, `EXEC CICS XCTL`, `EXEC CICS RETURN`
+| Java | Keywords incrementing the complexity: `if`, `for`, `while`, `case`, `catch`, `throw`, `&&`, `||`, `?`
+| JavaScript, PHP | Complexity is incremented by one for each: function (i.e non-abstract and non-anonymous constructors, functions, procedures or methods), `if`, short-circuit (AKA lazy) logical conjunction (`&&`), short-circuit (AKA lazy) logical disjunction (`||`), ternary conditional expressions, loop, `case` clause of a `switch` statement, `throw` and `catch` statement, `go to` statement (only for PHP)
+| PL/I | The following keywords increase the complexity by one: `PROC`, `PROCEDURE`, `GOTO`, `GO TO`, `DO`, `IF`, `WHEN`, `|`, `!`, `|=`, `!=`, `&`, `&=`
+| PL/SQL | The complexity gets incremented by one for: the main PL/SQL anonymous block (not inner ones), create procedure, create trigger, procedure_definition, basic loop statement, when_clause_statement (the “when” of simple_case_statement and searched_case_statement), continue_statement, cursor_for_loop_statement, continue_exit_when_clause (The “WHEN” part of the continue and exit statements), exception_handler (every individual “WHEN”), exit_statement, for_loop_statement, forall_statement, if_statement, elsif_clause, raise_statement, return_statement, while_loop_statement, and_expression (“and” reserved word used within PL/SQL expressions), or_expression (“or” reserved word used within PL/SQL expressions), when_clause_expression (the “when” of simple_case_expression and searched_case_expression)
+| VB.NET | The complexity gets incremented by one for: method or constructor declaration (Sub, Function), `AndAlso`, `Case`, `Continue`, `End`, `Error`, `Exit`, `If`, `Loop`, `On Error`, `GoTo`, `OrElse`, `Resume`, `Stop`, `Throw`, `Try`.
+
+**Cognitive Complexity** (`cognitive_complexity`)  
+How hard it is to understand the code's control flow. See [the Cognitive Complexity White Paper](https://www.sonarsource.com/resources/white-papers/cognitive-complexity.html) for a complete description of the mathematical model applied to compute this measure.
+
+---
+## Duplications
+**Duplicated blocks** (`duplicated_blocks`)  
+Number of duplicated blocks of lines.
+
+[[collapse]]
+| ## Language-specific details
+| For a block of code to be considered as duplicated:
+|
+| Non-Java projects:  
+| * There should be at least 100 successive and duplicated tokens.
+| * Those tokens should be spread at least on:
+| * 30 lines of code for COBOL
+| * 20 lines of code for ABAP
+| * 10 lines of code for other languages
+|
+|Java projects:  
+| There should be at least 10 successive and duplicated statements whatever the number of tokens and lines. Differences in indentation and in string literals are ignored while detecting duplications.
+**Duplicated files** (`duplicated_files`)  
+Number of files involved in duplications.
+
+**Duplicated lines** (`duplicated_lines`)  
+Number of lines involved in duplications.
+
+**Duplicated lines (%)** (`duplicated_lines_density`)  
+= `duplicated_lines` / `lines` * 100
+
+---
+## Issues
+**New issues** (`new_violations`)  
+Number of issues raised for the first time in the New Code period.
+
+**New xxx issues** (`new_xxx_violations`)  
+Number of issues of the specified severity raised for the first time in the New Code period, where xxx is one of: `blocker`, `critical`, `major`, `minor`, `info`.
+
+**Issues** (`violations`)  
+Total count of issues in all states.
+
+**xxx issues** (`xxx_issues`)  
+Total count of issues of the specified severity, where xxx is one of: `blocker`, `critical`, `major`, `minor`, `info`.
+
+**False positive issues** (`false_positive_issues`)  
+Total count of issues marked False Positive
+
+**Open issues** (`open_issues`)  
+Total count of issues in the Open state.
+
+**Confirmed issues** (`confirmed_issues`)  
+Total count of issues in the Confirmed state.
+
+**Reopened issues** (`reopened_issues`)  
+Total count of issues in the Reopened state
+
+---
+## Maintainability
+**Code Smells** (`code_smells`)   
+Total count of Code Smell issues.
+
+**New Code Smells** (`new_code_smells`)  
+Total count of Code Smell issues raised for the first time in the New Code period.
+
+**Maintainability Rating** (`sqale_rating`)  
+(Formerly the SQALE rating.)
+Rating given to your project related to the value of your Technical Debt Ratio. The default Maintainability Rating grid is:
+
+A=0-0.05, B=0.06-0.1, C=0.11-0.20, D=0.21-0.5, E=0.51-1
+
+The Maintainability Rating scale can be alternately stated by saying that if the outstanding remediation cost is:
+
+* <=5% of the time that has already gone into the application, the rating is A
+* between 6 to 10% the rating is a B
+* between 11 to 20% the rating is a C
+* between 21 to 50% the rating is a D
+* anything over 50% is an E
+
+**Technical Debt** (`sqale_index`)  
+Effort to fix all Code Smells. The measure is stored in minutes in the database. An 8-hour day is assumed when values are shown in days.
+
+**Technical Debt on New Code** (`new_technical_debt`)  
+Effort to fix all Code Smells raised for the first time in the New Code period.
+
+**Technical Debt Ratio** (`sqale_debt_ratio`)  
+Ratio between the cost to develop the software and the cost to fix it. The Technical Debt Ratio formula is:  
+       `Remediation cost / Development cost`  
+Which can be restated as:  
+       `Remediation cost / (Cost to develop 1 line of code * Number of lines of code)`  
+The value of the cost to develop a line of code is 0.06 days.
+
+**Technical Debt Ratio on New Code** (`new_sqale_debt_ratio`)  
+Ratio between the cost to develop the code changed in the New Code period and the cost of the issues linked to it.
+
+---
+## Quality Gates
+**Quality Gate Status** (`alert_status`)  
+State of the Quality Gate associated to your Project. Possible values are : `ERROR`, `WARN`, `OK`
+
+**Quality Gate Details** (`quality_gate_details`)  
+For all the conditions of your Quality Gate, you know which condition is failing and which is not.
+
+---
+## Reliability
+**Bugs** (`bugs`)  
+Number of bug issues.
+
+**New Bugs** (`new_bugs`)  
+Number of new bug issues.
+
+**Reliability Rating** (`reliability_rating`)  
+A = 0 Bugs  
+B = at least 1 Minor Bug  
+C = at least 1 Major Bug  
+D = at least 1 Critical Bug  
+E = at least 1 Blocker Bug  
+
+**Reliability remediation effort** (`reliability_remediation_effort`)  
+Effort to fix all bug issues. The measure is stored in minutes in the DB. An 8-hour day is assumed when values are shown in days.
+
+**Reliability remediation effort on new code** (`new_reliability_remediation_effort`)  
+Same as _Reliability remediation effort_ but on the code changed in the New Code period.
+
+---
+## Security
+**Vulnerabilities** (`vulnerabilities`)  
+Number of vulnerability issues.
+
+**New Vulnerabilities** (`new_vulnerabilities`)  
+Number of new vulnerability issues.
+
+**Security Rating** (`security_rating`)  
+A = 0 Vulnerabilities  
+B = at least 1 Minor Vulnerability  
+C = at least 1 Major Vulnerability  
+D = at least 1 Critical Vulnerability  
+E = at least 1 Blocker Vulnerability  
+
+**Security remediation effort** (`security_remediation_effort`)  
+Effort to fix all vulnerability issues. The measure is stored in minutes in the DB. An 8-hour day is assumed when values are shown in days.
+
+**Security remedation effort on new code** (`new_security_remediation_effort`)  
+Same as _Security remediation effort_ but on the code changed in the New Code period.
+
+---
+## Size
+**Classes** (`classes`)  
+Number of classes (including nested classes, interfaces, enums and annotations).
+
+**Comment lines** (`comment_lines`)  
+Number of lines containing either comment or commented-out code.
+
+Non-significant comment lines (empty comment lines, comment lines containing only special characters, etc.) do not increase the number of comment lines.
+
+The following piece of code contains 9 comment lines:
+```
+/**                                    +0 => empty comment line
+ *                                     +0 => empty comment line
+ * This is my documentation            +1 => significant comment
+ * although I don't                    +1 => significant comment
+ * have much                           +1 => significant comment
+ * to say                              +1 => significant comment
+ *                                     +0 => empty comment line
+ ***************************           +0 => non-significant comment
+ *                                     +0 => empty comment line
+ * blabla...                           +1 => significant comment
+ */                                    +0 => empty comment line
+  
+/**                                    +0 => empty comment line
+ * public String foo() {               +1 => commented-out code
+ *   System.out.println(message);      +1 => commented-out code
+ *   return message;                   +1 => commented-out code
+ * }                                   +1 => commented-out code
+ */                                    +0 => empty comment line
+ ```
+[[collapse]]
+| ## Language-specific details
+| Language | Note
+| ---|---
+| COBOL | Lines containing the following instructions are counted both as comments and lines of code: `AUTHOR`, `INSTALLATION`, `DATE-COMPILED`, `DATE-WRITTEN`, `SECURITY`.
+| Java | File headers are not counted as comment lines (becuase they usually define the license).
+
+**Comments (%)** (`comment_lines_density`)  
+Density of comment lines = Comment lines / (Lines of code + Comment lines) * 100
+
+With such a formula:
+* 50% means that the number of lines of code equals the number of comment lines  
+* 100% means that the file only contains comment lines  
+
+**Directories** (`directories`)  
+Number of directories.
+
+**Files** (`files`)  
+Number of files.
+
+**Lines** (`lines`)  
+Number of physical lines (number of carriage returns).
+
+**Lines of code** (`ncloc`)  
+Number of physical lines that contain at least one character which is neither a whitespace nor a tabulation nor part of a comment.
+[[collapse]]
+| ## Language-specific details
+| Language | Note
+| --- | ---
+| COBOL | Generated lines of code and pre-processing instructions (`SKIP1`, `SKIP2`, `SKIP3`, `COPY`, `EJECT`, `REPLACE`) are not counted as lines of code.
+
+**Lines of code per language** (`ncloc_language_distribution`)  
+Non Commenting Lines of Code Distributed By Language
+
+**Functions** (`functions`)  
+Number of functions. Depending on the language, a function is either a function or a method or a paragraph.
+[[collapse]]
+| ## Language-specific details
+| Language | Note
+| ---|---
+| COBOL | It is the number of paragraphs.
+| Java | Methods in anonymous classes are ignored.
+| VB.NET | Accesors are not considered to be methods.
+
+**Projects** (`projects`)  
+Number of projects in a Portfolio.
+
+**Statements** (`statements`)  
+Number of statements.
+
+---
+## Tests
+**Condition coverage** (`branch_coverage`)  
+On each line of code containing some boolean expressions, the condition coverage simply answers the following question: 'Has each boolean expression been evaluated both to true and false?'. This is the density of possible conditions in flow control structures that have been followed during unit tests execution.
+
+`Condition coverage = (CT + CF) / (2*B)`   
+where  
+* CT = conditions that have been evaluated to 'true' at least once
+* CF = conditions that have been evaluated to 'false' at least once
+* B = total number of conditions
+
+**Condition coverage on new code** (`new_branch_coverage`)  
+Identical to Condition coverage but restricted to new / updated source code.
+
+**Condition coverage hits** (`branch_coverage_hits_data`)  
+List of covered conditions.
+
+**Conditions by line** (`conditions_by_line`)  
+Number of conditions by line.
+
+**Covered conditions by line** (`covered_conditions_by_line`)  
+Number of covered conditions by line.
+
+**Coverage** (`coverage`)  
+It is a mix of Line coverage and Condition coverage. Its goal is to provide an even more accurate answer to the following question: How much of the source code has been covered by the unit tests?
+
+`Coverage = (CT + CF + LC)/(2*B + EL)`  
+where  
+* CT = conditions that have been evaluated to 'true' at least once
+* CF = conditions that have been evaluated to 'false' at least once
+* LC = covered lines = lines_to_cover - uncovered_lines
+* B = total number of conditions
+* EL = total number of executable lines (`lines_to_cover`)
+
+**Coverage on new code** (`new_coverage`)  
+Identical to Coverage but restricted to new / updated source code.
+
+**Line coverage (`line_coverage`)  
+On a given line of code, Line coverage simply answers the following question: Has this line of code been executed during the execution of the unit tests?. It is the density of covered lines by unit tests:
+
+`Line coverage = LC / EL`  
+where
+* LC = covered lines (`lines_to_cover` - `uncovered_lines`)
+* EL = total number of executable lines (`lines_to_cover`)
+
+**Line coverage on new code** (`new_line_coverage`)  
+Identical to Line coverage but restricted to new / updated source code.
+
+**Line coverage hits** (`coverage_line_hits_data`)  
+List of covered lines.
+
+**Lines to cover** (`lines_to_cover`)  
+Number of lines of code which could be covered by unit tests (for example, blank lines or full comments lines are not considered as lines to cover).
+
+**Lines to cover on new code** (`new_lines_to_cover`)  
+Identical to Lines to cover but restricted to new / updated source code.
+
+**Skipped unit tests** (`skipped_tests`)  
+Number of skipped unit tests.
+
+**Uncovered conditions** (`uncovered_conditions`)  
+Number of conditions which are not covered by unit tests.
+
+**Uncovered conditions on new code** (`new_uncovered_conditions`)  
+Identical to Uncovered conditions but restricted to new / updated source code.
+
+**Uncovered lines** (`uncovered_lines`)  
+Number of lines of code which are not covered by unit tests.
+
+**Uncovered lines on new code** (`new_uncovered_lines`)  
+Identical to Uncovered lines but restricted to new / updated source code.
+
+**Unit tests** (`tests`)  
+Number of unit tests.
+
+**Unit tests duration** (`test_execution_time`)  
+Time required to execute all the unit tests.
+
+**Unit test errors** (`test_errors`)  
+Number of unit tests that have failed.
+
+**Unit test failures** (`test_failures`)  
+Number of unit tests that have failed with an unexpected exception.
+
+**Unit test success density (%)** (`test_success_density`)  
+`Test success density = (Unit tests - (Unit test errors + Unit test failures)) / Unit tests * 100`
diff --git a/server/sonar-docs/src/pages/user-guide/quality-gates.md b/server/sonar-docs/src/pages/user-guide/quality-gates.md
new file mode 100644 (file)
index 0000000..8d422fb
--- /dev/null
@@ -0,0 +1,74 @@
+---
+title: Quality Gates
+url: /user-guide/quality-gates/
+---
+
+## Overview
+
+A quality gate is the best way to enforce a quality policy in your organization.
+It's there to answer ONE question: can I deliver my project to production today or not?
+
+In order to answer this question, you define a set of Boolean conditions based on measure thresholds against which projects are measured. For example:
+
+* No new blocker issues
+* Code coverage on new code greater than 80%
+* Etc.
+
+Ideally, all projects will be verified against the same quality gate, but that's not always practical. For instance, you may find that:
+
+* Technological implementation differs from one application to another (you might not require the same code coverage on new code for Web or Java applications).
+* You want to ensure stronger requirements on some of your applications (internal frameworks for example).
+* Etc.
+
+Which is why you can define as many quality gates as you wish. Quality Gates are defined and managed in the **[Quality Gates](/#sonarqube#/quality_gates)** page found in the top menu.
+
+## Use the Best Quality Gate Configuration
+
+The quality gate "Sonar way" is provided by SonarSource, activated by default and considered as built-in and so read-only. It represents our view of the best way to implement the [Fixing the Water Leak](/user-guide/fixing-the-water-leak/) concept. At each SonarQube release, we adjust automatically this default quality gate according to SonarQube's capabilities.
+
+Three metrics allow you to enforce a given Rating of Reliability, Security and Maintainability, not just overall but also on new code. These metrics are recommended and come as part of the default quality gate. We strongly advise you to adjust your own quality gates to use them to make feedback more clear to your developers looking at their quality gate on their project page.
+
+Don't forget also that quality gate conditions must use differential values. There is no point for example to check an absolute value such as : Number of Lines of Code is greater than 1000.
+
+### Recommended Quality Gate
+
+The `Sonar way` Built-in quality gate is recommended for most projects. If focuses on keeping new code clean, rather than spending a lot of effort remediating old code. Out of the box, it's already set as the default profile.
+
+## Quality Gate Status
+
+The current status is displayed prominently at the top of the Project Page:
+
+![Quality Gate Status](/images/quality-gate-status.jpeg)
+
+## Getting Notified When a Quality Gate Fails
+
+Thanks to the notification mechanism, users can be notified when a quality gate fails. To do so, subscribe to the **New quality gate status** notification either for all projects or a set of projects you're interested in.
+
+## Security
+
+Quality Gates can be accessed by any user (even anonymous users). All users can view every aspect of a quality gate.
+
+To make changes (create, edit or delete) users must be granted the **Administer Quality Profiles and Gates** permission.
+
+A **project administrator** can choose which quality gates his/her project is associated with. See Project Settings for more.
+
+## Defining Quality Gates
+
+To manage quality gates, go to **[Quality Gates](/#sonarqube#/quality_gates)** (top menu bar).
+
+Each Quality Gate condition is a combination of:
+
+* measure
+* period: **Value** (to date) or **New Code** (differential value over the New Code period)
+* comparison operator
+* warning value (optional)
+* error value (optional)
+
+For instance, a condition might be:
+
+* measure: Blocker issue
+* period: Value
+* comparison operator: >
+* error value: 0
+
+Which can be stated as: No blocker issues.
diff --git a/server/sonar-docs/src/pages/user-guide/security-reports.md b/server/sonar-docs/src/pages/user-guide/security-reports.md
new file mode 100644 (file)
index 0000000..f25d69c
--- /dev/null
@@ -0,0 +1,40 @@
+---
+title: Security Reports
+url: /user-guide/security-reports/
+---
+
+## What do the Security Reports show?
+The Security Reports are designed to quickly give you the big picture on your application's security, with breakdowns of just where you stand in regard to each of the [OWASP Top 10](https://www.owasp.org/index.php/Top_10-2017_Top_10), and [SANS Top 25](https://www.sans.org/top25-software-errors) categories, and [CWE](http://cwe.mitre.org/)-specific details.
+The Security Reports are fed by the analyzers, which rely on the rules activated in your quality profiles to raise security issues. If there are no rules corresponding to a given OWASP category activated in your Quality Profile, you will get no issues linked to that specific category and the rating displayed will be A. That won't mean you are safe for that category, but that you need to activate more rules (assuming some exist).
+
+## What's the difference between a Hotspot and a Vulnerability?
+Vulnerabilities are points in the code which are open to attack.
+Security Hotspots are security-sensitive pieces of code that should be carefully reviewed by someone with a security auditor hat. This person can be:
+* a member of the development team who is more sensitive to security problems 
+* someone outside the development team contracted for the purpose of reviewing these Hotspots.
+
+The main goal of Security Hotspots is to help focus the efforts of the security auditors who manually review application source code. The second goal is to educate developers and to increase their security-awareness. 
+Having a Hotspot in your application does not mean there is a problem. What it does mean is that a human, preferably a security auditor/expert should look over the code to see if the sensitive piece of code is being used in the safest manner.
+
+## Why don't I see any Hotspots?
+They are three reasons you might not see any Hotspots:
+* it is possible you really have none of them because the code has been written without using any security-sensitive API. 
+* it is possible that Hotspot rules are available, but not yet activated in your Quality Profile, and so naturally no issues are raised
+* it is more likely that the analyzer for the langauge you're using does not yet offer Hotspot rules, and so it doesn't raise any Hotspots regardless of the quality of how many are actually there, but this last option will disappear over time.
+
+## Why don't I see any Vulnerabilities?
+You might not see any Vulnerabilities for more or less the same reasons as for Hotspots, but it may be more surprising for Vulnerabilities because you may see some Vulnerabilities reported in the Project homepage, while there are none in the Security Reports. This is because the language analyzer may not yet provide the "Security Standards" metadata required for issues to be visible on the Security Reports. This metadata is basically the link between a Rule (and its issues) and the "OWASP Top 10" or "SANS Top 25" categories. Without this link, there is no way to associate an already existing Vulnerability to the Security Standard categories and so to display security issues correctly in the reports. Every analyzer version released by SonarSource after July 2018 should feed the "Security Standards" and be compatible with the Hotspot issue type. 
+
+## I'm a developer. Should I care about Hotspots?
+Probably not. Hotspots, as such, aren't really actionable. They simply mark *potential* problems, so there's really nothing to do immediately on the code. That's why you don't receive notifications when Hotspot issues are raised, and why Hotspots aren't shown in the Issues page by default.
+
+## What if my Hotspot really marks a Vulnerability?
+If you look at the code where a Hotspot is raised and realize that there really is a problem, click on the current status (probably `Open`) to register that you've *Detect*ed a Vulnerability at that point in the code. Once you do, it will be converted to a Vulnerability, and the developer who last touched the line will receive "new issue" notifications (if she's signed up to get them).
+
+## What happens after my Hotspot becomes a Vulnerability?
+Once you've *Detect*ed that there really is a problem at a Hotspot location, it will be assigned to the appropriate developer, who will make a fix, and must then `Request Review` *via the UI*. That request moves the issue from Vulnerability back to Hotspot. From there, it's up to the security auditor to either `Accept` or `Reject` the fix. Accepting the fix will mark it `Won't Fix`, and rejecting it will turn it back into a Vulnerability, putting it back in the developer's queue.
+
+## What does it mean for a Hotspot to be marked "Won't Fix"?
+The `Won't Fix` designation is used to indicate that a Hotspot has been reviewed and there is no way, as of now, to exploit this piece of code to create an attack.
+
+
diff --git a/server/sonar-docs/src/pages/user-guide/user-account.md b/server/sonar-docs/src/pages/user-guide/user-account.md
new file mode 100644 (file)
index 0000000..935823e
--- /dev/null
@@ -0,0 +1,20 @@
+---
+title: User Account
+url: /user-guide/user-account/
+---
+
+As a {instance} user you have your own space where you can see the things that are relevant to you:
+
+## Home Page
+
+It gives you a summary of:
+
+* your Groups
+* your SCM accounts
+
+## Security
+
+In addition to being able to change your password, if your instance is not using a 3rd party authentication mechanism such as LDAP or any OAuth provider (GitHub, Google Account, ...), you can manage your own authentication tokens.
+
+You can create as many Token as you want. Once a Token is created, you can use it to publish analysis to a project where you have the execute analysis permission.
+
diff --git a/server/sonar-docs/src/pages/webhooks.md b/server/sonar-docs/src/pages/webhooks.md
deleted file mode 100644 (file)
index 2c09225..0000000
+++ /dev/null
@@ -1,118 +0,0 @@
----
-title: Webhooks
----
-
-Webhooks notify external services when a project analysis is complete. An HTTP POST request including a JSON payload is sent to each URL. URLs may be specified at both the project and global levels. Project-level specification does not replace global-level webhooks. All hooks at both levels are called.
-Plugins
-
-The HTTP(S) call:
-
-* is made regardless of the status of the Background Task
-* includes a JSON document as payload, using the POST method
-* has a content type of "application/json", with UTF-8 encoding
-
-## Configuration
-
-You can configure up to 10 webhooks in in **Administration > Webhooks**.
-
-An additional set of 10 webhooks can be configured at the global level in **Administration > Configuration > Webhooks**.
-
-If configured, all 20 will be executed.
-
-## Delivery and Payload
-
-### Delivery
-
-The Webhook administration console shows the result and timestamp of the most recent delivery of each webhook with the payload available via the list icon. Results and payloads of earlier deliveries are available from the tools menu to the right of each webhook
-
-Response records are purged after 30 days.
-
-The URL must respond within 10 seconds or the delivery is marked as failed.
-
-### Payload
-
-An HTTP header "X-SonarQube-Project" with the project key is sent to allow quick identification of the project involved
-
-The Payload is a JSON document which includes:
-
-* when the analysis was performed: see "analysedAt"
-* the identification of the project analyzed: see "project"
-* each Quality Gate criterion checked and its status: see "qualityGate"
-* the Quality Gate status of the project: see "qualityGate.status"
-* the status and the identifier of the Background Task : see "status" and "taskId"
-* user-specified properties: see "properties"
-
-#### Example
-
-```
-{
-    "analysedAt": "2016-11-18T10:46:28+0100",
-    "project": {
-        "key": "org.sonarqube:example",
-        "name": "Example"
-    },
-    "properties": {
-    },
-    "qualityGate": {
-        "conditions": [
-            {
-                "errorThreshold": "1",
-                "metric": "new_security_rating",
-                "onLeakPeriod": true,
-                "operator": "GREATER_THAN",
-                "status": "OK",
-                "value": "1"
-            },
-            {
-                "errorThreshold": "1",
-                "metric": "new_reliability_rating",
-                "onLeakPeriod": true,
-                "operator": "GREATER_THAN",
-                "status": "OK",
-                "value": "1"
-            },
-            {
-                "errorThreshold": "1",
-                "metric": "new_maintainability_rating",
-                "onLeakPeriod": true,
-                "operator": "GREATER_THAN",
-                "status": "OK",
-                "value": "1"
-            },
-            {
-                "errorThreshold": "80",
-                "metric": "new_coverage",
-                "onLeakPeriod": true,
-                "operator": "LESS_THAN",
-                "status": "NO_VALUE"
-            }
-        ],
-        "name": "SonarQube way",
-        "status": "OK"
-    },
-    "serverUrl": "http://localhost:9000",
-    "status": "SUCCESS",
-    "taskId": "AVh21JS2JepAEhwQ-b3u"
-}
-```
-
-## Additional parameters
-
-A basic authentication mechanism is supported by providing user/password in the URL of the Webhook such as `https://myLogin:myPassword@my_server/foo`.
-
-If you provide additional properties to your SonarQube Scanner using the pattern `sonar.analysis.*`, these properties will be automatically added to the section "properties" of the payload.
-
-For example these additional parameters:
-
-```
-sonar-scanner -Dsonar.analysis.scmRevision=628f5175ada0d685fd7164baa7c6382c1f25cab4 -Dsonar.analysis.buildNumber=12345
-```
-
-Would add this to the payload:
-
-```
-"properties": {
-  "sonar.analysis.scmRevision": "628f5175ada0d685fd7164baa7c6382c1f25cab4",
-  "sonar.analysis.buildNumber": "12345"
-}
-```
index 75b02494ed38909670821c2fc32d4c3a23a47097..ad61a0fb70ccfb81a44b091eb303a33909bd924d 100644 (file)
@@ -51,10 +51,11 @@ export default class Page extends React.PureComponent {
     htmlWithInclusions = removeTableOfContents(htmlWithInclusions);
     htmlWithInclusions = createAnchorForHeadings(htmlWithInclusions, realHeadingsList);
     htmlWithInclusions = replaceDynamicLinks(htmlWithInclusions);
+    htmlWithInclusions = replaceInstanceTag(htmlWithInclusions);
 
     return (
       <div css={{ paddingTop: 24, paddingBottom: 24 }}>
-        <Helmet title={page.frontmatter.title}>
+        <Helmet title={page.frontmatter.title || 'Documentation'}>
           <html lang="en" />
         </Helmet>
         <HeaderList headers={realHeadingsList} />
@@ -99,6 +100,10 @@ export const query = graphql`
   }
 `;
 
+function replaceInstanceTag(content) {
+  return content.replace('{instance}', 'SonarQube');
+}
+
 function replaceDynamicLinks(content) {
   const version = process.env.GATSBY_DOCS_VERSION || '';
   const usePrefix = process.env.GATSBY_USE_PREFIX === '1';
@@ -115,12 +120,6 @@ function replaceDynamicLinks(content) {
     '<a href="http$1" target="_blank">$2</a>'
   );
 
-  // Add trailing slash to local link
-  content = content.replace(
-    /\<a href="(?!http)(.*)(?!\/)"\>(.*)\<\/a\>/gim,
-    '<a href="$1/">$2</a>'
-  );
-
   return content.replace(
     /\<a href="(.*)\/#(?:sonarqube|sonarcloud|sonarqube-admin)#.*"\>(.*)\<\/a\>/gim,
     '$2'
diff --git a/server/sonar-docs/static/README.md b/server/sonar-docs/static/README.md
new file mode 100644 (file)
index 0000000..4e3c36a
--- /dev/null
@@ -0,0 +1,24 @@
+These three `*.NavigationTree.json` files control the navigation trees of the three versions of the documentation.
+
+Each one contains a JSON array. Array elements may either be:
+
+* a path string
+* a node
+
+**Nodes contain two elements:**
+* title - string. This is the "parent" node name to be used in the navigation tree
+* children - array of path strings
+
+**Paths**
+* begin with '/'
+* end with '/'
+* match the `url:` value of a page. 
+* **do not** include the trailing `.md` in the file name
+
+**What is the URL value of a page?**  
+The url value can be implicitly defined by the document's path under the `pages` directory, or explicitly overridden by in the page metadata by setting `url: [path here]`.
+
+Paths must always start and end with '/'. That includes:
+* page metadata
+* navigation tree files
+* links between pages
diff --git a/server/sonar-docs/static/SonarCloudNavigationTree.json b/server/sonar-docs/static/SonarCloudNavigationTree.json
new file mode 100644 (file)
index 0000000..77f9677
--- /dev/null
@@ -0,0 +1,53 @@
+[
+  "/privacy/",
+  "/security/",
+  "/sonarcloud-pricing/",
+  "/analyze-a-project/",
+  {
+    "title": "Analyzing Source Code",
+    "children": [
+      "/analysis/overview/",
+      "/analysis/background-tasks/",
+      "/analysis/generic-issue/",
+      "/analysis/generic-test/",
+      "/analysis/pull-request/",
+      "/analysis/scm-integration/"
+    ]
+  },
+  {
+    "title": "Integrations",
+    "children": ["/integrations/bitbucketcloud/", "/integrations/github/", "/integrations/vsts/"]
+  },
+  {
+    "title": "Branches",
+    "children": [
+      "/branches/overview/",
+      "/branches/short-lived-branches/",
+      "/branches/long-lived-branches/",
+      "/branches/branches-faq/"
+    ]
+  },
+  {
+    "title": "User Guide",
+    "children": [
+      "/user-guide/fixing-the-water-leak/",
+      "/user-guide/keyboard-shortcuts/",
+      "/user-guide/quality-gates/",
+      "/user-guide/metric-definitions/",
+      "/user-guide/security-reports/",
+      "/user-guide/user-account/"
+    ]
+  },
+  {
+    "title": "Project Administration",
+    "children": ["/project-administration/webhooks/"]
+  },
+  {
+    "title": "Organizations",
+    "children": [
+      "/organizations/overview/",
+      "/organizations/manage-team/",
+      "/organizations/organization-visibility/"
+    ]
+  }
+]
diff --git a/server/sonar-docs/static/SonarQubeNavigationTree.json b/server/sonar-docs/static/SonarQubeNavigationTree.json
new file mode 100644 (file)
index 0000000..1d047f5
--- /dev/null
@@ -0,0 +1,46 @@
+[
+  {
+    "title": "Analyzing Source Code",
+    "children": [
+      "/analysis/overview/",
+      "/analysis/background-tasks/",
+      "/analysis/generic-issue/",
+      "/analysis/generic-test/",
+      "/analysis/pull-request/",
+      "/analysis/scm-integration/"
+    ]
+  },
+  {
+    "title": "Branches",
+    "children": [
+      "/branches/overview/",
+      "/branches/short-lived-branches/",
+      "/branches/long-lived-branches/",
+      "/branches/branches-faq/"
+    ]
+  },
+  {
+    "title": "User Guide",
+    "children": [
+      "/user-guide/fixing-the-water-leak/",
+      "/user-guide/keyboard-shortcuts/",
+      "/user-guide/quality-gates/",
+      "/user-guide/metric-definitions/",
+      "/user-guide/security-reports/",
+      "/user-guide/user-account/"
+    ]
+  },
+  {
+    "title": "Project Administration",
+    "children": ["/project-administration/webhooks/"]
+  },
+  {
+    "title": "Instance Administration",
+    "children": [
+      "/instance-administration/custom-measures/",
+      "/instance-administration/housekeeping/",
+      "/instance-administration/look-and-feel/",
+      "/instance-administration/quality-profiles/"
+    ]
+  }
+]
diff --git a/server/sonar-docs/static/StaticNavigationTree.json b/server/sonar-docs/static/StaticNavigationTree.json
new file mode 100644 (file)
index 0000000..ffbac95
--- /dev/null
@@ -0,0 +1,54 @@
+[
+  {
+    "title": "Requirements",
+    "children": []
+  },
+  {
+    "title": "Setup and Upgrade",
+    "children": []
+  },
+  {
+    "title": "Analyzing Source Code",
+    "children": [
+      "/analysis/overview/",
+      "/analysis/background-tasks/",
+      "/analysis/generic-issue/",
+      "/analysis/generic-test/",
+      "/analysis/pull-request/",
+      "/analysis/scm-integration/"
+    ]
+  },
+  {
+    "title": "Branches",
+    "children": [
+      "/branches/overview/",
+      "/branches/short-lived-branches/",
+      "/branches/long-lived-branches/",
+      "/branches/branches-faq/"
+    ]
+  },
+  {
+    "title": "User Guide",
+    "children": [
+      "/user-guide/fixing-the-water-leak/",
+      "/user-guide/keyboard-shortcuts/",
+      "/user-guide/quality-gates/",
+      "/user-guide/metric-definitions/",
+      "/user-guide/security-reports/",
+      "/user-guide/user-account/"
+    ]
+  },
+  {
+    "title": "Project Administration",
+    "children": ["/project-administration/webhooks/"]
+  },
+  {
+    "title": "Instance Administration",
+    "children": [
+      "/instance-administration/custom-measures/",
+      "/instance-administration/housekeeping/",
+      "/instance-administration/look-and-feel/",
+      "/instance-administration/quality-profiles/"
+    ]
+  }
+]
index 25a3cdbb78dc5f2732465c0c4b37f977c9512d42..e665dd4a216607836086fc328409de3b76eda2ec 100644 (file)
@@ -9,6 +9,20 @@
     elasticlunr "^0.9.5"
     graphql "^0.11.7"
 
+"@babel/code-frame@^7.0.0-beta.35":
+  version "7.0.0"
+  resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.0.0.tgz#06e2ab19bdb535385559aabb5ba59729482800f8"
+  dependencies:
+    "@babel/highlight" "^7.0.0"
+
+"@babel/highlight@^7.0.0":
+  version "7.0.0"
+  resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.0.0.tgz#f710c38c8d458e6dd9a201afb637fcb781ce99e4"
+  dependencies:
+    chalk "^2.0.0"
+    esutils "^2.0.2"
+    js-tokens "^4.0.0"
+
 "@types/configstore@^2.1.1":
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/@types/configstore/-/configstore-2.1.1.tgz#cd1e8553633ad3185c3f2f239ecff5d2643e92b6"
@@ -25,7 +39,7 @@
   version "0.0.4"
   resolved "https://registry.yarnpkg.com/@types/get-port/-/get-port-0.0.4.tgz#eb6bb7423d9f888b632660dc7d2fd3e69a35643e"
 
-"@types/glob@^5.0.30":
+"@types/glob@*", "@types/glob@^5.0.30":
   version "5.0.35"
   resolved "https://registry.yarnpkg.com/@types/glob/-/glob-5.0.35.tgz#1ae151c802cece940443b5ac246925c85189f32a"
   dependencies:
   version "0.0.32"
   resolved "https://registry.yarnpkg.com/@types/tmp/-/tmp-0.0.32.tgz#0d3cb31022f8427ea58c008af32b80da126ca4e3"
 
+abab@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.0.tgz#aba0ab4c5eee2d4c79d3487d85450fb2376ebb0f"
+
 abbrev@1:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
@@ -89,10 +107,20 @@ accepts@^1.3.0, accepts@~1.3.4, accepts@~1.3.5:
     mime-types "~2.1.18"
     negotiator "0.6.1"
 
+acorn-globals@^4.1.0:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-4.1.0.tgz#ab716025dbe17c54d3ef81d32ece2b2d99fe2538"
+  dependencies:
+    acorn "^5.0.0"
+
 acorn@^3.0.0:
   version "3.3.0"
   resolved "https://registry.yarnpkg.com/acorn/-/acorn-3.3.0.tgz#45e37fb39e8da3f25baee3ff5369e2bb5f22017a"
 
+acorn@^5.0.0, acorn@^5.5.3:
+  version "5.7.3"
+  resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.3.tgz#67aa231bf8812974b85235a96771eb6bd07ea279"
+
 address@1.0.3, address@^1.0.1:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/address/-/address-1.0.3.tgz#b5f50631f8d6cec8bd20c963963afb55e06cbce9"
@@ -101,7 +129,7 @@ after@0.8.2:
   version "0.8.2"
   resolved "https://registry.yarnpkg.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f"
 
-ajv@^5.1.0:
+ajv@^5.1.0, ajv@^5.3.0:
   version "5.5.2"
   resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.2.tgz#73b5eeca3fab653e3d3f9422b341ad42205dc965"
   dependencies:
@@ -152,7 +180,7 @@ ansi-styles@^2.2.1:
   version "2.2.1"
   resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
 
-ansi-styles@^3.2.1:
+ansi-styles@^3.2.0, ansi-styles@^3.2.1:
   version "3.2.1"
   resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
   dependencies:
@@ -173,6 +201,19 @@ anymatch@^1.3.0:
     micromatch "^2.1.5"
     normalize-path "^2.0.0"
 
+anymatch@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb"
+  dependencies:
+    micromatch "^3.1.4"
+    normalize-path "^2.1.1"
+
+append-transform@^0.4.0:
+  version "0.4.0"
+  resolved "https://registry.yarnpkg.com/append-transform/-/append-transform-0.4.0.tgz#d76ebf8ca94d276e247a36bad44a4b74ab611991"
+  dependencies:
+    default-require-extensions "^1.0.0"
+
 aproba@^1.0.3:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a"
@@ -225,6 +266,10 @@ array-each@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/array-each/-/array-each-1.0.1.tgz#a794af0c05ab1752846ee753a1f211a05ba0c44f"
 
+array-equal@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/array-equal/-/array-equal-1.0.0.tgz#8c2a5ef2472fd9ea742b04c77a75093ba2757c93"
+
 array-filter@~0.0.0:
   version "0.0.1"
   resolved "https://registry.yarnpkg.com/array-filter/-/array-filter-0.0.1.tgz#7da8cf2e26628ed732803581fd21f67cacd2eeec"
@@ -275,6 +320,10 @@ arraybuffer.slice@~0.0.7:
   version "0.0.7"
   resolved "https://registry.yarnpkg.com/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz#3bbc4275dd584cc1b10809b89d4e8b63a69e7675"
 
+arrify@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d"
+
 asap@~2.0.3:
   version "2.0.6"
   resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46"
@@ -305,6 +354,10 @@ assign-symbols@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367"
 
+astral-regex@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9"
+
 async-each@^1.0.0, async-each@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d"
@@ -327,6 +380,12 @@ async@^2.1.2:
   dependencies:
     lodash "^4.14.0"
 
+async@^2.1.4, async@^2.5.0:
+  version "2.6.1"
+  resolved "https://registry.yarnpkg.com/async/-/async-2.6.1.tgz#b245a23ca71930044ec53fa46aa00a3e87c6a610"
+  dependencies:
+    lodash "^4.17.10"
+
 async@~0.2.6:
   version "0.2.10"
   resolved "https://registry.yarnpkg.com/async/-/async-0.2.10.tgz#b6bbe0b0674b9d719708ca38de8c237cb526c3d1"
@@ -358,6 +417,10 @@ aws4@^1.6.0:
   version "1.7.0"
   resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.7.0.tgz#d4d0e9b9dbfca77bf08eeb0a8a471550fe39e289"
 
+aws4@^1.8.0:
+  version "1.8.0"
+  resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f"
+
 babel-cli@^6.26.0:
   version "6.26.0"
   resolved "https://registry.yarnpkg.com/babel-cli/-/babel-cli-6.26.0.tgz#502ab54874d7db88ad00b887a06383ce03d002f1"
@@ -387,6 +450,30 @@ babel-code-frame@6.26.0, babel-code-frame@^6.11.0, babel-code-frame@^6.22.0, bab
     esutils "^2.0.2"
     js-tokens "^3.0.2"
 
+babel-core@^6.0.0:
+  version "6.26.3"
+  resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.26.3.tgz#b2e2f09e342d0f0c88e2f02e067794125e75c207"
+  dependencies:
+    babel-code-frame "^6.26.0"
+    babel-generator "^6.26.0"
+    babel-helpers "^6.24.1"
+    babel-messages "^6.23.0"
+    babel-register "^6.26.0"
+    babel-runtime "^6.26.0"
+    babel-template "^6.26.0"
+    babel-traverse "^6.26.0"
+    babel-types "^6.26.0"
+    babylon "^6.18.0"
+    convert-source-map "^1.5.1"
+    debug "^2.6.9"
+    json5 "^0.5.1"
+    lodash "^4.17.4"
+    minimatch "^3.0.4"
+    path-is-absolute "^1.0.1"
+    private "^0.1.8"
+    slash "^1.0.0"
+    source-map "^0.5.7"
+
 babel-core@^6.24.1, babel-core@^6.26.0:
   version "6.26.0"
   resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.26.0.tgz#af32f78b31a6fcef119c87b0fd8d9753f03a0bb8"
@@ -411,7 +498,7 @@ babel-core@^6.24.1, babel-core@^6.26.0:
     slash "^1.0.0"
     source-map "^0.5.6"
 
-babel-generator@^6.24.1, babel-generator@^6.26.0:
+babel-generator@^6.18.0, babel-generator@^6.24.1, babel-generator@^6.26.0:
   version "6.26.1"
   resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.26.1.tgz#1844408d3b8f0d35a404ea7ac180f087a601bd90"
   dependencies:
@@ -550,6 +637,13 @@ babel-helpers@^6.24.1:
     babel-runtime "^6.22.0"
     babel-template "^6.24.1"
 
+babel-jest@^23.6.0:
+  version "23.6.0"
+  resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-23.6.0.tgz#a644232366557a2240a0c083da6b25786185a2f1"
+  dependencies:
+    babel-plugin-istanbul "^4.1.6"
+    babel-preset-jest "^23.2.0"
+
 babel-loader@^6.0.0:
   version "6.4.1"
   resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-6.4.1.tgz#0b34112d5b0748a8dcdbf51acf6f9bd42d50b8ca"
@@ -575,6 +669,19 @@ babel-plugin-check-es2015-constants@^6.22.0, babel-plugin-check-es2015-constants
   dependencies:
     babel-runtime "^6.22.0"
 
+babel-plugin-istanbul@^4.1.6:
+  version "4.1.6"
+  resolved "http://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-4.1.6.tgz#36c59b2192efce81c5b378321b74175add1c9a45"
+  dependencies:
+    babel-plugin-syntax-object-rest-spread "^6.13.0"
+    find-up "^2.1.0"
+    istanbul-lib-instrument "^1.10.1"
+    test-exclude "^4.2.1"
+
+babel-plugin-jest-hoist@^23.2.0:
+  version "23.2.0"
+  resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-23.2.0.tgz#e61fae05a1ca8801aadee57a6d66b8cefaf44167"
+
 babel-plugin-syntax-async-functions@^6.8.0:
   version "6.13.0"
   resolved "https://registry.yarnpkg.com/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz#cad9cad1191b5ad634bf30ae0872391e0647be95"
@@ -623,7 +730,7 @@ babel-plugin-syntax-jsx@^6.3.13, babel-plugin-syntax-jsx@^6.8.0:
   version "6.18.0"
   resolved "https://registry.yarnpkg.com/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz#0af32a9a6e13ca7a3fd5069e62d7b0f58d0d8946"
 
-babel-plugin-syntax-object-rest-spread@^6.8.0:
+babel-plugin-syntax-object-rest-spread@^6.13.0, babel-plugin-syntax-object-rest-spread@^6.8.0:
   version "6.13.0"
   resolved "https://registry.yarnpkg.com/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz#fd6536f2bce13836ffa3a5458c4903a597bb3bf5"
 
@@ -1077,6 +1184,13 @@ babel-preset-flow@^6.23.0:
   dependencies:
     babel-plugin-transform-flow-strip-types "^6.22.0"
 
+babel-preset-jest@^23.2.0:
+  version "23.2.0"
+  resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-23.2.0.tgz#8ec7a03a138f001a1a8fb1e8113652bf1a55da46"
+  dependencies:
+    babel-plugin-jest-hoist "^23.2.0"
+    babel-plugin-syntax-object-rest-spread "^6.13.0"
+
 babel-preset-react@^6.24.1:
   version "6.24.1"
   resolved "https://registry.yarnpkg.com/babel-preset-react/-/babel-preset-react-6.24.1.tgz#ba69dfaea45fc3ec639b6a4ecea6e17702c91380"
@@ -1142,7 +1256,7 @@ babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.23.0, babel-runti
     core-js "^2.4.0"
     regenerator-runtime "^0.11.0"
 
-babel-template@^6.24.1, babel-template@^6.26.0, babel-template@^6.9.0:
+babel-template@^6.16.0, babel-template@^6.24.1, babel-template@^6.26.0, babel-template@^6.9.0:
   version "6.26.0"
   resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.26.0.tgz#de03e2d16396b069f46dd9fff8521fb1a0e35e02"
   dependencies:
@@ -1152,7 +1266,7 @@ babel-template@^6.24.1, babel-template@^6.26.0, babel-template@^6.9.0:
     babylon "^6.18.0"
     lodash "^4.17.4"
 
-babel-traverse@^6.24.1, babel-traverse@^6.26.0:
+babel-traverse@^6.0.0, babel-traverse@^6.18.0, babel-traverse@^6.24.1, babel-traverse@^6.26.0:
   version "6.26.0"
   resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.26.0.tgz#46a9cbd7edcc62c8e5c064e2d2d8d0f4035766ee"
   dependencies:
@@ -1166,7 +1280,7 @@ babel-traverse@^6.24.1, babel-traverse@^6.26.0:
     invariant "^2.2.2"
     lodash "^4.17.4"
 
-babel-types@^6.19.0, babel-types@^6.24.1, babel-types@^6.26.0:
+babel-types@^6.0.0, babel-types@^6.18.0, babel-types@^6.19.0, babel-types@^6.24.1, babel-types@^6.26.0:
   version "6.26.0"
   resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.26.0.tgz#a3b073f94ab49eb6fa55cd65227a334380632497"
   dependencies:
@@ -1396,6 +1510,16 @@ brorand@^1.0.1:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f"
 
+browser-process-hrtime@^0.1.2:
+  version "0.1.2"
+  resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-0.1.2.tgz#425d68a58d3447f02a04aa894187fce8af8b7b8e"
+
+browser-resolve@^1.11.3:
+  version "1.11.3"
+  resolved "https://registry.yarnpkg.com/browser-resolve/-/browser-resolve-1.11.3.tgz#9b7cbb3d0f510e4cb86bdbd796124d28b5890af6"
+  dependencies:
+    resolve "1.1.7"
+
 browserify-aes@0.4.0:
   version "0.4.0"
   resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-0.4.0.tgz#067149b668df31c4b58533e02d01e806d8608e2c"
@@ -1495,6 +1619,10 @@ buffer-fill@^0.1.0:
   version "0.1.1"
   resolved "https://registry.yarnpkg.com/buffer-fill/-/buffer-fill-0.1.1.tgz#76d825c4d6e50e06b7a31eb520c04d08cc235071"
 
+buffer-from@^1.0.0:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
+
 buffer-peek-stream@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/buffer-peek-stream/-/buffer-peek-stream-1.0.1.tgz#53b47570a1347787c5bad4ca2ca3021f9d8b3cfd"
@@ -1545,6 +1673,10 @@ callsite@1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/callsite/-/callsite-1.0.0.tgz#280398e5d664bd74038b6f0905153e6e8af1bc20"
 
+callsites@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/callsites/-/callsites-2.0.0.tgz#06eb84f00eea413da86affefacbffb36093b3c50"
+
 camelcase@5.0.0:
   version "5.0.0"
   resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.0.0.tgz#03295527d58bd3cd4aa75363f35b2e8d97be2f42"
@@ -1578,6 +1710,12 @@ caniuse-lite@^1.0.30000792:
   version "1.0.30000830"
   resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000830.tgz#cb96b8a2dd3cbfe04acea2af3c4e894249095328"
 
+capture-exit@^1.2.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/capture-exit/-/capture-exit-1.2.0.tgz#1c5fcc489fd0ab00d4f1ac7ae1072e3173fbab6f"
+  dependencies:
+    rsvp "^3.3.3"
+
 capture-stack-trace@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/capture-stack-trace/-/capture-stack-trace-1.0.0.tgz#4a6fa07399c26bba47f0b2496b4d0fb408c5550d"
@@ -1756,6 +1894,14 @@ cliui@^3.2.0:
     strip-ansi "^3.0.1"
     wrap-ansi "^2.0.0"
 
+cliui@^4.0.0:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/cliui/-/cliui-4.1.0.tgz#348422dbe82d800b3022eef4f6ac10bf2e4d1b49"
+  dependencies:
+    string-width "^2.1.1"
+    strip-ansi "^4.0.0"
+    wrap-ansi "^2.0.0"
+
 clone@2.1.1:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.1.tgz#d217d1e961118e3ac9a4b8bba3285553bf647cdb"
@@ -1836,7 +1982,7 @@ colors@~1.1.2:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63"
 
-combined-stream@1.0.6, combined-stream@~1.0.5:
+combined-stream@1.0.6, combined-stream@~1.0.5, combined-stream@~1.0.6:
   version "1.0.6"
   resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.6.tgz#723e7df6e801ac5613113a7e445a9b69cb632818"
   dependencies:
@@ -1862,6 +2008,10 @@ commander@^2.11.0:
   version "2.15.1"
   resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f"
 
+commander@~2.17.1:
+  version "2.17.1"
+  resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf"
+
 common-tags@0.1.1:
   version "0.1.1"
   resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-0.1.1.tgz#d893486ecc6df22cffe6c393c88c12f71e7e8871"
@@ -1976,6 +2126,12 @@ convert-hrtime@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/convert-hrtime/-/convert-hrtime-2.0.0.tgz#19bfb2c9162f9e11c2f04c2c79de2b7e8095c627"
 
+convert-source-map@^1.4.0, convert-source-map@^1.5.1:
+  version "1.6.0"
+  resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.6.0.tgz#51b537a8c43e0f04dec1993bffcdd504e758ac20"
+  dependencies:
+    safe-buffer "~5.1.1"
+
 convert-source-map@^1.5.0:
   version "1.5.1"
   resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.1.tgz#b8278097b9bc229365de5c62cf5fcaed8b5599e5"
@@ -2221,6 +2377,16 @@ csso@~2.3.1:
     clap "^1.0.9"
     source-map "^0.5.3"
 
+cssom@0.3.x, "cssom@>= 0.3.2 < 0.4.0":
+  version "0.3.4"
+  resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.4.tgz#8cd52e8a3acfd68d3aed38ee0a640177d2f9d797"
+
+cssstyle@^1.0.0:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-1.1.1.tgz#18b038a9c44d65f7a8e428a653b9f6fe42faf5fb"
+  dependencies:
+    cssom "0.3.x"
+
 csstype@^2.2.0:
   version "2.4.0"
   resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.4.0.tgz#6c7d711cc135dcd90c812a80213eab006fc1acff"
@@ -2251,6 +2417,14 @@ data-uri-to-buffer@0.0.4:
   version "0.0.4"
   resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-0.0.4.tgz#46e13ab9da8e309745c8d01ce547213ebdb2fe3f"
 
+data-urls@^1.0.0:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-1.0.1.tgz#d416ac3896918f29ca84d81085bc3705834da579"
+  dependencies:
+    abab "^2.0.0"
+    whatwg-mimetype "^2.1.0"
+    whatwg-url "^7.0.0"
+
 date-now@^0.1.4:
   version "0.1.4"
   resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b"
@@ -2259,7 +2433,7 @@ death@^1.0.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/death/-/death-1.1.0.tgz#01aa9c401edd92750514470b8266390c66c67318"
 
-debug@2.6.9, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.3, debug@^2.6.6, debug@^2.6.8:
+debug@2.6.9, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.3, debug@^2.6.6, debug@^2.6.8, debug@^2.6.9:
   version "2.6.9"
   resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
   dependencies:
@@ -2289,10 +2463,24 @@ deep-equal@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5"
 
+deep-extend@^0.6.0:
+  version "0.6.0"
+  resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac"
+
 deep-extend@~0.4.0:
   version "0.4.2"
   resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.4.2.tgz#48b699c27e334bf89f10892be432f6e4c7d34a7f"
 
+deep-is@~0.1.3:
+  version "0.1.3"
+  resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34"
+
+default-require-extensions@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/default-require-extensions/-/default-require-extensions-1.0.0.tgz#f37ea15d3e13ffd9b437d33e1a75b5fb97874cb8"
+  dependencies:
+    strip-bom "^2.0.0"
+
 define-properties@^1.1.1:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.2.tgz#83a73f2fea569898fb737193c8f873caf6d45c94"
@@ -2300,6 +2488,12 @@ define-properties@^1.1.1:
     foreach "^2.0.5"
     object-keys "^1.0.8"
 
+define-properties@^1.1.2:
+  version "1.1.3"
+  resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1"
+  dependencies:
+    object-keys "^1.0.12"
+
 define-property@^0.2.5:
   version "0.2.5"
   resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116"
@@ -2391,6 +2585,10 @@ detect-libc@^1.0.2:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b"
 
+detect-newline@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-2.1.0.tgz#f41f1c10be4b00e87b5f13da680759f2c5bfd3e2"
+
 detect-port-alt@1.1.3:
   version "1.1.3"
   resolved "https://registry.yarnpkg.com/detect-port-alt/-/detect-port-alt-1.1.3.tgz#a4d2f061d757a034ecf37c514260a98750f2b131"
@@ -2426,6 +2624,10 @@ devcert-san@^0.3.3:
     tmp "^0.0.31"
     tslib "^1.6.0"
 
+diff@^3.2.0:
+  version "3.5.0"
+  resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12"
+
 diffie-hellman@^5.0.0:
   version "5.0.3"
   resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875"
@@ -2467,6 +2669,12 @@ domelementtype@~1.1.1:
   version "1.1.3"
   resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.1.3.tgz#bd28773e2642881aec51544924299c5cd822185b"
 
+domexception@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/domexception/-/domexception-1.0.1.tgz#937442644ca6a31261ef36e3ec677fe805582c90"
+  dependencies:
+    webidl-conversions "^4.0.2"
+
 domhandler@2.1:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.1.0.tgz#d2646f5e57f6c3bab11cf6cb05d3c0acf7412594"
@@ -2656,6 +2864,24 @@ error-stack-parser@^2.0.0:
   dependencies:
     stackframe "^1.0.3"
 
+es-abstract@^1.5.1:
+  version "1.12.0"
+  resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.12.0.tgz#9dbbdd27c6856f0001421ca18782d786bf8a6165"
+  dependencies:
+    es-to-primitive "^1.1.1"
+    function-bind "^1.1.1"
+    has "^1.0.1"
+    is-callable "^1.1.3"
+    is-regex "^1.0.4"
+
+es-to-primitive@^1.1.1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.1.1.tgz#45355248a88979034b6792e19bb81f2b7975dd0d"
+  dependencies:
+    is-callable "^1.1.1"
+    is-date-object "^1.0.1"
+    is-symbol "^1.0.1"
+
 es5-ext@^0.10.12, es5-ext@^0.10.35, es5-ext@^0.10.9, es5-ext@~0.10.14:
   version "0.10.42"
   resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.42.tgz#8c07dd33af04d5dcd1310b5cef13bea63a89ba8d"
@@ -2698,6 +2924,17 @@ escape-string-regexp@1.0.5, escape-string-regexp@^1.0.2, escape-string-regexp@^1
   version "1.0.5"
   resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
 
+escodegen@^1.9.1:
+  version "1.11.0"
+  resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.11.0.tgz#b27a9389481d5bfd5bec76f7bb1eb3f8f4556589"
+  dependencies:
+    esprima "^3.1.3"
+    estraverse "^4.2.0"
+    esutils "^2.0.2"
+    optionator "^0.8.1"
+  optionalDependencies:
+    source-map "~0.6.1"
+
 esniff@^1.1:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/esniff/-/esniff-1.1.0.tgz#c66849229f91464dede2e0d40201ed6abf65f2ac"
@@ -2709,10 +2946,18 @@ esprima@^2.6.0:
   version "2.7.3"
   resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581"
 
+esprima@^3.1.3:
+  version "3.1.3"
+  resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633"
+
 esprima@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.0.tgz#4499eddcd1110e0b218bacf2fa7f7f59f55ca804"
 
+estraverse@^4.2.0:
+  version "4.2.0"
+  resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13"
+
 esutils@^2.0.2:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b"
@@ -2782,6 +3027,10 @@ exenv@^1.2.1:
   version "1.2.2"
   resolved "https://registry.yarnpkg.com/exenv/-/exenv-1.2.2.tgz#2ae78e85d9894158670b03d47bec1f03bd91bb9d"
 
+exit@^0.1.2:
+  version "0.1.2"
+  resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c"
+
 expand-brackets@^0.1.4:
   version "0.1.5"
   resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-0.1.5.tgz#df07284e342a807cd733ac5af72411e581d1177b"
@@ -2818,6 +3067,17 @@ expand-tilde@^2.0.0, expand-tilde@^2.0.2:
   dependencies:
     homedir-polyfill "^1.0.1"
 
+expect@^23.6.0:
+  version "23.6.0"
+  resolved "https://registry.yarnpkg.com/expect/-/expect-23.6.0.tgz#1e0c8d3ba9a581c87bd71fb9bc8862d443425f98"
+  dependencies:
+    ansi-styles "^3.2.0"
+    jest-diff "^23.6.0"
+    jest-get-type "^22.1.0"
+    jest-matcher-utils "^23.6.0"
+    jest-message-util "^23.4.0"
+    jest-regex-util "^23.3.0"
+
 express-graphql@^0.6.6:
   version "0.6.12"
   resolved "https://registry.yarnpkg.com/express-graphql/-/express-graphql-0.6.12.tgz#dfcb2058ca72ed5190b140830ad8cdbf76a9128a"
@@ -2879,6 +3139,10 @@ extend@^3.0.0, extend@~3.0.1:
   version "3.0.1"
   resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444"
 
+extend@~3.0.2:
+  version "3.0.2"
+  resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
+
 external-editor@^2.0.4:
   version "2.2.0"
   resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-2.2.0.tgz#045511cfd8d133f3846673d1047c154e214ad3d5"
@@ -2993,6 +3257,13 @@ filename-regex@^2.0.0:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26"
 
+fileset@^2.0.2:
+  version "2.0.3"
+  resolved "https://registry.yarnpkg.com/fileset/-/fileset-2.0.3.tgz#8e7548a96d3cc2327ee5e674168723a333bba2a0"
+  dependencies:
+    glob "^7.0.3"
+    minimatch "^3.0.3"
+
 filesize@3.5.11:
   version "3.5.11"
   resolved "https://registry.yarnpkg.com/filesize/-/filesize-3.5.11.tgz#1919326749433bb3cf77368bd158caabcc19e9ee"
@@ -3054,7 +3325,7 @@ find-up@^1.0.0:
     path-exists "^2.0.0"
     pinkie-promise "^2.0.0"
 
-find-up@^2.0.0:
+find-up@^2.0.0, find-up@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7"
   dependencies:
@@ -3132,7 +3403,7 @@ forever-agent@~0.6.1:
   version "0.6.1"
   resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
 
-form-data@~2.3.1:
+form-data@~2.3.1, form-data@~2.3.2:
   version "2.3.2"
   resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.2.tgz#4970498be604c20c005d4f5c23aecd21d6b49099"
   dependencies:
@@ -3209,7 +3480,14 @@ fsevents@^1.0.0:
     nan "^2.9.2"
     node-pre-gyp "^0.9.0"
 
-function-bind@^1.0.2:
+fsevents@^1.2.3:
+  version "1.2.4"
+  resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.4.tgz#f41dcb1af2582af3692da36fc55cbd8e1041c426"
+  dependencies:
+    nan "^2.9.2"
+    node-pre-gyp "^0.10.0"
+
+function-bind@^1.0.2, function-bind@^1.1.1:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
 
@@ -3542,6 +3820,12 @@ glob-parent@^3.1.0:
     is-glob "^3.1.0"
     path-dirname "^1.0.0"
 
+glob-promise@3.4.0:
+  version "3.4.0"
+  resolved "https://registry.yarnpkg.com/glob-promise/-/glob-promise-3.4.0.tgz#b6b8f084504216f702dc2ce8c9bc9ac8866fdb20"
+  dependencies:
+    "@types/glob" "*"
+
 glob-to-regexp@^0.3.0:
   version "0.3.0"
   resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab"
@@ -3715,6 +3999,10 @@ gray-percentage@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/gray-percentage/-/gray-percentage-2.0.0.tgz#b72a274d1b1379104a0050b63b207dc53fe56f99"
 
+growly@^1.3.0:
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081"
+
 gzip-size@3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-3.0.0.tgz#546188e9bdc337f673772f81660464b389dce520"
@@ -3731,6 +4019,16 @@ handlebars@4.0.11:
   optionalDependencies:
     uglify-js "^2.6"
 
+handlebars@^4.0.3:
+  version "4.0.12"
+  resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.12.tgz#2c15c8a96d46da5e266700518ba8cb8d919d5bc5"
+  dependencies:
+    async "^2.5.0"
+    optimist "^0.6.1"
+    source-map "^0.6.1"
+  optionalDependencies:
+    uglify-js "^3.1.4"
+
 har-schema@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92"
@@ -3742,6 +4040,13 @@ har-validator@~5.0.3:
     ajv "^5.1.0"
     har-schema "^2.0.0"
 
+har-validator@~5.1.0:
+  version "5.1.0"
+  resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.0.tgz#44657f5688a22cfd4b72486e81b3a3fb11742c29"
+  dependencies:
+    ajv "^5.3.0"
+    har-schema "^2.0.0"
+
 has-ansi@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91"
@@ -3964,6 +4269,12 @@ html-comment-regex@^1.1.0:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.1.tgz#668b93776eaae55ebde8f3ad464b307a4963625e"
 
+html-encoding-sniffer@^1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz#e70d84b94da53aa375e11fe3a351be6642ca46f8"
+  dependencies:
+    whatwg-encoding "^1.0.1"
+
 html-entities@^1.2.0:
   version "1.2.1"
   resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.2.1.tgz#0df29351f0721163515dfb9e5543e5f6eed5162f"
@@ -4055,6 +4366,12 @@ iconv-lite@0.4.19:
   version "0.4.19"
   resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b"
 
+iconv-lite@0.4.23:
+  version "0.4.23"
+  resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.23.tgz#297871f63be507adcfbfca715d0cd0eed84e9a63"
+  dependencies:
+    safer-buffer ">= 2.1.2 < 3"
+
 iconv-lite@^0.4.17, iconv-lite@^0.4.4, iconv-lite@~0.4.13:
   version "0.4.21"
   resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.21.tgz#c47f8733d02171189ebc4a400f3218d348094798"
@@ -4083,6 +4400,13 @@ import-lazy@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43"
 
+import-local@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/import-local/-/import-local-1.0.0.tgz#5e4ffdc03f4fe6c009c6729beb29631c2f8227bc"
+  dependencies:
+    pkg-dir "^2.0.0"
+    resolve-cwd "^2.0.0"
+
 imurmurhash@^0.1.4:
   version "0.1.4"
   resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
@@ -4148,7 +4472,7 @@ interpret@^1.0.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.1.0.tgz#7ed1b1410c6a0e0f78cf95d3b8440c63f78b8614"
 
-invariant@^2.2.0, invariant@^2.2.1, invariant@^2.2.2:
+invariant@^2.2.0, invariant@^2.2.1, invariant@^2.2.2, invariant@^2.2.4:
   version "2.2.4"
   resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"
   dependencies:
@@ -4224,6 +4548,10 @@ is-builtin-module@^1.0.0:
   dependencies:
     builtin-modules "^1.0.0"
 
+is-callable@^1.1.1, is-callable@^1.1.3:
+  version "1.1.4"
+  resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.4.tgz#1e1adf219e1eeb684d691f9d6a05ff0d30a24d75"
+
 is-ci@^1.0.10:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.1.0.tgz#247e4162e7860cebbdaf30b774d6b0ac7dcfe7a5"
@@ -4242,6 +4570,10 @@ is-data-descriptor@^1.0.0:
   dependencies:
     kind-of "^6.0.0"
 
+is-date-object@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.1.tgz#9aa20eb6aeebbff77fbd33e74ca01b33581d3a16"
+
 is-decimal@^1.0.0:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/is-decimal/-/is-decimal-1.0.2.tgz#894662d6a8709d307f3a276ca4339c8fa5dff0ff"
@@ -4306,6 +4638,10 @@ is-fullwidth-code-point@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f"
 
+is-generator-fn@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-1.0.0.tgz#969d49e1bb3329f6bb7f09089be26578b2ddd46a"
+
 is-glob@^2.0.0, is-glob@^2.0.1:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863"
@@ -4417,6 +4753,12 @@ is-redirect@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/is-redirect/-/is-redirect-1.0.0.tgz#1d03dded53bd8db0f30c26e4f95d36fc7c87dc24"
 
+is-regex@^1.0.4:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.4.tgz#5517489b547091b0930e095654ced25ee97e9491"
+  dependencies:
+    has "^1.0.1"
+
 is-relative-url@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/is-relative-url/-/is-relative-url-2.0.0.tgz#72902d7fe04b3d4792e7db15f9db84b7204c9cef"
@@ -4453,6 +4795,10 @@ is-svg@^2.0.0:
   dependencies:
     html-comment-regex "^1.1.0"
 
+is-symbol@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.1.tgz#3cc59f00025194b6ab2e38dbae6689256b660572"
+
 is-typedarray@~1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
@@ -4544,6 +4890,69 @@ isstream@~0.1.2:
   version "0.1.2"
   resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
 
+istanbul-api@^1.3.1:
+  version "1.3.7"
+  resolved "https://registry.yarnpkg.com/istanbul-api/-/istanbul-api-1.3.7.tgz#a86c770d2b03e11e3f778cd7aedd82d2722092aa"
+  dependencies:
+    async "^2.1.4"
+    fileset "^2.0.2"
+    istanbul-lib-coverage "^1.2.1"
+    istanbul-lib-hook "^1.2.2"
+    istanbul-lib-instrument "^1.10.2"
+    istanbul-lib-report "^1.1.5"
+    istanbul-lib-source-maps "^1.2.6"
+    istanbul-reports "^1.5.1"
+    js-yaml "^3.7.0"
+    mkdirp "^0.5.1"
+    once "^1.4.0"
+
+istanbul-lib-coverage@^1.2.0, istanbul-lib-coverage@^1.2.1:
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.1.tgz#ccf7edcd0a0bb9b8f729feeb0930470f9af664f0"
+
+istanbul-lib-hook@^1.2.2:
+  version "1.2.2"
+  resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-1.2.2.tgz#bc6bf07f12a641fbf1c85391d0daa8f0aea6bf86"
+  dependencies:
+    append-transform "^0.4.0"
+
+istanbul-lib-instrument@^1.10.1, istanbul-lib-instrument@^1.10.2:
+  version "1.10.2"
+  resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-1.10.2.tgz#1f55ed10ac3c47f2bdddd5307935126754d0a9ca"
+  dependencies:
+    babel-generator "^6.18.0"
+    babel-template "^6.16.0"
+    babel-traverse "^6.18.0"
+    babel-types "^6.18.0"
+    babylon "^6.18.0"
+    istanbul-lib-coverage "^1.2.1"
+    semver "^5.3.0"
+
+istanbul-lib-report@^1.1.5:
+  version "1.1.5"
+  resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-1.1.5.tgz#f2a657fc6282f96170aaf281eb30a458f7f4170c"
+  dependencies:
+    istanbul-lib-coverage "^1.2.1"
+    mkdirp "^0.5.1"
+    path-parse "^1.0.5"
+    supports-color "^3.1.2"
+
+istanbul-lib-source-maps@^1.2.4, istanbul-lib-source-maps@^1.2.6:
+  version "1.2.6"
+  resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.6.tgz#37b9ff661580f8fca11232752ee42e08c6675d8f"
+  dependencies:
+    debug "^3.1.0"
+    istanbul-lib-coverage "^1.2.1"
+    mkdirp "^0.5.1"
+    rimraf "^2.6.1"
+    source-map "^0.5.3"
+
+istanbul-reports@^1.5.1:
+  version "1.5.1"
+  resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-1.5.1.tgz#97e4dbf3b515e8c484caea15d6524eebd3ff4e1a"
+  dependencies:
+    handlebars "^4.0.3"
+
 isurl@^1.0.0-alpha5:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/isurl/-/isurl-1.0.0.tgz#b27f4f49f3cdaa3ea44a0a5b7f3462e6edc39d67"
@@ -4559,61 +4968,393 @@ iterall@1.1.3:
   version "1.1.3"
   resolved "https://registry.yarnpkg.com/iterall/-/iterall-1.1.3.tgz#1cbbff96204056dde6656e2ed2e2226d0e6d72c9"
 
-joi@12.x.x:
-  version "12.0.0"
-  resolved "https://registry.yarnpkg.com/joi/-/joi-12.0.0.tgz#46f55e68f4d9628f01bbb695902c8b307ad8d33a"
+jest-changed-files@^23.4.2:
+  version "23.4.2"
+  resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-23.4.2.tgz#1eed688370cd5eebafe4ae93d34bb3b64968fe83"
   dependencies:
-    hoek "4.x.x"
-    isemail "3.x.x"
-    topo "2.x.x"
+    throat "^4.0.0"
 
-joi@9.0.0-0:
-  version "9.0.0-0"
-  resolved "https://registry.yarnpkg.com/joi/-/joi-9.0.0-0.tgz#a7ca4219602149ae0da7a7c5ca1d63d3c79e096b"
+jest-cli@^23.6.0:
+  version "23.6.0"
+  resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-23.6.0.tgz#61ab917744338f443ef2baa282ddffdd658a5da4"
   dependencies:
-    hoek "4.x.x"
-    isemail "2.x.x"
-    items "2.x.x"
-    moment "2.x.x"
-    topo "2.x.x"
-
-js-base64@^2.1.9:
-  version "2.4.3"
-  resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.4.3.tgz#2e545ec2b0f2957f41356510205214e98fad6582"
+    ansi-escapes "^3.0.0"
+    chalk "^2.0.1"
+    exit "^0.1.2"
+    glob "^7.1.2"
+    graceful-fs "^4.1.11"
+    import-local "^1.0.0"
+    is-ci "^1.0.10"
+    istanbul-api "^1.3.1"
+    istanbul-lib-coverage "^1.2.0"
+    istanbul-lib-instrument "^1.10.1"
+    istanbul-lib-source-maps "^1.2.4"
+    jest-changed-files "^23.4.2"
+    jest-config "^23.6.0"
+    jest-environment-jsdom "^23.4.0"
+    jest-get-type "^22.1.0"
+    jest-haste-map "^23.6.0"
+    jest-message-util "^23.4.0"
+    jest-regex-util "^23.3.0"
+    jest-resolve-dependencies "^23.6.0"
+    jest-runner "^23.6.0"
+    jest-runtime "^23.6.0"
+    jest-snapshot "^23.6.0"
+    jest-util "^23.4.0"
+    jest-validate "^23.6.0"
+    jest-watcher "^23.4.0"
+    jest-worker "^23.2.0"
+    micromatch "^2.3.11"
+    node-notifier "^5.2.1"
+    prompts "^0.1.9"
+    realpath-native "^1.0.0"
+    rimraf "^2.5.4"
+    slash "^1.0.0"
+    string-length "^2.0.0"
+    strip-ansi "^4.0.0"
+    which "^1.2.12"
+    yargs "^11.0.0"
 
-js-tokens@^3.0.0, js-tokens@^3.0.2:
-  version "3.0.2"
-  resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b"
+jest-config@^23.6.0:
+  version "23.6.0"
+  resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-23.6.0.tgz#f82546a90ade2d8c7026fbf6ac5207fc22f8eb1d"
+  dependencies:
+    babel-core "^6.0.0"
+    babel-jest "^23.6.0"
+    chalk "^2.0.1"
+    glob "^7.1.1"
+    jest-environment-jsdom "^23.4.0"
+    jest-environment-node "^23.4.0"
+    jest-get-type "^22.1.0"
+    jest-jasmine2 "^23.6.0"
+    jest-regex-util "^23.3.0"
+    jest-resolve "^23.6.0"
+    jest-util "^23.4.0"
+    jest-validate "^23.6.0"
+    micromatch "^2.3.11"
+    pretty-format "^23.6.0"
 
-js-yaml@^3.10.0, js-yaml@^3.5.2:
-  version "3.11.0"
-  resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.11.0.tgz#597c1a8bd57152f26d622ce4117851a51f5ebaef"
+jest-diff@^23.6.0:
+  version "23.6.0"
+  resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-23.6.0.tgz#1500f3f16e850bb3d71233408089be099f610c7d"
   dependencies:
-    argparse "^1.0.7"
-    esprima "^4.0.0"
+    chalk "^2.0.1"
+    diff "^3.2.0"
+    jest-get-type "^22.1.0"
+    pretty-format "^23.6.0"
 
-js-yaml@~3.7.0:
-  version "3.7.0"
-  resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.7.0.tgz#5c967ddd837a9bfdca5f2de84253abe8a1c03b80"
+jest-docblock@^23.2.0:
+  version "23.2.0"
+  resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-23.2.0.tgz#f085e1f18548d99fdd69b20207e6fd55d91383a7"
   dependencies:
-    argparse "^1.0.7"
-    esprima "^2.6.0"
+    detect-newline "^2.1.0"
 
-jsan@^3.1.5, jsan@^3.1.9:
-  version "3.1.9"
-  resolved "https://registry.yarnpkg.com/jsan/-/jsan-3.1.9.tgz#2705676c1058f0a7d9ac266ad036a5769cfa7c96"
+jest-each@^23.6.0:
+  version "23.6.0"
+  resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-23.6.0.tgz#ba0c3a82a8054387016139c733a05242d3d71575"
+  dependencies:
+    chalk "^2.0.1"
+    pretty-format "^23.6.0"
 
-jsbn@~0.1.0:
-  version "0.1.1"
-  resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
+jest-environment-jsdom@^23.4.0:
+  version "23.4.0"
+  resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-23.4.0.tgz#056a7952b3fea513ac62a140a2c368c79d9e6023"
+  dependencies:
+    jest-mock "^23.2.0"
+    jest-util "^23.4.0"
+    jsdom "^11.5.1"
 
-jsesc@^1.3.0:
-  version "1.3.0"
-  resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b"
+jest-environment-node@^23.4.0:
+  version "23.4.0"
+  resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-23.4.0.tgz#57e80ed0841dea303167cce8cd79521debafde10"
+  dependencies:
+    jest-mock "^23.2.0"
+    jest-util "^23.4.0"
 
-jsesc@~0.5.0:
-  version "0.5.0"
-  resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d"
+jest-get-type@^22.1.0:
+  version "22.4.3"
+  resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-22.4.3.tgz#e3a8504d8479342dd4420236b322869f18900ce4"
+
+jest-haste-map@^23.6.0:
+  version "23.6.0"
+  resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-23.6.0.tgz#2e3eb997814ca696d62afdb3f2529f5bbc935e16"
+  dependencies:
+    fb-watchman "^2.0.0"
+    graceful-fs "^4.1.11"
+    invariant "^2.2.4"
+    jest-docblock "^23.2.0"
+    jest-serializer "^23.0.1"
+    jest-worker "^23.2.0"
+    micromatch "^2.3.11"
+    sane "^2.0.0"
+
+jest-jasmine2@^23.6.0:
+  version "23.6.0"
+  resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-23.6.0.tgz#840e937f848a6c8638df24360ab869cc718592e0"
+  dependencies:
+    babel-traverse "^6.0.0"
+    chalk "^2.0.1"
+    co "^4.6.0"
+    expect "^23.6.0"
+    is-generator-fn "^1.0.0"
+    jest-diff "^23.6.0"
+    jest-each "^23.6.0"
+    jest-matcher-utils "^23.6.0"
+    jest-message-util "^23.4.0"
+    jest-snapshot "^23.6.0"
+    jest-util "^23.4.0"
+    pretty-format "^23.6.0"
+
+jest-leak-detector@^23.6.0:
+  version "23.6.0"
+  resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-23.6.0.tgz#e4230fd42cf381a1a1971237ad56897de7e171de"
+  dependencies:
+    pretty-format "^23.6.0"
+
+jest-matcher-utils@^23.6.0:
+  version "23.6.0"
+  resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-23.6.0.tgz#726bcea0c5294261a7417afb6da3186b4b8cac80"
+  dependencies:
+    chalk "^2.0.1"
+    jest-get-type "^22.1.0"
+    pretty-format "^23.6.0"
+
+jest-message-util@^23.4.0:
+  version "23.4.0"
+  resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-23.4.0.tgz#17610c50942349508d01a3d1e0bda2c079086a9f"
+  dependencies:
+    "@babel/code-frame" "^7.0.0-beta.35"
+    chalk "^2.0.1"
+    micromatch "^2.3.11"
+    slash "^1.0.0"
+    stack-utils "^1.0.1"
+
+jest-mock@^23.2.0:
+  version "23.2.0"
+  resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-23.2.0.tgz#ad1c60f29e8719d47c26e1138098b6d18b261134"
+
+jest-regex-util@^23.3.0:
+  version "23.3.0"
+  resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-23.3.0.tgz#5f86729547c2785c4002ceaa8f849fe8ca471bc5"
+
+jest-resolve-dependencies@^23.6.0:
+  version "23.6.0"
+  resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-23.6.0.tgz#b4526af24c8540d9a3fab102c15081cf509b723d"
+  dependencies:
+    jest-regex-util "^23.3.0"
+    jest-snapshot "^23.6.0"
+
+jest-resolve@^23.6.0:
+  version "23.6.0"
+  resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-23.6.0.tgz#cf1d1a24ce7ee7b23d661c33ba2150f3aebfa0ae"
+  dependencies:
+    browser-resolve "^1.11.3"
+    chalk "^2.0.1"
+    realpath-native "^1.0.0"
+
+jest-runner@^23.6.0:
+  version "23.6.0"
+  resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-23.6.0.tgz#3894bd219ffc3f3cb94dc48a4170a2e6f23a5a38"
+  dependencies:
+    exit "^0.1.2"
+    graceful-fs "^4.1.11"
+    jest-config "^23.6.0"
+    jest-docblock "^23.2.0"
+    jest-haste-map "^23.6.0"
+    jest-jasmine2 "^23.6.0"
+    jest-leak-detector "^23.6.0"
+    jest-message-util "^23.4.0"
+    jest-runtime "^23.6.0"
+    jest-util "^23.4.0"
+    jest-worker "^23.2.0"
+    source-map-support "^0.5.6"
+    throat "^4.0.0"
+
+jest-runtime@^23.6.0:
+  version "23.6.0"
+  resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-23.6.0.tgz#059e58c8ab445917cd0e0d84ac2ba68de8f23082"
+  dependencies:
+    babel-core "^6.0.0"
+    babel-plugin-istanbul "^4.1.6"
+    chalk "^2.0.1"
+    convert-source-map "^1.4.0"
+    exit "^0.1.2"
+    fast-json-stable-stringify "^2.0.0"
+    graceful-fs "^4.1.11"
+    jest-config "^23.6.0"
+    jest-haste-map "^23.6.0"
+    jest-message-util "^23.4.0"
+    jest-regex-util "^23.3.0"
+    jest-resolve "^23.6.0"
+    jest-snapshot "^23.6.0"
+    jest-util "^23.4.0"
+    jest-validate "^23.6.0"
+    micromatch "^2.3.11"
+    realpath-native "^1.0.0"
+    slash "^1.0.0"
+    strip-bom "3.0.0"
+    write-file-atomic "^2.1.0"
+    yargs "^11.0.0"
+
+jest-serializer@^23.0.1:
+  version "23.0.1"
+  resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-23.0.1.tgz#a3776aeb311e90fe83fab9e533e85102bd164165"
+
+jest-snapshot@^23.6.0:
+  version "23.6.0"
+  resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-23.6.0.tgz#f9c2625d1b18acda01ec2d2b826c0ce58a5aa17a"
+  dependencies:
+    babel-types "^6.0.0"
+    chalk "^2.0.1"
+    jest-diff "^23.6.0"
+    jest-matcher-utils "^23.6.0"
+    jest-message-util "^23.4.0"
+    jest-resolve "^23.6.0"
+    mkdirp "^0.5.1"
+    natural-compare "^1.4.0"
+    pretty-format "^23.6.0"
+    semver "^5.5.0"
+
+jest-util@^23.4.0:
+  version "23.4.0"
+  resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-23.4.0.tgz#4d063cb927baf0a23831ff61bec2cbbf49793561"
+  dependencies:
+    callsites "^2.0.0"
+    chalk "^2.0.1"
+    graceful-fs "^4.1.11"
+    is-ci "^1.0.10"
+    jest-message-util "^23.4.0"
+    mkdirp "^0.5.1"
+    slash "^1.0.0"
+    source-map "^0.6.0"
+
+jest-validate@^23.6.0:
+  version "23.6.0"
+  resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-23.6.0.tgz#36761f99d1ed33fcd425b4e4c5595d62b6597474"
+  dependencies:
+    chalk "^2.0.1"
+    jest-get-type "^22.1.0"
+    leven "^2.1.0"
+    pretty-format "^23.6.0"
+
+jest-watcher@^23.4.0:
+  version "23.4.0"
+  resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-23.4.0.tgz#d2e28ce74f8dad6c6afc922b92cabef6ed05c91c"
+  dependencies:
+    ansi-escapes "^3.0.0"
+    chalk "^2.0.1"
+    string-length "^2.0.0"
+
+jest-worker@^23.2.0:
+  version "23.2.0"
+  resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-23.2.0.tgz#faf706a8da36fae60eb26957257fa7b5d8ea02b9"
+  dependencies:
+    merge-stream "^1.0.1"
+
+jest@23.6.0:
+  version "23.6.0"
+  resolved "https://registry.yarnpkg.com/jest/-/jest-23.6.0.tgz#ad5835e923ebf6e19e7a1d7529a432edfee7813d"
+  dependencies:
+    import-local "^1.0.0"
+    jest-cli "^23.6.0"
+
+joi@12.x.x:
+  version "12.0.0"
+  resolved "https://registry.yarnpkg.com/joi/-/joi-12.0.0.tgz#46f55e68f4d9628f01bbb695902c8b307ad8d33a"
+  dependencies:
+    hoek "4.x.x"
+    isemail "3.x.x"
+    topo "2.x.x"
+
+joi@9.0.0-0:
+  version "9.0.0-0"
+  resolved "https://registry.yarnpkg.com/joi/-/joi-9.0.0-0.tgz#a7ca4219602149ae0da7a7c5ca1d63d3c79e096b"
+  dependencies:
+    hoek "4.x.x"
+    isemail "2.x.x"
+    items "2.x.x"
+    moment "2.x.x"
+    topo "2.x.x"
+
+js-base64@^2.1.9:
+  version "2.4.3"
+  resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.4.3.tgz#2e545ec2b0f2957f41356510205214e98fad6582"
+
+js-tokens@^3.0.0, js-tokens@^3.0.2:
+  version "3.0.2"
+  resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b"
+
+js-tokens@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
+
+js-yaml@^3.10.0, js-yaml@^3.5.2:
+  version "3.11.0"
+  resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.11.0.tgz#597c1a8bd57152f26d622ce4117851a51f5ebaef"
+  dependencies:
+    argparse "^1.0.7"
+    esprima "^4.0.0"
+
+js-yaml@^3.7.0:
+  version "3.12.0"
+  resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.12.0.tgz#eaed656ec8344f10f527c6bfa1b6e2244de167d1"
+  dependencies:
+    argparse "^1.0.7"
+    esprima "^4.0.0"
+
+js-yaml@~3.7.0:
+  version "3.7.0"
+  resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.7.0.tgz#5c967ddd837a9bfdca5f2de84253abe8a1c03b80"
+  dependencies:
+    argparse "^1.0.7"
+    esprima "^2.6.0"
+
+jsan@^3.1.5, jsan@^3.1.9:
+  version "3.1.9"
+  resolved "https://registry.yarnpkg.com/jsan/-/jsan-3.1.9.tgz#2705676c1058f0a7d9ac266ad036a5769cfa7c96"
+
+jsbn@~0.1.0:
+  version "0.1.1"
+  resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
+
+jsdom@^11.5.1:
+  version "11.12.0"
+  resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-11.12.0.tgz#1a80d40ddd378a1de59656e9e6dc5a3ba8657bc8"
+  dependencies:
+    abab "^2.0.0"
+    acorn "^5.5.3"
+    acorn-globals "^4.1.0"
+    array-equal "^1.0.0"
+    cssom ">= 0.3.2 < 0.4.0"
+    cssstyle "^1.0.0"
+    data-urls "^1.0.0"
+    domexception "^1.0.1"
+    escodegen "^1.9.1"
+    html-encoding-sniffer "^1.0.2"
+    left-pad "^1.3.0"
+    nwsapi "^2.0.7"
+    parse5 "4.0.0"
+    pn "^1.1.0"
+    request "^2.87.0"
+    request-promise-native "^1.0.5"
+    sax "^1.2.4"
+    symbol-tree "^3.2.2"
+    tough-cookie "^2.3.4"
+    w3c-hr-time "^1.0.1"
+    webidl-conversions "^4.0.2"
+    whatwg-encoding "^1.0.3"
+    whatwg-mimetype "^2.1.0"
+    whatwg-url "^6.4.1"
+    ws "^5.2.0"
+    xml-name-validator "^3.0.0"
+
+jsesc@^1.3.0:
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b"
+
+jsesc@~0.5.0:
+  version "0.5.0"
+  resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d"
 
 json-loader@^0.5.2:
   version "0.5.7"
@@ -4747,6 +5488,10 @@ kind-of@^6.0.0, kind-of@^6.0.2:
   version "6.0.2"
   resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051"
 
+kleur@^2.0.1:
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/kleur/-/kleur-2.0.2.tgz#b704f4944d95e255d038f0cb05fb8a602c55a300"
+
 latest-version@^3.0.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-3.1.0.tgz#a205383fea322b33b5ae3b18abee0dc2f356ee15"
@@ -4763,10 +5508,21 @@ lcid@^1.0.0:
   dependencies:
     invert-kv "^1.0.0"
 
-leven@2.1.0, leven@^2.0.0:
+left-pad@^1.3.0:
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/left-pad/-/left-pad-1.3.0.tgz#5b8a3a7765dfe001261dde915589e782f8c94d1e"
+
+leven@2.1.0, leven@^2.0.0, leven@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/leven/-/leven-2.1.0.tgz#c2e7a9f772094dee9d34202ae8acce4687875580"
 
+levn@~0.3.0:
+  version "0.3.0"
+  resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee"
+  dependencies:
+    prelude-ls "~1.1.2"
+    type-check "~0.3.2"
+
 liftoff@^2.2.0:
   version "2.5.0"
   resolved "https://registry.yarnpkg.com/liftoff/-/liftoff-2.5.0.tgz#2009291bb31cea861bbf10a7c15a28caf75c31ec"
@@ -4935,6 +5691,10 @@ lodash.some@^4.4.0:
   version "4.6.0"
   resolved "https://registry.yarnpkg.com/lodash.some/-/lodash.some-4.6.0.tgz#1bb9f314ef6b8baded13b549169b2a945eb68e4d"
 
+lodash.sortby@^4.7.0:
+  version "4.7.0"
+  resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438"
+
 lodash.template@^4.2.4:
   version "4.4.0"
   resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-4.4.0.tgz#e73a0385c8355591746e020b99679c690e68fba0"
@@ -4960,7 +5720,7 @@ lodash@3.10.1:
   version "3.10.1"
   resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6"
 
-lodash@4, lodash@4.17.10, lodash@^4.0.0, lodash@^4.1.0, lodash@^4.13.1, lodash@^4.14.0, lodash@^4.17.2, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0, lodash@^4.6.1:
+lodash@4, lodash@4.17.10, lodash@^4.0.0, lodash@^4.1.0, lodash@^4.13.1, lodash@^4.14.0, lodash@^4.17.10, lodash@^4.17.2, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0, lodash@^4.6.1:
   version "4.17.10"
   resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7"
 
@@ -5180,6 +5940,12 @@ merge-descriptors@1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
 
+merge-stream@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-1.0.1.tgz#4041202d508a342ba00174008df0c251b8c135e1"
+  dependencies:
+    readable-stream "^2.0.1"
+
 merge@^1.1.3, merge@^1.2.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/merge/-/merge-1.2.0.tgz#7531e39d4949c281a66b8c5a6e0265e8b05894da"
@@ -5221,7 +5987,7 @@ micromatch@^2.1.5, micromatch@^2.3.11, micromatch@^2.3.7:
     parse-glob "^3.0.4"
     regex-cache "^0.4.2"
 
-micromatch@^3.0.3, micromatch@^3.0.4:
+micromatch@^3.0.3, micromatch@^3.0.4, micromatch@^3.1.4:
   version "3.1.10"
   resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23"
   dependencies:
@@ -5250,12 +6016,22 @@ miller-rabin@^4.0.0:
   version "1.33.0"
   resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.33.0.tgz#a3492050a5cb9b63450541e39d9788d2272783db"
 
+mime-db@~1.36.0:
+  version "1.36.0"
+  resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.36.0.tgz#5020478db3c7fe93aad7bbcc4dcf869c43363397"
+
 mime-types@2.1.18, mime-types@^2.1.12, mime-types@~2.1.17, mime-types@~2.1.18:
   version "2.1.18"
   resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.18.tgz#6f323f60a83d11146f831ff11fd66e2fe5503bb8"
   dependencies:
     mime-db "~1.33.0"
 
+mime-types@~2.1.19:
+  version "2.1.20"
+  resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.20.tgz#930cb719d571e903738520f8470911548ca2cc19"
+  dependencies:
+    mime-db "~1.36.0"
+
 mime@1.3.x:
   version "1.3.6"
   resolved "https://registry.yarnpkg.com/mime/-/mime-1.3.6.tgz#591d84d3653a6b0b4a3b9df8de5aa8108e72e5e0"
@@ -5387,6 +6163,10 @@ nanomatch@^1.2.9:
     snapdragon "^0.8.1"
     to-regex "^3.0.1"
 
+natural-compare@^1.4.0:
+  version "1.4.0"
+  resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
+
 ncp@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/ncp/-/ncp-2.0.0.tgz#195a21d6c46e361d2fb1281ba38b91e9df7bdbb3"
@@ -5399,6 +6179,14 @@ needle@^2.2.0:
     iconv-lite "^0.4.4"
     sax "^1.2.4"
 
+needle@^2.2.1:
+  version "2.2.3"
+  resolved "https://registry.yarnpkg.com/needle/-/needle-2.2.3.tgz#c1b04da378cd634d8befe2de965dc2cfb0fd65ca"
+  dependencies:
+    debug "^2.1.2"
+    iconv-lite "^0.4.4"
+    sax "^1.2.4"
+
 negotiator@0.6.1:
   version "0.6.1"
   resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9"
@@ -5492,6 +6280,30 @@ node-libs-browser@^2.0.0:
     util "^0.10.3"
     vm-browserify "0.0.4"
 
+node-notifier@^5.2.1:
+  version "5.2.1"
+  resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-5.2.1.tgz#fa313dd08f5517db0e2502e5758d664ac69f9dea"
+  dependencies:
+    growly "^1.3.0"
+    semver "^5.4.1"
+    shellwords "^0.1.1"
+    which "^1.3.0"
+
+node-pre-gyp@^0.10.0:
+  version "0.10.3"
+  resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.10.3.tgz#3070040716afdc778747b61b6887bf78880b80fc"
+  dependencies:
+    detect-libc "^1.0.2"
+    mkdirp "^0.5.1"
+    needle "^2.2.1"
+    nopt "^4.0.1"
+    npm-packlist "^1.1.6"
+    npmlog "^4.0.2"
+    rc "^1.2.7"
+    rimraf "^2.6.1"
+    semver "^5.3.0"
+    tar "^4"
+
 node-pre-gyp@^0.9.0:
   version "0.9.1"
   resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.9.1.tgz#f11c07516dd92f87199dbc7e1838eab7cd56c9e0"
@@ -5605,10 +6417,18 @@ number-is-nan@^1.0.0:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d"
 
+nwsapi@^2.0.7:
+  version "2.0.9"
+  resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.0.9.tgz#77ac0cdfdcad52b6a1151a84e73254edc33ed016"
+
 oauth-sign@~0.8.2:
   version "0.8.2"
   resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43"
 
+oauth-sign@~0.9.0:
+  version "0.9.0"
+  resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455"
+
 object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
   version "4.1.1"
   resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
@@ -5625,6 +6445,10 @@ object-copy@^0.1.0:
     define-property "^0.2.5"
     kind-of "^3.0.3"
 
+object-keys@^1.0.12:
+  version "1.0.12"
+  resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.12.tgz#09c53855377575310cca62f55bb334abff7b3ed2"
+
 object-keys@^1.0.8:
   version "1.0.11"
   resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.11.tgz#c54601778ad560f1142ce0e01bcca8b56d13426d"
@@ -5648,6 +6472,13 @@ object.defaults@^1.1.0:
     for-own "^1.0.0"
     isobject "^3.0.0"
 
+object.getownpropertydescriptors@^2.0.3:
+  version "2.0.3"
+  resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz#8758c846f5b407adab0f236e0986f14b051caa16"
+  dependencies:
+    define-properties "^1.1.2"
+    es-abstract "^1.5.1"
+
 object.map@^1.0.0:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/object.map/-/object.map-1.0.1.tgz#cf83e59dc8fcc0ad5f4250e1f78b3b81bd801d37"
@@ -5721,6 +6552,17 @@ optimist@^0.6.1, optimist@~0.6.0, optimist@~0.6.1:
     minimist "~0.0.1"
     wordwrap "~0.0.2"
 
+optionator@^0.8.1:
+  version "0.8.2"
+  resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.2.tgz#364c5e409d3f4d6301d6c0b4c05bba50180aeb64"
+  dependencies:
+    deep-is "~0.1.3"
+    fast-levenshtein "~2.0.4"
+    levn "~0.3.0"
+    prelude-ls "~1.1.2"
+    type-check "~0.3.2"
+    wordwrap "~1.0.0"
+
 original@>=0.0.5:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/original/-/original-1.0.0.tgz#9147f93fa1696d04be61e01bd50baeaca656bd3b"
@@ -5853,6 +6695,17 @@ parse-entities@^1.0.2:
     is-decimal "^1.0.0"
     is-hexadecimal "^1.0.0"
 
+parse-entities@^1.1.0:
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-1.1.2.tgz#9eaf719b29dc3bd62246b4332009072e01527777"
+  dependencies:
+    character-entities "^1.0.0"
+    character-entities-legacy "^1.0.0"
+    character-reference-invalid "^1.0.0"
+    is-alphanumerical "^1.0.0"
+    is-decimal "^1.0.0"
+    is-hexadecimal "^1.0.0"
+
 parse-filepath@^1.0.1:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/parse-filepath/-/parse-filepath-1.0.2.tgz#a632127f53aaf3d15876f5872f3ffac763d6c891"
@@ -5892,6 +6745,10 @@ parse-unit@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/parse-unit/-/parse-unit-1.0.1.tgz#7e1bb6d5bef3874c28e392526a2541170291eecf"
 
+parse5@4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/parse5/-/parse5-4.0.0.tgz#6d78656e3da8d78b4ec0b906f7c08ef1dfe3f608"
+
 parse5@^3.0.3:
   version "3.0.3"
   resolved "https://registry.yarnpkg.com/parse5/-/parse5-3.0.3.tgz#042f792ffdd36851551cf4e9e066b3874ab45b5c"
@@ -6051,6 +6908,12 @@ pkg-dir@^1.0.0:
   dependencies:
     find-up "^1.0.0"
 
+pkg-dir@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-2.0.0.tgz#f6d5d1109e19d63edf428e0bd57e12777615334b"
+  dependencies:
+    find-up "^2.1.0"
+
 pkg-resolve@^0.1.7:
   version "0.1.14"
   resolved "https://registry.yarnpkg.com/pkg-resolve/-/pkg-resolve-0.1.14.tgz#329b2e76ccbb372e22e6a3a41cb30ab0457836ba"
@@ -6065,6 +6928,10 @@ pleeease-filters@^3.0.0:
     onecolor "~2.4.0"
     postcss "^5.0.4"
 
+pn@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/pn/-/pn-1.1.0.tgz#e2f4cef0e219f463c179ab37463e4e1ecdccbafb"
+
 posix-character-classes@^0.1.0:
   version "0.1.1"
   resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab"
@@ -6571,6 +7438,10 @@ postcss@^6.0.1, postcss@^6.0.14:
     source-map "^0.6.1"
     supports-color "^5.3.0"
 
+prelude-ls@~1.1.2:
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
+
 prepend-http@^1.0.0, prepend-http@^1.0.1:
   version "1.0.4"
   resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc"
@@ -6594,7 +7465,14 @@ pretty-error@^2.1.1:
     renderkid "^2.0.1"
     utila "~0.4"
 
-private@^0.1.6, private@^0.1.7:
+pretty-format@^23.6.0:
+  version "23.6.0"
+  resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-23.6.0.tgz#5eaac8eeb6b33b987b7fe6097ea6a8a146ab5760"
+  dependencies:
+    ansi-regex "^3.0.0"
+    ansi-styles "^3.2.0"
+
+private@^0.1.6, private@^0.1.7, private@^0.1.8:
   version "0.1.8"
   resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff"
 
@@ -6622,6 +7500,13 @@ promise@^7.1.1:
   dependencies:
     asap "~2.0.3"
 
+prompts@^0.1.9:
+  version "0.1.14"
+  resolved "https://registry.yarnpkg.com/prompts/-/prompts-0.1.14.tgz#a8e15c612c5c9ec8f8111847df3337c9cbd443b2"
+  dependencies:
+    kleur "^2.0.1"
+    sisteransi "^0.1.1"
+
 prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.8:
   version "15.6.1"
   resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.1.tgz#36644453564255ddda391191fb3a125cbdf654ca"
@@ -6658,6 +7543,10 @@ pseudomap@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3"
 
+psl@^1.1.24:
+  version "1.1.29"
+  resolved "https://registry.yarnpkg.com/psl/-/psl-1.1.29.tgz#60f580d360170bb722a797cc704411e6da850c67"
+
 public-encrypt@^4.0.0:
   version "4.0.2"
   resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.2.tgz#46eb9107206bf73489f8b85b69d91334c6610994"
@@ -6687,6 +7576,10 @@ punycode@^1.2.4, punycode@^1.4.1:
   version "1.4.1"
   resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
 
+punycode@^2.1.0:
+  version "2.1.1"
+  resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
+
 q@^1.1.2:
   version "1.5.1"
   resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7"
@@ -6695,6 +7588,10 @@ qs@6.5.1, qs@~6.5.1:
   version "6.5.1"
   resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8"
 
+qs@~6.5.2:
+  version "6.5.2"
+  resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
+
 query-string@^4.1.0:
   version "4.3.4"
   resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.4.tgz#bbb693b9ca915c232515b228b1a02b609043dbeb"
@@ -6764,6 +7661,15 @@ rc@^1.0.1, rc@^1.1.6, rc@^1.1.7:
     minimist "^1.2.0"
     strip-json-comments "~2.0.1"
 
+rc@^1.2.7:
+  version "1.2.8"
+  resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
+  dependencies:
+    deep-extend "^0.6.0"
+    ini "~1.3.0"
+    minimist "^1.2.0"
+    strip-json-comments "~2.0.1"
+
 react-deep-force-update@^2.1.1:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/react-deep-force-update/-/react-deep-force-update-2.1.1.tgz#8ea4263cd6455a050b37445b3f08fd839d86e909"
@@ -6953,6 +7859,12 @@ readdirp@^2.0.0:
     readable-stream "^2.0.2"
     set-immediate-shim "^1.0.1"
 
+realpath-native@^1.0.0:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/realpath-native/-/realpath-native-1.0.2.tgz#cd51ce089b513b45cf9b1516c82989b51ccc6560"
+  dependencies:
+    util.promisify "^1.0.0"
+
 rechoir@^0.6.2:
   version "0.6.2"
   resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384"
@@ -7162,6 +8074,26 @@ remark-parse@^4.0.0:
     vfile-location "^2.0.0"
     xtend "^4.0.1"
 
+remark-parse@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/remark-parse/-/remark-parse-5.0.0.tgz#4c077f9e499044d1d5c13f80d7a98cf7b9285d95"
+  dependencies:
+    collapse-white-space "^1.0.2"
+    is-alphabetical "^1.0.0"
+    is-decimal "^1.0.0"
+    is-whitespace-character "^1.0.0"
+    is-word-character "^1.0.0"
+    markdown-escapes "^1.0.0"
+    parse-entities "^1.1.0"
+    repeat-string "^1.5.4"
+    state-toggle "^1.0.0"
+    trim "0.0.1"
+    trim-trailing-lines "^1.0.0"
+    unherit "^1.0.4"
+    unist-util-remove-position "^1.0.0"
+    vfile-location "^2.0.0"
+    xtend "^4.0.1"
+
 remark-retext@^3.1.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/remark-retext/-/remark-retext-3.1.0.tgz#1b3df2d49469c0d3596cad86e91503a8b600fdcc"
@@ -7206,6 +8138,33 @@ remark-stringify@^4.0.0:
     unherit "^1.0.4"
     xtend "^4.0.1"
 
+remark-stringify@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/remark-stringify/-/remark-stringify-5.0.0.tgz#336d3a4d4a6a3390d933eeba62e8de4bd280afba"
+  dependencies:
+    ccount "^1.0.0"
+    is-alphanumeric "^1.0.0"
+    is-decimal "^1.0.0"
+    is-whitespace-character "^1.0.0"
+    longest-streak "^2.0.1"
+    markdown-escapes "^1.0.0"
+    markdown-table "^1.1.0"
+    mdast-util-compact "^1.0.0"
+    parse-entities "^1.0.2"
+    repeat-string "^1.5.4"
+    state-toggle "^1.0.0"
+    stringify-entities "^1.0.1"
+    unherit "^1.0.4"
+    xtend "^4.0.1"
+
+remark@9.0.0:
+  version "9.0.0"
+  resolved "https://registry.yarnpkg.com/remark/-/remark-9.0.0.tgz#c5cfa8ec535c73a67c4b0f12bfdbd3a67d8b2f60"
+  dependencies:
+    remark-parse "^5.0.0"
+    remark-stringify "^5.0.0"
+    unified "^6.0.0"
+
 remark@^7.0.1:
   version "7.0.1"
   resolved "https://registry.yarnpkg.com/remark/-/remark-7.0.1.tgz#a5de4dacfabf0f60a49826ef24c479807f904bfb"
@@ -7273,6 +8232,20 @@ replace-ext@1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-1.0.0.tgz#de63128373fcbf7c3ccfa4de5a480c45a67958eb"
 
+request-promise-core@1.1.1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.1.tgz#3eee00b2c5aa83239cfb04c5700da36f81cd08b6"
+  dependencies:
+    lodash "^4.13.1"
+
+request-promise-native@^1.0.5:
+  version "1.0.5"
+  resolved "https://registry.yarnpkg.com/request-promise-native/-/request-promise-native-1.0.5.tgz#5281770f68e0c9719e5163fd3fab482215f4fda5"
+  dependencies:
+    request-promise-core "1.1.1"
+    stealthy-require "^1.1.0"
+    tough-cookie ">=2.3.3"
+
 request@^2.58.0, request@^2.67.0, request@^2.74.0:
   version "2.85.0"
   resolved "https://registry.yarnpkg.com/request/-/request-2.85.0.tgz#5a03615a47c61420b3eb99b7dba204f83603e1fa"
@@ -7300,6 +8273,31 @@ request@^2.58.0, request@^2.67.0, request@^2.74.0:
     tunnel-agent "^0.6.0"
     uuid "^3.1.0"
 
+request@^2.87.0:
+  version "2.88.0"
+  resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef"
+  dependencies:
+    aws-sign2 "~0.7.0"
+    aws4 "^1.8.0"
+    caseless "~0.12.0"
+    combined-stream "~1.0.6"
+    extend "~3.0.2"
+    forever-agent "~0.6.1"
+    form-data "~2.3.2"
+    har-validator "~5.1.0"
+    http-signature "~1.2.0"
+    is-typedarray "~1.0.0"
+    isstream "~0.1.2"
+    json-stringify-safe "~5.0.1"
+    mime-types "~2.1.19"
+    oauth-sign "~0.9.0"
+    performance-now "^2.1.0"
+    qs "~6.5.2"
+    safe-buffer "^5.1.2"
+    tough-cookie "~2.4.3"
+    tunnel-agent "^0.6.0"
+    uuid "^3.3.2"
+
 require-directory@^2.1.1:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
@@ -7348,6 +8346,10 @@ resolve-url@^0.2.1:
   version "0.2.1"
   resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a"
 
+resolve@1.1.7:
+  version "1.1.7"
+  resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b"
+
 resolve@^1.1.6, resolve@^1.1.7:
   version "1.7.1"
   resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.7.1.tgz#aadd656374fd298aee895bc026b8297418677fd3"
@@ -7421,7 +8423,7 @@ rollup@^0.36.3:
   dependencies:
     source-map-support "^0.4.0"
 
-rsvp@^3.0.13, rsvp@^3.0.18:
+rsvp@^3.0.13, rsvp@^3.0.18, rsvp@^3.3.3:
   version "3.6.2"
   resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-3.6.2.tgz#2e96491599a96cde1b515d5674a8f7a91452926a"
 
@@ -7445,6 +8447,10 @@ safe-buffer@5.1.1, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, s
   version "5.1.1"
   resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853"
 
+safe-buffer@^5.1.2:
+  version "5.1.2"
+  resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
+
 safe-buffer@~5.0.1:
   version "5.0.1"
   resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.0.1.tgz#d263ca54696cd8a306b5ca6551e92de57918fbe7"
@@ -7455,7 +8461,7 @@ safe-regex@^1.1.0:
   dependencies:
     ret "~0.1.10"
 
-safer-buffer@^2.1.0:
+"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.1.0:
   version "2.1.2"
   resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
 
@@ -7471,6 +8477,21 @@ sane@^1.3.3:
     walker "~1.0.5"
     watch "~0.10.0"
 
+sane@^2.0.0:
+  version "2.5.2"
+  resolved "https://registry.yarnpkg.com/sane/-/sane-2.5.2.tgz#b4dc1861c21b427e929507a3e751e2a2cb8ab3fa"
+  dependencies:
+    anymatch "^2.0.0"
+    capture-exit "^1.2.0"
+    exec-sh "^0.2.0"
+    fb-watchman "^2.0.0"
+    micromatch "^3.1.4"
+    minimist "^1.1.1"
+    walker "~1.0.5"
+    watch "~0.18.0"
+  optionalDependencies:
+    fsevents "^1.2.3"
+
 sanitize-html@^1.14.1:
   version "1.18.2"
   resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-1.18.2.tgz#61877ba5a910327e42880a28803c2fbafa8e4642"
@@ -7531,6 +8552,10 @@ semver@^4.3.3:
   version "4.3.6"
   resolved "https://registry.yarnpkg.com/semver/-/semver-4.3.6.tgz#300bc6e0e86374f7ba61068b5b1ecd57fc6532da"
 
+semver@^5.4.1, semver@^5.5.0:
+  version "5.5.1"
+  resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.1.tgz#7dfdd8814bdb7cabc7be0fb1d734cfb66c940477"
+
 send@0.16.2:
   version "0.16.2"
   resolved "https://registry.yarnpkg.com/send/-/send-0.16.2.tgz#6ecca1e0f8c156d141597559848df64730a6bbc1"
@@ -7685,6 +8710,10 @@ shelljs@0.7.0:
     interpret "^1.0.0"
     rechoir "^0.6.2"
 
+shellwords@^0.1.1:
+  version "0.1.1"
+  resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b"
+
 shortid@^2.2.6:
   version "2.2.8"
   resolved "https://registry.yarnpkg.com/shortid/-/shortid-2.2.8.tgz#033b117d6a2e975804f6f0969dbe7d3d0b355131"
@@ -7701,6 +8730,10 @@ signedsource@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/signedsource/-/signedsource-1.0.0.tgz#1ddace4981798f93bd833973803d80d52e93ad6a"
 
+sisteransi@^0.1.1:
+  version "0.1.1"
+  resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-0.1.1.tgz#5431447d5f7d1675aac667ccd0b865a4994cb3ce"
+
 slash@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55"
@@ -7842,6 +8875,13 @@ source-map-support@^0.4.0, source-map-support@^0.4.15:
   dependencies:
     source-map "^0.5.6"
 
+source-map-support@^0.5.6:
+  version "0.5.9"
+  resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.9.tgz#41bc953b2534267ea2d605bccfa7bfa3111ced5f"
+  dependencies:
+    buffer-from "^1.0.0"
+    source-map "^0.6.0"
+
 source-map-support@~0.2.8:
   version "0.2.10"
   resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.2.10.tgz#ea5a3900a1c1cb25096a0ae8cc5c2b4b10ded3dc"
@@ -7872,7 +8912,7 @@ source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7, source-map@~0.5.1, sour
   version "0.5.7"
   resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
 
-source-map@^0.6.1:
+source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1:
   version "0.6.1"
   resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
 
@@ -7955,6 +8995,10 @@ stack-trace@^0.0.10:
   version "0.0.10"
   resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0"
 
+stack-utils@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-1.0.1.tgz#d4f33ab54e8e38778b0ca5cfd3b3afb12db68620"
+
 stackframe@^0.3.1:
   version "0.3.1"
   resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-0.3.1.tgz#33aa84f1177a5548c8935533cbfeb3420975f5a4"
@@ -7992,6 +9036,10 @@ statuses@~1.4.0:
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.4.0.tgz#bb73d446da2796106efcc1b601a253d6c46bd087"
 
+stealthy-require@^1.1.0:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b"
+
 steno@^0.4.1:
   version "0.4.4"
   resolved "https://registry.yarnpkg.com/steno/-/steno-0.4.4.tgz#071105bdfc286e6615c0403c27e9d7b5dcb855cb"
@@ -8023,6 +9071,13 @@ strict-uri-encode@^1.0.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713"
 
+string-length@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/string-length/-/string-length-2.0.0.tgz#d40dbb686a3ace960c1cffca562bf2c45f8363ed"
+  dependencies:
+    astral-regex "^1.0.0"
+    strip-ansi "^4.0.0"
+
 string-similarity@^1.2.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/string-similarity/-/string-similarity-1.2.0.tgz#d75153cb383846318b7a39a8d9292bb4db4e9c30"
@@ -8083,16 +9138,16 @@ strip-bom-string@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/strip-bom-string/-/strip-bom-string-1.0.0.tgz#e5211e9224369fbb81d633a2f00044dc8cedad92"
 
+strip-bom@3.0.0, strip-bom@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3"
+
 strip-bom@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e"
   dependencies:
     is-utf8 "^0.2.0"
 
-strip-bom@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3"
-
 strip-eof@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf"
@@ -8111,7 +9166,7 @@ supports-color@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
 
-supports-color@^3.1.0, supports-color@^3.1.1, supports-color@^3.2.3:
+supports-color@^3.1.0, supports-color@^3.1.1, supports-color@^3.1.2, supports-color@^3.2.3:
   version "3.2.3"
   resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6"
   dependencies:
@@ -8139,6 +9194,10 @@ symbol-observable@^1.0.2, symbol-observable@^1.0.3:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804"
 
+symbol-tree@^3.2.2:
+  version "3.2.2"
+  resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.2.tgz#ae27db38f660a7ae2e1c3b7d1bc290819b8519e6"
+
 symbol@^0.2.1:
   version "0.2.3"
   resolved "https://registry.yarnpkg.com/symbol/-/symbol-0.2.3.tgz#3b9873b8a901e47c6efe21526a3ac372ef28bbc7"
@@ -8215,10 +9274,24 @@ term-size@^1.2.0:
   dependencies:
     execa "^0.7.0"
 
+test-exclude@^4.2.1:
+  version "4.2.3"
+  resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-4.2.3.tgz#a9a5e64474e4398339245a0a769ad7c2f4a97c20"
+  dependencies:
+    arrify "^1.0.1"
+    micromatch "^2.3.11"
+    object-assign "^4.1.0"
+    read-pkg-up "^1.0.1"
+    require-main-filename "^1.0.1"
+
 text-table@0.2.0:
   version "0.2.0"
   resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
 
+throat@^4.0.0:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/throat/-/throat-4.1.0.tgz#89037cbc92c56ab18926e6ba4cbb200e15672a6a"
+
 through2@^2.0.1:
   version "2.0.3"
   resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.3.tgz#0004569b37c7c74ba39c43f3ced78d1ad94140be"
@@ -8300,12 +9373,25 @@ topo@2.x.x:
   dependencies:
     hoek "4.x.x"
 
+tough-cookie@>=2.3.3, tough-cookie@^2.3.4, tough-cookie@~2.4.3:
+  version "2.4.3"
+  resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781"
+  dependencies:
+    psl "^1.1.24"
+    punycode "^1.4.1"
+
 tough-cookie@~2.3.3:
   version "2.3.4"
   resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.4.tgz#ec60cee38ac675063ffc97a5c18970578ee83655"
   dependencies:
     punycode "^1.4.1"
 
+tr46@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09"
+  dependencies:
+    punycode "^2.1.0"
+
 traceur@0.0.105:
   version "0.0.105"
   resolved "https://registry.yarnpkg.com/traceur/-/traceur-0.0.105.tgz#5cf9dee83d6b77861c3d6c44d53859aed7ab0479"
@@ -8354,6 +9440,12 @@ tweetnacl@^0.14.3, tweetnacl@~0.14.0:
   version "0.14.5"
   resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
 
+type-check@~0.3.2:
+  version "0.3.2"
+  resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72"
+  dependencies:
+    prelude-ls "~1.1.2"
+
 type-is@~1.6.15, type-is@~1.6.16:
   version "1.6.16"
   resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.16.tgz#f89ce341541c672b25ee7ae3c73dee3b2be50194"
@@ -8394,6 +9486,13 @@ uglify-js@^2.6, uglify-js@^2.6.1:
   optionalDependencies:
     uglify-to-browserify "~1.0.0"
 
+uglify-js@^3.1.4:
+  version "3.4.9"
+  resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.4.9.tgz#af02f180c1207d76432e473ed24a28f4a782bae3"
+  dependencies:
+    commander "~2.17.1"
+    source-map "~0.6.1"
+
 uglify-js@~2.7.3:
   version "2.7.5"
   resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.7.5.tgz#4612c0c7baaee2ba7c487de4904ae122079f2ca8"
@@ -8484,6 +9583,10 @@ unist-util-is@^2.0.0, unist-util-is@^2.1.1:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-2.1.1.tgz#0c312629e3f960c66e931e812d3d80e77010947b"
 
+unist-util-is@^2.1.2:
+  version "2.1.2"
+  resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-2.1.2.tgz#1193fa8f2bfbbb82150633f3a8d2eb9a1c1d55db"
+
 unist-util-modify-children@^1.0.0:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/unist-util-modify-children/-/unist-util-modify-children-1.1.1.tgz#66d7e6a449e6f67220b976ab3cb8b5ebac39e51d"
@@ -8516,6 +9619,18 @@ unist-util-visit-children@^1.0.0:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/unist-util-visit-children/-/unist-util-visit-children-1.1.1.tgz#eba63b371116231181068837118b6e6e10ec8844"
 
+unist-util-visit-parents@^2.0.0:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-2.0.1.tgz#63fffc8929027bee04bfef7d2cce474f71cb6217"
+  dependencies:
+    unist-util-is "^2.1.2"
+
+unist-util-visit@1.4.0:
+  version "1.4.0"
+  resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-1.4.0.tgz#1cb763647186dc26f5e1df5db6bd1e48b3cc2fb1"
+  dependencies:
+    unist-util-visit-parents "^2.0.0"
+
 unist-util-visit@^1.0.0, unist-util-visit@^1.1.0, unist-util-visit@^1.1.1:
   version "1.3.0"
   resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-1.3.0.tgz#41ca7c82981fd1ce6c762aac397fc24e35711444"
@@ -8626,6 +9741,13 @@ util-deprecate@^1.0.2, util-deprecate@~1.0.1:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
 
+util.promisify@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.0.tgz#440f7165a459c9a16dc145eb8e72f35687097030"
+  dependencies:
+    define-properties "^1.1.2"
+    object.getownpropertydescriptors "^2.0.3"
+
 util@0.10.3, util@^0.10.3:
   version "0.10.3"
   resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9"
@@ -8648,6 +9770,10 @@ uuid@^3.0.0, uuid@^3.0.1, uuid@^3.1.0:
   version "3.2.1"
   resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.2.1.tgz#12c528bb9d58d0b9265d9a2f6f0fe8be17ff1f14"
 
+uuid@^3.3.2:
+  version "3.3.2"
+  resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131"
+
 v8-compile-cache@^1.1.0:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-1.1.2.tgz#8d32e4f16974654657e676e0e467a348e89b0dc4"
@@ -8718,6 +9844,12 @@ vm-browserify@0.0.4:
   dependencies:
     indexof "0.0.1"
 
+w3c-hr-time@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz#82ac2bff63d950ea9e3189a58a65625fedf19045"
+  dependencies:
+    browser-process-hrtime "^0.1.2"
+
 walker@~1.0.5:
   version "1.0.7"
   resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.7.tgz#2f7f9b8fd10d677262b18a884e28d19618e028fb"
@@ -8734,6 +9866,13 @@ watch@~0.10.0:
   version "0.10.0"
   resolved "https://registry.yarnpkg.com/watch/-/watch-0.10.0.tgz#77798b2da0f9910d595f1ace5b0c2258521f21dc"
 
+watch@~0.18.0:
+  version "0.18.0"
+  resolved "https://registry.yarnpkg.com/watch/-/watch-0.18.0.tgz#28095476c6df7c90c963138990c0a5423eb4b986"
+  dependencies:
+    exec-sh "^0.2.0"
+    minimist "^1.2.0"
+
 watchpack@^0.2.1:
   version "0.2.9"
   resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-0.2.9.tgz#62eaa4ab5e5ba35fdfc018275626e3c0f5e3fb0b"
@@ -8746,6 +9885,10 @@ web-namespaces@^1.0.0:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/web-namespaces/-/web-namespaces-1.1.2.tgz#c8dc267ab639505276bae19e129dbd6ae72b22b4"
 
+webidl-conversions@^4.0.2:
+  version "4.0.2"
+  resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad"
+
 webpack-configurator@^0.3.0:
   version "0.3.1"
   resolved "https://registry.yarnpkg.com/webpack-configurator/-/webpack-configurator-0.3.1.tgz#d16802afa674101a0cbfa6fc344d415c9649540b"
@@ -8873,10 +10016,36 @@ websocket-extensions@>=0.1.1:
   version "0.1.3"
   resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.3.tgz#5d2ff22977003ec687a4b87073dfbbac146ccf29"
 
+whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.4.tgz#63fb016b7435b795d9025632c086a5209dbd2621"
+  dependencies:
+    iconv-lite "0.4.23"
+
 whatwg-fetch@>=0.10.0:
   version "2.0.4"
   resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz#dde6a5df315f9d39991aa17621853d720b85566f"
 
+whatwg-mimetype@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.1.0.tgz#f0f21d76cbba72362eb609dbed2a30cd17fcc7d4"
+
+whatwg-url@^6.4.1:
+  version "6.5.0"
+  resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-6.5.0.tgz#f2df02bff176fd65070df74ad5ccbb5a199965a8"
+  dependencies:
+    lodash.sortby "^4.7.0"
+    tr46 "^1.0.1"
+    webidl-conversions "^4.0.2"
+
+whatwg-url@^7.0.0:
+  version "7.0.0"
+  resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-7.0.0.tgz#fde926fa54a599f3adf82dff25a9f7be02dc6edd"
+  dependencies:
+    lodash.sortby "^4.7.0"
+    tr46 "^1.0.1"
+    webidl-conversions "^4.0.2"
+
 when@^3.7.5:
   version "3.7.8"
   resolved "https://registry.yarnpkg.com/when/-/when-3.7.8.tgz#c7130b6a7ea04693e842cdc9e7a1f2aa39a39f82"
@@ -8895,6 +10064,12 @@ which@^1.0.9, which@^1.1.1, which@^1.2.12, which@^1.2.14, which@^1.2.9:
   dependencies:
     isexe "^2.0.0"
 
+which@^1.3.0:
+  version "1.3.1"
+  resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
+  dependencies:
+    isexe "^2.0.0"
+
 wide-align@^1.1.0:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.2.tgz#571e0f1b0604636ebc0dfc21b0339bbe31341710"
@@ -8923,6 +10098,10 @@ wordwrap@~0.0.2:
   version "0.0.3"
   resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107"
 
+wordwrap@~1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb"
+
 wrap-ansi@^2.0.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85"
@@ -8934,7 +10113,7 @@ wrappy@1:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
 
-write-file-atomic@^2.0.0:
+write-file-atomic@^2.0.0, write-file-atomic@^2.1.0:
   version "2.3.0"
   resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-2.3.0.tgz#1ff61575c2e2a4e8e510d6fa4e243cce183999ab"
   dependencies:
@@ -8949,6 +10128,12 @@ ws@3.0.0:
     safe-buffer "~5.0.1"
     ultron "~1.1.0"
 
+ws@^5.2.0:
+  version "5.2.2"
+  resolved "https://registry.yarnpkg.com/ws/-/ws-5.2.2.tgz#dffef14866b8e8dc9133582514d1befaf96e980f"
+  dependencies:
+    async-limiter "~1.0.0"
+
 ws@~3.3.1:
   version "3.3.3"
   resolved "https://registry.yarnpkg.com/ws/-/ws-3.3.3.tgz#f1cf84fe2d5e901ebce94efaece785f187a228f2"
@@ -8973,6 +10158,10 @@ xdg-basedir@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4"
 
+xml-name-validator@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a"
+
 xmlhttprequest-ssl@~1.5.4:
   version "1.5.5"
   resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz#c2876b06168aadc40e57d97e81191ac8f4398b3e"
@@ -9012,6 +10201,12 @@ yargs-parser@^7.0.0:
   dependencies:
     camelcase "^4.1.0"
 
+yargs-parser@^9.0.2:
+  version "9.0.2"
+  resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-9.0.2.tgz#9ccf6a43460fe4ed40a9bb68f48d43b8a68cc077"
+  dependencies:
+    camelcase "^4.1.0"
+
 yargs@4.7.1:
   version "4.7.1"
   resolved "https://registry.yarnpkg.com/yargs/-/yargs-4.7.1.tgz#e60432658a3387ff269c028eacde4a512e438dff"
@@ -9030,6 +10225,23 @@ yargs@4.7.1:
     y18n "^3.2.1"
     yargs-parser "^2.4.0"
 
+yargs@^11.0.0:
+  version "11.1.0"
+  resolved "http://registry.npmjs.org/yargs/-/yargs-11.1.0.tgz#90b869934ed6e871115ea2ff58b03f4724ed2d77"
+  dependencies:
+    cliui "^4.0.0"
+    decamelize "^1.1.1"
+    find-up "^2.1.0"
+    get-caller-file "^1.0.1"
+    os-locale "^2.0.0"
+    require-directory "^2.1.1"
+    require-main-filename "^1.0.1"
+    set-blocking "^2.0.0"
+    string-width "^2.0.0"
+    which-module "^2.0.0"
+    y18n "^3.2.1"
+    yargs-parser "^9.0.2"
+
 yargs@^8.0.2:
   version "8.0.2"
   resolved "https://registry.yarnpkg.com/yargs/-/yargs-8.0.2.tgz#6299a9055b1cefc969ff7e79c1d918dceb22c360"
index 848d860851cb0903f66bebc0512e151b1975314f..55fa10d84c74c461ec83bf219938ed307e1c2705 100644 (file)
@@ -20,6 +20,8 @@
 import * as React from 'react';
 import Helmet from 'react-helmet';
 import { Link } from 'react-router';
+import * as navigationTreeSonarQube from 'Docs/../static/SonarQubeNavigationTree.json';
+import * as navigationTreeSonarCloud from 'Docs/../static/SonarCloudNavigationTree.json';
 import Sidebar from './Sidebar';
 import getPages from '../pages';
 import NotFound from '../../../app/components/NotFound';
@@ -27,6 +29,7 @@ import ScreenPositionHelper from '../../../components/common/ScreenPositionHelpe
 import DocMarkdownBlock from '../../../components/docs/DocMarkdownBlock';
 import { translate } from '../../../helpers/l10n';
 import { isSonarCloud } from '../../../helpers/system';
+import { DocsNavigationItem } from '../utils';
 import '../styles.css';
 
 interface Props {
@@ -52,8 +55,11 @@ export default class App extends React.PureComponent<Props> {
   }
 
   render() {
-    const { splat = 'index' } = this.props.params;
-    const page = this.pages.find(p => p.relativeName === splat);
+    const tree = isSonarCloud()
+      ? ((navigationTreeSonarCloud as any).default as DocsNavigationItem[])
+      : ((navigationTreeSonarQube as any).default as DocsNavigationItem[]);
+    const { splat = '' } = this.props.params;
+    const page = this.pages.find(p => p.url === '/' + splat);
     const mainTitle = translate('documentation.page');
 
     if (!page) {
@@ -71,7 +77,7 @@ export default class App extends React.PureComponent<Props> {
 
     return (
       <div className="layout-page">
-        <Helmet title={isIndex ? mainTitle : `${page.title} - ${mainTitle}`}>
+        <Helmet title={isIndex || !page.title ? mainTitle : `${page.title} - ${mainTitle}`}>
           {!isSonarCloud() && <meta content="noindex nofollow" name="robots" />}
         </Helmet>
 
@@ -85,7 +91,7 @@ export default class App extends React.PureComponent<Props> {
                       <h1>{translate('documentation.page')}</h1>
                     </Link>
                   </div>
-                  <Sidebar pages={this.pages} splat={splat} />
+                  <Sidebar navigation={tree} pages={this.pages} splat={splat} />
                 </div>
               </div>
             </div>
index e4763bf53d87500dd93cd66201d223095f07aa6a..4db8919d7fbaa29c22c15cc301b76f259e0dffd2 100644 (file)
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import * as React from 'react';
-import { Link } from 'react-router';
-import * as classNames from 'classnames';
-import { sortBy } from 'lodash';
+import MenuBlock from './MenuBlock';
+import { MenuItem } from './MenuItem';
+import { MenuExternalLink } from './MenuExternalLink';
 import {
-  getEntryChildren,
   DocumentationEntry,
-  activeOrChildrenActive,
-  getEntryRoot
+  DocsNavigationBlock,
+  getNodeFromUrl,
+  isDocsNavigationBlock,
+  isDocsNavigationExternalLink,
+  DocsNavigationItem
 } from '../utils';
-import OpenCloseIcon from '../../../components/icons-components/OpenCloseIcon';
 
 interface Props {
+  navigation: DocsNavigationItem[];
   pages: DocumentationEntry[];
   splat: string;
 }
 
-type EntryWithChildren = DocumentationEntry & { children?: DocumentationEntry[] };
+interface State {
+  openBlockTitle: string;
+}
 
-export default class Menu extends React.PureComponent<Props> {
-  getMenuEntriesHierarchy = (root?: string): EntryWithChildren[] => {
-    const topLevelEntries = getEntryChildren(this.props.pages, root);
-    return sortBy(
-      topLevelEntries.map(entry => {
-        const entryRoot = getEntryRoot(entry.relativeName);
-        const children = entryRoot !== '' ? this.getMenuEntriesHierarchy(entryRoot) : [];
-        return { ...entry, children };
-      }),
-      entry => entry.order
-    );
-  };
+export default class Menu extends React.PureComponent<Props, State> {
+  constructor(props: Props) {
+    super(props);
+    this.state = {
+      openBlockTitle: this.getOpenBlockFromLocation(this.props.splat)
+    };
+  }
 
-  renderEntry = (entry: EntryWithChildren, depth: number): React.ReactNode => {
-    const active = entry.relativeName === this.props.splat;
-    const opened = activeOrChildrenActive(this.props.splat || '', entry);
-    const offset = 10 + 25 * depth;
-    const { children = [] } = entry;
-    return (
-      <React.Fragment key={entry.relativeName}>
-        <Link
-          className={classNames('list-group-item', { active })}
-          style={{ paddingLeft: offset }}
-          to={'/documentation/' + entry.relativeName}>
-          <h3 className="list-group-item-heading">
-            {children.length > 0 && <OpenCloseIcon className="little-spacer-right" open={opened} />}
-            {entry.title}
-          </h3>
-        </Link>
-        {opened && children.map(entry => this.renderEntry(entry, depth + 1))}
-      </React.Fragment>
+  componentWillReceiveProps(nextProps: Props) {
+    if (this.props.splat !== nextProps.splat) {
+      this.setState({ openBlockTitle: this.getOpenBlockFromLocation(nextProps.splat) });
+    }
+  }
+
+  getOpenBlockFromLocation(splat: string) {
+    const element = this.props.navigation.find(
+      item => isDocsNavigationBlock(item) && item.children.some(child => '/' + splat === child)
     );
+    return element ? (element as DocsNavigationBlock).title : '';
+  }
+
+  toggleBlock = (title: string) => {
+    this.setState(state => ({ openBlockTitle: state.openBlockTitle === title ? '' : title }));
   };
 
   render() {
-    return <>{this.getMenuEntriesHierarchy().map(entry => this.renderEntry(entry, 0))}</>;
+    return this.props.navigation.map(item => {
+      if (isDocsNavigationBlock(item)) {
+        return (
+          <MenuBlock
+            block={item}
+            key={item.title}
+            onToggle={this.toggleBlock}
+            open={this.state.openBlockTitle === item.title}
+            pages={this.props.pages}
+            splat={this.props.splat}
+            title={item.title}
+          />
+        );
+      }
+      if (isDocsNavigationExternalLink(item)) {
+        return <MenuExternalLink key={item.title} title={item.title} url={item.url} />;
+      }
+      return (
+        <MenuItem
+          indent={false}
+          key={item}
+          node={getNodeFromUrl(this.props.pages, item)}
+          splat={this.props.splat}
+        />
+      );
+    });
   }
 }
diff --git a/server/sonar-web/src/main/js/apps/documentation/components/MenuBlock.tsx b/server/sonar-web/src/main/js/apps/documentation/components/MenuBlock.tsx
new file mode 100644 (file)
index 0000000..fe48972
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import { MenuItem } from './MenuItem';
+import { DocumentationEntry, DocsNavigationBlock, getNodeFromUrl } from '../utils';
+import OpenCloseIcon from '../../../components/icons-components/OpenCloseIcon';
+
+interface Props {
+  block: DocsNavigationBlock;
+  onToggle: (title: string) => void;
+  open: boolean;
+  pages: DocumentationEntry[];
+  splat: string;
+  title: string;
+}
+
+export default class MenuBlock extends React.PureComponent<Props> {
+  handleClick = (event: React.MouseEvent<HTMLAnchorElement>) => {
+    event.stopPropagation();
+    event.preventDefault();
+    this.props.onToggle(this.props.title);
+  };
+
+  render() {
+    const { open, block, pages, title, splat } = this.props;
+    return (
+      <>
+        <a className="list-group-item" href="#" onClick={this.handleClick}>
+          <h3 className="list-group-item-heading">
+            <OpenCloseIcon className="little-spacer-right" open={this.props.open} />
+            {title}
+          </h3>
+        </a>
+        {open &&
+          block.children.map(item => (
+            <MenuItem indent={true} key={item} node={getNodeFromUrl(pages, item)} splat={splat} />
+          ))}
+      </>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/documentation/components/MenuExternalLink.tsx b/server/sonar-web/src/main/js/apps/documentation/components/MenuExternalLink.tsx
new file mode 100644 (file)
index 0000000..f150665
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import DetachIcon from '../../../components/icons-components/DetachIcon';
+
+interface Props {
+  title: string;
+  url: string;
+}
+
+export function MenuExternalLink({ title, url }: Props) {
+  return (
+    <a href={url} key={title} target="_blank">
+      <h3 className="list-group-item-heading">
+        <DetachIcon className="spacer-right" />
+        {title}
+      </h3>
+    </a>
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/documentation/components/MenuItem.tsx b/server/sonar-web/src/main/js/apps/documentation/components/MenuItem.tsx
new file mode 100644 (file)
index 0000000..0b67f4d
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import * as classNames from 'classnames';
+import { Link } from 'react-router';
+import { DocumentationEntry } from '../utils';
+
+interface Props {
+  indent: boolean;
+  node: DocumentationEntry | undefined;
+  splat: string;
+}
+
+export function MenuItem({ indent, node, splat }: Props) {
+  if (!node) {
+    return null;
+  }
+
+  const active = node.url === '/' + splat;
+  return (
+    <Link
+      className={classNames('list-group-item', { active })}
+      key={node.url}
+      style={{ paddingLeft: indent ? 31 : 10 }}
+      to={'/documentation' + node.url}>
+      <h3 className="list-group-item-heading">{node.title}</h3>
+    </Link>
+  );
+}
index 436dfb56e95e2fc66cfc84319739707df20802a1..094b45410a9a86415135238ec2a3abb97e8da68e 100644 (file)
@@ -37,7 +37,7 @@ export default function SearchResultEntry({ active, result }: Props) {
   return (
     <Link
       className={classNames('list-group-item', { active })}
-      to={'/documentation/' + result.page.relativeName}>
+      to={'/documentation' + result.page.url}>
       <SearchResultTitle result={result} />
       <SearchResultText result={result} />
     </Link>
index b42ba425fd9e3d3d88633afc42a91a7fedc0c621..2beeb11b9f9df7b9855a62be13f0f4a4512feff9 100644 (file)
@@ -21,9 +21,10 @@ import * as React from 'react';
 import lunr, { LunrIndex } from 'lunr';
 import { sortBy } from 'lodash';
 import SearchResultEntry, { SearchResult } from './SearchResultEntry';
-import { DocumentationEntry } from '../utils';
+import { DocumentationEntry, getUrlsList, DocsNavigationItem } from '../utils';
 
 interface Props {
+  navigation: DocsNavigationItem[];
   pages: DocumentationEntry[];
   query: string;
   splat: string;
@@ -41,7 +42,9 @@ export default class SearchResults extends React.PureComponent<Props> {
 
       this.metadataWhitelist = ['position'];
 
-      props.pages.forEach(page => this.add(page));
+      props.pages
+        .filter(page => getUrlsList(props.navigation).includes(page.url))
+        .forEach(page => this.add(page));
     });
   }
 
index 43c3802a680f886040625640aa4efe6f92284a0b..9374eee9943833c37754e6c2f06e9db7a8f046e4 100644 (file)
 import * as React from 'react';
 import Menu from './Menu';
 import SearchResults from './SearchResults';
-import { DocumentationEntry } from '../utils';
+import { DocumentationEntry, DocsNavigationItem } from '../utils';
 import SearchBox from '../../../components/controls/SearchBox';
 
 interface Props {
+  navigation: DocsNavigationItem[];
   pages: DocumentationEntry[];
   splat: string;
 }
@@ -53,12 +54,17 @@ export default class Sidebar extends React.PureComponent<Props, State> {
           <div className="list-group">
             {this.state.query ? (
               <SearchResults
+                navigation={this.props.navigation}
                 pages={this.props.pages}
                 query={this.state.query}
                 splat={this.props.splat}
               />
             ) : (
-              <Menu pages={this.props.pages} splat={this.props.splat} />
+              <Menu
+                navigation={this.props.navigation}
+                pages={this.props.pages}
+                splat={this.props.splat}
+              />
             )}
           </div>
         </div>
index 2f71253a2d336706631b64083388b5e2b11b8928..1985bc439f6a74e85ac559899947c32f6e58bf66 100644 (file)
@@ -22,7 +22,7 @@ import { shallow } from 'enzyme';
 import Menu from '../Menu';
 
 function createPage(title: string, relativeName: string, text = '') {
-  return { relativeName, title, order: -1, text, content: text };
+  return { relativeName, url: '/' + relativeName, title, text, content: text };
 }
 
 const pages = [
@@ -44,5 +44,13 @@ const pages = [
 ];
 
 it('should render hierarchical menu', () => {
-  expect(shallow(<Menu pages={pages} splat="lorem/origin" />)).toMatchSnapshot();
+  expect(
+    shallow(
+      <Menu
+        navigation={[{ title: 'Block', children: ['/lorem/index', '/lorem/origin'] }, 'foobar']}
+        pages={pages}
+        splat="lorem/origin"
+      />
+    )
+  ).toMatchSnapshot();
 });
diff --git a/server/sonar-web/src/main/js/apps/documentation/components/__tests__/MenuBlock-test.tsx b/server/sonar-web/src/main/js/apps/documentation/components/__tests__/MenuBlock-test.tsx
new file mode 100644 (file)
index 0000000..9dcfe46
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import MenuBlock from '../MenuBlock';
+
+const block = {
+  title: 'Foo',
+  children: ['/bar/', '/baz/']
+};
+
+const pages = [
+  {
+    content: 'bar',
+    relativeName: '/bar/',
+    text: 'bar',
+    title: 'Bar',
+    url: '/bar/'
+  },
+  {
+    content: 'baz',
+    relativeName: '/baz/',
+    text: 'baz',
+    title: 'baz',
+    url: '/baz/'
+  }
+];
+
+it('should render a closed menu block', () => {
+  expect(
+    shallow(
+      <MenuBlock
+        block={block}
+        onToggle={jest.fn()}
+        open={false}
+        pages={pages}
+        splat="/foobar/"
+        title="Foobarbaz"
+      />
+    )
+  ).toMatchSnapshot();
+});
+
+it('should render an opened menu block', () => {
+  expect(
+    shallow(
+      <MenuBlock
+        block={block}
+        onToggle={jest.fn()}
+        open={true}
+        pages={pages}
+        splat="/foo/"
+        title="Foo"
+      />
+    )
+  ).toMatchSnapshot();
+});
index 6785fd66bf8eedf751812a4a2109802810098058..f38330cb5858c392b7ba7d967b77f45a048b51d4 100644 (file)
@@ -27,8 +27,8 @@ import SearchResultEntry, {
 
 const page = {
   content: '',
-  order: -1,
   relativeName: 'foo/bar',
+  url: '/foo/bar',
   text: 'Foobar is a universal variable understood to represent whatever is being discussed.',
   title: 'Foobar'
 };
index d4151e45220c69a6cb3f0dc4452eeb089e401695..be38eb5a2f6493f2eb55fdd23a47dea66624ac56 100644 (file)
@@ -37,7 +37,7 @@ jest.mock('lunr', () => ({
 }));
 
 function createPage(title: string, relativeName: string, text = '') {
-  return { relativeName, title, order: -1, text, content: text };
+  return { relativeName, url: '/' + relativeName, title, text, content: text };
 }
 
 const pages = [
@@ -59,7 +59,14 @@ const pages = [
 ];
 
 it('should search', () => {
-  const wrapper = shallow(<SearchResults pages={pages} query="from" splat="foobar" />);
+  const wrapper = shallow(
+    <SearchResults
+      navigation={['lorem/index', 'lorem/origin', 'foobar']}
+      pages={pages}
+      query="from"
+      splat="foobar"
+    />
+  );
   expect(wrapper).toMatchSnapshot();
   expect(lunr).toBeCalled();
   expect((wrapper.instance() as SearchResults).index.search).toBeCalledWith('from~1 from*');
index 6fc596b466f006116c1af186bf9534a86baeaee9..ab41a1a9afeca1a0c60c7989800157e35b335d6a 100644 (file)
@@ -22,7 +22,7 @@ import { shallow } from 'enzyme';
 import Sidebar from '../Sidebar';
 
 function createPage(title: string, relativeName: string, text = '') {
-  return { relativeName, title, order: -1, text, content: text };
+  return { relativeName, url: '/' + relativeName, title, text, content: text };
 }
 
 const pages = [
@@ -31,11 +31,25 @@ const pages = [
 ];
 
 it('should render menu', () => {
-  expect(shallow(<Sidebar pages={pages} splat="foobar" />)).toMatchSnapshot();
+  expect(
+    shallow(
+      <Sidebar
+        navigation={[{ title: 'Block', children: ['/lorem/index'] }, 'foobar']}
+        pages={pages}
+        splat="foobar"
+      />
+    )
+  ).toMatchSnapshot();
 });
 
 it('should search', () => {
-  const wrapper = shallow(<Sidebar pages={pages} splat="foobar" />);
+  const wrapper = shallow(
+    <Sidebar
+      navigation={[{ title: 'Block', children: ['/lorem/index'] }, 'foobar']}
+      pages={pages}
+      splat="foobar"
+    />
+  );
   wrapper.find('SearchBox').prop<Function>('onChange')('foo');
   wrapper.update();
   expect(wrapper).toMatchSnapshot();
index dbc7ad1e5a0c900332850731e17114b12bc938b8..469a3f41a6db1bab2c58b056723839f25a2a983b 100644 (file)
@@ -1,70 +1,52 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
 exports[`should render hierarchical menu 1`] = `
-<React.Fragment>
-  <React.Fragment
-    key="lorem/index"
-  >
-    <Link
-      className="list-group-item"
-      onlyActiveOnIndex={false}
-      style={
-        Object {
-          "paddingLeft": 10,
-        }
+Array [
+  <MenuBlock
+    block={
+      Object {
+        "children": Array [
+          "/lorem/index",
+          "/lorem/origin",
+        ],
+        "title": "Block",
       }
-      to="/documentation/lorem/index"
-    >
-      <h3
-        className="list-group-item-heading"
-      >
-        <OpenCloseIcon
-          className="little-spacer-right"
-          open={true}
-        />
-        Lorem Ipsum
-      </h3>
-    </Link>
-    <React.Fragment
-      key="lorem/origin"
-    >
-      <Link
-        className="list-group-item active"
-        onlyActiveOnIndex={false}
-        style={
-          Object {
-            "paddingLeft": 35,
-          }
-        }
-        to="/documentation/lorem/origin"
-      >
-        <h3
-          className="list-group-item-heading"
-        >
-          Where does it come from?
-        </h3>
-      </Link>
-    </React.Fragment>
-  </React.Fragment>
-  <React.Fragment
-    key="foobar"
-  >
-    <Link
-      className="list-group-item"
-      onlyActiveOnIndex={false}
-      style={
+    }
+    key="Block"
+    onToggle={[Function]}
+    open={true}
+    pages={
+      Array [
         Object {
-          "paddingLeft": 10,
-        }
-      }
-      to="/documentation/foobar"
-    >
-      <h3
-        className="list-group-item-heading"
-      >
-        Where does Foobar come from?
-      </h3>
-    </Link>
-  </React.Fragment>
-</React.Fragment>
+          "content": "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book.",
+          "relativeName": "lorem/index",
+          "text": "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book.",
+          "title": "Lorem Ipsum",
+          "url": "/lorem/index",
+        },
+        Object {
+          "content": "Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words.",
+          "relativeName": "lorem/origin",
+          "text": "Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words.",
+          "title": "Where does it come from?",
+          "url": "/lorem/origin",
+        },
+        Object {
+          "content": "Foobar is a universal variable understood to represent whatever is being discussed.",
+          "relativeName": "foobar",
+          "text": "Foobar is a universal variable understood to represent whatever is being discussed.",
+          "title": "Where does Foobar come from?",
+          "url": "/foobar",
+        },
+      ]
+    }
+    splat="lorem/origin"
+    title="Block"
+  />,
+  <MenuItem
+    indent={false}
+    key="foobar"
+    splat="lorem/origin"
+  />,
+]
 `;
diff --git a/server/sonar-web/src/main/js/apps/documentation/components/__tests__/__snapshots__/MenuBlock-test.tsx.snap b/server/sonar-web/src/main/js/apps/documentation/components/__tests__/__snapshots__/MenuBlock-test.tsx.snap
new file mode 100644 (file)
index 0000000..bdf2bad
--- /dev/null
@@ -0,0 +1,69 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render a closed menu block 1`] = `
+<React.Fragment>
+  <a
+    className="list-group-item"
+    href="#"
+    onClick={[Function]}
+  >
+    <h3
+      className="list-group-item-heading"
+    >
+      <OpenCloseIcon
+        className="little-spacer-right"
+        open={false}
+      />
+      Foobarbaz
+    </h3>
+  </a>
+</React.Fragment>
+`;
+
+exports[`should render an opened menu block 1`] = `
+<React.Fragment>
+  <a
+    className="list-group-item"
+    href="#"
+    onClick={[Function]}
+  >
+    <h3
+      className="list-group-item-heading"
+    >
+      <OpenCloseIcon
+        className="little-spacer-right"
+        open={true}
+      />
+      Foo
+    </h3>
+  </a>
+  <MenuItem
+    indent={true}
+    key="/bar/"
+    node={
+      Object {
+        "content": "bar",
+        "relativeName": "/bar/",
+        "text": "bar",
+        "title": "Bar",
+        "url": "/bar/",
+      }
+    }
+    splat="/foo/"
+  />
+  <MenuItem
+    indent={true}
+    key="/baz/"
+    node={
+      Object {
+        "content": "baz",
+        "relativeName": "/baz/",
+        "text": "baz",
+        "title": "baz",
+        "url": "/baz/",
+      }
+    }
+    splat="/foo/"
+  />
+</React.Fragment>
+`;
index 3be4c54be97a00907927027404efa385ff16b634..6dd40778630675367d99a5abec20ebb0a0fcb1cf 100644 (file)
@@ -14,10 +14,10 @@ exports[`SearchResultEntry should render 1`] = `
         "longestTerm": "",
         "page": Object {
           "content": "",
-          "order": -1,
           "relativeName": "foo/bar",
           "text": "Foobar is a universal variable understood to represent whatever is being discussed.",
           "title": "Foobar",
+          "url": "/foo/bar",
         },
       }
     }
@@ -29,10 +29,10 @@ exports[`SearchResultEntry should render 1`] = `
         "longestTerm": "",
         "page": Object {
           "content": "",
-          "order": -1,
           "relativeName": "foo/bar",
           "text": "Foobar is a universal variable understood to represent whatever is being discussed.",
           "title": "Foobar",
+          "url": "/foo/bar",
         },
       }
     }
index cc6081342c887e380a3096d798055dcd5e3a9b62..d9e4c0dd961c2aa8138a9501fc9cf07e2ae222cc 100644 (file)
@@ -24,10 +24,10 @@ exports[`should search 1`] = `
         "longestTerm": "from",
         "page": Object {
           "content": "Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words.",
-          "order": -1,
           "relativeName": "lorem/origin",
           "text": "Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words.",
           "title": "Where does it come from?",
+          "url": "/lorem/origin",
         },
       }
     }
@@ -48,10 +48,10 @@ exports[`should search 1`] = `
         "longestTerm": "from",
         "page": Object {
           "content": "Foobar is a universal variable understood to represent whatever is being discussed.",
-          "order": -1,
           "relativeName": "foobar",
           "text": "Foobar is a universal variable understood to represent whatever is being discussed.",
           "title": "Where does Foobar come from?",
+          "url": "/foobar",
         },
       }
     }
index c03a4b3469c9d5730225b6eb316e20f28c4986b8..422bf65f9c01d89f6abeca31f9a861bc9e0efad1 100644 (file)
@@ -16,21 +16,32 @@ exports[`should render menu 1`] = `
       className="list-group"
     >
       <Menu
+        navigation={
+          Array [
+            Object {
+              "children": Array [
+                "/lorem/index",
+              ],
+              "title": "Block",
+            },
+            "foobar",
+          ]
+        }
         pages={
           Array [
             Object {
               "content": "",
-              "order": -1,
               "relativeName": "lorem/index",
               "text": "",
               "title": "Lorem Ipsum",
+              "url": "/lorem/index",
             },
             Object {
               "content": "",
-              "order": -1,
               "relativeName": "foobar",
               "text": "",
               "title": "Where does Foobar come from?",
+              "url": "/foobar",
             },
           ]
         }
@@ -57,21 +68,32 @@ exports[`should search 1`] = `
       className="list-group"
     >
       <SearchResults
+        navigation={
+          Array [
+            Object {
+              "children": Array [
+                "/lorem/index",
+              ],
+              "title": "Block",
+            },
+            "foobar",
+          ]
+        }
         pages={
           Array [
             Object {
               "content": "",
-              "order": -1,
               "relativeName": "lorem/index",
               "text": "",
               "title": "Lorem Ipsum",
+              "url": "/lorem/index",
             },
             Object {
               "content": "",
-              "order": -1,
               "relativeName": "foobar",
               "text": "",
               "title": "Where does Foobar come from?",
+              "url": "/foobar",
             },
           ]
         }
diff --git a/server/sonar-web/src/main/js/apps/documentation/components/__tests__/pages-test.ts b/server/sonar-web/src/main/js/apps/documentation/components/__tests__/pages-test.ts
deleted file mode 100644 (file)
index 45761c8..0000000
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-import getPages from '../../pages';
-import { isSonarCloud } from '../../../../helpers/system';
-
-// mock `remark` and `remark-react` to work around the issue with cjs imports
-jest.mock('remark', () => {
-  const remark = require.requireActual('remark');
-  return { default: remark };
-});
-
-jest.mock('unist-util-visit', () => {
-  const exp = require.requireActual('unist-util-visit');
-  return { default: exp };
-});
-
-jest.mock('../../documentation.directory-loader', () => [
-  {
-    path: 'all',
-    content: `
-    ---
-    title: All
-    ---
-
-    all all all`
-  },
-  {
-    path: 'sonarqube-foo',
-    content: `
-    ---
-    title: Foo
-    scope: sonarqube
-    ---
-
-    foo foo foo`
-  },
-  {
-    path: 'sonarcloud-bar',
-    content: `
-    ---
-    title: Bar
-    scope: sonarcloud
-    ---
-
-    bar bar bar`
-  },
-  {
-    path: 'static-baz',
-    content: `
-    ---
-    title: Baz
-    scope: static
-    ---
-
-    baz baz baz`
-  }
-]);
-
-jest.mock('../../../../helpers/system', () => ({ isSonarCloud: jest.fn() }));
-
-it('should filter pages for SonarQube', () => {
-  (isSonarCloud as jest.Mock).mockReturnValue(false);
-  expect(getPages().map(page => page.title)).toEqual(['All', 'Foo']);
-});
-
-it('should filter pages for SonarCloud', () => {
-  (isSonarCloud as jest.Mock).mockReturnValue(true);
-  expect(getPages().map(page => page.title)).toEqual(['All', 'Bar']);
-});
index e377c21151cde056a76d2f9f266fdea1ec79b8d5..898f70b1befe4fbd549512ef87b84a9befa4ede2 100644 (file)
@@ -22,7 +22,6 @@ import visit from 'unist-util-visit';
 import { DocumentationEntry, DocumentationEntryScope } from './utils';
 import * as Docs from './documentation.directory-loader';
 import { separateFrontMatter, filterContent } from '../../helpers/markdown';
-import { isSonarCloud } from '../../helpers/system';
 
 export default function getPages(): DocumentationEntry[] {
   return Docs.map((file: any) => {
@@ -32,6 +31,7 @@ export default function getPages(): DocumentationEntry[] {
 
     return {
       relativeName: file.path,
+      url: parsed.frontmatter.url || `/${file.path}`,
       title: parsed.frontmatter.title,
       order: Number(parsed.frontmatter.order || -1),
       scope: parsed.frontmatter.scope
@@ -40,11 +40,6 @@ export default function getPages(): DocumentationEntry[] {
       text,
       content: file.content
     };
-  }).filter((page: DocumentationEntry) => {
-    if (!page.scope) {
-      return true;
-    }
-    return isSonarCloud() ? page.scope === 'sonarcloud' : page.scope === 'sonarqube';
   });
 }
 
index 64ed645736707974301da0bc219c4ee6d0e94a0e..3c08b6a26b4b0c5d0503194d10283a727b921f86 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.
  */
-import { sortBy } from 'lodash';
+import { sortBy, flatten } from 'lodash';
 
 export type DocumentationEntryScope = 'sonarqube' | 'sonarcloud' | 'static';
 
+export interface DocsNavigationBlock {
+  title: string;
+  children: string[];
+}
+
+export interface DocsNavigationExternalLink {
+  title: string;
+  url: string;
+}
+
 export interface DocumentationEntry {
   content: string;
-  order: number;
   relativeName: string;
-  scope?: DocumentationEntryScope;
   text: string;
   title: string;
+  url: string;
 }
 
-export function activeOrChildrenActive(root: string, entry: DocumentationEntry) {
-  return root.indexOf(getEntryRoot(entry.relativeName)) === 0;
+export type DocsNavigationItem = string | DocsNavigationBlock | DocsNavigationExternalLink;
+
+export function isDocsNavigationBlock(item: DocsNavigationItem): item is DocsNavigationBlock {
+  return typeof item === 'object' && !(item as any).url;
 }
 
-export function getEntryRoot(name: string) {
-  if (name.endsWith('index')) {
-    return name
-      .split('/')
-      .slice(0, -1)
-      .join('/');
-  }
-  return name;
+export function isDocsNavigationExternalLink(
+  item: DocsNavigationItem
+): item is DocsNavigationExternalLink {
+  return typeof item === 'object' && (item as any).url;
+}
+
+export function getUrlsList(navigation: DocsNavigationItem[]): string[] {
+  return flatten(
+    navigation
+      .filter(item => !isDocsNavigationExternalLink(item))
+      .map(
+        (item: string | DocsNavigationBlock) =>
+          isDocsNavigationBlock(item) ? item.children : [item]
+      )
+  );
 }
 
-export function getEntryChildren(entries: DocumentationEntry[], root?: string) {
-  return entries.filter(entry => {
-    const parts = entry.relativeName.split('/');
-    const depth = root ? root.split('/').length : 0;
-    return (
-      (!root || entry.relativeName.indexOf(root) === 0) &&
-      ((parts.length === depth + 1 && parts[depth] !== 'index') || parts[depth + 1] === 'index')
-    );
-  });
+export function getNodeFromUrl(pages: DocumentationEntry[], url: string) {
+  return pages.find(p => p.url === url);
 }
 
 const WORDS = 6;
index 64944d0496a9552062524c75ad0e74c778772b3d..303e048e85b06a2075196ea21b0e89f73c60c90a 100644 (file)
@@ -33,7 +33,10 @@ jest.mock('remark-react', () => {
   return { default: remarkReact };
 });
 
-jest.mock('../../../helpers/system', () => ({ isSonarCloud: jest.fn() }));
+jest.mock('../../../helpers/system', () => ({
+  getInstance: jest.fn(),
+  isSonarCloud: jest.fn()
+}));
 
 it('should render simple markdown', () => {
   expect(shallow(<DocMarkdownBlock content="this is *bold* text" />)).toMatchSnapshot();
index 633c34502892a3e8a1003178e2dd8e2c23f8bb52..b41e590fcadb39d4f77847403585c27237f6a658 100644 (file)
@@ -72,8 +72,9 @@ function parseFrontMatter(lines) {
  * @returns {string}
  */
 function filterContent(content) {
-  const { isSonarCloud } = require('./system');
-  const contentWithoutStatic = cutConditionalContent(content, 'static');
+  const { isSonarCloud, getInstance } = require('./system');
+  const contentWithInstance = content.replace('{instance}', getInstance());
+  const contentWithoutStatic = cutConditionalContent(contentWithInstance, 'static');
   return isSonarCloud()
     ? cutConditionalContent(contentWithoutStatic, 'sonarqube')
     : cutConditionalContent(contentWithoutStatic, 'sonarcloud');