Browse Source

SONAR-17683 Remove server/sonar-docs

tags/9.8.0.63668
Wouter Admiraal 1 year ago
parent
commit
53038a7704
100 changed files with 1 additions and 12338 deletions
  1. 1
    1
      .cirrus.yml
  2. 0
    1
      .github/renovate.json
  3. 0
    9
      server/sonar-docs/.eslintrc
  4. 0
    19
      server/sonar-docs/.gitignore
  5. 0
    801
      server/sonar-docs/.yarn/releases/yarn-3.2.4.cjs
  6. 0
    5
      server/sonar-docs/.yarnrc.yml
  7. 0
    334
      server/sonar-docs/README.md
  8. 0
    163
      server/sonar-docs/build.gradle
  9. 0
    20
      server/sonar-docs/config/jest/CSSStub.js
  10. 0
    20
      server/sonar-docs/config/jest/FileStub.js
  11. 0
    23
      server/sonar-docs/config/jest/SetupEnzyme.js
  12. 0
    26
      server/sonar-docs/config/jest/SetupJest.ts
  13. 0
    65
      server/sonar-docs/gatsby-config.js
  14. 0
    78
      server/sonar-docs/gatsby-node.js
  15. 0
    120
      server/sonar-docs/package.json
  16. 0
    48
      server/sonar-docs/plugins/sonarsource-source-filesystem/__tests__/index-test.js
  17. 0
    23
      server/sonar-docs/plugins/sonarsource-source-filesystem/gatsby-node.js
  18. 0
    235
      server/sonar-docs/plugins/sonarsource-source-filesystem/index.js
  19. 0
    10
      server/sonar-docs/plugins/sonarsource-source-filesystem/package.json
  20. 0
    5728
      server/sonar-docs/src/@types/graphql-types.d.ts
  21. 0
    26
      server/sonar-docs/src/@types/hast-util-select.d.ts
  22. 0
    60
      server/sonar-docs/src/@types/lunr.d.ts
  23. 0
    51
      server/sonar-docs/src/@types/types.d.ts
  24. 0
    202
      server/sonar-docs/src/__tests__/BrokenLinkSafetyNet.test.js
  25. 0
    98
      server/sonar-docs/src/components/CategoryBlockLink.tsx
  26. 0
    35
      server/sonar-docs/src/components/ExternalLink.tsx
  27. 0
    47
      server/sonar-docs/src/components/Footer.tsx
  28. 0
    46
      server/sonar-docs/src/components/HeaderList.tsx
  29. 0
    55
      server/sonar-docs/src/components/HeaderListProvider.tsx
  30. 0
    47
      server/sonar-docs/src/components/HeadingAnchor.tsx
  31. 0
    136
      server/sonar-docs/src/components/HeadingsLink.tsx
  32. 0
    101
      server/sonar-docs/src/components/MetaData.css
  33. 0
    124
      server/sonar-docs/src/components/MetaData.tsx
  34. 0
    98
      server/sonar-docs/src/components/MetaDataVersion.tsx
  35. 0
    84
      server/sonar-docs/src/components/MetaDataVersions.tsx
  36. 0
    60
      server/sonar-docs/src/components/OutsideClickHandler.tsx
  37. 0
    52
      server/sonar-docs/src/components/PageLink.tsx
  38. 0
    214
      server/sonar-docs/src/components/Search.tsx
  39. 0
    114
      server/sonar-docs/src/components/SearchEntryResult.tsx
  40. 0
    240
      server/sonar-docs/src/components/Sidebar.tsx
  41. 0
    78
      server/sonar-docs/src/components/VersionSelect.tsx
  42. 0
    43
      server/sonar-docs/src/components/__tests__/CategoryBlockLink-test.tsx
  43. 0
    28
      server/sonar-docs/src/components/__tests__/ExternalLink-test.tsx
  44. 0
    39
      server/sonar-docs/src/components/__tests__/HeadingsLink-test.tsx
  45. 0
    85
      server/sonar-docs/src/components/__tests__/MetaData-test.tsx
  46. 0
    45
      server/sonar-docs/src/components/__tests__/MetaDataVersion-test.tsx
  47. 0
    51
      server/sonar-docs/src/components/__tests__/MetaDataVersions-test.tsx
  48. 0
    31
      server/sonar-docs/src/components/__tests__/PageLink-test.tsx
  49. 0
    126
      server/sonar-docs/src/components/__tests__/Search-test.tsx
  50. 0
    112
      server/sonar-docs/src/components/__tests__/Sidebar-test.tsx
  51. 0
    62
      server/sonar-docs/src/components/__tests__/VersionSelect-test.tsx
  52. 0
    51
      server/sonar-docs/src/components/__tests__/__snapshots__/CategoryBlockLink-test.tsx.snap
  53. 0
    13
      server/sonar-docs/src/components/__tests__/__snapshots__/ExternalLink-test.tsx.snap
  54. 0
    33
      server/sonar-docs/src/components/__tests__/__snapshots__/HeadingsLink-test.tsx.snap
  55. 0
    133
      server/sonar-docs/src/components/__tests__/__snapshots__/MetaData-test.tsx.snap
  56. 0
    113
      server/sonar-docs/src/components/__tests__/__snapshots__/MetaDataVersion-test.tsx.snap
  57. 0
    28
      server/sonar-docs/src/components/__tests__/__snapshots__/MetaDataVersions-test.tsx.snap
  58. 0
    13
      server/sonar-docs/src/components/__tests__/__snapshots__/PageLink-test.tsx.snap
  59. 0
    24
      server/sonar-docs/src/components/__tests__/__snapshots__/Search-test.tsx.snap
  60. 0
    807
      server/sonar-docs/src/components/__tests__/__snapshots__/Sidebar-test.tsx.snap
  61. 0
    109
      server/sonar-docs/src/components/__tests__/__snapshots__/VersionSelect-test.tsx.snap
  62. 0
    90
      server/sonar-docs/src/components/__tests__/navTreeUtils-test.ts
  63. 0
    32
      server/sonar-docs/src/components/icons/AlertWarnIcon.tsx
  64. 0
    32
      server/sonar-docs/src/components/icons/ChevronDownIcon.tsx
  65. 0
    32
      server/sonar-docs/src/components/icons/ChevronUpIcon.tsx
  66. 0
    32
      server/sonar-docs/src/components/icons/ClearIcon.tsx
  67. 0
    32
      server/sonar-docs/src/components/icons/DetachIcon.tsx
  68. 0
    36
      server/sonar-docs/src/components/icons/DownloadIcon.tsx
  69. 0
    70
      server/sonar-docs/src/components/icons/Icon.tsx
  70. 0
    57
      server/sonar-docs/src/components/mocks/update-center-metadata.ts
  71. 0
    95
      server/sonar-docs/src/components/navTreeUtils.ts
  72. 0
    48
      server/sonar-docs/src/components/update-center-metadata.ts
  73. 0
    128
      server/sonar-docs/src/components/utils.tsx
  74. BIN
      server/sonar-docs/src/images/AzurePipelinesAnalysis.png
  75. BIN
      server/sonar-docs/src/images/SQ-instance-components.png
  76. 0
    1
      server/sonar-docs/src/images/SonarQubeIcon.svg
  77. BIN
      server/sonar-docs/src/images/activate_rule_compare1.png
  78. BIN
      server/sonar-docs/src/images/add-ADO-project.png
  79. BIN
      server/sonar-docs/src/images/add-bitbucket-project.png
  80. BIN
      server/sonar-docs/src/images/add-github-project.png
  81. BIN
      server/sonar-docs/src/images/add-gitlab-project.png
  82. 0
    1
      server/sonar-docs/src/images/alerts/danger.svg
  83. 0
    1
      server/sonar-docs/src/images/alerts/info.svg
  84. 0
    1
      server/sonar-docs/src/images/alerts/wrong.svg
  85. 0
    2
      server/sonar-docs/src/images/alm/azure.svg
  86. 0
    2
      server/sonar-docs/src/images/alm/bitbucket.svg
  87. 0
    2
      server/sonar-docs/src/images/alm/github.svg
  88. 0
    1
      server/sonar-docs/src/images/alm/gitlab.svg
  89. BIN
      server/sonar-docs/src/images/architecture-integrate.png
  90. BIN
      server/sonar-docs/src/images/architecture-scanning.png
  91. BIN
      server/sonar-docs/src/images/astSample.png
  92. BIN
      server/sonar-docs/src/images/azure/saml-azure-attributes.jpg
  93. BIN
      server/sonar-docs/src/images/azure/saml-azure-basic-saml.jpg
  94. BIN
      server/sonar-docs/src/images/azure/saml-azure-certificate.jpg
  95. BIN
      server/sonar-docs/src/images/azure/saml-azure-create-application.jpg
  96. BIN
      server/sonar-docs/src/images/azure/saml-azure-encryption.jpg
  97. BIN
      server/sonar-docs/src/images/azure/saml-azure-group-claim.jpg
  98. BIN
      server/sonar-docs/src/images/azure/saml-azure-links.jpg
  99. BIN
      server/sonar-docs/src/images/azure/saml-azure-mapping.jpg
  100. 0
    0
      server/sonar-docs/src/images/azure/saml-azure-new.jpg

+ 1
- 1
.cirrus.yml View File

@@ -102,7 +102,7 @@ yarn_cache_template: &YARN_CACHE_TEMPLATE
fingerprint_script: |
cat \
server/sonar-web/yarn.lock \
server/sonar-docs/yarn.lock \
private/core-extension-developer-server/yarn.lock \
private/core-extension-enterprise-server/yarn.lock \
private/core-extension-license/yarn.lock \
private/core-extension-securityreport/yarn.lock

+ 0
- 1
.github/renovate.json View File

@@ -48,7 +48,6 @@
"labels": [
"dependencies"
],
"ignorePaths": ["**/server/sonar-docs/**"],
"packageRules": [
{
"matchManagers": "maven",

+ 0
- 9
server/sonar-docs/.eslintrc View File

@@ -1,9 +0,0 @@
{
"extends": "sonarqube",

"rules": {
// some dependencies are implictly provided by gatsby
"import/no-extraneous-dependencies": "off",
"testing-library/render-result-naming-convention": "warn"
}
}

+ 0
- 19
server/sonar-docs/.gitignore View File

@@ -1,19 +0,0 @@
# Gatsby files
.cache/
public

# tests
lcov.info
coverage/

# eslint
eslint-report.json

# yarn
.yarn/*
!.yarn/patches
!.yarn/releases
!.yarn/plugins
!.yarn/sdks
!.yarn/versions
.pnp.*

+ 0
- 801
server/sonar-docs/.yarn/releases/yarn-3.2.4.cjs
File diff suppressed because it is too large
View File


+ 0
- 5
server/sonar-docs/.yarnrc.yml View File

@@ -1,5 +0,0 @@
enableGlobalCache: true

nodeLinker: node-modules

yarnPath: .yarn/releases/yarn-3.2.4.cjs

+ 0
- 334
server/sonar-docs/README.md View File

@@ -1,334 +0,0 @@
# sonar-docs

### General

The documentation content lives in `sonar-enterprise/server/sonar-docs`
We use an augmented GitHub markdown syntax:

- general: https://guides.github.com/features/mastering-markdown/
- general: https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet
- linebreaks: https://gist.github.com/shaunlebron/746476e6e7a4d698b373

## The first time

- Install nodejs v10, which is the current LTS version.
- Install https://yarnpkg.com/en
- Set the following properties in `~/.gradle/gradle.properties`:
- `artifactoryUsername=<Artifactory username>`
- `artifactoryPassword=<Artifactory API key>`
- Run the following to set up the dev servers:

```
cd sonar-enterprise/server/sonar-web
yarn
cd ../sonar-docs
yarn
```

## Each time

Start a SonarQube server (yarn needs this running).


To start the SonarQube Embedded docs dev server on port 3000:

```
cd sonar-enterprise/server/sonar-web
yarn start
```

To start the SonarCloud Embedded docs dev server on port 3001:

```
cd sonar-enterprise/server/sonar-web
INSTANCE=SonarCloud PORT=3001 yarn start
```

_You can have both SonarQube and SonarCloud embedded doc dev server running in parallel when you start them on different ports._

To start the Static docs dev server on port 8000:

```
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.

## Committing

**Always start your commit message with "DOCS".**

The convention is to start commit messages with the ticket number the changes are for. Since docs changes are often made without tickets, use "DOCS" instead.

## 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.
- The `url` tag is required and defines the path at which to publish the page. Reminder: end this with a trailing slash.
- The `nav` tag is optional and controls the title of the page inside the navigation tree.

Ex.:

```
---
title: Demo page
nav: Demo
url: /sonarcloud-pricing/
---
```

**Metadata conventions**

- Metadata tags can appear in any order, but by convention, `title` should come first.
- The `url` tag is required, and should start and end with `/`

### Includes

Basic syntax: `@include tooltips/quality-gates/quality-gate`

- path omits trailing '.md'
- path starts from 'src', regardless of where the including page is.

### Conditional Content

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 drop in "SonarQube" or "SonarCloud" as appropriate, use:

```
{instance}
```

To display/hide some other part of the content, use special comments:

```md
<!-- sonarcloud -->

this content is displayed only on SonarCloud

<!-- /sonarcloud -->

<!-- sonarqube -->

this content is displayed in SonarQube and in the static website

<!-- /sonarqube -->

<!-- static -->

this content is displayed only in the static website

<!-- /static -->

<!-- embedded -->

this content is displayed only in the embedded documentation

<!-- /embedded -->
```

You can also use these comments inline:

```md
this content is displayed on <!-- sonarcloud -->SonarCloud<!-- /sonarcloud --><!-- sonarqube -->SonarQube<!-- /sonarqube -->
```

### Formatting

#### Links

- External page (automatically opens in a new tab): `[Link text](https://www.sonarsource.com/)`
- Another documentation page: `[Link text](/short-lived-branches/)`
- path omits trailing '.md'
- links inside tooltips always open in a new tab
- Internal SonarCloud app page: `[Link text](/#sonarcloud#/onboarding)`
- it is possible to reference app pages only inside SonarCloud documentation page (`scope: sonarcloud`), because these links are not valid on the static documentation

#### Smart Links

Use this syntax to conditionally link from the embedded docs to pages within the SonarQube application. Specifically, in the static website links will be suppressed, but the link text will be shown. In the embedded documentation, administrative links will only be linked for administrative users.

- Internal SonarQube app page: `[Link text](/#sonarqube#/...)`
- On SonarCloud, only the link text will be displayed, not wrapped into a link tag
- Internal SonarQube app page: `[Link text](/#sonarqube-admin#/...)`

#### Linebreaks

By default, single linebreaks are removed in rendering. I.e.

```
foo
bar
baz
```

Will render as

```
foo bar baz
```

To get a `<br/>` effect, add 2 spaces at the end of the line

```
foo //<- 2 spaces
bar //<- 2 spaces
baz
```

Yields

```
foo
bar
baz
```

#### Collapsible block

Basic syntax:

```
[[collapse]]
| ## Block title
| Block content
```

The first line of the block must be a title. You can have as many lines as you want after that.

#### Images

Basic syntax: `![alt text.](/images/short-lived-branch-concept.png)`

- images are auto-sized to 100% if they're larger than 100%
- paths start from 'src', regardless of where the calling page is

#### Icons

- :warning: `![](/images/exclamation.svg)`
- :information_source: `![](/images/info.svg)`
- :heavy_check_mark: `![](/images/check.svg)`
- :x: `![](/images/cross.svg)`

#### Message box

Basic syntax:

```
[[warning]]
| This is a **warning** message.
```

**There must be a linebreak before the first '|'**

There are four options:

- danger (red)
- warning (yellow)
- success (green)
- info (blue)

#### Iframes

_Note: at this time, iframes are only supported for the static documentation, and will be stripped from the embedded documentation._

You can add iframes directly in the source:

```html
<iframe src="http://www.sonarsource.com"></iframe>

```

Make sure to leave an empty line _after_ the closing tag, otherwise formatting of the following line could be incorrect:

_Incorrect:_
```md
<iframe src="http://www.sonarsource.com"></iframe>
*Lorem ipsum* dolor sit amet.
```

_Correct:_
```md
<iframe src="http://www.sonarsource.com"></iframe>

*Lorem ipsum* dolor sit amet.
```

By default, an iframe will have a height of 150px (as per browser specs). You can override this by adding a `height` attribute:

```html
<iframe src="http://www.sonarsource.com" height="400px"></iframe>

```

You cannot change the width, which is always 100%.

Note that an iframe is **not** a self-closing tag. This means that the following syntax _won't work_ and will break the page in unexpected ways:

```html
<iframe src="http://www.sonarsource.com" />

```

#### Dynamic Scanner Version Info

_Only supported by the static documentation_

You can dynamically include a scanner version block to any page, using the following special tag:

```html
<update-center updatecenterkey="SCANNER_KEY"></update-center>
```

For example, for gradle's scanner, use:

```html
<update-center updatecenterkey="sonargradle"></update-center>
```

You can include multiple boxes per page, if needed.

## URL Rewrites
The code in this section replaces 0-n pages that used to live on Confluence. To ease the transition (search results, user bookmarks, etc.) we've put server-level redirects in place from the old Confluence pages to the static site. Those redirects are maintained here:

* https://github.com/SonarSource/marlin/blob/master/ansible/installs/docs.yml
* https://github.com/SonarSource/marlin/blob/master/ansible/installs/docs3.yml
* https://github.com/SonarSource/test-infra/blob/master/tests/docs_sonarqube_org_test.py


+ 0
- 163
server/sonar-docs/build.gradle View File

@@ -1,163 +0,0 @@
import java.util.regex.Matcher
import java.util.regex.Pattern

Pattern PLUGIN_NAME_PATTERN = Pattern.compile("(sonar-.*-plugin)(.*)")


/**
* This module is building the zip file containing the static web site
*/

sonar {
skipProject = true
}

group = 'com.sonarsource.sonarqube'

configurations {
bundledPlugin {
transitive = false
}
}

// loads the bundled_plugins.gradle of each edition
// (they will all add there own bundled plugins to the bundledPlugin dependency configuration)
apply from: new File(rootDir, 'sonar-application/bundled_plugins.gradle')
File closeSourceDir = new File(rootDir, 'private');
if (closeSourceDir.exists()) {
apply from: new File(closeSourceDir, 'edition-developer/bundled_plugins.gradle')
apply from: new File(closeSourceDir, 'edition-enterprise/bundled_plugins.gradle')
apply from: new File(closeSourceDir, 'edition-datacenter/bundled_plugins.gradle')
}

task extractAnalyzerDocFiles {
doLast {
configurations.bundledPlugin.files.each {
File file = it
copy {
from(zipTree(file).matching { include 'static/documentation.md', 'META-INF/MANIFEST.MF' }) {
eachFile { fcd ->
if (fcd.getName().endsWith('.md')) {
fcd.relativePath = new RelativePath(true, 'documentation' + '.md')
} else {
fcd.relativePath = new RelativePath(true, 'MANIFEST' + '.MF')
}
}
includeEmptyDirs = false
}
Matcher m = PLUGIN_NAME_PATTERN.matcher(file.getName())
if (m.find()) {
into "$buildDir/tmp/plugin-documentation/" + m.group(1)
}

}
}
}
}

task yarn_run(type: Exec) {
def docsVersion = version.split("[.-]").take(2).join('.')
inputs.property('version', docsVersion)
inputs.file(rootProject.file('build.gradle'));
inputs.dir('src').withPathSensitivity(PathSensitivity.RELATIVE)
['build.gradle', 'gatsby-config.js', 'gatsby-node.js', 'package.json', 'yarn.lock', 'tsconfig.json'].each {
inputs.file(it).withPathSensitivity(PathSensitivity.RELATIVE)
}
outputs.dir('public')
outputs.cacheIf { true }

environment += [ GATSBY_DOCS_VERSION: docsVersion, GATSBY_TELEMETRY_DISABLED: 1 ]
commandLine osAdaptiveCommand(['npm', 'run', 'build'])
}
yarn_run.dependsOn(extractAnalyzerDocFiles)
build.dependsOn(yarn_run)

// To clean outputs outside of "build" directory:
clean.dependsOn(cleanYarn_run)

task "yarn_check-ci"(type: Exec) {
// Note that outputs are not relocatable, because contain absolute paths, and that's why inputs are not relativized
['config', 'src'].each {
inputs.dir(it)
}
['package.json', 'yarn.lock', 'tsconfig.json'].each {
inputs.file(it)
}

commandLine osAdaptiveCommand(['npm', 'run', 'check-ci'])
}

task "yarn_validate-ci"(type: Exec) {
// Note that outputs are not relocatable, because contain absolute paths, and that's why inputs are not relativized
['config', 'src'].each {
inputs.dir(it)
}
['package.json', 'yarn.lock', 'tsconfig.json', '.eslintrc'].each {
inputs.file(it)
}
outputs.cacheIf { true }

commandLine osAdaptiveCommand(['npm', 'run', 'validate-ci'])
}

task zip(type: Zip) {
def archiveDir = "$version"
duplicatesStrategy DuplicatesStrategy.EXCLUDE
baseName "sonar-docs"

into("${archiveDir}") {
from tasks.getByName('yarn_run').outputs
}
}
zip.dependsOn yarn_run
assemble.dependsOn zip

publishing {
publications {
docs(MavenPublication) {
artifactId 'sonar-docs'
artifact zip
}
}
}

artifactory {
publish {
repository {
repoKey = System.getenv('ARTIFACTORY_DEPLOY_REPO_PRIVATE')
username = System.getenv('ARTIFACTORY_DEPLOY_USERNAME') ?: project.properties.artifactoryUsername
password = System.getenv('ARTIFACTORY_DEPLOY_PASSWORD') ?: project.properties.artifactoryPaswword
}
defaults {
properties = [
'build.name' : 'sonar-enterprise',
'build.number' : System.getenv('BUILD_NUMBER'),
'pr.branch.target': System.getenv('GITHUB_BASE_BRANCH'),
'pr.number' : System.getenv('PULL_REQUEST'),
'vcs.branch' : System.getenv('GITHUB_BRANCH'),
'vcs.revision' : System.getenv('GIT_SHA1'),
'version' : version
]
publishPom = true
publishIvy = false
}
}
}

artifactoryPublish {
skip = false
publishPom = false
publications(publishing.publications.docs)
}

def sources = fileTree(dir: "src") + fileTree(dir: "config") + fileTree(dir: "plugins") + file("gatsby-config.js") + file("gatsby-node.js")

task licenseCheckWeb(type: com.hierynomus.gradle.license.tasks.LicenseCheck) {
source = sources
}
licenseMain.dependsOn licenseCheckWeb

task licenseFormatWeb(type: com.hierynomus.gradle.license.tasks.LicenseFormat) {
source = sources
}
licenseFormat.dependsOn licenseFormatWeb

+ 0
- 20
server/sonar-docs/config/jest/CSSStub.js View File

@@ -1,20 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2022 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.
*/
module.exports = {};

+ 0
- 20
server/sonar-docs/config/jest/FileStub.js View File

@@ -1,20 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2022 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.
*/
module.exports = 'test-file-stub';

+ 0
- 23
server/sonar-docs/config/jest/SetupEnzyme.js View File

@@ -1,23 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2022 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 Enzyme = require('enzyme');
const Adapter = require('enzyme-adapter-react-16');

Enzyme.configure({ adapter: new Adapter() });

+ 0
- 26
server/sonar-docs/config/jest/SetupJest.ts View File

@@ -1,26 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2022 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 { GlobalWithFetchMock } from 'jest-fetch-mock';

const customGlobal: GlobalWithFetchMock = global as GlobalWithFetchMock;

customGlobal.fetch = require('jest-fetch-mock');

customGlobal.fetchMock = customGlobal.fetch;

+ 0
- 65
server/sonar-docs/gatsby-config.js View File

@@ -1,65 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2022 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 DOCS_VERSION = process.env.GATSBY_DOCS_VERSION || '1.0';

module.exports = {
pathPrefix: '/' + DOCS_VERSION,
siteMetadata: {
title: 'SonarQube Documentation'
},
plugins: [
`gatsby-plugin-typescript`,
`gatsby-plugin-layout`,
'gatsby-plugin-react-helmet',
{
resolve: `gatsby-plugin-polyfill-io`,
options: {
features: [`Promise`, `fetch`, `Object.assign`, `Symbol`, `Array.from`]
}
},
{
resolve: `sonarsource-source-filesystem`,
options: { name: 'src', path: `${__dirname}/src/pages/` }
},
{
resolve: 'gatsby-plugin-typography',
options: { pathToConfigModule: `src/utils/typography` }
},
{
resolve: `gatsby-transformer-remark`,
options: {
plugins: [
{
resolve: `gatsby-remark-custom-blocks`,
options: {
blocks: {
danger: { classes: 'alert alert-danger' },
warning: { classes: 'alert alert-warning' },
info: { classes: 'alert alert-info' },
success: { classes: 'alert alert-success' },
collapse: { classes: 'collapse' }
}
}
}
]
}
}
]
};

+ 0
- 78
server/sonar-docs/gatsby-node.js View File

@@ -1,78 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2022 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 path = require('path');
const fs = require('fs-extra');
const { createFilePath } = require('gatsby-source-filesystem');

exports.onCreateNode = ({ node, getNode, actions }) => {
const { createNodeField } = actions;
if (node.internal.type === 'MarkdownRemark') {
const slug = createFilePath({ node, getNode, basePath: 'pages' });
createNodeField({
node,
name: 'slug',
value: slug
});
}
};

exports.createPages = ({ graphql, actions }) => {
const { createPage } = actions;
return new Promise((resolve, reject) => {
graphql(`
{
allMarkdownRemark {
edges {
node {
frontmatter {
url
}
headings {
depth
value
}
fields {
slug
}
}
}
}
}
`).then(result => {
result.data.allMarkdownRemark.edges.forEach(({ node }) => {
createPage({
path: node.frontmatter.url || node.fields.slug,
component: path.resolve('./src/templates/page.tsx'),
context: {
// Data passed to context is available in page queries as GraphQL variables.
slug: node.fields.slug
}
});
});
resolve();
});
});
};

exports.onPostBootstrap = () => {
const from = path.resolve(__dirname, 'src/images');
const to = path.resolve(__dirname, 'public/images');
return fs.copy(from, to);
};

+ 0
- 120
server/sonar-docs/package.json View File

@@ -1,120 +0,0 @@
{
"name": "sonar-docs",
"version": "0.0.0",
"license": "LGPL-3.0",
"private": true,
"dependencies": {
"@andrew-codes/gatsby-plugin-elasticlunr-search": "1.0.4",
"classnames": "2.3.1",
"gatsby": "2.19.49",
"gatsby-plugin-layout": "1.1.24",
"gatsby-plugin-polyfill-io": "1.1.0",
"gatsby-plugin-react-helmet": "3.1.24",
"gatsby-plugin-typescript": "2.2.5",
"gatsby-plugin-typography": "2.3.25",
"gatsby-remark-custom-blocks": "2.1.27",
"gatsby-source-filesystem": "2.1.57",
"gatsby-transformer-remark": "2.6.59",
"hast-util-select": "4.0.0",
"lodash": "4.17.21",
"lunr": "2.3.9",
"react": "16.13.0",
"react-dom": "16.13.0",
"react-helmet": "5.2.1",
"react-typography": "0.16.20",
"rehype-react": "6.1.0",
"typography": "0.16.21"
},
"devDependencies": {
"@swc/core": "1.3.9",
"@swc/jest": "0.2.23",
"@types/classnames": "2.3.0",
"@types/enzyme": "3.10.5",
"@types/jest": "27.4.1",
"@types/lodash": "4.14.149",
"@types/lunr": "2.3.4",
"@types/react": "16.8.23",
"@types/react-dom": "16.8.4",
"@types/react-helmet": "5.0.15",
"@types/rehype-react": "4.0.0",
"@typescript-eslint/eslint-plugin": "5.40.1",
"@typescript-eslint/parser": "5.40.1",
"enzyme": "3.11.0",
"enzyme-adapter-react-16": "1.15.6",
"enzyme-to-json": "3.6.2",
"eslint": "8.25.0",
"eslint-config-sonarqube": "2.1.0",
"eslint-plugin-import": "2.26.0",
"eslint-plugin-jest": "27.1.3",
"eslint-plugin-jest-dom": "4.0.2",
"eslint-plugin-jsx-a11y": "6.6.1",
"eslint-plugin-promise": "6.1.0",
"eslint-plugin-react": "7.31.10",
"eslint-plugin-react-hooks": "4.6.0",
"eslint-plugin-testing-library": "5.7.3",
"fs-extra": "7.0.1",
"glob-promise": "4.2.2",
"jest": "27.5.1",
"jest-fetch-mock": "2.1.2",
"prettier": "1.19.1",
"react-test-renderer": "16.8.5",
"remark": "11.0.2",
"typescript": "4.8.4",
"unist-util-visit": "2.0.2"
},
"scripts": {
"build": "yarn install --immutable && yarn gatsby clean && yarn gatsby build --prefix-paths",
"develop": "gatsby develop",
"graphql-types": "gql-gen --url http://localhost:8000/___graphql --template typescript --out ./src/@types/graphql-types.d.ts",
"test": "jest",
"format": "prettier --write --list-different \"src/**/*.{js,ts,tsx,css}\"",
"format-check": "prettier --list-different \"src/**/*.{js,ts,tsx,css}\"",
"lint": "eslint --ext js,ts,tsx --quiet src",
"ts-check": "tsc --noEmit",
"validate": "yarn lint && yarn ts-check && yarn format-check && yarn test",
"validate-ci": "yarn install --immutable && yarn test --ci",
"check-ci": "yarn install --immutable && yarn ts-check && yarn format-check"
},
"prettier": {
"jsxBracketSameLine": true,
"printWidth": 100,
"singleQuote": true
},
"jest": {
"moduleFileExtensions": [
"ts",
"tsx",
"js",
"json"
],
"moduleNameMapper": {
"^.+\\.(hbs|jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/config/jest/FileStub.js",
"^.+\\.css$": "<rootDir>/config/jest/CSSStub.js"
},
"setupFiles": [
"<rootDir>/config/jest/SetupEnzyme.js",
"<rootDir>/config/jest/SetupJest.ts"
],
"snapshotSerializers": [
"enzyme-to-json/serializer"
],
"testPathIgnorePatterns": [
"<rootDir>/node_modules",
"<rootDir>/config",
"<rootDir>/.cache"
],
"testEnvironment": "jsdom",
"testRegex": "(/__tests__/.*|\\-test)\\.(ts|tsx|js)$",
"transform": {
"^.+\\.(t|j)sx?$": [
"@swc/jest",
{
"jsc": {
"target": "es2018"
}
}
]
}
},
"packageManager": "yarn@3.2.4"
}

+ 0
- 48
server/sonar-docs/plugins/sonarsource-source-filesystem/__tests__/index-test.js View File

@@ -1,48 +0,0 @@
/**
* @jest-environment node
*/

/*
* SonarQube
* Copyright (C) 2009-2022 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 { cutAdditionalContent, cleanContent } = require('../index');

it('should cut additional content properly', () => {
const tag = 'special';
const content = 'This section contains a <!-- special -->special content<!-- /special -->.';
expect(cutAdditionalContent(content, tag)).toBe('This section contains a .');
});

it('should clean the content', () => {
const content = `
<!-- sonarcloud -->This is a sonarcloud content to cut<!-- /sonarcloud -->
<!-- sonarqube -->This is a sonarqube content to preserve<!-- /sonarqube -->
<!-- embedded -->This is an embedded content to cut<!-- /embedded -->
<!-- static -->This a static content to preserve<!-- /static -->
This is a {instance} instance
`;
expect(cleanContent(content)).toBe(`

This is a sonarqube content to preserve

This a static content to preserve
This is a SonarQube instance
`);
});

+ 0
- 23
server/sonar-docs/plugins/sonarsource-source-filesystem/gatsby-node.js View File

@@ -1,23 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2022 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 { sourceNodes, setFieldsOnGraphQLNodeType } = require('gatsby-source-filesystem/gatsby-node');

exports.sourceNodes = sourceNodes;
exports.setFieldsOnGraphQLNodeType = setFieldsOnGraphQLNodeType;

+ 0
- 235
server/sonar-docs/plugins/sonarsource-source-filesystem/index.js View File

@@ -1,235 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2022 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 { createFilePath, createRemoteFileNode } = require('gatsby-source-filesystem');
const fs = require('fs-extra');
const path = require('path');

const DOCUMENTATION_FILE_NAME = 'documentation.md';
const MANIFEST_FILE_NAME = 'manifest.mf';
const MANIFEST_ISSUE_TRACKER_URL_SECTION = 'Plugin-IssueTrackerUrl';
const LANG_TO_ANALYZER_MAP = {
vb6: 'vb'
};
const STATIC_NAV_TREE_DEFINITION = path.normalize(
`${__dirname}/../../static/StaticNavigationTree.json`
);
const EXPANDED_PLUGIN_DOCS_DIR = path.normalize(
`${__dirname}/../../build/tmp/plugin-documentation`
);

let overrides;

function processPluginOverrides(content) {
if (overrides === undefined) {
const pluginFileList = getPluginFilesList();

overrides = pluginFileList
.map(fileList => processPluginFileList(fileList))
.filter(res => !!res)
.map(addIssueTrackerLink)
.reduce((prev, cur) => {
prev[cur.url] = cur.content;
return prev;
}, {});
}

const match = content.match(/^url\s*:\s*(.+)$/m);
if (match && match[1] && overrides[match[1]]) {
return overrides[match[1]];
}

return content;
}

function addIssueTrackerLink(page) {
if (page.issueTrackerUrl) {
let issueTrackerLink = '## Issue Tracker';
issueTrackerLink += '\r\n';
issueTrackerLink += `Check the [issue tracker](${page.issueTrackerUrl}) for this language.`;

page.content = `${page.content}\r\n${issueTrackerLink}`;
}

return page;
}

function getPluginFilesList() {
const analyzers = [];
const walk = leaf => {
if (typeof leaf === 'object') {
if (leaf.children) {
leaf.children.forEach(walk);
}
} else {
const match = leaf.match(/^\/analysis\/languages\/(\w+)\/$/);
if (match && match[1] && match[1] !== 'overview') {
analyzers.push(LANG_TO_ANALYZER_MAP[match[1]] || match[1]);
}
}
};

const tree = JSON.parse(fs.readFileSync(STATIC_NAV_TREE_DEFINITION, 'utf8'));
tree.forEach(walk);

return analyzers.map(analyzer => {
const analyzerDir = path.normalize(`${EXPANDED_PLUGIN_DOCS_DIR}/sonar-${analyzer}-plugin`);

if (!fs.pathExistsSync(analyzerDir)) {
throw Error(
`Couldn't find the "${analyzer}" analyzer in the build dir. Looked for "${analyzerDir}", but couldn't find it.`
);
}

return fs
.readdirSync(analyzerDir)
.map(fileName => path.normalize(`${analyzerDir}/${fileName}`));
});
}

function processPluginFileList(pluginFileList) {
let md;
let mf;
let pluginDir;

pluginFileList.forEach(fileFullName => {
const fileName = path.basename(fileFullName);

if (pluginDir === undefined) {
pluginDir = path.dirname(fileFullName);
}

if (fileName.toLowerCase() === DOCUMENTATION_FILE_NAME.toLowerCase()) {
md = fileFullName;
} else if (fileName.toLowerCase() === MANIFEST_FILE_NAME.toLowerCase()) {
mf = fileFullName;
}
});

if (!md) {
throw Error(`Couldn't find the "${DOCUMENTATION_FILE_NAME}" file in "${pluginDir}".`);
}

return { ...parsePluginMarkdownFile(md), ...parsePluginManifestFile(mf) };
}

function parsePluginMarkdownFile(fileFullPath) {
let mdContent = fs.readFileSync(fileFullPath, 'utf-8');
const regex = /^key\s*:\s*(.+)$/m;
const match = mdContent.match(regex);

if (match && match[1]) {
const url = `/analysis/languages/${match[1]}/`;
mdContent = mdContent.replace(regex, `url: ${url}\n`);
return { url, content: mdContent };
}

return undefined;
}

function parsePluginManifestFile(fileFullPath) {
if (!fileFullPath) {
return undefined;
}

const mfContent = fs.readFileSync(fileFullPath, 'utf-8');
const regex = /^Plugin-IssueTrackerUrl:\s*(.+\r?\n?\s?\w+)$/m;
const match = mfContent.match(regex);

if (match && match[1]) {
// Manifest value might be split on many lines and contains space => get rid of it
const issueTrackerUrl = match[1].replace(/\r\n\s/m, '');
return { issueTrackerUrl };
}

return undefined;
}

function loadNodeContent(fileNode) {
return Promise.resolve(loadNodeContentSync(fileNode));
}

function loadNodeContentSync(fileNode) {
let content = processPluginOverrides(fs.readFileSync(fileNode.absolutePath, 'utf-8'));

content = cleanContent(content);
content = handleIncludes(content, fileNode);

return content;
}

function cleanContent(content) {
content = cutAdditionalContent(content, 'sonarcloud');
content = cutAdditionalContent(content, 'embedded');
content = removeRemainingContentTags(content);
content = replaceInstanceTag(content);

return content;
}

function removeRemainingContentTags(content) {
const regexBase = '<!-- \\/?(sonarqube|sonarcloud|static|embedded) -->';
return content
.replace(new RegExp(`^${regexBase}(\n|\r|\r\n|$)`, 'gm'), '')
.replace(new RegExp(`${regexBase}`, 'g'), '');
}

function cutAdditionalContent(content, tag) {
const beginning = '<!-- ' + tag + ' -->';
const ending = '<!-- /' + tag + ' -->';

let newContent = content;
let start = newContent.indexOf(beginning);
let end = newContent.indexOf(ending);
while (start !== -1 && end !== -1) {
newContent = newContent.substring(0, start) + newContent.substring(end + ending.length);
start = newContent.indexOf(beginning);
end = newContent.indexOf(ending);
}

return newContent;
}

function handleIncludes(content, fileNode) {
return content.replace(/@include (.*)/g, (_, path) => {
const relativePath = `${path}.md`;
const absolutePath = `${__dirname}/../../src/${relativePath}`;

if (relativePath === fileNode.relativePath) {
throw new Error(`Error in ${fileNode.relativePath}: The file is trying to include itself.`);
} else if (!fs.existsSync(absolutePath)) {
throw new Error(
`Error in ${fileNode.relativePath}: Couldn't load "${relativePath}" for inclusion.`
);
} else {
const fileContent = loadNodeContentSync({ absolutePath, relativePath });
return fileContent.replace(/^---[\w\W]+?---$/m, '').trim();
}
});
}

function replaceInstanceTag(content) {
return content.replace(/{instance}/gi, 'SonarQube');
}

exports.createFilePath = createFilePath;
exports.createRemoteFileNode = createRemoteFileNode;
exports.loadNodeContent = loadNodeContent;
exports.cleanContent = cleanContent;
exports.cutAdditionalContent = cutAdditionalContent;

+ 0
- 10
server/sonar-docs/plugins/sonarsource-source-filesystem/package.json View File

@@ -1,10 +0,0 @@
{
"name": "sonarsource-source-filesystem",
"version": "0.0.0",
"license": "LGPL-3.0",
"private": true,
"main": "create-file-node.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
}
}

+ 0
- 5728
server/sonar-docs/src/@types/graphql-types.d.ts
File diff suppressed because it is too large
View File


+ 0
- 26
server/sonar-docs/src/@types/hast-util-select.d.ts View File

@@ -1,26 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2022 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 { HtmlAST, HtmlASTNode } from '../types/hast';

declare module 'hast-util-select' {
export function matches(selector: string, ode: HtmlASTNode): boolean;
export function selectAll(selector: string, tree: HtmlAST): HtmlASTNode[];
export function select(selector: string, tree: HtmlAST): HtmlASTNode;
}

+ 0
- 60
server/sonar-docs/src/@types/lunr.d.ts View File

@@ -1,60 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2022 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.
*/
declare module 'lunr' {
export interface Lunr {
add(doc: any): void;

field(field: string, options?: { boost?: number }): void;

ref(field: string): void;

use(fn: Function): void;

metadataWhitelist?: string[];
}

export interface LunrBuilder {
pipeline: any;
metadataWhitelist: string[];
}

export interface LunrIndex {
search(query: string): LunrMatch[];
}

export interface LunrInit {
(this: Lunr): void;
}

export interface LunrMatch {
ref: string;
score: number;
matchData: { metadata: any };
}

export interface LunrToken {
str: string;
metadata: any;
}

function lunr(initializer: LunrInit): LunrIndex;

export default lunr;
}

+ 0
- 51
server/sonar-docs/src/@types/types.d.ts View File

@@ -1,51 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2022 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.
*/
export type Dict<T> = { [key: string]: T };

export interface DocVersion {
current: boolean;
lts?: boolean;
value: string;
}

export type DocNavigationItem = string | DocsNavigationBlock | DocsNavigationExternalLink;

export interface DocsNavigationBlock {
title: string;
children: DocNavigationItem[];
}

export interface DocsNavigationExternalLink {
title: string;
url: string;
}

export interface SearchResult {
exactMatch?: boolean;
highlights: { [field: string]: [number, number][] };
longestTerm: string;
page: {
id: string;
text: string;
title: string;
url: string;
};
query: string;
}

+ 0
- 202
server/sonar-docs/src/__tests__/BrokenLinkSafetyNet.test.js View File

@@ -1,202 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2022 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 fs = require('fs');
const path = require('path');
const remark = require('remark');
const glob = require('glob-promise');
const visit = require('unist-util-visit');

const rootPath = path.resolve(path.join(__dirname, '/..'));
let files;
let parsedFiles;

beforeAll(async () => {
files = await loadGlobFiles('/pages/**/*.md');
parsedFiles = files.map(file => {
return { ...separateFrontMatter(file.content), path: file.path };
});
});

it('should have at least one instance of all possible frontmatter fields', () => {
const pageWithTitle = parsedFiles.find(file => file.frontmatter.title !== undefined);
const pageWithNav = parsedFiles.find(file => file.frontmatter.nav !== undefined);
const pageWithUrl = parsedFiles.find(file => file.frontmatter.url !== undefined);
expect(pageWithTitle).toBeDefined();
expect(pageWithNav).toBeDefined();
expect(pageWithUrl).toBeDefined();
});

/* eslint-disable no-console */
it('should have valid links in trees files', () => {
const trees = ['StaticNavigationTree.json'];
let hasErrors = false;
trees.forEach(file => {
const tree = JSON.parse(fs.readFileSync(path.join(rootPath, '..', 'static', file), 'utf8'));
const walk = leaf => {
if (typeof leaf === 'object') {
if (leaf.children) {
leaf.children.forEach(walk);
}
} else if (!urlExists(parsedFiles, leaf)) {
// Check markdown file path validity
console.log(`[${leaf}] is not a valid link, in ${file}`);
hasErrors = true;
}
};
tree.forEach(walk);
});
expect(hasErrors).toBe(false);
});

function collectErrors() {
let urlLists = [];
let hasErrors = false;
parsedFiles.forEach(file => {
if (!file.frontmatter.url) {
console.log(`[${file.path}] has no url metadata`);
hasErrors = true;
} else if (!checkUrlFormat(file.frontmatter.url, file.path)) {
hasErrors = true;
} else if (urlLists.includes(file.frontmatter.url)) {
console.log(`[${file.path}] has an url that is not unique ${file.frontmatter.url}`);
hasErrors = true;
}

urlLists = [...urlLists, file.frontmatter.url];
});
return hasErrors;
}

it('should have valid and uniq links in url metadata field', () => {
const hasErrors = collectErrors();
expect(hasErrors).toBe(false);
});

it('should have valid links pointing to documentation inside pages', () => {
checkContentUrl(parsedFiles);
});

it('should have valid links inside tooltips', async () => {
const files = await loadGlobFiles('/tooltips/**/*.md');
checkContentUrl(files);
});

function handleIncludes(content, rootPath) {
return content.replace(/@include (.+)/, (match, p) => {
const filePath = path.join(rootPath, '..', `${p}.md`);
return fs.readFileSync(filePath, 'utf8');
});
}

function checkContentUrl(files) {
let hasErrors = false;
files.forEach(file => {
visit(remark().parse(file.content), node => {
if (node.type === 'image' && !node.url.startsWith('http')) {
// Check image path validity
if (!fs.existsSync(path.join(rootPath, node.url))) {
console.log('[', node.url, '] is not a valid image path, in', file.path + '.md');
hasErrors = true;
}
} else if (
node.type === 'link' &&
!node.url.startsWith('http') &&
!node.url.startsWith('/#')
) {
// Check markdown file path validity, and ignore anchors
const url = node.url.split('#')[0];
if (!urlExists(parsedFiles, url)) {
console.log('[', node.url, '] is not a valid link, in', file.path + '.md');
hasErrors = true;
}
}
});
});
expect(hasErrors).toBe(false);
}

function urlExists(files, url) {
return files.find(f => f.frontmatter.url === url) !== undefined;
}

function checkUrlFormat(url, file) {
let noError = true;

if (!url.startsWith('/')) {
console.log('[', file, '] should starts with a slash', url);
noError = false;
}
if (!url.endsWith('/')) {
console.log('[', file, '] should ends with a slash', url);
noError = false;
}
return noError;
}

function loadGlobFiles(globPath) {
return glob(path.join(rootPath, globPath))
.then(files => files.map(file => file.substr(rootPath.length + 1)))
.then(files =>
files.map(file => ({
path: file.slice(0, -3),
content: handleIncludes(fs.readFileSync(path.join(rootPath, file), 'utf8'), rootPath)
}))
);
}

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 };
}
}

+ 0
- 98
server/sonar-docs/src/components/CategoryBlockLink.tsx View File

@@ -1,98 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2022 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 classNames from 'classnames';
import * as React from 'react';
import { MarkdownRemark } from '../@types/graphql-types';
import ChevronDownIcon from './icons/ChevronDownIcon';
import ChevronUpIcon from './icons/ChevronUpIcon';
import PageLink from './PageLink';

interface Props {
children: (MarkdownRemark | JSX.Element)[];
location: Location;
openByDefault: boolean;
title: string;
}

interface State {
open: boolean;
}

export default class CategoryLink extends React.PureComponent<Props, State> {
state: State;

static defaultProps = {
openByDefault: false
};

constructor(props: Props) {
super(props);

this.state = {
open: props.openByDefault
};
}

handleToggle = (event: React.MouseEvent<HTMLAnchorElement>) => {
event.preventDefault();
event.stopPropagation();
this.setState(prevState => ({
open: !prevState.open
}));
};

isMarkdownRemark = (child: any): child is MarkdownRemark => {
return child.id !== undefined;
};

render() {
const { children, location, title } = this.props;
const { open } = this.state;
return (
<div>
<a
className={classNames('page-indexes-link', { active: open })}
href="#"
onClick={this.handleToggle}>
{open ? <ChevronUpIcon /> : <ChevronDownIcon />}
{title}
</a>
{children && open && (
<div className="sub-menu">
{children.map((child, i) => {
if (this.isMarkdownRemark(child)) {
return (
<PageLink
className="sub-menu-link"
key={child.id}
location={location}
node={child}
/>
);
} else {
return <React.Fragment key={`child-${i}`}>{child}</React.Fragment>;
}
})}
</div>
)}
</div>
);
}
}

+ 0
- 35
server/sonar-docs/src/components/ExternalLink.tsx View File

@@ -1,35 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2022 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';

interface Props {
external: string;
title: string;
}

export default function ExternalLink({ external, title }: Props) {
return (
<a className="page-indexes-link" href={external} rel="noopener noreferrer" target="_blank">
<DetachIcon />
{title}
</a>
);
}

+ 0
- 47
server/sonar-docs/src/components/Footer.tsx View File

@@ -1,47 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2022 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 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">
<img
alt="Creative Commons License"
src="https://licensebuttons.net/l/by-nc/3.0/us/88x31.png"
/>
</a>
© 2008-2022, 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">
Creative Commons Attribution-NonCommercial 3.0 United States License.
</a>{' '}
SONARQUBE is a trademark of SonarSource SA. All other trademarks and copyrights are the
property of their respective owners.
</div>
);
}

+ 0
- 46
server/sonar-docs/src/components/HeaderList.tsx View File

@@ -1,46 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2022 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 PropTypes from 'prop-types';
import * as React from 'react';
import { MarkdownHeading } from '../@types/graphql-types';

interface Props {
headers: MarkdownHeading[];
}

export default class HeaderList extends React.PureComponent<Props> {
static contextTypes = {
headers: PropTypes.object.isRequired
};

componentDidMount() {
this.context.headers.setHeaders(this.props.headers);
}

componentDidUpdate(prevProps: Props) {
if (prevProps.headers.length !== this.props.headers.length) {
this.context.headers.setHeaders(prevProps.headers);
}
}

render() {
return null;
}
}

+ 0
- 55
server/sonar-docs/src/components/HeaderListProvider.tsx View File

@@ -1,55 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2022 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 PropTypes from 'prop-types';
import * as React from 'react';
import { MarkdownHeading } from '../@types/graphql-types';

interface Props {
children: (props: { headers: MarkdownHeading[] }) => React.ReactNode;
}

interface State {
headers: MarkdownHeading[];
}

export default class HeaderListProvider extends React.Component<Props, State> {
state = { headers: [] };

static childContextTypes = {
headers: PropTypes.object
};

getChildContext() {
return {
headers: {
setHeaders: this.setHeaders
}
};
}

setHeaders = (headers: MarkdownHeading[]) => {
this.setState({ headers });
};

render() {
const { headers } = this.state;
return this.props.children({ headers });
}
}

+ 0
- 47
server/sonar-docs/src/components/HeadingAnchor.tsx View File

@@ -1,47 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2022 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 classNames from 'classnames';
import * as React from 'react';

interface Props {
active: boolean;
children: React.ReactNode;
clickHandler: (index: number) => void;
index: number;
}

export default class HeadingAnchor extends React.PureComponent<Props> {
handleClick = (event: React.MouseEvent<HTMLAnchorElement>) => {
event.stopPropagation();
event.preventDefault();
this.props.clickHandler(this.props.index);
};

render() {
const { active, children, index } = this.props;
return (
<li>
<a className={classNames({ active })} href={'#header-' + index} onClick={this.handleClick}>
{children}
</a>
</li>
);
}
}

+ 0
- 136
server/sonar-docs/src/components/HeadingsLink.tsx View File

@@ -1,136 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2022 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 { MarkdownHeading } from '../@types/graphql-types';
import HeadingAnchor from './HeadingAnchor';

const HEADER_SCROLL_MARGIN = 100;

interface Props {
headers: MarkdownHeading[];
}

interface State {
activeIndex: number;
headers: MarkdownHeading[];
}

export default class HeadingsLink extends React.PureComponent<Props, State> {
skipScrollingHandler = false;

constructor(props: Props) {
super(props);
this.state = {
activeIndex: -1,
headers: props.headers.filter(
h => h.depth === 2 && h.value && h.value.toLowerCase() !== 'table of contents'
)
};
}

componentDidMount() {
document.addEventListener('scroll', this.scrollHandler, true);
}

componentWillReceiveProps(nextProps: Props) {
this.setState({
activeIndex: -1,
headers: nextProps.headers.filter(
h => h.depth === 2 && h.value && h.value.toLowerCase() !== 'table of contents'
)
});
}

componentWillUnmount() {
document.removeEventListener('scroll', this.scrollHandler, true);
}

scrollHandler = () => {
if (this.skipScrollingHandler) {
this.skipScrollingHandler = false;
return;
}

const scrollTop = window.pageYOffset || document.body.scrollTop;
this.highlightHeading(scrollTop);
};

highlightHeading = (scrollTop: number) => {
let headingIndex = 0;
for (let i = 0; i < this.state.headers.length; i++) {
const headerItem = document.querySelector<HTMLElement>(`#header-${i + 1}`);
if (headerItem && headerItem.offsetTop > scrollTop + HEADER_SCROLL_MARGIN) {
break;
}
headingIndex = i;
}
this.setState({ activeIndex: headingIndex });
this.markH2(headingIndex + 1, false);
};

markH2 = (index: number, scrollTo: boolean) => {
const previousNode = document.querySelector('.targetted-heading');
if (previousNode) {
previousNode.classList.remove('targetted-heading');
}

const node = document.querySelector<HTMLElement>('#header-' + index);
if (node) {
node.classList.add('targetted-heading');
if (scrollTo) {
this.skipScrollingHandler = true;
window.scrollTo(0, node.offsetTop - HEADER_SCROLL_MARGIN);
this.highlightHeading(node.offsetTop - HEADER_SCROLL_MARGIN);
}
}
};

clickHandler = (index: number) => {
this.markH2(index, true);
};

render() {
const { headers } = this.state;
if (headers.length < 2) {
return null;
}

return (
<div className="headings-container">
<div className="headings-container-fixed">
<span>On this page</span>
<ul>
{headers.map((header, index) => {
return (
<HeadingAnchor
active={this.state.activeIndex === index}
clickHandler={this.clickHandler}
index={index + 1}
key={index}>
{header.value}
</HeadingAnchor>
);
})}
</ul>
</div>
</div>
);
}
}

+ 0
- 101
server/sonar-docs/src/components/MetaData.css View File

@@ -1,101 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2022 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.
*/
.update-center-meta-data {
margin: 16px 0;
padding: 16px 16px 8px 16px;
background: #f9f9fb;
border: 1px solid #e6e6e6;
border-radius: 3px;
}

.update-center-meta-data a svg {
margin-right: 8px;
}

.update-center-meta-data-header {
border-bottom: 1px solid #cfd3d7;
padding-bottom: 16px;
}

.update-center-meta-data-header,
.update-center-meta-data-version-release-info,
.update-center-meta-data-version-links {
display: flex;
}

.update-center-meta-data-header > * + *,
.update-center-meta-data-version-release-info > * + * {
margin-left: 16px;
}

.update-center-meta-data-header > * + * {
padding-left: 16px;
border-left: 1px solid #cfd3d7;
}

.update-center-meta-data-versions {
margin-top: 16px;
}

.update-center-meta-data-versions-show-more {
font-size: 14px;
float: right;
color: #51575a;
border-color: #7b8184;
border-width: 0 0 1px 0;
padding-left: 0;
padding-right: 0;
background: transparent;
cursor: pointer;
}

.update-center-meta-data-versions-show-more:hover {
color: #2d3032;
border-color: #2d3032;
}

.update-center-meta-data-version {
margin-bottom: 16px;
}

.update-center-meta-data-version + .update-center-meta-data-version {
padding-top: 8px;
border-top: 1px dashed #cfd3d7;
}

.update-center-meta-data-version-version {
font-weight: bold;
font-size: 18px;
}

.update-center-meta-data-version-release-info {
margin-top: 8px;
font-style: italic;
}

.update-center-meta-data-version-release-description {
margin-top: 8px;
}

.update-center-meta-data-version-download > a,
.update-center-meta-data-version-release-notes > a {
display: inline-block;
margin: 8px 16px 0 0;
}

+ 0
- 124
server/sonar-docs/src/components/MetaData.tsx View File

@@ -1,124 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2022 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 './MetaData.css';
import MetaDataVersions from './MetaDataVersions';
import { MetaDataInformation } from './update-center-metadata';

interface Props {
updateCenterKey?: string;
}

interface State {
data?: MetaDataInformation;
}

export default class MetaData extends React.Component<Props, State> {
mounted = false;
state: State = {};

componentDidMount() {
this.mounted = true;
this.fetchData();
}

componentDidUpdate(prevProps: Props) {
if (prevProps.updateCenterKey !== this.props.updateCenterKey) {
this.fetchData();
}
}

componentWillUnmount() {
this.mounted = false;
}

isSuccessStatus = (status: number) => {
return status >= 200 && status < 300;
};

fetchData() {
const { updateCenterKey } = this.props;

if (updateCenterKey) {
window
.fetch(`https://update.sonarsource.org/${updateCenterKey}.json`)
.then((response: Response) => {
if (this.isSuccessStatus(response.status)) {
return response.json();
}
return Promise.reject(response);
})
.then(data => {
if (this.mounted) {
this.setState({ data });
}
})
.catch(() => {
if (this.mounted) {
this.setState({ data: undefined });
}
});
} else {
this.setState({ data: undefined });
}
}

render() {
const { data } = this.state;

if (!data) {
return null;
}

const { isSonarSourceCommercial, issueTrackerURL, license, organization, versions } = data;

let vendor;
if (organization) {
vendor = organization.name;
if (organization.url) {
vendor = (
<a href={organization.url} rel="noopener noreferrer" target="_blank">
{vendor}
</a>
);
}
}

return (
<div className="update-center-meta-data">
<div className="update-center-meta-data-header">
{vendor && <span className="update-center-meta-data-vendor">By {vendor}</span>}
{license && <span className="update-center-meta-data-license">{license}</span>}
{issueTrackerURL && (
<span className="update-center-meta-data-issue-tracker">
<a href={issueTrackerURL} rel="noopener noreferrer" target="_blank">
Issue Tracker
</a>
</span>
)}
{isSonarSourceCommercial && (
<span className="update-center-meta-data-supported">Supported by SonarSource</span>
)}
</div>
{versions && versions.length > 0 && <MetaDataVersions versions={versions} />}
</div>
);
}
}

+ 0
- 98
server/sonar-docs/src/components/MetaDataVersion.tsx View File

@@ -1,98 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2022 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 classNames from 'classnames';
import * as React from 'react';
import { AdvancedDownloadUrl, MetaDataVersionInformation } from './update-center-metadata';

export interface MetaDataVersionProps {
versionInformation: MetaDataVersionInformation;
}

export default function MetaDataVersion(props: MetaDataVersionProps) {
const {
versionInformation: {
archived,
changeLogUrl,
compatibility,
date,
description,
downloadURL,
version
}
} = props;

const fallbackLabel = 'Download';

const advancedDownloadUrls = isAdvancedDownloadUrlArray(downloadURL)
? downloadURL.map(url => ({ ...url, label: url.label || fallbackLabel }))
: [{ label: fallbackLabel, url: downloadURL }];

return (
<div
className={classNames('update-center-meta-data-version', {
'update-center-meta-data-version-archived': archived
})}>
<div className="update-center-meta-data-version-version">{version}</div>

<div className="update-center-meta-data-version-release-info">
{date && <time className="update-center-meta-data-version-release-date">{date}</time>}

{compatibility && (
<span className="update-center-meta-data-version-compatibility">{compatibility}</span>
)}
</div>

{description && (
<div className="update-center-meta-data-version-release-description">{description}</div>
)}

{(advancedDownloadUrls.length > 0 || changeLogUrl) && (
<div className="update-center-meta-data-version-release-links">
{advancedDownloadUrls.length > 0 &&
advancedDownloadUrls.map(
(advancedDownloadUrl, i) =>
advancedDownloadUrl.url && (
// eslint-disable-next-line react/no-array-index-key
<span className="update-center-meta-data-version-download" key={i}>
<a href={advancedDownloadUrl.url} rel="noopener noreferrer" target="_blank">
{advancedDownloadUrl.label}
</a>
</span>
)
)}

{changeLogUrl && (
<span className="update-center-meta-data-version-release-notes">
<a href={changeLogUrl} rel="noopener noreferrer" target="_blank">
Release notes
</a>
</span>
)}
</div>
)}
</div>
);
}

function isAdvancedDownloadUrlArray(
downloadUrl: string | AdvancedDownloadUrl[] | undefined
): downloadUrl is AdvancedDownloadUrl[] {
return !!downloadUrl && typeof downloadUrl !== 'string';
}

+ 0
- 84
server/sonar-docs/src/components/MetaDataVersions.tsx View File

@@ -1,84 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2022 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 MetaDataVersion from './MetaDataVersion';
import { MetaDataVersionInformation } from './update-center-metadata';

interface Props {
versions: MetaDataVersionInformation[];
}

interface State {
collapsed: boolean;
}

export default class MetaDataVersions extends React.Component<Props, State> {
state: State = {
collapsed: true
};

componentDidUpdate(prevProps: Props) {
if (prevProps.versions !== this.props.versions) {
this.setState({ collapsed: true });
}
}

handleClick = (event: React.SyntheticEvent<HTMLButtonElement>) => {
event.preventDefault();
event.currentTarget.blur();
this.setState(({ collapsed }) => ({ collapsed: !collapsed }));
};

render() {
const { versions } = this.props;
const { collapsed } = this.state;

const archivedVersions = versions.filter(version => version.archived);
const currentVersions = versions.filter(version => !version.archived);

return (
<div className="update-center-meta-data-versions">
{archivedVersions.length > 0 && (
<button
className="update-center-meta-data-versions-show-more"
onClick={this.handleClick}
type="button">
{collapsed ? 'Show more versions' : 'Show fewer versions'}
</button>
)}

{currentVersions.map(versionInformation => (
<MetaDataVersion
key={versionInformation.version}
versionInformation={versionInformation}
/>
))}

{!collapsed &&
archivedVersions.map(archivedVersionInformation => (
<MetaDataVersion
key={archivedVersionInformation.version}
versionInformation={archivedVersionInformation}
/>
))}
</div>
);
}
}

+ 0
- 60
server/sonar-docs/src/components/OutsideClickHandler.tsx View File

@@ -1,60 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2022 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 { findDOMNode } from 'react-dom';

interface Props {
children: React.ReactNode;
onClickOutside: () => void;
}

export default class OutsideClickHandler extends React.Component<Props> {
element?: Element | null;

componentDidMount() {
setTimeout(() => {
this.addClickHandler();
}, 0);
}

componentWillUnmount() {
this.removeClickHandler();
}

addClickHandler = () => {
window.addEventListener('click', this.handleWindowClick);
};

removeClickHandler = () => {
window.removeEventListener('click', this.handleWindowClick);
};

handleWindowClick = (event: MouseEvent) => {
// eslint-disable-next-line react/no-find-dom-node
const node = findDOMNode(this);
if (!node || !node.contains(event.target as Node)) {
this.props.onClickOutside();
}
};

render() {
return this.props.children;
}
}

+ 0
- 52
server/sonar-docs/src/components/PageLink.tsx View File

@@ -1,52 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2022 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 classNames from 'classnames';
import { Link } from 'gatsby';
import * as React from 'react';
import { MarkdownRemark } from '../@types/graphql-types';
import { getMarkdownRemarkTitle, getMarkdownRemarkUrl } from './utils';

interface Props {
className?: string;
location: Location;
node?: MarkdownRemark;
}

const PREFIX = process.env.GATSBY_DOCS_VERSION ? '/' + process.env.GATSBY_DOCS_VERSION : '';

export default function PageLink({ className, location, node }: Props) {
const title = getMarkdownRemarkTitle(node);
const url = getMarkdownRemarkUrl(node);

if (!url || !title) {
return null;
}

return (
<div>
<Link
className={classNames(className, { active: location.pathname === PREFIX + url })}
to={url}
title={title}>
{title}
</Link>
</div>
);
}

+ 0
- 214
server/sonar-docs/src/components/Search.tsx View File

@@ -1,214 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2022 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 } from 'lodash';
import lunr, { LunrBuilder, LunrIndex, LunrMatch, LunrToken } from 'lunr';
import * as React from 'react';
import { MarkdownRemark } from '../@types/graphql-types';
import { DocNavigationItem, SearchResult } from '../@types/types';
import ClearIcon from './icons/ClearIcon';
import { getUrlsList } from './navTreeUtils';
import { getMarkdownRemarkTitle, getMarkdownRemarkUrl, isDefined } from './utils';

interface Props {
navigation: DocNavigationItem[];
onResultsChange: (results: SearchResult[], query: string) => void;
pages: MarkdownRemark[];
}

interface State {
value: string;
}

export default class Search extends React.PureComponent<Props, State> {
index?: LunrIndex;
input?: HTMLInputElement | null;

constructor(props: Props) {
super(props);
this.state = { value: '' };
this.index = lunr(function() {
this.use(tokenContextPlugin);
this.ref('id');
this.field('title', { boost: 10 });
this.field('text');

this.metadataWhitelist = ['position', 'tokenContext'];

props.pages
.filter(page => getUrlsList(props.navigation).includes(getMarkdownRemarkUrl(page) || ''))
.forEach(page => {
if (page.html) {
this.add({
id: page.id,
text: page.html.replace(/<(?:.|\n)*?>/gm, '').replace(/&#x3C;(?:.|\n)*?>/gm, ''),
title: getMarkdownRemarkTitle(page)
});
}
});
});
}

getFormattedResults = (query: string, results: LunrMatch[]) => {
const formattedResults: SearchResult[] = results
.map(match => {
const page = this.props.pages.find(page => page.id === match.ref);
if (!page) {
return undefined;
}

const searchResultPage: SearchResult['page'] = {
id: page.id,
text: (page.html || '').replace(/<(?:.|\n)*?>/gm, '').replace(/&#x3C;(?:.|\n)*?>/gm, ''),
title: getMarkdownRemarkTitle(page) || '',
url: getMarkdownRemarkUrl(page) || ''
};

const highlights: { [field: string]: [number, number][] } = {};
let longestTerm = '';
let exactMatch = false;

// Loop over all matching terms/tokens.
Object.keys(match.matchData.metadata).forEach(term => {
// Remember the longest term that matches the query as close as possible.
if (query.includes(term.toLowerCase()) && longestTerm.length < term.length) {
longestTerm = term;
}

Object.keys(match.matchData.metadata[term]).forEach(fieldName => {
const { position: positions, tokenContext: tokenContexts } = match.matchData.metadata[
term
][fieldName];

highlights[fieldName] = [...(highlights[fieldName] || []), ...positions];

// Check if we have an *exact match*.
if (!exactMatch && tokenContexts) {
tokenContexts.forEach((tokenContext: string) => {
if (!exactMatch && tokenContext.includes(query)) {
exactMatch = true;
}
});
}
});
});

return {
page: searchResultPage,
exactMatch,
highlights,
query,
longestTerm
};
})
.filter(isDefined);

// Re-order results by the length of the longest matched term and by exact
// match (if applicable). The longer the matched term is, the higher the
// chance the result is more relevant.
return sortBy(
// Sort by longest term.
sortBy(formattedResults, result => -result.longestTerm.length),
// Sort by exact match.
result => result.exactMatch && -1
);
};

handleClear = () => {
this.setState({ value: '' });
this.props.onResultsChange([], '');
if (this.input) {
this.input.focus();
}
};

handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const { value } = event.currentTarget;
this.setState({ value });
if (value !== '' && this.index) {
const results = this.getFormattedResults(
value,
this.index.search(
value
.replace(/[\^\-+:~*]/g, '')
.split(/\s+/)
.map(s => `${s}~1 ${s}*`)
.join(' ')
)
);
this.props.onResultsChange(results, value);
} else {
this.props.onResultsChange([], value);
}
};

render() {
return (
<div className="search-container">
<input
aria-label="Search"
className="search-input"
onChange={this.handleChange}
placeholder="Search..."
ref={node => (this.input = node)}
type="search"
value={this.state.value}
/>
{this.state.value && (
<button onClick={this.handleClear} type="button">
<ClearIcon size={8} />
</button>
)}
</div>
);
}
}

// Lunr doesn't support exact multiple-term matching. Meaning "foo bar" will not
// boost a sentence like "Foo bar baz" more than "Baz bar foo". In order to
// provide more accurate results, we store the token context, to see if we can
// perform an "exact match". Unfortunately, we cannot extend the search logic,
// only the tokenizer at *index time*. This is why we store the context as
// meta-data, and post-process the matches before rendering (see above). For
// performance reasons, we only add 2 extra tokens, one in front, one after.
// This means we support "exact macthing" for up to 3 terms. More search terms
// would fallback to the regular matching algorithm, which is OK: the more terms
// searched for, the better the standard algorithm will perform anyway. In the
// end, the best would be for Lunr to support multi-term matching, as extending
// the search algorithm for this would be way too complicated.
function tokenContextPlugin(builder: LunrBuilder) {
const label = 'tokenContext';
const pipelineFunction = (token: LunrToken, index: number, tokens: LunrToken[]) => {
const prevToken = tokens[index - 1] || '';
const nextToken = tokens[index + 1] || '';
token.metadata[label] = [prevToken.toString(), token.toString(), nextToken.toString()]
.filter(s => s.length)
.join(' ')
.toLowerCase();
return token;
};

if (label in (lunr as any).Pipeline.registeredFunctions) {
return;
}

(lunr as any).Pipeline.registerFunction(pipelineFunction, label);
builder.pipeline.before((lunr as any).stemmer, pipelineFunction);
builder.metadataWhitelist.push(label);
}

+ 0
- 114
server/sonar-docs/src/components/SearchEntryResult.tsx View File

@@ -1,114 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2022 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 { Link } from 'gatsby';
import * as React from 'react';
import { SearchResult } from '../@types/types';
import { cutWords, highlightMarks } from './utils';

interface ResultProps {
result: SearchResult;
}

interface Props extends ResultProps {
active: boolean;
}

export default function SearchResultEntry({ active, result }: Props) {
return (
<Link className={active ? 'active search-result' : 'search-result'} to={result.page.url}>
<SearchResultTitle result={result} />
<SearchResultText result={result} />
</Link>
);
}

export function SearchResultTitle({ result }: ResultProps) {
let titleWithMarks;

const titleHighlights = result.highlights.title;
if (titleHighlights && titleHighlights.length > 0) {
const { title } = result.page;
const tokens = highlightMarks(
title,
titleHighlights.map(h => ({ from: h[0], to: h[0] + h[1] }))
);
titleWithMarks = <SearchResultTokens tokens={tokens} />;
} else {
titleWithMarks = result.page.title;
}

return <div className="search-result">{titleWithMarks}</div>;
}

export function SearchResultText({ result }: ResultProps) {
const textHighlights = result.highlights.text;
const { text } = result.page;
let tokens: { text: string; marked: boolean }[] = [];

if (result.exactMatch) {
const pageText = result.page.text.toLowerCase();
const highlights = [];
let start = 0;
let index = pageText.indexOf(result.query, start);
let loopCount = 0;

while (index > -1 && loopCount < 10) {
loopCount++;
highlights.push({ from: index, to: index + result.query.length });
start = index + 1;
index = pageText.indexOf(result.query, start);
}

if (highlights.length) {
tokens = highlightMarks(text, highlights);
}
}

if (tokens.length === 0 && textHighlights && textHighlights.length > 0) {
tokens = highlightMarks(
text,
textHighlights.map(h => ({ from: h[0], to: h[0] + h[1] }))
);
}

if (tokens.length) {
return (
<div className="note">
<SearchResultTokens tokens={cutWords(tokens)} />
</div>
);
} else {
return null;
}
}

export function SearchResultTokens({
tokens
}: {
tokens: Array<{ text: string; marked: boolean }>;
}) {
return (
<span>
{tokens.map((token, index) => (
<span key={index}>{token.marked ? <mark key={index}>{token.text}</mark> : token.text}</span>
))}
</span>
);
}

+ 0
- 240
server/sonar-docs/src/components/Sidebar.tsx View File

@@ -1,240 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2022 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 { Link } from 'gatsby';
import * as React from 'react';
import { MarkdownRemark } from '../@types/graphql-types';
import { DocNavigationItem, DocVersion, SearchResult } from '../@types/types';
import CategoryBlockLink from './CategoryBlockLink';
import ExternalLink from './ExternalLink';
import DownloadIcon from './icons/DownloadIcon';
import {
getNavTree,
getOpenChainFromPath,
isDocsNavigationBlock,
isDocsNavigationExternalLink,
testPathAgainstUrl
} from './navTreeUtils';
import PageLink from './PageLink';
import Search from './Search';
import SearchEntryResult from './SearchEntryResult';
import VersionSelect from './VersionSelect';

interface Props {
location: Location;
pages: MarkdownRemark[];
version: string;
}

interface State {
loaded: boolean;
navTree: DocNavigationItem[];
openChain?: DocNavigationItem[];
query: string;
results: SearchResult[];
versions: DocVersion[];
}

export default class Sidebar extends React.PureComponent<Props, State> {
constructor(props: Props) {
super(props);
const navTree = getNavTree();
this.state = {
loaded: false,
navTree,
openChain: getOpenChainFromPath(this.props.location.pathname, navTree),
query: '',
results: [],
versions: []
};
}

componentDidMount() {
this.loadVersions();
}

componentDidUpdate(prevProps: Props) {
if (this.props.location.pathname !== prevProps.location.pathname) {
this.setState(({ navTree }) => ({
openChain: getOpenChainFromPath(this.props.location.pathname, navTree)
}));
}
}

loadVersions() {
const headers = new Headers([
['Cache-Control', 'no-cache'],
['Pragma', 'no-cache']
]);
fetch('/DocsVersions.json', { headers })
.then(response => response.json())
.then(json => {
this.setState({ loaded: true, versions: json });
})
.catch(() => {});
}

getNodeFromUrl = (url: string) => {
return this.props.pages.find(p => {
if (p.fields && p.fields.slug && testPathAgainstUrl(p.fields.slug, url)) {
return true;
}

if (p.frontmatter && p.frontmatter.url && testPathAgainstUrl(p.frontmatter.url, url)) {
return true;
}

return false;
});
};

handleSearch = (results: SearchResult[], query: string) => {
this.setState({ results, query });
};

renderCategory = (leaf: DocNavigationItem) => {
if (isDocsNavigationBlock(leaf)) {
let children: (MarkdownRemark | JSX.Element)[] = [];
leaf.children.forEach(child => {
if (typeof child === 'string') {
const node = this.getNodeFromUrl(child);
if (node) {
children.push(node);
}
} else {
children = children.concat(this.renderCategory(child));
}
});
return (
<CategoryBlockLink
key={leaf.title}
location={this.props.location}
openByDefault={this.state.openChain && this.state.openChain.includes(leaf)}
title={leaf.title}>
{children}
</CategoryBlockLink>
);
}

if (isDocsNavigationExternalLink(leaf)) {
return <ExternalLink external={leaf.url} key={leaf.title} title={leaf.title} />;
}

return (
<PageLink
className="page-indexes-link"
key={leaf}
location={this.props.location}
node={this.getNodeFromUrl(leaf)}
/>
);
};

renderCategories = () => {
return <nav>{this.state.navTree.map(this.renderCategory)}</nav>;
};

renderResults = () => {
return (
<div>
{this.state.results.map(result => (
<SearchEntryResult
active={
(this.props.location.pathname === result.page.url && result.page.url === '/') ||
(result.page.url !== '/' && this.props.location.pathname.endsWith(result.page.url))
}
key={result.page.id}
result={result}
/>
))}
</div>
);
};

render() {
const { versions } = this.state;
const { version } = this.props;

const currentVersion = versions.find(v => v.current);
const ltsVersion = versions.find(v => v.lts);

const selectedVersionValue =
currentVersion && version === 'latest' ? currentVersion.value : version;
const isOnCurrentVersion = !currentVersion || selectedVersionValue === currentVersion.value;
const isOnLTSVersion = ltsVersion && version === ltsVersion.value;

return (
<div className="page-sidebar">
<div className="sidebar-header">
<Link to="/">
<img
alt="Continuous Code Quality"
className="sidebar-logo"
src={`/${version}/images/SonarQubeIcon.svg`}
title="Continuous Code Quality"
width="160"
/>
</Link>
<VersionSelect
isOnCurrentVersion={isOnCurrentVersion}
selectedVersionValue={selectedVersionValue}
versions={versions}
/>
{this.state.loaded && !isOnCurrentVersion && !isOnLTSVersion && (
<div className="alert alert-warning">
This is an archived version of the doc for <b>SonarQube version {version}</b>.{' '}
<a href="/">See Documentation</a> for current functionality.
</div>
)}
</div>
<div className="page-indexes">
<Search
navigation={this.state.navTree}
onResultsChange={this.handleSearch}
pages={this.props.pages}
/>
{this.state.query !== '' ? this.renderResults() : this.renderCategories()}
</div>
<div className="sidebar-footer">
<a href="https://www.sonarqube.org/" rel="noopener noreferrer" target="_blank">
<DownloadIcon /> SonarQube
</a>
<a href="https://community.sonarsource.com/" rel="noopener noreferrer" target="_blank">
<img alt="Community" src={`/${version}/images/community.svg`} /> Community
</a>
<a
className="icon-only"
href="https://twitter.com/SonarQube"
rel="noopener noreferrer"
target="_blank">
<img alt="Twitter" src={`/${version}/images/twitter.svg`} />
</a>
<a
className="icon-only"
href="https://www.sonarqube.org/whats-new/"
rel="noopener noreferrer"
target="_blank">
<img alt="Product News" src={`/${version}/images/newspaper.svg`} />
<span className="tooltip">Product News</span>
</a>
</div>
</div>
);
}
}

+ 0
- 78
server/sonar-docs/src/components/VersionSelect.tsx View File

@@ -1,78 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2022 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 { DocVersion } from '../@types/types';
import ChevronDownIcon from './icons/ChevronDownIcon';
import ChevronUpIcon from './icons/ChevronUpIcon';
import OutsideClickHandler from './OutsideClickHandler';

interface Props {
isOnCurrentVersion: boolean;
selectedVersionValue: string;
versions: DocVersion[];
}

interface State {
open: boolean;
}

export default class VersionSelect extends React.PureComponent<Props, State> {
state = { open: false };

handleClick = () => {
this.setState(state => ({ open: !state.open }));
};

handleClickOutside = () => {
this.setState({ open: false });
};

render() {
const { isOnCurrentVersion, selectedVersionValue, versions } = this.props;
const hasVersions = versions.length > 1;

return (
<div className="version-select">
<button onClick={this.handleClick} type="button">
Docs <span className={isOnCurrentVersion ? 'current' : ''}>{selectedVersionValue}</span>
{hasVersions && !this.state.open && <ChevronDownIcon size={10} />}
{hasVersions && this.state.open && <ChevronUpIcon size={10} />}
</button>
{this.state.open && hasVersions && (
<OutsideClickHandler onClickOutside={this.handleClickOutside}>
<ul>
{versions.map(version => {
return (
<li key={version.value}>
<a href={version.current ? '/' : '/' + version.value}>
<span className={version.current || version.lts ? 'current' : ''}>
{version.value + (version.lts ? ' LTS' : '')}
</span>
</a>
</li>
);
})}
</ul>
</OutsideClickHandler>
)}
</div>
);
}
}

+ 0
- 43
server/sonar-docs/src/components/__tests__/CategoryBlockLink-test.tsx View File

@@ -1,43 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2022 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 { shallow } from 'enzyme';
import * as React from 'react';
import { MarkdownRemark } from '../../@types/graphql-types';
import CategoryBlockLink from '../CategoryBlockLink';

it('should render correctly', () => {
expect(shallowRender({})).toMatchSnapshot();
});

it('should render correctly when closed', () => {
expect(shallowRender({ openByDefault: false })).toMatchSnapshot();
});

function shallowRender(props: Partial<CategoryBlockLink['props']> = {}) {
return shallow(
<CategoryBlockLink
location={{} as Location}
openByDefault={true}
title="My category"
{...props}>
{[{ id: '1' }, { id: '2' }] as MarkdownRemark[]}
</CategoryBlockLink>
);
}

+ 0
- 28
server/sonar-docs/src/components/__tests__/ExternalLink-test.tsx View File

@@ -1,28 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2022 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 { shallow } from 'enzyme';
import * as React from 'react';
import ExternalLink from '../ExternalLink';

it('should render correctly', () => {
expect(
shallow(<ExternalLink external="https://external.link" title="My link" />)
).toMatchSnapshot();
});

+ 0
- 39
server/sonar-docs/src/components/__tests__/HeadingsLink-test.tsx View File

@@ -1,39 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2022 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 { shallow } from 'enzyme';
import * as React from 'react';
import HeadingsLink from '../HeadingsLink';

it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot();
});

function shallowRender(props: Partial<HeadingsLink['props']> = {}) {
return shallow(
<HeadingsLink
headers={[
{ value: 'Table of Contents', depth: 2 },
{ value: 'Foo', depth: 2 },
{ value: 'Br', depth: 2 }
]}
{...props}
/>
);
}

+ 0
- 85
server/sonar-docs/src/components/__tests__/MetaData-test.tsx View File

@@ -1,85 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2022 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 { shallow } from 'enzyme';
import * as React from 'react';
import { waitAndUpdate } from '../../utils/testUtils';
import MetaData from '../MetaData';
import { mockMetaDataInformation } from '../mocks/update-center-metadata';
import { MetaDataInformation } from '../update-center-metadata';

beforeAll(() => {
window.fetch = jest.fn();
});

beforeEach(() => {
jest.resetAllMocks();
});

it('should render correctly', async () => {
const metaDataInfo = mockMetaDataInformation();
mockFetchReturnValue(metaDataInfo);

const wrapper = shallowRender();
await waitAndUpdate(wrapper);
expect(wrapper).toMatchSnapshot();
});

it('should render correctly with organization', async () => {
const metaDataInfo = mockMetaDataInformation({
organization: { name: 'test-org', url: 'test-org-url' }
});
mockFetchReturnValue(metaDataInfo);

const wrapper = shallowRender();
await waitAndUpdate(wrapper);
expect(wrapper).toMatchSnapshot();
});

it('should not render anything if call for metadata fails', async () => {
const metaDataInfo = mockMetaDataInformation();
mockFetchReturnValue(metaDataInfo, '404');

const wrapper = shallowRender();
await waitAndUpdate(wrapper);
expect(wrapper.type()).toBeNull();
});

it('should fetch metadata again if the update center key if modified', async () => {
const metaDataInfo = mockMetaDataInformation();
mockFetchReturnValue(metaDataInfo);

const wrapper = shallowRender();
await waitAndUpdate(wrapper);

expect(window.fetch).toHaveBeenCalledTimes(1);

mockFetchReturnValue(metaDataInfo);
wrapper.setProps({ updateCenterKey: 'abap' });

expect(window.fetch).toHaveBeenCalledTimes(2);
});

function shallowRender(props?: Partial<MetaData['props']>) {
return shallow<MetaData>(<MetaData updateCenterKey="apex" {...props} />);
}

function mockFetchReturnValue(metaDataInfo: MetaDataInformation, status = '200') {
(window.fetch as jest.Mock).mockResolvedValueOnce({ status, json: () => metaDataInfo });
}

+ 0
- 45
server/sonar-docs/src/components/__tests__/MetaDataVersion-test.tsx View File

@@ -1,45 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2022 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 { shallow } from 'enzyme';
import * as React from 'react';
import MetaDataVersion, { MetaDataVersionProps } from '../MetaDataVersion';
import { mockMetaDataVersionInformation } from '../mocks/update-center-metadata';

it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot();
expect(
shallowRender({
versionInformation: mockMetaDataVersionInformation({
downloadURL: [{ label: 'macos 64 bits', url: '' }]
})
})
).toMatchSnapshot('with advanced downloadUrl');
expect(
shallowRender({
versionInformation: { version: '2.0' }
})
).toMatchSnapshot('with very few info');
});

function shallowRender(props?: Partial<MetaDataVersionProps>) {
return shallow(
<MetaDataVersion versionInformation={mockMetaDataVersionInformation()} {...props} />
);
}

+ 0
- 51
server/sonar-docs/src/components/__tests__/MetaDataVersions-test.tsx View File

@@ -1,51 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2022 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 { shallow } from 'enzyme';
import * as React from 'react';
import { click } from '../../utils/testUtils';
import MetaDataVersion from '../MetaDataVersion';
import MetaDataVersions from '../MetaDataVersions';
import { mockMetaDataVersionInformation } from '../mocks/update-center-metadata';

it('should render correctly', () => {
const wrapper = shallowRender();
expect(wrapper).toMatchSnapshot();
});

it('should properly handle show more / show less', () => {
const wrapper = shallowRender();
expect(wrapper.find(MetaDataVersion).length).toBe(1);

click(wrapper.find('.update-center-meta-data-versions-show-more'));
expect(wrapper.find(MetaDataVersion).length).toBe(3);
});

function shallowRender(props?: Partial<MetaDataVersions['props']>) {
return shallow<MetaDataVersions>(
<MetaDataVersions
versions={[
mockMetaDataVersionInformation({ version: '3.0' }),
mockMetaDataVersionInformation({ version: '2.0', archived: true }),
mockMetaDataVersionInformation({ version: '1.0', archived: true })
]}
{...props}
/>
);
}

+ 0
- 31
server/sonar-docs/src/components/__tests__/PageLink-test.tsx View File

@@ -1,31 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2022 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 { shallow } from 'enzyme';
import * as React from 'react';
import { MarkdownRemark } from '../../@types/graphql-types';
import PageLink from '../PageLink';

const page = { frontmatter: { title: 'Foo', url: '/foo' } } as MarkdownRemark;

it('should render correctly', () => {
expect(
shallow(<PageLink location={{ pathname: '/foo' } as Location} node={page} />)
).toMatchSnapshot();
});

+ 0
- 126
server/sonar-docs/src/components/__tests__/Search-test.tsx View File

@@ -1,126 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2022 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 { shallow } from 'enzyme';
import lunr from 'lunr';
import * as React from 'react';
import { MarkdownRemark } from '../../@types/graphql-types';
import Search from '../Search';

jest.mock('lunr', () => ({
__esModule: true,
default: jest.fn(() => ({
search: jest.fn(() => [
{
ref: 'lorem/origin',
matchData: {
metadata: {
simply: {
title: { position: [[19, 5]] },
text: {
position: [
[15, 6],
[28, 4]
],
tokenContext: ['is simply dummy', 'simply dummy text']
}
}
}
}
},
{
ref: 'foobar',
matchData: {
metadata: {
simply: {
title: { position: [[23, 4]] },
text: {
position: [
[111, 6],
[118, 4]
],
tokenContext: ['keywords simply text']
}
}
}
}
}
])
}))
}));

function mockMarkdownRemark(override: Partial<MarkdownRemark>): MarkdownRemark {
return {
id: 'id',
parent: null,
children: null,
internal: null,
frontmatter: null,
rawMarkdownBody: null,
fileAbsolutePath: null,
fields: null,
html: null,
htmlAst: null,
excerpt: null,
headings: null,
timeToRead: null,
tableOfContents: null,
wordCount: null,
...override
};
}

const pages = [
mockMarkdownRemark({
html:
'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.'
}),
mockMarkdownRemark({
html:
'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.'
}),
mockMarkdownRemark({
html:
'Foobar is a universal variable understood to represent whatever is ' +
'being discussed. Now we need some keywords: simply text.'
})
];

it('should search', () => {
const wrapper = shallow<Search>(
<Search
navigation={['lorem/index', 'lorem/origin', 'foobar']}
pages={pages}
onResultsChange={jest.fn()}
/>
);
wrapper.instance().handleChange({ currentTarget: { value: 'simply text+:' } } as any);
expect(wrapper).toMatchSnapshot();
expect(lunr).toHaveBeenCalled();
expect(wrapper.instance().index).toBeDefined();
expect((wrapper.instance().index as any).search).toHaveBeenCalledWith(
'simply~1 simply* text~1 text*'
);
});

+ 0
- 112
server/sonar-docs/src/components/__tests__/Sidebar-test.tsx View File

@@ -1,112 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2022 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 { shallow } from 'enzyme';
import { FetchMock } from 'jest-fetch-mock';
import * as React from 'react';
import { setImmediate } from 'timers';
import { MarkdownRemark } from '../../@types/graphql-types';
import Sidebar from '../Sidebar';

jest.mock('../navTreeUtils', () => {
return {
...jest.requireActual('../navTreeUtils'),
getNavTree: jest.fn().mockReturnValue([
'/foo/',
{
title: 'Foo subs',
children: [
'/foo/bar/',
'/foo/baz/',
{
title: 'Foo Baz subs',
children: [
'/foo/baz/bar/',
'/foo/baz/foo/',
{
title: 'Foo Baz Foo subs',
children: ['/foo/baz/foo/bar/', '/foo/baz/foo/baz']
}
]
}
]
},
'/bar/',
{
title: 'Bar subs',
children: [{ title: 'External link 1', url: 'http://example.com/1' }, '/bar/foo/']
},
{ title: 'External link 2', url: 'http://example.com/2' }
])
};
});

beforeEach(() => {
(fetch as FetchMock).resetMocks();
(fetch as FetchMock).mockResponse(`[
{ "value": "3.0", "current": true },
{ "value": "2.0", "current": false, "lts": true },
{ "value": "1.0", "current": false }
]`);
});

it('should render correctly', async () => {
const wrapper = shallowRender();
await new Promise(setImmediate);

expect(wrapper).toMatchSnapshot('default');
expect(wrapper.setProps({ version: '1.0' })).toMatchSnapshot('show warning');
expect(wrapper.setProps({ version: '2.0' })).toMatchSnapshot('lts');
});

function shallowRender(props: Partial<Sidebar['props']> = {}) {
return shallow<Sidebar>(
<Sidebar
location={{ pathname: '/2.0/foo/baz/foo/bar' } as Location}
pages={[
{
fields: {
slug: '/foo/'
},
frontmatter: {
title: 'Foo'
}
} as MarkdownRemark,
{
fields: {
slug: '/foo/baz/bar'
},
frontmatter: {
title: 'Foo Baz Bar'
}
} as MarkdownRemark,
{
fields: {
slug: '/bar/'
},
frontmatter: {
title: 'Bar'
}
} as MarkdownRemark
]}
version="3.0"
{...props}
/>
);
}

+ 0
- 62
server/sonar-docs/src/components/__tests__/VersionSelect-test.tsx View File

@@ -1,62 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2022 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 { shallow } from 'enzyme';
import * as React from 'react';
import { click } from '../../utils/testUtils';
import OutsideClickHandler from '../OutsideClickHandler';
import VersionSelect from '../VersionSelect';

it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot('default');

const wrapper = shallowRender();
wrapper.setState({ open: true });
expect(wrapper).toMatchSnapshot('open');

expect(shallowRender({ isOnCurrentVersion: true })).toMatchSnapshot('on current version');
});

it('should handle open/closing the list', () => {
const wrapper = shallowRender();

click(wrapper.find('button'));
expect(wrapper.state().open).toBe(true);
click(wrapper.find('button'));
expect(wrapper.state().open).toBe(false);

wrapper.setState({ open: true });
wrapper.find(OutsideClickHandler).prop('onClickOutside')();
expect(wrapper.state().open).toBe(false);
});

function shallowRender(props: Partial<VersionSelect['props']> = {}) {
return shallow<VersionSelect>(
<VersionSelect
isOnCurrentVersion={false}
selectedVersionValue="1.0"
versions={[
{ value: '3.0', current: true },
{ value: '2.0', current: false, lts: true },
{ value: '1.0', current: false }
]}
{...props}
/>
);
}

+ 0
- 51
server/sonar-docs/src/components/__tests__/__snapshots__/CategoryBlockLink-test.tsx.snap View File

@@ -1,51 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render correctly 1`] = `
<div>
<a
className="page-indexes-link active"
href="#"
onClick={[Function]}
>
<ChevronUpIcon />
My category
</a>
<div
className="sub-menu"
>
<PageLink
className="sub-menu-link"
key="1"
location={Object {}}
node={
Object {
"id": "1",
}
}
/>
<PageLink
className="sub-menu-link"
key="2"
location={Object {}}
node={
Object {
"id": "2",
}
}
/>
</div>
</div>
`;

exports[`should render correctly when closed 1`] = `
<div>
<a
className="page-indexes-link"
href="#"
onClick={[Function]}
>
<ChevronDownIcon />
My category
</a>
</div>
`;

+ 0
- 13
server/sonar-docs/src/components/__tests__/__snapshots__/ExternalLink-test.tsx.snap View File

@@ -1,13 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render correctly 1`] = `
<a
className="page-indexes-link"
href="https://external.link"
rel="noopener noreferrer"
target="_blank"
>
<DetachIcon />
My link
</a>
`;

+ 0
- 33
server/sonar-docs/src/components/__tests__/__snapshots__/HeadingsLink-test.tsx.snap View File

@@ -1,33 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render correctly 1`] = `
<div
className="headings-container"
>
<div
className="headings-container-fixed"
>
<span>
On this page
</span>
<ul>
<HeadingAnchor
active={false}
clickHandler={[Function]}
index={1}
key="0"
>
Foo
</HeadingAnchor>
<HeadingAnchor
active={false}
clickHandler={[Function]}
index={2}
key="1"
>
Br
</HeadingAnchor>
</ul>
</div>
</div>
`;

+ 0
- 133
server/sonar-docs/src/components/__tests__/__snapshots__/MetaData-test.tsx.snap View File

@@ -1,133 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render correctly 1`] = `
<div
className="update-center-meta-data"
>
<div
className="update-center-meta-data-header"
>
<span
className="update-center-meta-data-vendor"
>
By
<a
href="http://www.sonarsource.com/"
rel="noopener noreferrer"
target="_blank"
>
SonarSource
</a>
</span>
<span
className="update-center-meta-data-license"
>
SonarSource
</span>
<span
className="update-center-meta-data-issue-tracker"
>
<a
href="https://jira.sonarsource.com/browse/SONARJAVA"
rel="noopener noreferrer"
target="_blank"
>
Issue Tracker
</a>
</span>
<span
className="update-center-meta-data-supported"
>
Supported by SonarSource
</span>
</div>
<MetaDataVersions
versions={
Array [
Object {
"archived": false,
"changeLogUrl": "https://example.com/sonar-java-plugin/release",
"compatibility": "6.7",
"date": "2019-05-31",
"downloadURL": "https://example.com/sonar-java-plugin-5.13.0.18197.jar",
"version": "2.0",
},
Object {
"archived": true,
"changeLogUrl": "https://example.com/sonar-java-plugin/release",
"compatibility": "6.7",
"date": "2019-05-31",
"downloadURL": "https://example.com/sonar-java-plugin-5.13.0.18197.jar",
"version": "1.0",
},
]
}
/>
</div>
`;

exports[`should render correctly with organization 1`] = `
<div
className="update-center-meta-data"
>
<div
className="update-center-meta-data-header"
>
<span
className="update-center-meta-data-vendor"
>
By
<a
href="test-org-url"
rel="noopener noreferrer"
target="_blank"
>
test-org
</a>
</span>
<span
className="update-center-meta-data-license"
>
SonarSource
</span>
<span
className="update-center-meta-data-issue-tracker"
>
<a
href="https://jira.sonarsource.com/browse/SONARJAVA"
rel="noopener noreferrer"
target="_blank"
>
Issue Tracker
</a>
</span>
<span
className="update-center-meta-data-supported"
>
Supported by SonarSource
</span>
</div>
<MetaDataVersions
versions={
Array [
Object {
"archived": false,
"changeLogUrl": "https://example.com/sonar-java-plugin/release",
"compatibility": "6.7",
"date": "2019-05-31",
"downloadURL": "https://example.com/sonar-java-plugin-5.13.0.18197.jar",
"version": "2.0",
},
Object {
"archived": true,
"changeLogUrl": "https://example.com/sonar-java-plugin/release",
"compatibility": "6.7",
"date": "2019-05-31",
"downloadURL": "https://example.com/sonar-java-plugin-5.13.0.18197.jar",
"version": "1.0",
},
]
}
/>
</div>
`;

+ 0
- 113
server/sonar-docs/src/components/__tests__/__snapshots__/MetaDataVersion-test.tsx.snap View File

@@ -1,113 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render correctly 1`] = `
<div
className="update-center-meta-data-version"
>
<div
className="update-center-meta-data-version-version"
>
5.13
</div>
<div
className="update-center-meta-data-version-release-info"
>
<time
className="update-center-meta-data-version-release-date"
>
2019-05-31
</time>
<span
className="update-center-meta-data-version-compatibility"
>
6.7
</span>
</div>
<div
className="update-center-meta-data-version-release-links"
>
<span
className="update-center-meta-data-version-download"
key="0"
>
<a
href="https://example.com/sonar-java-plugin-5.13.0.18197.jar"
rel="noopener noreferrer"
target="_blank"
>
Download
</a>
</span>
<span
className="update-center-meta-data-version-release-notes"
>
<a
href="https://example.com/sonar-java-plugin/release"
rel="noopener noreferrer"
target="_blank"
>
Release notes
</a>
</span>
</div>
</div>
`;

exports[`should render correctly: with advanced downloadUrl 1`] = `
<div
className="update-center-meta-data-version"
>
<div
className="update-center-meta-data-version-version"
>
5.13
</div>
<div
className="update-center-meta-data-version-release-info"
>
<time
className="update-center-meta-data-version-release-date"
>
2019-05-31
</time>
<span
className="update-center-meta-data-version-compatibility"
>
6.7
</span>
</div>
<div
className="update-center-meta-data-version-release-links"
>
<span
className="update-center-meta-data-version-release-notes"
>
<a
href="https://example.com/sonar-java-plugin/release"
rel="noopener noreferrer"
target="_blank"
>
Release notes
</a>
</span>
</div>
</div>
`;

exports[`should render correctly: with very few info 1`] = `
<div
className="update-center-meta-data-version"
>
<div
className="update-center-meta-data-version-version"
>
2.0
</div>
<div
className="update-center-meta-data-version-release-info"
/>
<div
className="update-center-meta-data-version-release-links"
/>
</div>
`;

+ 0
- 28
server/sonar-docs/src/components/__tests__/__snapshots__/MetaDataVersions-test.tsx.snap View File

@@ -1,28 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render correctly 1`] = `
<div
className="update-center-meta-data-versions"
>
<button
className="update-center-meta-data-versions-show-more"
onClick={[Function]}
type="button"
>
Show more versions
</button>
<MetaDataVersion
key="3.0"
versionInformation={
Object {
"archived": false,
"changeLogUrl": "https://example.com/sonar-java-plugin/release",
"compatibility": "6.7",
"date": "2019-05-31",
"downloadURL": "https://example.com/sonar-java-plugin-5.13.0.18197.jar",
"version": "3.0",
}
}
/>
</div>
`;

+ 0
- 13
server/sonar-docs/src/components/__tests__/__snapshots__/PageLink-test.tsx.snap View File

@@ -1,13 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render correctly 1`] = `
<div>
<ForwardRef
className="active"
title="Foo"
to="/foo"
>
Foo
</ForwardRef>
</div>
`;

+ 0
- 24
server/sonar-docs/src/components/__tests__/__snapshots__/Search-test.tsx.snap View File

@@ -1,24 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should search 1`] = `
<div
className="search-container"
>
<input
aria-label="Search"
className="search-input"
onChange={[Function]}
placeholder="Search..."
type="search"
value="simply text+:"
/>
<button
onClick={[Function]}
type="button"
>
<ClearIcon
size={8}
/>
</button>
</div>
`;

+ 0
- 807
server/sonar-docs/src/components/__tests__/__snapshots__/Sidebar-test.tsx.snap View File

@@ -1,807 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render correctly: default 1`] = `
<div
className="page-sidebar"
>
<div
className="sidebar-header"
>
<ForwardRef
to="/"
>
<img
alt="Continuous Code Quality"
className="sidebar-logo"
src="/3.0/images/SonarQubeIcon.svg"
title="Continuous Code Quality"
width="160"
/>
</ForwardRef>
<VersionSelect
isOnCurrentVersion={true}
selectedVersionValue="3.0"
versions={
Array [
Object {
"current": true,
"value": "3.0",
},
Object {
"current": false,
"lts": true,
"value": "2.0",
},
Object {
"current": false,
"value": "1.0",
},
]
}
/>
</div>
<div
className="page-indexes"
>
<Search
navigation={
Array [
"/foo/",
Object {
"children": Array [
"/foo/bar/",
"/foo/baz/",
Object {
"children": Array [
"/foo/baz/bar/",
"/foo/baz/foo/",
Object {
"children": Array [
"/foo/baz/foo/bar/",
"/foo/baz/foo/baz",
],
"title": "Foo Baz Foo subs",
},
],
"title": "Foo Baz subs",
},
],
"title": "Foo subs",
},
"/bar/",
Object {
"children": Array [
Object {
"title": "External link 1",
"url": "http://example.com/1",
},
"/bar/foo/",
],
"title": "Bar subs",
},
Object {
"title": "External link 2",
"url": "http://example.com/2",
},
]
}
onResultsChange={[Function]}
pages={
Array [
Object {
"fields": Object {
"slug": "/foo/",
},
"frontmatter": Object {
"title": "Foo",
},
},
Object {
"fields": Object {
"slug": "/foo/baz/bar",
},
"frontmatter": Object {
"title": "Foo Baz Bar",
},
},
Object {
"fields": Object {
"slug": "/bar/",
},
"frontmatter": Object {
"title": "Bar",
},
},
]
}
/>
<nav>
<PageLink
className="page-indexes-link"
key="/foo/"
location={
Object {
"pathname": "/2.0/foo/baz/foo/bar",
}
}
node={
Object {
"fields": Object {
"slug": "/foo/",
},
"frontmatter": Object {
"title": "Foo",
},
}
}
/>
<CategoryLink
key="Foo subs"
location={
Object {
"pathname": "/2.0/foo/baz/foo/bar",
}
}
openByDefault={true}
title="Foo subs"
>
<CategoryLink
key="Foo Baz subs"
location={
Object {
"pathname": "/2.0/foo/baz/foo/bar",
}
}
openByDefault={true}
title="Foo Baz subs"
>
<Component />
<CategoryLink
key="Foo Baz Foo subs"
location={
Object {
"pathname": "/2.0/foo/baz/foo/bar",
}
}
openByDefault={true}
title="Foo Baz Foo subs"
/>
</CategoryLink>
</CategoryLink>
<PageLink
className="page-indexes-link"
key="/bar/"
location={
Object {
"pathname": "/2.0/foo/baz/foo/bar",
}
}
node={
Object {
"fields": Object {
"slug": "/bar/",
},
"frontmatter": Object {
"title": "Bar",
},
}
}
/>
<CategoryLink
key="Bar subs"
location={
Object {
"pathname": "/2.0/foo/baz/foo/bar",
}
}
openByDefault={false}
title="Bar subs"
>
<ExternalLink
external="http://example.com/1"
key="External link 1"
title="External link 1"
/>
</CategoryLink>
<ExternalLink
external="http://example.com/2"
key="External link 2"
title="External link 2"
/>
</nav>
</div>
<div
className="sidebar-footer"
>
<a
href="https://www.sonarqube.org/"
rel="noopener noreferrer"
target="_blank"
>
<DownloadIcon />
SonarQube
</a>
<a
href="https://community.sonarsource.com/"
rel="noopener noreferrer"
target="_blank"
>
<img
alt="Community"
src="/3.0/images/community.svg"
/>
Community
</a>
<a
className="icon-only"
href="https://twitter.com/SonarQube"
rel="noopener noreferrer"
target="_blank"
>
<img
alt="Twitter"
src="/3.0/images/twitter.svg"
/>
</a>
<a
className="icon-only"
href="https://www.sonarqube.org/whats-new/"
rel="noopener noreferrer"
target="_blank"
>
<img
alt="Product News"
src="/3.0/images/newspaper.svg"
/>
<span
className="tooltip"
>
Product News
</span>
</a>
</div>
</div>
`;

exports[`should render correctly: lts 1`] = `
<div
className="page-sidebar"
>
<div
className="sidebar-header"
>
<ForwardRef
to="/"
>
<img
alt="Continuous Code Quality"
className="sidebar-logo"
src="/2.0/images/SonarQubeIcon.svg"
title="Continuous Code Quality"
width="160"
/>
</ForwardRef>
<VersionSelect
isOnCurrentVersion={false}
selectedVersionValue="2.0"
versions={
Array [
Object {
"current": true,
"value": "3.0",
},
Object {
"current": false,
"lts": true,
"value": "2.0",
},
Object {
"current": false,
"value": "1.0",
},
]
}
/>
</div>
<div
className="page-indexes"
>
<Search
navigation={
Array [
"/foo/",
Object {
"children": Array [
"/foo/bar/",
"/foo/baz/",
Object {
"children": Array [
"/foo/baz/bar/",
"/foo/baz/foo/",
Object {
"children": Array [
"/foo/baz/foo/bar/",
"/foo/baz/foo/baz",
],
"title": "Foo Baz Foo subs",
},
],
"title": "Foo Baz subs",
},
],
"title": "Foo subs",
},
"/bar/",
Object {
"children": Array [
Object {
"title": "External link 1",
"url": "http://example.com/1",
},
"/bar/foo/",
],
"title": "Bar subs",
},
Object {
"title": "External link 2",
"url": "http://example.com/2",
},
]
}
onResultsChange={[Function]}
pages={
Array [
Object {
"fields": Object {
"slug": "/foo/",
},
"frontmatter": Object {
"title": "Foo",
},
},
Object {
"fields": Object {
"slug": "/foo/baz/bar",
},
"frontmatter": Object {
"title": "Foo Baz Bar",
},
},
Object {
"fields": Object {
"slug": "/bar/",
},
"frontmatter": Object {
"title": "Bar",
},
},
]
}
/>
<nav>
<PageLink
className="page-indexes-link"
key="/foo/"
location={
Object {
"pathname": "/2.0/foo/baz/foo/bar",
}
}
node={
Object {
"fields": Object {
"slug": "/foo/",
},
"frontmatter": Object {
"title": "Foo",
},
}
}
/>
<CategoryLink
key="Foo subs"
location={
Object {
"pathname": "/2.0/foo/baz/foo/bar",
}
}
openByDefault={true}
title="Foo subs"
>
<CategoryLink
key="Foo Baz subs"
location={
Object {
"pathname": "/2.0/foo/baz/foo/bar",
}
}
openByDefault={true}
title="Foo Baz subs"
>
<Component />
<CategoryLink
key="Foo Baz Foo subs"
location={
Object {
"pathname": "/2.0/foo/baz/foo/bar",
}
}
openByDefault={true}
title="Foo Baz Foo subs"
/>
</CategoryLink>
</CategoryLink>
<PageLink
className="page-indexes-link"
key="/bar/"
location={
Object {
"pathname": "/2.0/foo/baz/foo/bar",
}
}
node={
Object {
"fields": Object {
"slug": "/bar/",
},
"frontmatter": Object {
"title": "Bar",
},
}
}
/>
<CategoryLink
key="Bar subs"
location={
Object {
"pathname": "/2.0/foo/baz/foo/bar",
}
}
openByDefault={false}
title="Bar subs"
>
<ExternalLink
external="http://example.com/1"
key="External link 1"
title="External link 1"
/>
</CategoryLink>
<ExternalLink
external="http://example.com/2"
key="External link 2"
title="External link 2"
/>
</nav>
</div>
<div
className="sidebar-footer"
>
<a
href="https://www.sonarqube.org/"
rel="noopener noreferrer"
target="_blank"
>
<DownloadIcon />
SonarQube
</a>
<a
href="https://community.sonarsource.com/"
rel="noopener noreferrer"
target="_blank"
>
<img
alt="Community"
src="/2.0/images/community.svg"
/>
Community
</a>
<a
className="icon-only"
href="https://twitter.com/SonarQube"
rel="noopener noreferrer"
target="_blank"
>
<img
alt="Twitter"
src="/2.0/images/twitter.svg"
/>
</a>
<a
className="icon-only"
href="https://www.sonarqube.org/whats-new/"
rel="noopener noreferrer"
target="_blank"
>
<img
alt="Product News"
src="/2.0/images/newspaper.svg"
/>
<span
className="tooltip"
>
Product News
</span>
</a>
</div>
</div>
`;

exports[`should render correctly: show warning 1`] = `
<div
className="page-sidebar"
>
<div
className="sidebar-header"
>
<ForwardRef
to="/"
>
<img
alt="Continuous Code Quality"
className="sidebar-logo"
src="/1.0/images/SonarQubeIcon.svg"
title="Continuous Code Quality"
width="160"
/>
</ForwardRef>
<VersionSelect
isOnCurrentVersion={false}
selectedVersionValue="1.0"
versions={
Array [
Object {
"current": true,
"value": "3.0",
},
Object {
"current": false,
"lts": true,
"value": "2.0",
},
Object {
"current": false,
"value": "1.0",
},
]
}
/>
<div
className="alert alert-warning"
>
This is an archived version of the doc for
<b>
SonarQube version
1.0
</b>
.
<a
href="/"
>
See Documentation
</a>
for current functionality.
</div>
</div>
<div
className="page-indexes"
>
<Search
navigation={
Array [
"/foo/",
Object {
"children": Array [
"/foo/bar/",
"/foo/baz/",
Object {
"children": Array [
"/foo/baz/bar/",
"/foo/baz/foo/",
Object {
"children": Array [
"/foo/baz/foo/bar/",
"/foo/baz/foo/baz",
],
"title": "Foo Baz Foo subs",
},
],
"title": "Foo Baz subs",
},
],
"title": "Foo subs",
},
"/bar/",
Object {
"children": Array [
Object {
"title": "External link 1",
"url": "http://example.com/1",
},
"/bar/foo/",
],
"title": "Bar subs",
},
Object {
"title": "External link 2",
"url": "http://example.com/2",
},
]
}
onResultsChange={[Function]}
pages={
Array [
Object {
"fields": Object {
"slug": "/foo/",
},
"frontmatter": Object {
"title": "Foo",
},
},
Object {
"fields": Object {
"slug": "/foo/baz/bar",
},
"frontmatter": Object {
"title": "Foo Baz Bar",
},
},
Object {
"fields": Object {
"slug": "/bar/",
},
"frontmatter": Object {
"title": "Bar",
},
},
]
}
/>
<nav>
<PageLink
className="page-indexes-link"
key="/foo/"
location={
Object {
"pathname": "/2.0/foo/baz/foo/bar",
}
}
node={
Object {
"fields": Object {
"slug": "/foo/",
},
"frontmatter": Object {
"title": "Foo",
},
}
}
/>
<CategoryLink
key="Foo subs"
location={
Object {
"pathname": "/2.0/foo/baz/foo/bar",
}
}
openByDefault={true}
title="Foo subs"
>
<CategoryLink
key="Foo Baz subs"
location={
Object {
"pathname": "/2.0/foo/baz/foo/bar",
}
}
openByDefault={true}
title="Foo Baz subs"
>
<Component />
<CategoryLink
key="Foo Baz Foo subs"
location={
Object {
"pathname": "/2.0/foo/baz/foo/bar",
}
}
openByDefault={true}
title="Foo Baz Foo subs"
/>
</CategoryLink>
</CategoryLink>
<PageLink
className="page-indexes-link"
key="/bar/"
location={
Object {
"pathname": "/2.0/foo/baz/foo/bar",
}
}
node={
Object {
"fields": Object {
"slug": "/bar/",
},
"frontmatter": Object {
"title": "Bar",
},
}
}
/>
<CategoryLink
key="Bar subs"
location={
Object {
"pathname": "/2.0/foo/baz/foo/bar",
}
}
openByDefault={false}
title="Bar subs"
>
<ExternalLink
external="http://example.com/1"
key="External link 1"
title="External link 1"
/>
</CategoryLink>
<ExternalLink
external="http://example.com/2"
key="External link 2"
title="External link 2"
/>
</nav>
</div>
<div
className="sidebar-footer"
>
<a
href="https://www.sonarqube.org/"
rel="noopener noreferrer"
target="_blank"
>
<DownloadIcon />
SonarQube
</a>
<a
href="https://community.sonarsource.com/"
rel="noopener noreferrer"
target="_blank"
>
<img
alt="Community"
src="/1.0/images/community.svg"
/>
Community
</a>
<a
className="icon-only"
href="https://twitter.com/SonarQube"
rel="noopener noreferrer"
target="_blank"
>
<img
alt="Twitter"
src="/1.0/images/twitter.svg"
/>
</a>
<a
className="icon-only"
href="https://www.sonarqube.org/whats-new/"
rel="noopener noreferrer"
target="_blank"
>
<img
alt="Product News"
src="/1.0/images/newspaper.svg"
/>
<span
className="tooltip"
>
Product News
</span>
</a>
</div>
</div>
`;

+ 0
- 109
server/sonar-docs/src/components/__tests__/__snapshots__/VersionSelect-test.tsx.snap View File

@@ -1,109 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render correctly: default 1`] = `
<div
className="version-select"
>
<button
onClick={[Function]}
type="button"
>
Docs
<span
className=""
>
1.0
</span>
<ChevronDownIcon
size={10}
/>
</button>
</div>
`;

exports[`should render correctly: on current version 1`] = `
<div
className="version-select"
>
<button
onClick={[Function]}
type="button"
>
Docs
<span
className="current"
>
1.0
</span>
<ChevronDownIcon
size={10}
/>
</button>
</div>
`;

exports[`should render correctly: open 1`] = `
<div
className="version-select"
>
<button
onClick={[Function]}
type="button"
>
Docs
<span
className=""
>
1.0
</span>
<ChevronUpIcon
size={10}
/>
</button>
<OutsideClickHandler
onClickOutside={[Function]}
>
<ul>
<li
key="3.0"
>
<a
href="/"
>
<span
className="current"
>
3.0
</span>
</a>
</li>
<li
key="2.0"
>
<a
href="/2.0"
>
<span
className="current"
>
2.0 LTS
</span>
</a>
</li>
<li
key="1.0"
>
<a
href="/1.0"
>
<span
className=""
>
1.0
</span>
</a>
</li>
</ul>
</OutsideClickHandler>
</div>
`;

+ 0
- 90
server/sonar-docs/src/components/__tests__/navTreeUtils-test.ts View File

@@ -1,90 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2022 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 { getOpenChainFromPath, getUrlsList, testPathAgainstUrl } from '../navTreeUtils';

const navTree = [
'path/value',
{
title: 'My paths',
children: [
'child/path/1',
{
title: 'Child paths',
children: [
'sub/child/path/1',
{
title: 'External link 2',
url: 'http://example.com/2'
},
{
title: 'Last ones, promised',
children: ['sub/sub/child/path/1']
},
'sub/child/path/3'
]
},
'child/path/2'
]
},
{
title: 'External link',
url: 'http://example.com'
}
];

describe('getUrlsList', () => {
it('should return the correct values for a list of paths', () => {
expect(getUrlsList(navTree)).toEqual([
'path/value',
'child/path/1',
'sub/child/path/1',
'http://example.com/2',
'sub/sub/child/path/1',
'sub/child/path/3',
'child/path/2',
'http://example.com'
]);
});
});

describe('getOpenChainFromPath', () => {
it('should correctly fetch the chain of open elements for a given path', () => {
expect(getOpenChainFromPath('path/value/', navTree)).toEqual([navTree[0]]);
expect(getOpenChainFromPath('latest/path/value/', navTree)).toEqual([navTree[0]]);
expect(getOpenChainFromPath('sub/child/path/3', navTree)).toEqual([
navTree[1],
(navTree as any)[1].children[1],
(navTree as any)[1].children[1].children[3]
]);
});
});

describe('testPathAgainstUrl', () => {
it('should handle paths with trailing and/or leading slashes', () => {
expect(testPathAgainstUrl('path/foo/', 'path/bar')).toBe(false);
expect(testPathAgainstUrl('/path/foo/', '/path/bar/')).toBe(false);
expect(testPathAgainstUrl('path/foo/', 'path/foo')).toBe(true);
expect(testPathAgainstUrl('path/foo', 'path/foo/')).toBe(true);
expect(testPathAgainstUrl('/path/foo/', 'path/foo')).toBe(true);
expect(testPathAgainstUrl('path/foo', '/path/foo/')).toBe(true);
expect(testPathAgainstUrl('/path/foo', '/1.0/path/foo/')).toBe(true);
expect(testPathAgainstUrl('/path/foo', '/latest/path/foo/')).toBe(true);
});
});

+ 0
- 32
server/sonar-docs/src/components/icons/AlertWarnIcon.tsx View File

@@ -1,32 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2022 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, { IconProps } from './Icon';

export default function AlertWarnIcon({ className, fill = '#ed7d20', size }: IconProps) {
return (
<Icon className={className} size={size}>
<path
d="M8 1.143q1.866 0 3.442.92t2.496 2.496.92 3.442-.92 3.442-2.496 2.496-3.442.92-3.442-.92-2.496-2.496-.92-3.442.92-3.442 2.496-2.496T8 1.143zm1.143 11.134v-1.696q0-.125-.08-.21t-.196-.085H7.153q-.116 0-.205.089t-.089.205v1.696q0 .116.089.205t.205.089h1.714q.116 0 .196-.085t.08-.21zm-.018-3.072l.161-5.545q0-.107-.089-.161-.089-.071-.214-.071H7.019q-.125 0-.214.071-.089.054-.089.161l.152 5.545q0 .089.089.156t.214.067h1.652q.125 0 .21-.067t.094-.156z"
style={{ fill }}
/>
</Icon>
);
}

+ 0
- 32
server/sonar-docs/src/components/icons/ChevronDownIcon.tsx View File

@@ -1,32 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2022 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, { IconProps } from './Icon';

export default function ChevronDownIcon({ className, fill = 'currentColor', size }: IconProps) {
return (
<Icon className={className} size={size}>
<path
d="M3.2,5.6c0-0.1,0-0.2,0.1-0.3c0.2-0.2,0.5-0.2,0.6,0l4.1,4.1l4.1-4.1c0.2-0.2,0.5-0.2,0.6,0 c0.2,0.2,0.2,0.5,0,0.6c0,0,0,0,0,0l0,0l-4.5,4.5c-0.2,0.2-0.5,0.2-0.6,0l0,0L3.3,5.9C3.2,5.9,3.2,5.7,3.2,5.6z"
style={{ fill }}
/>
</Icon>
);
}

+ 0
- 32
server/sonar-docs/src/components/icons/ChevronUpIcon.tsx View File

@@ -1,32 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2022 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, { IconProps } from './Icon';

export default function ChevronUpIcon({ className, fill = 'currentColor', size }: IconProps) {
return (
<Icon className={className} size={size}>
<path
d="M13,10c0,0.1,0,0.2-0.1,0.3c-0.2,0.2-0.5,0.2-0.6,0L8.1,6.2L4,10.3c-0.2,0.2-0.5,0.2-0.6,0 c-0.2-0.2-0.2-0.5,0-0.6c0,0,0,0,0,0l0,0l4.5-4.5c0.2-0.2,0.5-0.2,0.6,0l0,0l4.4,4.4C13,9.7,13,9.8,13,10z"
style={{ fill }}
/>
</Icon>
);
}

+ 0
- 32
server/sonar-docs/src/components/icons/ClearIcon.tsx View File

@@ -1,32 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2022 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, { IconProps } from './Icon';

export default function ClearIcon({ className, fill = 'currentColor', size }: IconProps) {
return (
<Icon className={className} size={size} viewBox="0 0 48 48">
<path
d="M28.24 24L47.07 5.16A3 3 0 1 0 42.93.83l-.09.1L24 19.76 5.16.93A3 3 0 0 0 .93 5.16L19.76 24 .93 42.84a3 3 0 1 0 4.14 4.33l.09-.1L24 28.24l18.84 18.83a3 3 0 1 0 4.33-4.14l-.1-.09z"
style={{ fill }}
/>
</Icon>
);
}

+ 0
- 32
server/sonar-docs/src/components/icons/DetachIcon.tsx View File

@@ -1,32 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2022 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, { IconProps } from './Icon';

export default function DetachIcon({ className, fill = 'currentColor', size }: IconProps) {
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>
);
}

+ 0
- 36
server/sonar-docs/src/components/icons/DownloadIcon.tsx View File

@@ -1,36 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2022 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, { IconProps } from './Icon';

export default function DownloadIcon({ className, fill = 'currentColor', size }: IconProps) {
return (
<Icon className={className} size={size} viewBox="0 0 48 48">
<path
d="M45.68 22.86a1.31 1.31 0 0 0-1.32 1.32v12a5.91 5.91 0 0 1-5.9 5.91H9.54a5.91 5.91 0 0 1-5.9-5.91V24A1.32 1.32 0 0 0 1 24v12.16a8.56 8.56 0 0 0 8.54 8.55h28.92A8.56 8.56 0 0 0 47 36.16v-12a1.32 1.32 0 0 0-1.32-1.3z"
style={{ fill }}
/>
<path
d="M23.07 34.24a1.36 1.36 0 0 0 .93.39 1.32 1.32 0 0 0 .93-.39l8.37-8.38A1.32 1.32 0 0 0 31.44 24l-6.12 6.13V3.39a1.32 1.32 0 0 0-2.64 0v26.74L16.55 24a1.32 1.32 0 0 0-1.86 1.86z"
style={{ fill }}
/>
</Icon>
);
}

+ 0
- 70
server/sonar-docs/src/components/icons/Icon.tsx View File

@@ -1,70 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2022 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 interface IconProps {
className?: string;
fill?: string;
size?: number;
}

interface Props {
children: React.ReactNode;
className?: string;
size?: number;
style?: React.CSSProperties;

// try to avoid using these:
width?: number;
height?: number;
viewBox?: string;
}

export default function Icon({
children,
className,
size = 16,
style,
height = size,
width = size,
viewBox = '0 0 16 16',
...other
}: Props) {
return (
<svg
className={className}
height={height}
style={{
fillRule: 'evenodd',
clipRule: 'evenodd',
strokeLinejoin: 'round',
strokeMiterlimit: 1.41421,
...style
}}
version="1.1"
viewBox={viewBox}
width={width}
xmlSpace="preserve"
xmlnsXlink="http://www.w3.org/1999/xlink"
{...other}>
{children}
</svg>
);
}

+ 0
- 57
server/sonar-docs/src/components/mocks/update-center-metadata.ts View File

@@ -1,57 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2022 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 { MetaDataInformation, MetaDataVersionInformation } from '../update-center-metadata';

export function mockMetaDataVersionInformation(
overrides?: Partial<MetaDataVersionInformation>
): MetaDataVersionInformation {
return {
version: '5.13',
date: '2019-05-31',
compatibility: '6.7',
archived: false,
downloadURL: 'https://example.com/sonar-java-plugin-5.13.0.18197.jar',
changeLogUrl: 'https://example.com/sonar-java-plugin/release',
...overrides
};
}

export function mockMetaDataInformation(
overrides?: Partial<MetaDataInformation>
): MetaDataInformation {
return {
name: 'SonarJava',
key: 'java',
isSonarSourceCommercial: true,
organization: {
name: 'SonarSource',
url: 'http://www.sonarsource.com/'
},
category: 'Languages',
license: 'SonarSource',
issueTrackerURL: 'https://jira.sonarsource.com/browse/SONARJAVA',
sourcesURL: 'https://github.com/SonarSource/sonar-java',
versions: [
mockMetaDataVersionInformation({ version: '2.0' }),
mockMetaDataVersionInformation({ version: '1.0', archived: true })
],
...overrides
};
}

+ 0
- 95
server/sonar-docs/src/components/navTreeUtils.ts View File

@@ -1,95 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2022 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 { flatten } from 'lodash';
import NavigationTree from '../../static/StaticNavigationTree.json';
import {
DocNavigationItem,
DocsNavigationBlock,
DocsNavigationExternalLink
} from '../@types/types';

export function getNavTree() {
return NavigationTree as DocNavigationItem[];
}

export function getUrlsList(navTree: DocNavigationItem[]): string[] {
return flatten(
navTree.map(leaf => {
if (isDocsNavigationBlock(leaf)) {
return getUrlsList(leaf.children);
}
if (isDocsNavigationExternalLink(leaf)) {
return [leaf.url];
}
return [leaf];
})
);
}

export function getOpenChainFromPath(pathname: string, navTree: DocNavigationItem[]) {
let chain: DocNavigationItem[] = [];

let found = false;
const walk = (leaf: DocNavigationItem, parents: DocNavigationItem[] = []) => {
if (found) {
return;
}

parents = parents.concat(leaf);

if (isDocsNavigationBlock(leaf)) {
leaf.children.forEach(child => {
if (typeof child === 'string' && testPathAgainstUrl(child, pathname)) {
chain = parents.concat(child);
found = true;
} else {
walk(child, parents);
}
});
} else if (typeof leaf === 'string' && testPathAgainstUrl(leaf, pathname)) {
chain = parents;
found = true;
}
};

navTree.forEach(leaf => walk(leaf));

return chain;
}

export function isDocsNavigationBlock(leaf?: DocNavigationItem): leaf is DocsNavigationBlock {
return typeof leaf === 'object' && (leaf as DocsNavigationBlock).children !== undefined;
}

export function isDocsNavigationExternalLink(
leaf?: DocNavigationItem
): leaf is DocsNavigationExternalLink {
return typeof leaf === 'object' && (leaf as DocsNavigationExternalLink).url !== undefined;
}

export function testPathAgainstUrl(path: string, url: string) {
return trimSlashes(url).replace(/^(latest|\d+\.\d+)\//, '') === trimSlashes(path);
}

function trimSlashes(string: string) {
const leadingRegEx = /^\//;
const trailingRegEx = /\/$/;
return string.replace(leadingRegEx, '').replace(trailingRegEx, '');
}

+ 0
- 48
server/sonar-docs/src/components/update-center-metadata.ts View File

@@ -1,48 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2022 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.
*/
export interface MetaDataInformation {
category?: string;
isSonarSourceCommercial?: boolean;
issueTrackerURL?: string;
key?: string;
license?: string;
name: string;
organization?: {
name: string;
url?: string;
};
sourcesURL?: string;
versions?: MetaDataVersionInformation[];
}

export interface MetaDataVersionInformation {
archived?: boolean;
changeLogUrl?: string;
compatibility?: string;
date?: string;
description?: string;
downloadURL?: string | AdvancedDownloadUrl[];
version: string;
}

export interface AdvancedDownloadUrl {
label?: string;
url: string;
}

+ 0
- 128
server/sonar-docs/src/components/utils.tsx View File

@@ -1,128 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2022 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 } from 'lodash';
import { MarkdownRemark } from '../@types/graphql-types';

const WORDS = 6;

function cutLeadingWords(str: string) {
let words = 0;
for (let i = str.length - 1; i >= 0; i--) {
if (/\s/.test(str[i])) {
words++;
}
if (words === WORDS) {
return i > 0 ? `...${str.substring(i + 1)}` : str;
}
}
return str;
}

function cutTrailingWords(str: string) {
let words = 0;
for (let i = 0; i < str.length; i++) {
if (/\s/.test(str[i])) {
words++;
}
if (words === WORDS) {
return i < str.length - 1 ? `${str.substring(0, i)}...` : str;
}
}
return str;
}

export function cutWords(tokens: Array<{ text: string; marked: boolean }>) {
const result = [];
let length = 0;

const highlightPos = tokens.findIndex(token => token.marked);
if (highlightPos > 0) {
const text = cutLeadingWords(tokens[highlightPos - 1].text);
result.push({ text, marked: false });
length += text.length;
}

result.push(tokens[highlightPos]);
length += tokens[highlightPos].text.length;

for (let i = highlightPos + 1; i < tokens.length; i++) {
if (length + tokens[i].text.length > 100) {
const text = cutTrailingWords(tokens[i].text);
result.push({ text, marked: false });
return result;
} else {
result.push(tokens[i]);
length += tokens[i].text.length;
}
}

return result;
}

export function getMarkdownRemarkTitle(node?: MarkdownRemark) {
return node && node.frontmatter && (node.frontmatter.nav || node.frontmatter.title);
}

export function getMarkdownRemarkUrl(node?: MarkdownRemark) {
return (
(node && node.frontmatter && node.frontmatter.url) || (node && node.fields && node.fields.slug)
);
}

export function highlightMarks(str: string, marks: Array<{ from: number; to: number }>) {
const sortedMarks = sortBy(
[
...marks.map(mark => ({ pos: mark.from, start: true })),
...marks.map(mark => ({ pos: mark.to, start: false }))
],
mark => mark.pos,
mark => Number(!mark.start)
);

const cuts = [];
let start = 0;
let balance = 0;

for (const mark of sortedMarks) {
if (mark.start) {
if (balance === 0 && start !== mark.pos) {
cuts.push({ text: str.substring(start, mark.pos), marked: false });
start = mark.pos;
}
balance++;
} else {
balance--;
if (balance === 0 && start !== mark.pos) {
cuts.push({ text: str.substring(start, mark.pos), marked: true });
start = mark.pos;
}
}
}

if (start < str.length - 1) {
cuts.push({ text: str.substr(start), marked: false });
}

return cuts;
}

export function isDefined<T>(x: T | undefined | null): x is T {
return x !== undefined && x !== null;
}

BIN
server/sonar-docs/src/images/AzurePipelinesAnalysis.png View File


BIN
server/sonar-docs/src/images/SQ-instance-components.png View File


+ 0
- 1
server/sonar-docs/src/images/SonarQubeIcon.svg View File

@@ -1 +0,0 @@
<svg id="Calque_1" data-name="Calque 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 540.33 156.33"><defs><style>.cls-1{fill:#1b171b}.cls-3{fill:#4e9bcd}</style></defs><path class="cls-1" d="M11.89 101.92a29.92 29.92 0 0 0 13.23 3.74c4.65 0 6.57-1.62 6.57-4.14s-1.51-3.74-7.27-5.66c-10.21-3.44-14.15-9-14-14.85 0-9.2 7.89-16.17 20.11-16.17a33.07 33.07 0 0 1 13.95 2.83l-2.78 10.6A24.24 24.24 0 0 0 31 75.44c-3.74 0-5.87 1.51-5.87 4 0 2.33 1.93 3.54 8 5.66 9.4 3.23 13.34 8 13.44 15.26 0 9.19-7.27 16-21.42 16-6.47 0-12.22-1.42-16-3.44zM100.63 90.09c0 18.09-12.83 26.38-26.08 26.38C60.11 116.48 49 107 49 91s10.5-26.17 26.37-26.17c15.16 0 25.26 10.41 25.26 25.26zm-35.78.51c0 8.49 3.54 14.85 10.11 14.85 6 0 9.8-6 9.8-14.85 0-7.38-2.83-14.87-9.8-14.87-7.37.01-10.11 7.59-10.11 14.87zM106.11 81.71c0-6.16-.2-11.42-.41-15.76H119l.7 6.76h.31a18.08 18.08 0 0 1 15.25-7.88c10.11 0 17.69 6.66 17.69 21.22v29.31h-15.31V88c0-6.37-2.22-10.71-7.78-10.71a8.18 8.18 0 0 0-7.78 5.71 10.41 10.41 0 0 0-.61 3.84v28.51h-15.36zM189.39 115.36l-.91-5h-.3c-3.23 3.95-8.3 6.07-14.15 6.07-10 0-16-7.29-16-15.16 0-12.83 11.52-19 29-18.91v-.7c0-2.63-1.42-6.37-9-6.37a27.8 27.8 0 0 0-13.64 3.73l-2.84-9.9c3.44-1.93 10.21-4.35 19.2-4.35 16.48 0 21.73 9.7 21.73 21.32v17.18a75.92 75.92 0 0 0 .71 12zM187.58 92c-8.08-.1-14.35 1.83-14.35 7.78 0 3.95 2.63 5.87 6.07 5.87a8.39 8.39 0 0 0 8-5.66 10.87 10.87 0 0 0 .31-2.63zM210.63 82.21c0-7.27-.2-12-.41-16.26h13.24L224 75h.4c2.53-7.17 8.59-10.2 13.34-10.2a16.56 16.56 0 0 1 3.26.2v14.48a21.82 21.82 0 0 0-4.14-.41c-5.66 0-9.5 3-10.52 7.78a18.94 18.94 0 0 0-.3 3.44v25.07h-15.41zM342.35 102c0 5 .1 9.5.41 13.34h-7.89l-.51-8h-.19a18.43 18.43 0 0 1-16.17 9.1c-7.68 0-16.89-4.24-16.89-21.42V66.44H310v27.09c0 9.29 2.83 15.57 10.92 15.57a12.88 12.88 0 0 0 11.72-8.1 13.15 13.15 0 0 0 .81-4.55v-30h8.9zM352.67 115.36c.2-3.34.4-8.3.4-12.64V43.6h8.79v30.73h.2c3.13-5.46 8.79-9 16.68-9 12.12 0 20.71 10.11 20.61 25 0 17.49-11 26.18-21.92 26.18-7.08 0-12.73-2.73-16.37-9.2h-.31l-.4 8.09zm9.19-19.61a16.48 16.48 0 0 0 .41 3.23 13.71 13.71 0 0 0 13.33 10.41c9.31 0 14.85-7.58 14.85-18.79 0-9.8-5-18.19-14.55-18.19a14.17 14.17 0 0 0-13.54 10.91 17.47 17.47 0 0 0-.51 3.64zM411.5 92.52c.19 12 7.88 17 16.77 17a32.24 32.24 0 0 0 13.54-2.52l1.52 6.37c-3.13 1.41-8.49 3-16.27 3-15.06 0-24.06-9.9-24.06-24.65s8.69-26.38 22.94-26.38c16 0 20.21 14 20.21 23a33.67 33.67 0 0 1-.3 4.14zm26.07-6.37c.1-5.66-2.31-14.46-12.32-14.46-9 0-12.94 8.3-13.65 14.46z"/><path d="M290.55 75.25a26.41 26.41 0 1 0-11.31 39.07l10.22 16.6 8.11-5.51-10.22-16.6a26.42 26.42 0 0 0 3.2-33.56M279.1 105.4a18.5 18.5 0 1 1 4.9-25.7 18.52 18.52 0 0 1-4.9 25.7" fill-rule="evenodd" fill="#1b171b"/><path class="cls-3" d="M506.94 115.57h-6.27c0-50.44-41.62-91.48-92.78-91.48v-6.26c54.62 0 99.05 43.84 99.05 97.74z"/><path class="cls-3" d="M511.27 81.93c-7.52-31.65-33.16-58.06-65.27-67.29l1.44-5c33.93 9.74 61 37.65 68.95 71.1zM516.09 52.23a96 96 0 0 0-37.17-41.49l2.17-3.57a100.24 100.24 0 0 1 38.8 43.31z"/></svg>

BIN
server/sonar-docs/src/images/activate_rule_compare1.png View File


BIN
server/sonar-docs/src/images/add-ADO-project.png View File


BIN
server/sonar-docs/src/images/add-bitbucket-project.png View File


BIN
server/sonar-docs/src/images/add-github-project.png View File


BIN
server/sonar-docs/src/images/add-gitlab-project.png View File


+ 0
- 1
server/sonar-docs/src/images/alerts/danger.svg View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 23.36 20.088"><defs><style>.cls-1{fill:#cda251}</style></defs><g id="danger" transform="translate(0 -35.857)"><g id="Groupe_5722" data-name="Groupe 5722" transform="translate(0 35.857)"><g id="Groupe_5721" data-name="Groupe 5721"><path id="Tracé_9564" data-name="Tracé 9564" class="cls-1" d="M23.059 52.763l-9.582-15.891a2.1 2.1 0 0 0-3.594 0L.3 52.763a2.1 2.1 0 0 0 1.8 3.182h19.162a2.1 2.1 0 0 0 1.8-3.182zm-1.294 1.368a.579.579 0 0 1-.5.294H2.1a.578.578 0 0 1-.495-.876l9.582-15.891a.578.578 0 0 1 .989 0l9.582 15.891a.578.578 0 0 1 .007.582z" transform="translate(0 -35.857)"/></g></g><g id="Groupe_5724" data-name="Groupe 5724" transform="translate(10.657 42.114)"><g id="Groupe_5723" data-name="Groupe 5723"><path id="Tracé_9565" data-name="Tracé 9565" class="cls-1" d="M234.608 173.005c-.579 0-1.03.31-1.03.861 0 1.679.2 4.092.2 5.771 0 .437.381.621.833.621.339 0 .818-.183.818-.621 0-1.679.2-4.092.2-5.771a.907.907 0 0 0-1.021-.861z" transform="translate(-233.578 -173.005)"/></g></g><g id="Groupe_5726" data-name="Groupe 5726" transform="translate(10.614 50.341)"><g id="Groupe_5725" data-name="Groupe 5725"><path id="Tracé_9566" data-name="Tracé 9566" class="cls-1" d="M233.738 353.306a1.087 1.087 0 1 0 0 2.173 1.087 1.087 0 0 0 0-2.173z" transform="translate(-232.651 -353.306)"/></g></g></g></svg>

+ 0
- 1
server/sonar-docs/src/images/alerts/info.svg View File

@@ -1 +0,0 @@
<svg data-name="Calque 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48"><title>info</title><path d="M24 12.2a2.37 2.37 0 1 0 2.37 2.37A2.37 2.37 0 0 0 24 12.2zM24 20.49a1.78 1.78 0 0 0-1.78 1.78v10.66a1.78 1.78 0 1 0 3.55 0V22.27A1.78 1.78 0 0 0 24 20.49z"/><path d="M24 3.91a19.55 19.55 0 1 0 19.58 19.54A19.57 19.57 0 0 0 24 3.91zm0 35.54a16 16 0 1 1 16-16 16 16 0 0 1-16 16z"/></svg>

+ 0
- 1
server/sonar-docs/src/images/alerts/wrong.svg View File

@@ -1 +0,0 @@
<svg data-name="Calque 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48"><title>wrong</title><path d="M14.39 33a1.77 1.77 0 0 0 2.51 0l7-7 7 7a1.78 1.78 0 0 0 1.25.52 1.8 1.8 0 0 0 1.3-.52 1.78 1.78 0 0 0 0-2.51l-7-7 7-7A1.77 1.77 0 0 0 30.94 14l-7 7-7-7a1.77 1.77 0 0 0-2.51 2.51l7 7-7 7a1.78 1.78 0 0 0-.04 2.49z"/><path d="M24 3.91a19.55 19.55 0 1 0 19.58 19.54A19.57 19.57 0 0 0 24 3.91zm0 35.54a16 16 0 1 1 16-16 16 16 0 0 1-16 16z"/></svg>

+ 0
- 2
server/sonar-docs/src/images/alm/azure.svg View File

@@ -1,2 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22.009 22"><path fill="#0078d7" d="M2.916 15.015v-7.25l19.093-3.758v13.446L16.62 21.92l-8.226-2.757V22l-5.478-6.985 13.216 1.728V5.052L9.812 0l.044 2.3-7.516 3L0 8.1v6.3z"/></svg>


+ 0
- 2
server/sonar-docs/src/images/alm/bitbucket.svg View File

@@ -1,2 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-361.924 -3545.014 58.441 52.551"><defs><linearGradient id="a" x1="1.086" x2=".469" y1=".138" y2=".788" gradientUnits="objectBoundingBox"><stop offset=".18" stop-color="#0052cc"/><stop offset="1" stop-color="#2684ff"/></linearGradient></defs><path fill="#2684ff" d="M-360.027-3545.013a1.872 1.872 0 0 0-1.871 2.172l7.947 48.253a2.547 2.547 0 0 0 2.49 2.125h38.133a1.872 1.872 0 0 0 1.872-1.573l7.949-48.8a1.872 1.872 0 0 0-1.872-2.172zm33.47 34.875h-12.171l-3.3-17.217h18.42z"/><path fill="url(#a)" d="M56.464 25.12H38.891l-2.949 17.217H23.771L9.4 59.4a2.537 2.537 0 0 0 1.638.618H49.18a1.872 1.872 0 0 0 1.872-1.573z" transform="translate(-362.499 -3552.476)"/></svg>


+ 0
- 2
server/sonar-docs/src/images/alm/github.svg View File

@@ -1,2 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-738.601 -3545.014 54.017 52.551"><path fill="#191717" fill-rule="evenodd" d="M-711.675-3545.014a26.975 26.975 0 0 0-8.59 52.53c1.322.165 1.817-.661 1.817-1.322v-4.625c-7.433 1.652-9.085-3.634-9.085-3.634-1.156-3.139-2.973-3.965-2.973-3.965-2.478-1.652.165-1.652.165-1.652 2.643.165 4.13 2.808 4.13 2.808 2.478 4.13 6.277 2.973 7.764 2.313a5.752 5.752 0 0 1 1.646-3.634c-5.947-.661-12.224-2.973-12.224-13.38a10.24 10.24 0 0 1 2.808-7.268 9.781 9.781 0 0 1 .33-6.938s2.313-.661 7.433 2.808a23.083 23.083 0 0 1 6.773-.826 30.4 30.4 0 0 1 6.773.826c5.121-3.469 7.433-2.808 7.433-2.808a10.343 10.343 0 0 1 .33 7.1 10.684 10.684 0 0 1 2.815 7.267c0 10.407-6.277 12.554-12.224 13.215.991.826 1.817 2.478 1.817 4.956v7.433c0 .661.5 1.487 1.817 1.322a26.976 26.976 0 0 0-8.755-52.526z"/></svg>


+ 0
- 1
server/sonar-docs/src/images/alm/gitlab.svg View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="90 95 200 190"><defs><style>.cls-1{fill:#e24329;}.cls-2{fill:#fc6d26;}.cls-3{fill:#fca326;}</style></defs><g id="LOGO"><path class="cls-1" d="M282.83,170.73l-.27-.69-26.14-68.22a6.81,6.81,0,0,0-2.69-3.24,7,7,0,0,0-8,.43,7,7,0,0,0-2.32,3.52l-17.65,54H154.29l-17.65-54A6.86,6.86,0,0,0,134.32,99a7,7,0,0,0-8-.43,6.87,6.87,0,0,0-2.69,3.24L97.44,170l-.26.69a48.54,48.54,0,0,0,16.1,56.1l.09.07.24.17,39.82,29.82,19.7,14.91,12,9.06a8.07,8.07,0,0,0,9.76,0l12-9.06,19.7-14.91,40.06-30,.1-.08A48.56,48.56,0,0,0,282.83,170.73Z"/><path class="cls-2" d="M282.83,170.73l-.27-.69a88.3,88.3,0,0,0-35.15,15.8L190,229.25c19.55,14.79,36.57,27.64,36.57,27.64l40.06-30,.1-.08A48.56,48.56,0,0,0,282.83,170.73Z"/><path class="cls-3" d="M153.43,256.89l19.7,14.91,12,9.06a8.07,8.07,0,0,0,9.76,0l12-9.06,19.7-14.91S209.55,244,190,229.25C170.45,244,153.43,256.89,153.43,256.89Z"/><path class="cls-2" d="M132.58,185.84A88.19,88.19,0,0,0,97.44,170l-.26.69a48.54,48.54,0,0,0,16.1,56.1l.09.07.24.17,39.82,29.82s17-12.85,36.57-27.64Z"/></g></svg>

BIN
server/sonar-docs/src/images/architecture-integrate.png View File


BIN
server/sonar-docs/src/images/architecture-scanning.png View File


BIN
server/sonar-docs/src/images/astSample.png View File


BIN
server/sonar-docs/src/images/azure/saml-azure-attributes.jpg View File


BIN
server/sonar-docs/src/images/azure/saml-azure-basic-saml.jpg View File


BIN
server/sonar-docs/src/images/azure/saml-azure-certificate.jpg View File


BIN
server/sonar-docs/src/images/azure/saml-azure-create-application.jpg View File


BIN
server/sonar-docs/src/images/azure/saml-azure-encryption.jpg View File


BIN
server/sonar-docs/src/images/azure/saml-azure-group-claim.jpg View File


BIN
server/sonar-docs/src/images/azure/saml-azure-links.jpg View File


BIN
server/sonar-docs/src/images/azure/saml-azure-mapping.jpg View File


+ 0
- 0
server/sonar-docs/src/images/azure/saml-azure-new.jpg View File


Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save