<iframe src="http://www.sonarsource.com" />
```
+
+#### Dynamic Plugin Version Info
+
+_Note: at this time, this is only supported for the static documentation, and will be stripped from the embedded documentation._
+
+You can dynamically include a plugin version block to any page, using the following special tag:
+
+```html
+<!-- update_center:PLUGIN_KEY -->
+```
+
+For example, for Sonar Java, use:
+
+```html
+<!-- update_center:java -->
+```
+
+You can include multiple boxes per page, if needed.
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+export type Dict<T> = { [key: string]: T };
+
export interface DocVersion {
current: boolean;
value: string;
url: string;
}
+export type PluginMetaDataInfo = {
+ category?: string;
+ isSonarSourceCommercial: boolean;
+ issueTrackerURL?: string;
+ key?: string;
+ license?: string;
+ name: string;
+ organization?: {
+ name: string;
+ url?: string;
+ };
+ sourcesURL?: string;
+ versions?: PluginVersionInfo[];
+};
+
+export type PluginVersionInfo = {
+ archived?: boolean;
+ changeLogUrl?: string;
+ compatibility?: string;
+ date?: string;
+ description?: string;
+ downloadURL?: string;
+ version: string;
+};
+
export interface SearchResult {
exactMatch?: boolean;
highlights: { [field: string]: [number, number][] };
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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 { createPortal } from 'react-dom';
+import PluginVersionMetaData from './PluginVersionMetaData';
+import { getPluginMetaData } from './utils';
+import { Dict, PluginMetaDataInfo } from '../@types/types';
+
+interface Props {
+ location: Pick<Location, 'pathname'>;
+}
+
+interface State {
+ data: Dict<PluginMetaDataInfo>;
+ wrappers: Dict<HTMLDivElement>;
+}
+
+export default class PluginMetaData extends React.Component<Props, State> {
+ state: State = {
+ data: {},
+ wrappers: {}
+ };
+
+ componentDidMount() {
+ this.searchForCommentNodes();
+ }
+
+ componentDidUpdate({ location }: Props) {
+ if (location.pathname !== this.props.location.pathname) {
+ this.clearWrapperNodes();
+ this.searchForCommentNodes();
+ }
+ }
+
+ clearWrapperNodes = () => {
+ const { wrappers } = this.state;
+
+ Object.keys(wrappers).forEach(key => {
+ const node = wrappers[key];
+ const { parentNode } = node;
+ if (parentNode) {
+ parentNode.removeChild(node);
+ }
+
+ delete wrappers[key];
+ });
+
+ this.setState({ data: {}, wrappers: {} });
+ };
+
+ fetchAndRender = () => {
+ const { wrappers } = this.state;
+
+ Object.keys(wrappers).forEach(key => {
+ getPluginMetaData(key).then(
+ (payload: PluginMetaDataInfo) => {
+ this.setState(({ data }) => ({ data: { ...data, [key]: payload } }));
+ },
+ () => {}
+ );
+ });
+ };
+
+ searchForCommentNodes = () => {
+ const pageContainer = document.querySelector('.page-container');
+
+ if (pageContainer) {
+ const iterator = document.createNodeIterator(pageContainer, NodeFilter.SHOW_COMMENT, {
+ acceptNode: (_: Node) => NodeFilter.FILTER_ACCEPT
+ });
+
+ let node;
+ const wrappers: Dict<HTMLDivElement> = {};
+ while ((node = iterator.nextNode())) {
+ if (node.nodeValue && /update_center\s*:/.test(node.nodeValue)) {
+ let [, key] = node.nodeValue.split(':');
+ key = key.trim();
+
+ const wrapper = document.createElement('div');
+ wrapper.className = 'plugin-meta-data-wrapper';
+ wrappers[key] = wrapper;
+
+ node.parentNode!.insertBefore(wrapper, node);
+ }
+ }
+ this.setState({ wrappers }, this.fetchAndRender);
+ }
+ };
+
+ renderMetaData = ({
+ isSonarSourceCommercial,
+ issueTrackerURL,
+ license,
+ organization,
+ versions
+ }: PluginMetaDataInfo) => {
+ 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="plugin-meta-data">
+ <div className="plugin-meta-data-header">
+ {vendor && <span className="plugin-meta-data-vendor">By {vendor}</span>}
+ {license && <span className="plugin-meta-data-license">{license}</span>}
+ {issueTrackerURL && (
+ <span className="plugin-meta-data-issue-tracker">
+ <a href={issueTrackerURL} rel="noopener noreferrer" target="_blank">
+ Issue Tracker
+ </a>
+ </span>
+ )}
+ {isSonarSourceCommercial && (
+ <span className="plugin-meta-data-supported">Supported by SonarSource</span>
+ )}
+ </div>
+ {versions && versions.length > 0 && <PluginVersionMetaData versions={versions} />}
+ </div>
+ );
+ };
+
+ render() {
+ const { data, wrappers } = this.state;
+ const keys = Object.keys(data);
+
+ if (keys.length === 0) {
+ return null;
+ }
+
+ return keys.map(key => {
+ if (wrappers[key] !== undefined && data[key] !== undefined) {
+ return createPortal(this.renderMetaData(data[key]), wrappers[key]);
+ } else {
+ return null;
+ }
+ });
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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 classNames from 'classnames';
+import { PluginVersionInfo } from '../@types/types';
+
+interface Props {
+ versions: PluginVersionInfo[];
+}
+
+interface State {
+ collapsed: boolean;
+}
+
+export default class PluginVersionMetaData extends React.Component<Props, State> {
+ state: State = {
+ collapsed: true
+ };
+
+ handleClick = (event: React.SyntheticEvent<HTMLButtonElement>) => {
+ event.preventDefault();
+ event.currentTarget.blur();
+ this.setState(({ collapsed }) => ({ collapsed: !collapsed }));
+ };
+
+ renderVersion = ({
+ archived,
+ changeLogUrl,
+ compatibility,
+ date,
+ description,
+ downloadURL,
+ version
+ }: PluginVersionInfo) => {
+ return (
+ <div
+ className={classNames('plugin-meta-data-version', {
+ 'plugin-meta-data-version-archived': archived
+ })}
+ key={version}>
+ <div className="plugin-meta-data-version-version">{version}</div>
+
+ <div className="plugin-meta-data-version-release-info">
+ {date && <time className="plugin-meta-data-version-release-date">{date}</time>}
+
+ {compatibility && (
+ <span className="plugin-meta-data-version-compatibility">{compatibility}</span>
+ )}
+ </div>
+
+ {description && (
+ <div className="plugin-meta-data-version-release-description">{description}</div>
+ )}
+
+ {(downloadURL || changeLogUrl) && (
+ <div className="plugin-meta-data-version-release-links">
+ {downloadURL && (
+ <span className="plugin-meta-data-version-download">
+ <a href={downloadURL} rel="noopener noreferrer" target="_blank">
+ Download
+ </a>
+ </span>
+ )}
+
+ {changeLogUrl && (
+ <span className="plugin-meta-data-version-release-notes">
+ <a href={changeLogUrl} rel="noopener noreferrer" target="_blank">
+ Release notes
+ </a>
+ </span>
+ )}
+ </div>
+ )}
+ </div>
+ );
+ };
+
+ 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="plugin-meta-data-versions">
+ {archivedVersions.length > 0 && (
+ <button
+ className="plugin-meta-data-versions-show-more"
+ onClick={this.handleClick}
+ type="button">
+ {collapsed ? 'Show more versions' : 'Show fewer version'}
+ </button>
+ )}
+
+ {currentVersions.map(version => this.renderVersion(version))}
+
+ {!collapsed && archivedVersions.map(version => this.renderVersion(version))}
+ </div>
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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 { mount } from 'enzyme';
+import PluginMetaData from '../PluginMetaData';
+import { getPluginMetaData } from '../utils';
+
+jest.mock('../utils', () => ({
+ getPluginMetaData: jest.fn().mockResolvedValue({
+ 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: [
+ {
+ version: '4.2',
+ compatibilityRange: { minimum: '6.0', maximum: '6.6' },
+ archived: false,
+ downloadURL: 'https://example.com/sonar-java-plugin-5.13.0.18197.jar'
+ },
+ {
+ version: '3.2',
+ date: '2015-04-30',
+ compatibilityRange: { maximum: '6.0' },
+ archived: true,
+ changeLogUrl: 'https://example.com/sonar-java-plugin/release',
+ downloadURL: 'https://example.com/sonar-java-plugin-3.2.jar'
+ }
+ ]
+ })
+}));
+
+beforeAll(() => {
+ (global as any).document.body.innerHTML = `
+<div class="page-container">
+ <p>Lorem ipsum</p>
+ <!-- update_center:java -->
+ <p>Dolor sit amet</p>
+ <!-- update_center : python -->
+ <p>Foo Bar</p>
+ <!--update_center : abap-->
+</div>
+`;
+});
+
+it('should render correctly', async () => {
+ const wrapper = shallowRender();
+ await new Promise(setImmediate);
+ expect(wrapper).toMatchSnapshot();
+ expect(getPluginMetaData).toBeCalledWith('java');
+ expect(getPluginMetaData).toBeCalledWith('python');
+ expect(getPluginMetaData).toBeCalledWith('abap');
+});
+
+function shallowRender(props: Partial<PluginMetaData['props']> = {}) {
+ return mount(<PluginMetaData location={{ pathname: 'foo' }} {...props} />);
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import PluginVersionMetaData from '../PluginVersionMetaData';
+
+it('should render correctly', () => {
+ const wrapper = shallowRender();
+ expect(wrapper).toMatchSnapshot();
+});
+
+it('should correctly show all versions', () => {
+ const wrapper = shallowRender();
+ expect(wrapper.find('.plugin-meta-data-version').length).toBe(2);
+ wrapper.instance().setState({ collapsed: false });
+ expect(wrapper.find('.plugin-meta-data-version').length).toBe(5);
+});
+
+function shallowRender(props: Partial<PluginVersionMetaData['props']> = {}) {
+ return shallow(
+ <PluginVersionMetaData
+ versions={[
+ {
+ 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'
+ },
+ {
+ version: '4.2',
+ archived: false,
+ downloadURL: 'https://example.com/sonar-java-plugin-5.13.0.18197.jar'
+ },
+ {
+ version: '3.2',
+ date: '2015-04-30',
+ compatibility: '6.0 to 7.1',
+ archived: true,
+ changeLogUrl: 'https://example.com/sonar-java-plugin/release',
+ downloadURL: 'https://example.com/sonar-java-plugin-3.2.jar'
+ },
+ {
+ version: '3.1',
+ description: 'Lorem ipsum dolor sit amet',
+ archived: true,
+ changeLogUrl: 'https://example.com/sonar-java-plugin/release',
+ downloadURL: 'https://example.com/sonar-java-plugin-3.1.jar'
+ },
+ {
+ version: '2.1',
+ archived: true,
+ downloadURL: 'https://example.com/sonar-java-plugin-2.1.jar'
+ }
+ ]}
+ {...props}
+ />
+ );
+}
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<PluginMetaData
+ location={
+ Object {
+ "pathname": "foo",
+ }
+ }
+/>
+`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<div
+ className="plugin-meta-data-versions"
+>
+ <button
+ className="plugin-meta-data-versions-show-more"
+ onClick={[Function]}
+ type="button"
+ >
+ Show more versions
+ </button>
+ <div
+ className="plugin-meta-data-version"
+ key="5.13"
+ >
+ <div
+ className="plugin-meta-data-version-version"
+ >
+ 5.13
+ </div>
+ <div
+ className="plugin-meta-data-version-release-info"
+ >
+ <time
+ className="plugin-meta-data-version-release-date"
+ >
+ 2019-05-31
+ </time>
+ <span
+ className="plugin-meta-data-version-compatibility"
+ >
+ 6.7
+ </span>
+ </div>
+ <div
+ className="plugin-meta-data-version-release-links"
+ >
+ <span
+ className="plugin-meta-data-version-download"
+ >
+ <a
+ href="https://example.com/sonar-java-plugin-5.13.0.18197.jar"
+ rel="noopener noreferrer"
+ target="_blank"
+ >
+ Download
+ </a>
+ </span>
+ <span
+ className="plugin-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>
+ <div
+ className="plugin-meta-data-version"
+ key="4.2"
+ >
+ <div
+ className="plugin-meta-data-version-version"
+ >
+ 4.2
+ </div>
+ <div
+ className="plugin-meta-data-version-release-info"
+ />
+ <div
+ className="plugin-meta-data-version-release-links"
+ >
+ <span
+ className="plugin-meta-data-version-download"
+ >
+ <a
+ href="https://example.com/sonar-java-plugin-5.13.0.18197.jar"
+ rel="noopener noreferrer"
+ target="_blank"
+ >
+ Download
+ </a>
+ </span>
+ </div>
+ </div>
+</div>
+`;
*/
import { sortBy } from 'lodash';
import { MarkdownRemark } from '../@types/graphql-types';
+import { PluginMetaDataInfo } from '../@types/types';
const WORDS = 6;
export function isDefined<T>(x: T | undefined | null): x is T {
return x !== undefined && x !== null;
}
+
+export function getPluginMetaData(key: string): Promise<PluginMetaDataInfo> {
+ return (
+ window
+ .fetch(`https://update.sonarsource.org/${key}.json`)
+ .then((response: Response) => {
+ if (response.status >= 200 && response.status < 300) {
+ return response.json();
+ }
+ return Promise.reject(response);
+ })
+ /* eslint-disable no-console */
+ .catch(console.error)
+ );
+}
import Footer from '../components/Footer';
import HeaderListProvider from '../components/HeaderListProvider';
import HeadingsLink from '../components/HeadingsLink';
+import PluginMetaData from '../components/PluginMetaData';
import Sidebar from '../components/Sidebar';
import { MarkdownRemarkConnection, MarkdownRemark } from '../@types/graphql-types';
import './layout.css';
<div className="markdown-container">{children}</div>
</div>
<Footer />
+ <PluginMetaData location={location} />
</div>
</div>
)}
margin-bottom: 0;
top: 0 !important;
}
+
+.plugin-meta-data {
+ margin: 16px 0;
+ padding: 16px 16px 8px 16px;
+ background: #f9f9fb;
+ border: 1px solid #e6e6e6;
+ border-radius: 3px;
+}
+
+.plugin-meta-data a svg {
+ margin-right: 8px;
+}
+
+.plugin-meta-data-header {
+ border-bottom: 1px solid #cfd3d7;
+ padding-bottom: 16px;
+}
+
+.plugin-meta-data-header,
+.plugin-meta-data-version-release-info,
+.plugin-meta-data-version-links {
+ display: flex;
+}
+
+.plugin-meta-data-header > * + *,
+.plugin-meta-data-version-release-info > * + *,
+.plugin-meta-data-version-release-links > * + * {
+ margin-left: 16px;
+}
+
+.plugin-meta-data-header > * + * {
+ padding-left: 16px;
+ border-left: 1px solid #cfd3d7;
+}
+
+.plugin-meta-data-versions {
+ margin-top: 16px;
+}
+
+.plugin-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;
+}
+
+.plugin-meta-data-versions-show-more:hover {
+ color: #2d3032;
+ border-color: #2d3032;
+}
+
+.plugin-meta-data-version {
+ margin-bottom: 16px;
+}
+
+.plugin-meta-data-version + .plugin-meta-data-version {
+ padding-top: 8px;
+ padding-top: 8px;
+ border-top: 1px dashed #cfd3d7;
+}
+
+.plugin-meta-data-version-version {
+ font-weight: bold;
+ font-size: 18px;
+}
+
+.plugin-meta-data-version-release-info {
+ margin-top: 8px;
+ font-style: italic;
+}
+
+.plugin-meta-data-version-release-description {
+ margin-top: 8px;
+}
+
+.plugin-meta-data-version-release-links {
+ margin-top: 8px;
+}