aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/src/main
diff options
context:
space:
mode:
authorGrégoire Aubert <gregoire.aubert@sonarsource.com>2018-03-06 17:41:23 +0100
committerSonarTech <sonartech@sonarsource.com>2018-03-26 20:20:57 +0200
commitcb0a23c978efcc296cf29837c9e6e1a657403404 (patch)
tree7e408a33f49254e99e47753c9d7b272c269c53c6 /server/sonar-web/src/main
parentb4125add7a55db6d2dc71a1bd0b2cadbe5ff7887 (diff)
downloadsonarqube-cb0a23c978efcc296cf29837c9e6e1a657403404.tar.gz
sonarqube-cb0a23c978efcc296cf29837c9e6e1a657403404.zip
VSTS-141 Add VSTS Quality widget
Diffstat (limited to 'server/sonar-web/src/main')
-rw-r--r--server/sonar-web/src/main/js/api/measures.ts2
-rw-r--r--server/sonar-web/src/main/js/app/integration/vsts/components/Configuration.tsx147
-rw-r--r--server/sonar-web/src/main/js/app/integration/vsts/components/QGWidget.tsx57
-rw-r--r--server/sonar-web/src/main/js/app/integration/vsts/components/Widget.tsx106
-rw-r--r--server/sonar-web/src/main/js/app/integration/vsts/index.js42
-rw-r--r--server/sonar-web/src/main/js/app/integration/vsts/vsts.css89
-rw-r--r--server/sonar-web/src/main/js/app/styles/components/spinner.css82
-rw-r--r--server/sonar-web/src/main/js/app/styles/init/icons.css67
-rw-r--r--server/sonar-web/src/main/js/app/styles/sonar.css1
-rw-r--r--server/sonar-web/src/main/js/libs/third-party/VSS.SDK.min.js957
10 files changed, 1482 insertions, 68 deletions
diff --git a/server/sonar-web/src/main/js/api/measures.ts b/server/sonar-web/src/main/js/api/measures.ts
index 40d219fcfa3..b5d1e649525 100644
--- a/server/sonar-web/src/main/js/api/measures.ts
+++ b/server/sonar-web/src/main/js/api/measures.ts
@@ -29,7 +29,7 @@ export function getMeasures(
return getJSON('/api/measures/component', data).then(r => r.component.measures, throwGlobalError);
}
-interface MeasureComponent {
+export interface MeasureComponent {
key: string;
description?: string;
measures: Measure[];
diff --git a/server/sonar-web/src/main/js/app/integration/vsts/components/Configuration.tsx b/server/sonar-web/src/main/js/app/integration/vsts/components/Configuration.tsx
new file mode 100644
index 00000000000..838114c4cbd
--- /dev/null
+++ b/server/sonar-web/src/main/js/app/integration/vsts/components/Configuration.tsx
@@ -0,0 +1,147 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { searchProjects } from '../../../../api/components';
+
+interface Settings {
+ project: string;
+}
+
+interface Props {
+ widgetHelpers: any;
+}
+
+interface State {
+ loading: boolean;
+ organizations?: Array<{ key: string; name: string }>;
+ projects?: Array<{ label: string; value: string }>;
+ settings: Settings;
+ widgetConfigurationContext?: any;
+}
+
+declare const VSS: any;
+
+export default class Configuration extends React.PureComponent<Props, State> {
+ mounted = false;
+ state: State = { loading: true, settings: { project: '' } };
+
+ componentDidMount() {
+ this.mounted = true;
+ this.props.widgetHelpers.IncludeWidgetConfigurationStyles();
+ VSS.register('e56c6ff0-c6f9-43d0-bdef-b3f1aa0dc6dd', () => {
+ return { load: this.load, onSave: this.onSave };
+ });
+ }
+
+ componentWillUnmount() {
+ this.mounted = false;
+ }
+
+ load = (widgetSettings: any, widgetConfigurationContext: any) => {
+ const settings: Settings = JSON.parse(widgetSettings.customSettings.data);
+ if (this.mounted) {
+ this.setState({ settings: settings || {}, widgetConfigurationContext });
+ this.fetchProjects();
+ }
+ return this.props.widgetHelpers.WidgetStatusHelper.Success();
+ };
+
+ onSave = () => {
+ if (!this.state.settings || !this.state.settings.project) {
+ return this.props.widgetHelpers.WidgetConfigurationSave.Invalid();
+ }
+ return this.props.widgetHelpers.WidgetConfigurationSave.Valid({
+ data: JSON.stringify(this.state.settings)
+ });
+ };
+
+ fetchProjects = (organization?: string) => {
+ this.setState({ loading: true });
+ searchProjects({ organization, ps: 100 }).then(
+ ({ components }) => {
+ if (this.mounted) {
+ this.setState({
+ projects: components.map(c => ({ label: c.name, value: c.key })),
+ loading: false
+ });
+ }
+ },
+ () => {
+ this.setState({
+ projects: [],
+ loading: false
+ });
+ }
+ );
+ };
+
+ handleProjectChange = (
+ event: React.ChangeEvent<HTMLSelectElement> | React.FocusEvent<HTMLSelectElement>
+ ) => {
+ const { value } = event.currentTarget;
+ this.setState(
+ ({ settings }) => ({ settings: { ...settings, project: value } }),
+ this.notifyChange
+ );
+ };
+
+ notifyChange = ({ settings, widgetConfigurationContext } = this.state) => {
+ const { widgetHelpers } = this.props;
+ if (widgetConfigurationContext && widgetConfigurationContext.notify) {
+ const eventName = widgetHelpers.WidgetEvent.ConfigurationChange;
+ const eventArgs = widgetHelpers.WidgetEvent.Args({ data: JSON.stringify(settings) });
+ widgetConfigurationContext.notify(eventName, eventArgs);
+ }
+ };
+
+ render() {
+ const { projects, loading, settings } = this.state;
+ if (loading) {
+ return (
+ <div className="vsts-loading">
+ <i className="spinner global-loading-spinner" />
+ </div>
+ );
+ }
+ return (
+ <div className="widget-configuration">
+ <div className="dropdown" id="project">
+ <label>SonarCloud project</label>
+ <div className="wrapper">
+ <select
+ onBlur={this.handleProjectChange}
+ onChange={this.handleProjectChange}
+ value={settings.project}>
+ <option disabled={true} hidden={true} value="">
+ Select a project...
+ </option>
+ {projects &&
+ projects.map(project => (
+ <option key={project.value} value={project.value}>
+ {project.label}
+ </option>
+ ))}
+ </select>
+ </div>
+ </div>
+ </div>
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/app/integration/vsts/components/QGWidget.tsx b/server/sonar-web/src/main/js/app/integration/vsts/components/QGWidget.tsx
new file mode 100644
index 00000000000..d7e3910f9e1
--- /dev/null
+++ b/server/sonar-web/src/main/js/app/integration/vsts/components/QGWidget.tsx
@@ -0,0 +1,57 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import * as classNames from 'classnames';
+import { MeasureComponent } from '../../../../api/measures';
+import { Metric } from '../../../types';
+import { getPathUrlAsString, getProjectUrl } from '../../../../helpers/urls';
+
+interface Props {
+ component: MeasureComponent;
+ metrics: Metric[];
+}
+
+const QG_LEVELS: { [level: string]: string } = {
+ ERROR: 'Failed',
+ WARN: 'Warning',
+ OK: 'Passed',
+ NONE: 'None'
+};
+
+export default function QGWidget({ component, metrics }: Props) {
+ const qgMetric = metrics && metrics.find(m => m.key === 'alert_status');
+ const qgMeasure = component && component.measures.find(m => m.metric === 'alert_status');
+
+ if (!qgMeasure || !qgMeasure.value) {
+ return <p>Project Quality Gate not computed.</p>;
+ }
+
+ return (
+ <div className={classNames('widget dark-widget clickable', 'level-' + qgMeasure.value)}>
+ <a href={getPathUrlAsString(getProjectUrl(component.key))} target="_blank">
+ <h2 className="title truncated-text-ellipsis">{component.name}</h2>
+ <div className="big-value truncated-text-ellipsis">{QG_LEVELS[qgMeasure.value]}</div>
+ <div className="footer truncated-text-ellipsis">
+ {qgMetric ? qgMetric.name : 'Quality Gate'}
+ </div>
+ </a>
+ </div>
+ );
+}
diff --git a/server/sonar-web/src/main/js/app/integration/vsts/components/Widget.tsx b/server/sonar-web/src/main/js/app/integration/vsts/components/Widget.tsx
new file mode 100644
index 00000000000..98ac6c85aa7
--- /dev/null
+++ b/server/sonar-web/src/main/js/app/integration/vsts/components/Widget.tsx
@@ -0,0 +1,106 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import QGWidget from './QGWidget';
+import { getMeasuresAndMeta, MeasureComponent } from '../../../../api/measures';
+import { Metric } from '../../../types';
+
+interface Props {
+ widgetHelpers: any;
+}
+
+interface State {
+ component?: MeasureComponent;
+ loading: boolean;
+ metrics?: Metric[];
+}
+
+declare const VSS: any;
+
+export default class Widget extends React.PureComponent<Props, State> {
+ mounted = false;
+ state: State = { loading: true };
+
+ componentDidMount() {
+ this.mounted = true;
+ this.props.widgetHelpers.IncludeWidgetStyles();
+ VSS.register('3c598f25-01c1-4c09-97c6-926476882688', () => {
+ return { load: this.load, reload: this.load };
+ });
+ }
+
+ componentWillUnmount() {
+ this.mounted = false;
+ }
+
+ load = (widgetSettings: any) => {
+ const settings = JSON.parse(widgetSettings.customSettings.data);
+ if (this.mounted) {
+ if (settings && settings.project) {
+ this.fetchProjectMeasures(settings.project);
+ } else {
+ this.setState({ loading: false });
+ }
+ }
+ return this.props.widgetHelpers.WidgetStatusHelper.Success();
+ };
+
+ fetchProjectMeasures = (project: string) => {
+ this.setState({ loading: true });
+ getMeasuresAndMeta(project, ['alert_status'], { additionalFields: 'metrics' }).then(
+ ({ component, metrics }) => {
+ if (this.mounted) {
+ this.setState({ component, loading: false, metrics });
+ }
+ },
+ () => {
+ this.setState({ loading: false });
+ }
+ );
+ };
+
+ render() {
+ const { component, loading, metrics } = this.state;
+ if (loading) {
+ return (
+ <div className="vsts-loading">
+ <i className="spinner global-loading-spinner" />
+ </div>
+ );
+ }
+
+ if (!component || !metrics) {
+ return (
+ <div className="vsts-widget-configure widget">
+ <h2 className="title">Quality Widget</h2>
+ <div className="content">
+ <div>Configure widget</div>
+ <img
+ alt=""
+ src="https://cdn.vsassets.io/v/20180301T143409/_content/Dashboards/unconfigured-small.png"
+ />
+ </div>
+ </div>
+ );
+ }
+
+ return <QGWidget component={component} metrics={metrics} />;
+ }
+}
diff --git a/server/sonar-web/src/main/js/app/integration/vsts/index.js b/server/sonar-web/src/main/js/app/integration/vsts/index.js
new file mode 100644
index 00000000000..433334447b6
--- /dev/null
+++ b/server/sonar-web/src/main/js/app/integration/vsts/index.js
@@ -0,0 +1,42 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import { parse } from 'querystring';
+import React from 'react';
+import { render } from 'react-dom';
+import Configuration from './components/Configuration';
+import Widget from './components/Widget';
+import './vsts.css';
+
+VSS.init({
+ explicitNotifyLoaded: true,
+ usePlatformStyles: true
+});
+
+VSS.require('TFS/Dashboards/WidgetHelpers', widgetHelpers => {
+ const container = document.getElementById('content');
+ const query = parse(window.location.search.replace('?', ''));
+
+ if (query.type === 'configuration') {
+ render(<Configuration widgetHelpers={widgetHelpers} />, container);
+ } else {
+ render(<Widget widgetHelpers={widgetHelpers} />, container);
+ }
+ VSS.notifyLoadSucceeded();
+});
diff --git a/server/sonar-web/src/main/js/app/integration/vsts/vsts.css b/server/sonar-web/src/main/js/app/integration/vsts/vsts.css
new file mode 100644
index 00000000000..8c0b5a62242
--- /dev/null
+++ b/server/sonar-web/src/main/js/app/integration/vsts/vsts.css
@@ -0,0 +1,89 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+@import '../../styles/components/spinner.css';
+@import '../../styles/components/global-loading.css';
+
+#content {
+ height: 100%;
+}
+
+.vsts-loading {
+ height: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.vsts-widget-configure {
+ display: block;
+ position: relative;
+ width: 100%;
+ height: 100%;
+ padding: 10px 14px;
+ font-size: 16px;
+}
+
+.vsts-widget-configure .title {
+ color: #333;
+ font-weight: normal;
+}
+
+.vsts-widget-configure .content {
+ padding-top: 10%;
+ text-align: center;
+ color: #666;
+}
+
+.vsts-widget-configure img {
+ height: 40px;
+ margin-top: 10px;
+}
+
+.widget.dark-widget.clickable > a {
+ color: white;
+}
+
+.big-value {
+ font-size: 36px;
+ line-height: 68px;
+ margin: 20px 0 10px 0;
+ font-weight: 300;
+}
+
+.level-OK {
+ background-color: var(--green);
+}
+
+.level-WARN {
+ background-color: var(--orange);
+}
+
+.level-ERROR {
+ background-color: var(--red);
+}
+
+.level-NONE {
+ background-color: var(--gray71);
+}
+
+.Select {
+ width: 100%;
+}
diff --git a/server/sonar-web/src/main/js/app/styles/components/spinner.css b/server/sonar-web/src/main/js/app/styles/components/spinner.css
new file mode 100644
index 00000000000..4482584983f
--- /dev/null
+++ b/server/sonar-web/src/main/js/app/styles/components/spinner.css
@@ -0,0 +1,82 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+.spinner {
+ position: relative;
+ vertical-align: middle;
+ width: 16px;
+ height: 16px;
+ border: 2px solid var(--blue);
+ border-radius: 50%;
+ animation: spin 0.75s infinite linear;
+}
+
+.spinner-placeholder {
+ position: relative;
+ display: inline-block;
+ vertical-align: middle;
+ width: 16px;
+ height: 16px;
+ visibility: hidden;
+}
+
+.spinner:before,
+.spinner:after {
+ left: -2px;
+ top: -2px;
+ display: none;
+ position: absolute;
+ content: '';
+ width: inherit;
+ height: inherit;
+ border: inherit;
+ border-radius: inherit;
+}
+
+.spinner,
+.spinner:before,
+.spinner:after {
+ display: inline-block;
+ box-sizing: border-box;
+ border-color: transparent;
+ border-top-color: var(--blue);
+ animation-duration: 1.2s;
+}
+
+.spinner:before {
+ transform: rotate(120deg);
+}
+
+.spinner:after {
+ transform: rotate(240deg);
+}
+
+.spinner-margin {
+ margin: 10px;
+}
+
+@keyframes spin {
+ from {
+ transform: rotate(0deg);
+ }
+
+ to {
+ transform: rotate(360deg);
+ }
+}
diff --git a/server/sonar-web/src/main/js/app/styles/init/icons.css b/server/sonar-web/src/main/js/app/styles/init/icons.css
index b6c9cdb078b..25b007c1ed6 100644
--- a/server/sonar-web/src/main/js/app/styles/init/icons.css
+++ b/server/sonar-web/src/main/js/app/styles/init/icons.css
@@ -783,70 +783,3 @@ a:hover > .icon-radio {
background-image: url(data:image/svg+xml,%3Csvg%20width%3D%2214%22%20height%3D%2214%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20stroke-linejoin%3D%22round%22%20stroke-miterlimit%3D%221.414%22%3E%3Cpath%20d%3D%22M2.977%2012.656c0%20.417-.142.745-.426.985-.283.24-.636.36-1.058.36-.552%200-1-.172-1.344-.516l.446-.687c.255.234.53.35.828.35.15%200%20.282-.036.394-.112.112-.075.168-.186.168-.332%200-.333-.273-.48-.82-.437l-.203-.438c.043-.052.127-.165.255-.34.127-.174.238-.315.332-.422.094-.106.19-.207.29-.3v-.008c-.084%200-.21.002-.38.008-.17.005-.296.007-.38.007v.415H.25V10h2.602v.688l-.743.898c.265.062.476.19.632.383.156.19.235.42.235.686zm.015-4.898V9H.164c-.03-.188-.047-.328-.047-.422%200-.265.06-.508.184-.726.123-.22.27-.396.442-.532.172-.135.344-.26.516-.37.172-.113.32-.226.44-.34.124-.115.185-.232.185-.352%200-.13-.038-.23-.113-.3-.076-.07-.18-.106-.31-.106-.24%200-.45.15-.632.453l-.664-.46c.125-.267.31-.474.56-.622.246-.15.52-.223.823-.223.38%200%20.7.108.96.324.26.216.39.51.39.88%200%20.26-.087.498-.264.714-.177.216-.373.384-.586.504-.214.12-.41.25-.59.394-.18.144-.272.28-.277.41h.992V7.76h.82zM14%2010.25v1.5c0%20.068-.025.126-.074.176-.05.05-.108.074-.176.074h-9.5c-.068%200-.126-.025-.176-.074-.05-.05-.074-.108-.074-.176v-1.5c0-.073.023-.133.07-.18.047-.047.107-.07.18-.07h9.5c.068%200%20.126.025.176.074.05.05.074.108.074.176zM3%203.227V4H.383v-.773h.836c0-.214%200-.532.003-.954l.004-.945v-.094H1.21c-.04.09-.17.23-.39.422l-.554-.593L1.328.07h.828v3.157H3zM14%206.25v1.5c0%20.068-.025.126-.074.176-.05.05-.108.074-.176.074h-9.5c-.068%200-.126-.025-.176-.074C4.024%207.876%204%207.818%204%207.75v-1.5c0-.073.023-.133.07-.18.047-.047.107-.07.18-.07h9.5c.068%200%20.126.025.176.074.05.05.074.108.074.176zm0-4v1.5c0%20.068-.025.126-.074.176-.05.05-.108.074-.176.074h-9.5c-.068%200-.126-.025-.176-.074C4.024%203.876%204%203.818%204%203.75v-1.5c0-.068.025-.126.074-.176.05-.05.108-.074.176-.074h9.5c.068%200%20.126.025.176.074.05.05.074.108.074.176z%22%20fill%3D%22%23236A97%22%20fill-rule%3D%22nonzero%22%2F%3E%3C%2Fsvg%3E);
background-repeat: no-repeat;
}
-
-/*
- * Spinner
- */
-.spinner {
- position: relative;
- vertical-align: middle;
- width: 16px;
- height: 16px;
- border: 2px solid var(--blue);
- border-radius: 50%;
- animation: spin 0.75s infinite linear;
-}
-
-.spinner-placeholder {
- position: relative;
- display: inline-block;
- vertical-align: middle;
- width: 16px;
- height: 16px;
- visibility: hidden;
-}
-
-.spinner:before,
-.spinner:after {
- left: -2px;
- top: -2px;
- display: none;
- position: absolute;
- content: '';
- width: inherit;
- height: inherit;
- border: inherit;
- border-radius: inherit;
-}
-
-.spinner,
-.spinner:before,
-.spinner:after {
- display: inline-block;
- box-sizing: border-box;
- border-color: transparent;
- border-top-color: var(--blue);
- animation-duration: 1.2s;
-}
-
-.spinner:before {
- transform: rotate(120deg);
-}
-
-.spinner:after {
- transform: rotate(240deg);
-}
-
-.spinner-margin {
- margin: 10px;
-}
-
-@keyframes spin {
- from {
- transform: rotate(0deg);
- }
-
- to {
- transform: rotate(360deg);
- }
-}
diff --git a/server/sonar-web/src/main/js/app/styles/sonar.css b/server/sonar-web/src/main/js/app/styles/sonar.css
index 714ed9d74b2..5b6284ebeed 100644
--- a/server/sonar-web/src/main/js/app/styles/sonar.css
+++ b/server/sonar-web/src/main/js/app/styles/sonar.css
@@ -27,6 +27,7 @@
@import './init/misc.css';
@import './components/ui.css';
+@import './components/spinner.css';
@import './components/global-loading.css';
@import './components/bubble-popup.css';
@import './components/modals.css';
diff --git a/server/sonar-web/src/main/js/libs/third-party/VSS.SDK.min.js b/server/sonar-web/src/main/js/libs/third-party/VSS.SDK.min.js
new file mode 100644
index 00000000000..5f6a76663bd
--- /dev/null
+++ b/server/sonar-web/src/main/js/libs/third-party/VSS.SDK.min.js
@@ -0,0 +1,957 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+// Copyright (C) Microsoft Corporation. All rights reserved.
+var XDM, VSS;
+(function(n) {
+ function u() {
+ return new o();
+ }
+ function s() {
+ return (
+ Math.floor(Math.random() * (f - t) + t).toString(36) +
+ Math.floor(Math.random() * (f - t) + t).toString(36)
+ );
+ }
+ var i, r, e;
+ n.createDeferred = u;
+ var o = (function() {
+ function n() {
+ var n = this;
+ this._resolveCallbacks = [];
+ this._rejectCallbacks = [];
+ this._isResolved = !1;
+ this._isRejected = !1;
+ this.resolve = function(t) {
+ n._resolve(t);
+ };
+ this.reject = function(t) {
+ n._reject(t);
+ };
+ this.promise = {};
+ this.promise.then = function(t, i) {
+ return n._then(t, i);
+ };
+ }
+ return (
+ (n.prototype._then = function(t, i) {
+ var u = this,
+ r;
+ return (!t && !i) || (this._isResolved && !t) || (this._isRejected && !i)
+ ? this.promise
+ : ((r = new n()),
+ this._resolveCallbacks.push(function(n) {
+ u._wrapCallback(t, n, r, !1);
+ }),
+ this._rejectCallbacks.push(function(n) {
+ u._wrapCallback(i, n, r, !0);
+ }),
+ this._isResolved
+ ? this._resolve(this._resolvedValue)
+ : this._isRejected && this._reject(this._rejectValue),
+ r.promise);
+ }),
+ (n.prototype._wrapCallback = function(n, t, i, r) {
+ if (!n) {
+ r ? i.reject(t) : i.resolve(t);
+ return;
+ }
+ var u;
+ try {
+ u = n(t);
+ } catch (f) {
+ i.reject(f);
+ return;
+ }
+ u === undefined
+ ? i.resolve(t)
+ : u && typeof u.then == 'function'
+ ? u.then(
+ function(n) {
+ i.resolve(n);
+ },
+ function(n) {
+ i.reject(n);
+ }
+ )
+ : i.resolve(u);
+ }),
+ (n.prototype._resolve = function(n) {
+ if (
+ (this._isRejected ||
+ this._isResolved ||
+ ((this._isResolved = !0), (this._resolvedValue = n)),
+ this._isResolved && this._resolveCallbacks.length > 0)
+ ) {
+ var t = this._resolveCallbacks.splice(0);
+ window.setTimeout(function() {
+ for (var i = 0, r = t.length; i < r; i++) t[i](n);
+ });
+ }
+ }),
+ (n.prototype._reject = function(n) {
+ if (
+ (this._isRejected ||
+ this._isResolved ||
+ ((this._isRejected = !0),
+ (this._rejectValue = n),
+ this._rejectCallbacks.length === 0 &&
+ window.console &&
+ window.console.warn &&
+ (console.warn('Rejected XDM promise with no reject callbacks'),
+ n && console.warn(n))),
+ this._isRejected && this._rejectCallbacks.length > 0)
+ ) {
+ var t = this._rejectCallbacks.splice(0);
+ window.setTimeout(function() {
+ for (var i = 0, r = t.length; i < r; i++) t[i](n);
+ });
+ }
+ }),
+ n
+ );
+ })(),
+ t = parseInt('10000000000', 36),
+ f = Number.MAX_SAFE_INTEGER || 9007199254740991;
+ i = (function() {
+ function n() {
+ this._registeredObjects = {};
+ }
+ return (
+ (n.prototype.register = function(n, t) {
+ this._registeredObjects[n] = t;
+ }),
+ (n.prototype.unregister = function(n) {
+ delete this._registeredObjects[n];
+ }),
+ (n.prototype.getInstance = function(n, t) {
+ var i = this._registeredObjects[n];
+ return i ? (typeof i == 'function' ? i(t) : i) : null;
+ }),
+ n
+ );
+ })();
+ n.XDMObjectRegistry = i;
+ n.globalObjectRegistry = new i();
+ r = (function() {
+ function t(n, r) {
+ r === void 0 && (r = null);
+ this._nextMessageId = 1;
+ this._deferreds = {};
+ this._nextProxyFunctionId = 1;
+ this._proxyFunctions = {};
+ this._postToWindow = n;
+ this._targetOrigin = r;
+ this._channelObjectRegistry = new i();
+ this._channelId = t._nextChannelId++;
+ this._targetOrigin || (this._handshakeToken = s());
+ }
+ return (
+ (t.prototype.getObjectRegistry = function() {
+ return this._channelObjectRegistry;
+ }),
+ (t.prototype.invokeRemoteMethod = function(n, t, i, r, f) {
+ var e = {
+ id: this._nextMessageId++,
+ methodName: n,
+ instanceId: t,
+ instanceContext: r,
+ params: this._customSerializeObject(i, f),
+ jsonrpc: '2.0',
+ serializationSettings: f
+ },
+ o;
+ return (
+ this._targetOrigin || (e.handshakeToken = this._handshakeToken),
+ (o = u()),
+ (this._deferreds[e.id] = o),
+ this._sendRpcMessage(e),
+ o.promise
+ );
+ }),
+ (t.prototype.getRemoteObjectProxy = function(n, t) {
+ return this.invokeRemoteMethod(null, n, null, t);
+ }),
+ (t.prototype.invokeMethod = function(n, t) {
+ var f = this,
+ r,
+ u,
+ i;
+ if (!t.methodName) {
+ this._success(t, n, t.handshakeToken);
+ return;
+ }
+ if (((r = n[t.methodName]), typeof r != 'function')) {
+ this._error(t, new Error('RPC method not found: ' + t.methodName), t.handshakeToken);
+ return;
+ }
+ try {
+ u = [];
+ t.params && (u = this._customDeserializeObject(t.params));
+ i = r.apply(n, u);
+ i && i.then && typeof i.then == 'function'
+ ? i.then(
+ function(n) {
+ f._success(t, n, t.handshakeToken);
+ },
+ function(n) {
+ f._error(t, n, t.handshakeToken);
+ }
+ )
+ : this._success(t, i, t.handshakeToken);
+ } catch (e) {
+ this._error(t, e, t.handshakeToken);
+ }
+ }),
+ (t.prototype.getRegisteredObject = function(t, i) {
+ if (t === '__proxyFunctions') return this._proxyFunctions;
+ var r = this._channelObjectRegistry.getInstance(t, i);
+ return r || (r = n.globalObjectRegistry.getInstance(t, i)), r;
+ }),
+ (t.prototype.onMessage = function(n) {
+ var u = this,
+ t = n,
+ i,
+ r;
+ if (t.instanceId) {
+ if (((i = this.getRegisteredObject(t.instanceId, t.instanceContext)), !i)) return !1;
+ typeof i.then == 'function'
+ ? i.then(
+ function(n) {
+ u.invokeMethod(n, t);
+ },
+ function(n) {
+ u._error(t, n, t.handshakeToken);
+ }
+ )
+ : this.invokeMethod(i, t);
+ } else {
+ if (((r = this._deferreds[t.id]), !r)) return !1;
+ t.error
+ ? r.reject(this._customDeserializeObject([t.error])[0])
+ : r.resolve(this._customDeserializeObject([t.result])[0]);
+ delete this._deferreds[t.id];
+ }
+ return !0;
+ }),
+ (t.prototype.owns = function(n, t, i) {
+ var r = i;
+ if (this._postToWindow === n) {
+ if (this._targetOrigin)
+ return t
+ ? t.toLowerCase() === 'null' ||
+ this._targetOrigin.toLowerCase().indexOf(t.toLowerCase()) === 0
+ : !1;
+ if (r.handshakeToken && r.handshakeToken === this._handshakeToken)
+ return (this._targetOrigin = t), !0;
+ }
+ return !1;
+ }),
+ (t.prototype.error = function(n, t) {
+ var i = n;
+ this._error(i, t, i.handshakeToken);
+ }),
+ (t.prototype._error = function(n, t, i) {
+ var r = {
+ id: n.id,
+ error: this._customSerializeObject([t], n.serializationSettings)[0],
+ jsonrpc: '2.0',
+ handshakeToken: i
+ };
+ this._sendRpcMessage(r);
+ }),
+ (t.prototype._success = function(n, t, i) {
+ var r = {
+ id: n.id,
+ result: this._customSerializeObject([t], n.serializationSettings)[0],
+ jsonrpc: '2.0',
+ handshakeToken: i
+ };
+ this._sendRpcMessage(r);
+ }),
+ (t.prototype._sendRpcMessage = function(n) {
+ var t = JSON.stringify(n);
+ this._postToWindow.postMessage(t, '*');
+ }),
+ (t.prototype._shouldSkipSerialization = function(n) {
+ for (var r, i = 0, u = t.WINDOW_TYPES_TO_SKIP_SERIALIZATION.length; i < u; i++)
+ if (((r = t.WINDOW_TYPES_TO_SKIP_SERIALIZATION[i]), window[r] && n instanceof window[r]))
+ return !0;
+ if (window.jQuery)
+ for (i = 0, u = t.JQUERY_TYPES_TO_SKIP_SERIALIZATION.length; i < u; i++)
+ if (
+ ((r = t.JQUERY_TYPES_TO_SKIP_SERIALIZATION[i]),
+ window.jQuery[r] && n instanceof window.jQuery[r])
+ )
+ return !0;
+ return !1;
+ }),
+ (t.prototype._customSerializeObject = function(n, i, r, u, f) {
+ var h = this,
+ a,
+ o,
+ l,
+ v,
+ e,
+ c,
+ s;
+ if (
+ (r === void 0 && (r = null),
+ u === void 0 && (u = 1),
+ f === void 0 && (f = 1),
+ !n || f > t.MAX_XDM_DEPTH) ||
+ this._shouldSkipSerialization(n)
+ )
+ return null;
+ if (
+ ((a = function(t, e, o) {
+ var s, c, l, a, v;
+ try {
+ s = t[o];
+ } catch (y) {}
+ ((c = typeof s), c !== 'undefined') &&
+ ((l = -1),
+ c === 'object' && (l = r.originalObjects.indexOf(s)),
+ l >= 0
+ ? ((a = r.newObjects[l]),
+ a.__circularReferenceId || (a.__circularReferenceId = u++),
+ (e[o] = { __circularReference: a.__circularReferenceId }))
+ : c === 'function'
+ ? ((v = h._nextProxyFunctionId++),
+ (e[o] = {
+ __proxyFunctionId: h._registerProxyFunction(s, n),
+ __channelId: h._channelId
+ }))
+ : c === 'object'
+ ? (e[o] =
+ s && s instanceof Date
+ ? { __proxyDate: s.getTime() }
+ : h._customSerializeObject(s, i, r, u, f + 1))
+ : o !== '__proxyFunctionId' && (e[o] = s));
+ }),
+ r || (r = { newObjects: [], originalObjects: [] }),
+ r.originalObjects.push(n),
+ n instanceof Array)
+ )
+ for (o = [], r.newObjects.push(o), e = 0, c = n.length; e < c; e++) a(n, o, e);
+ else {
+ o = {};
+ r.newObjects.push(o);
+ l = {};
+ try {
+ for (s in n) l[s] = !0;
+ for (v = Object.getOwnPropertyNames(n), e = 0, c = v.length; e < c; e++) l[v[e]] = !0;
+ } catch (y) {}
+ for (s in l) ((s && s[0] !== '_') || (i && i.includeUnderscoreProperties)) && a(n, o, s);
+ }
+ return r.originalObjects.pop(), r.newObjects.pop(), o;
+ }),
+ (t.prototype._registerProxyFunction = function(n, t) {
+ var i = this._nextProxyFunctionId++;
+ return (
+ (this._proxyFunctions['proxy' + i] = function() {
+ return n.apply(t, Array.prototype.slice.call(arguments, 0));
+ }),
+ i
+ );
+ }),
+ (t.prototype._customDeserializeObject = function(n, t) {
+ var e = this,
+ o = this,
+ r,
+ i,
+ u,
+ f;
+ if (!n) return null;
+ if (
+ (t || (t = {}),
+ (r = function(n, i) {
+ var r = n[i],
+ u = typeof r;
+ i === '__circularReferenceId' && u === 'number'
+ ? ((t[r] = n), delete n[i])
+ : u === 'object' &&
+ r &&
+ (r.__proxyFunctionId
+ ? (n[i] = function() {
+ return o.invokeRemoteMethod(
+ 'proxy' + r.__proxyFunctionId,
+ '__proxyFunctions',
+ Array.prototype.slice.call(arguments, 0),
+ null,
+ { includeUnderscoreProperties: !0 }
+ );
+ })
+ : r.__proxyDate
+ ? (n[i] = new Date(r.__proxyDate))
+ : r.__circularReference
+ ? (n[i] = t[r.__circularReference])
+ : e._customDeserializeObject(r, t));
+ }),
+ n instanceof Array)
+ )
+ for (i = 0, u = n.length; i < u; i++) r(n, i);
+ else if (typeof n == 'object') for (f in n) r(n, f);
+ return n;
+ }),
+ (t._nextChannelId = 1),
+ (t.MAX_XDM_DEPTH = 100),
+ (t.WINDOW_TYPES_TO_SKIP_SERIALIZATION = ['Node', 'Window', 'Event']),
+ (t.JQUERY_TYPES_TO_SKIP_SERIALIZATION = ['jQuery']),
+ t
+ );
+ })();
+ n.XDMChannel = r;
+ e = (function() {
+ function n() {
+ this._channels = [];
+ this._subscribe(window);
+ }
+ return (
+ (n.get = function() {
+ return this._default || (this._default = new n()), this._default;
+ }),
+ (n.prototype.addChannel = function(n, t) {
+ var i = new r(n, t);
+ return this._channels.push(i), i;
+ }),
+ (n.prototype.removeChannel = function(n) {
+ this._channels = this._channels.filter(function(t) {
+ return t !== n;
+ });
+ }),
+ (n.prototype._handleMessageReceived = function(n) {
+ var i, e, r, t, u, f;
+ if (typeof n.data == 'string')
+ try {
+ t = JSON.parse(n.data);
+ } catch (o) {}
+ if (t) {
+ for (u = !1, i = 0, e = this._channels.length; i < e; i++)
+ (r = this._channels[i]),
+ r.owns(n.source, n.origin, t) && ((f = r), (u = r.onMessage(t, n.origin) || u));
+ !f ||
+ u ||
+ (window.console &&
+ console.error('No handler found on any channel for message: ' + JSON.stringify(t)),
+ t.instanceId &&
+ f.error(t, 'The registered object ' + t.instanceId + ' could not be found.'));
+ }
+ }),
+ (n.prototype._subscribe = function(n) {
+ var t = this;
+ n.addEventListener
+ ? n.addEventListener('message', function(n) {
+ t._handleMessageReceived(n);
+ })
+ : n.attachEvent('onmessage', function(n) {
+ t._handleMessageReceived(n);
+ });
+ }),
+ n
+ );
+ })();
+ n.XDMChannelManager = e;
+})(XDM || (XDM = {})),
+ (function(n) {
+ function at() {
+ function r() {
+ n ||
+ (n = setTimeout(function() {
+ n = 0;
+ tt();
+ }, 50));
+ }
+ var n,
+ i = !1,
+ t;
+ try {
+ i = typeof document.cookie == 'string';
+ } catch (f) {}
+ i ||
+ Object.defineProperty(Document.prototype, 'cookie', {
+ get: function() {
+ return '';
+ },
+ set: function() {}
+ });
+ t = !1;
+ try {
+ t = !!window.localStorage;
+ } catch (f) {}
+ t ||
+ (delete window.localStorage,
+ (u = new g(r)),
+ Object.defineProperty(window, 'localStorage', { value: u }),
+ delete window.sessionStorage,
+ Object.defineProperty(window, 'sessionStorage', { value: new g() }));
+ }
+ function nt(f) {
+ r = f || {};
+ e = r.usePlatformScripts;
+ a = r.usePlatformStyles;
+ window.setTimeout(function() {
+ var f = {
+ notifyLoadSucceeded: !r.explicitNotifyLoaded,
+ extensionReusedCallback: r.extensionReusedCallback,
+ vssSDKVersion: n.VssSDKVersion
+ };
+ i.invokeRemoteMethod('initialHandshake', 'VSS.HostControl', [f]).then(function(n) {
+ var f, r, o, h, l, s, v, i;
+ if (
+ ((t = n.pageContext),
+ (b = t.webContext),
+ (k = n.initialConfig || {}),
+ (d = n.contribution),
+ (c = n.extensionContext),
+ n.sandboxedStorage)
+ ) {
+ if (((f = !1), u))
+ if (n.sandboxedStorage.localStorage) {
+ for (
+ r = n.sandboxedStorage.localStorage, o = 0, h = Object.keys(u);
+ o < h.length;
+ o++
+ )
+ (i = h[o]), (l = u.getItem(i)), l !== r[i] && ((r[i] = l), (f = !0));
+ for (s = 0, v = Object.keys(r); s < v.length; s++) (i = v[s]), u.setItem(i, r[i]);
+ } else u.length > 0 && (f = !0);
+ lt = !0;
+ f && tt();
+ }
+ e || a ? ht() : w();
+ });
+ }, 0);
+ }
+ function tt() {
+ var n = { localStorage: JSON.stringify(u || {}) };
+ i.invokeRemoteMethod('updateSandboxedStorage', 'VSS.HostControl', [n]);
+ }
+ function pt(n, t) {
+ var i;
+ i = typeof n == 'string' ? [n] : n;
+ t || (t = function() {});
+ l
+ ? it(i, t)
+ : (r ? e || ((e = !0), s && ((s = !1), ht())) : nt({ usePlatformScripts: !0 }),
+ rt(function() {
+ it(i, t);
+ }));
+ }
+ function it(n, i) {
+ t.diagnostics.bundlingEnabled
+ ? window.require(['VSS/Bundling'], function(t) {
+ t.requireModules(n).spread(function() {
+ i.apply(this, arguments);
+ });
+ })
+ : window.require(n, i);
+ }
+ function rt(n) {
+ s ? window.setTimeout(n, 0) : (f || (f = []), f.push(n));
+ }
+ function wt() {
+ i.invokeRemoteMethod('notifyLoadSucceeded', 'VSS.HostControl');
+ }
+ function ut(n) {
+ i.invokeRemoteMethod('notifyLoadFailed', 'VSS.HostControl', [n]);
+ }
+ function ft() {
+ return b;
+ }
+ function bt() {
+ return k;
+ }
+ function et() {
+ return c;
+ }
+ function kt() {
+ return d;
+ }
+ function dt(n, t) {
+ return ot(n).then(function(n) {
+ return (
+ t || (t = {}),
+ t.webContext || (t.webContext = ft()),
+ t.extensionContext || (t.extensionContext = et()),
+ n.getInstance(n.id, t)
+ );
+ });
+ }
+ function ot(t) {
+ var r = XDM.createDeferred();
+ return (
+ n.ready(function() {
+ i
+ .invokeRemoteMethod('getServiceContribution', 'vss.hostManagement', [t])
+ .then(function(n) {
+ var t = n;
+ t.getInstance = function(t, i) {
+ return st(n, t, i);
+ };
+ r.resolve(t);
+ }, r.reject);
+ }),
+ r.promise
+ );
+ }
+ function gt(t) {
+ var r = XDM.createDeferred();
+ return (
+ n.ready(function() {
+ i
+ .invokeRemoteMethod('getContributionsForTarget', 'vss.hostManagement', [t])
+ .then(function(n) {
+ var t = [];
+ n.forEach(function(n) {
+ var i = n;
+ i.getInstance = function(t, i) {
+ return st(n, t, i);
+ };
+ t.push(i);
+ });
+ r.resolve(t);
+ }, r.reject);
+ }),
+ r.promise
+ );
+ }
+ function st(t, r, u) {
+ var f = XDM.createDeferred();
+ return (
+ n.ready(function() {
+ i
+ .invokeRemoteMethod('getBackgroundContributionInstance', 'vss.hostManagement', [
+ t,
+ r,
+ u
+ ])
+ .then(f.resolve, f.reject);
+ }),
+ f.promise
+ );
+ }
+ function ni(n, t) {
+ i.getObjectRegistry().register(n, t);
+ }
+ function ti(n) {
+ i.getObjectRegistry().unregister(n);
+ }
+ function ii(n, t) {
+ return i.getObjectRegistry().getInstance(n, t);
+ }
+ function ri() {
+ return i.invokeRemoteMethod('getAccessToken', 'VSS.HostControl');
+ }
+ function ui() {
+ return i.invokeRemoteMethod('getAppToken', 'VSS.HostControl');
+ }
+ function fi(n, t) {
+ o || (o = document.getElementsByTagName('body').item(0));
+ var r = typeof n == 'number' ? n : o.scrollWidth,
+ u = typeof t == 'number' ? t : o.scrollHeight;
+ i.invokeRemoteMethod('resize', 'VSS.HostControl', [r, u]);
+ }
+ function ht() {
+ var i = si(t.webContext),
+ f,
+ g,
+ n,
+ s,
+ o,
+ b,
+ k,
+ nt,
+ tt,
+ d,
+ u;
+ if (
+ ((window.__vssPageContext = t),
+ (window.__cultureInfo = t.microsoftAjaxConfig.cultureInfo),
+ a !== !1 &&
+ t.coreReferences.stylesheets &&
+ t.coreReferences.stylesheets.forEach(function(n) {
+ if (n.isCoreStylesheet) {
+ var t = document.createElement('link');
+ t.href = h(n.url, i);
+ t.rel = 'stylesheet';
+ p(t, 'head');
+ }
+ }),
+ !e)
+ ) {
+ l = !0;
+ w();
+ return;
+ }
+ if (
+ ((f = []),
+ (g = !1),
+ t.coreReferences.scripts &&
+ (t.coreReferences.scripts.forEach(function(n) {
+ if (n.isCoreModule) {
+ var r = !1,
+ t = window;
+ n.identifier === 'JQuery'
+ ? (r = !!t.jQuery)
+ : n.identifier === 'JQueryUI'
+ ? (r = !!(t.jQuery && t.jQuery.ui && t.jQuery.ui.version))
+ : n.identifier === 'AMDLoader' &&
+ (r = typeof t.define == 'function' && !!t.define.amd);
+ r ? (g = !0) : f.push({ source: h(n.url, i) });
+ }
+ }),
+ t.coreReferences.coreScriptsBundle &&
+ !g &&
+ (f = [{ source: h(t.coreReferences.coreScriptsBundle.url, i) }]),
+ t.coreReferences.extensionCoreReferences &&
+ f.push({ source: h(t.coreReferences.extensionCoreReferences.url, i) })),
+ (n = { baseUrl: c.baseUri, contributionPaths: null, paths: {}, shim: {} }),
+ r.moduleLoaderConfig &&
+ (r.moduleLoaderConfig.baseUrl && (n.baseUrl = r.moduleLoaderConfig.baseUrl),
+ oi(r.moduleLoaderConfig, n),
+ ct(r.moduleLoaderConfig, n)),
+ t.moduleLoaderConfig &&
+ (ct(t.moduleLoaderConfig, n), (s = t.moduleLoaderConfig.contributionPaths), s))
+ )
+ for (o in s)
+ if (
+ s.hasOwnProperty(o) &&
+ !n.paths[o] &&
+ ((b = s[o].value),
+ (n.paths[o] = b.match('^https?://') ? b : i + b),
+ (k = t.moduleLoaderConfig.paths),
+ k)
+ ) {
+ nt = o + '/';
+ tt = v(i, t.moduleLoaderConfig.baseUrl);
+ for (d in k)
+ ei(d, nt) &&
+ ((u = k[d]),
+ u.match('^https?://') || (u = u[0] === '/' ? v(i, u) : v(tt, u)),
+ (n.paths[d] = u));
+ }
+ window.__vssModuleLoaderConfig = n;
+ f.push({ content: 'require.config(' + JSON.stringify(n) + ');' });
+ y(f, 0, function() {
+ l = !0;
+ w();
+ });
+ }
+ function ei(n, t) {
+ return n && n.length >= t.length ? n.substr(0, t.length).localeCompare(t) === 0 : !1;
+ }
+ function v(n, t) {
+ var i = n || '';
+ return i[i.length - 1] !== '/' && (i += '/'), t && (i += t[0] === '/' ? t.substr(1) : t), i;
+ }
+ function oi(n, t, i) {
+ var r, u;
+ if (n.paths) {
+ t.paths || (t.paths = {});
+ for (r in n.paths)
+ n.paths.hasOwnProperty(r) &&
+ ((u = n.paths[r]), i && (u = i(r, n.paths[r])), u && (t.paths[r] = u));
+ }
+ }
+ function ct(n, t) {
+ if (n.shim) {
+ t.shim || (t.shim = {});
+ for (var i in n.shim) n.shim.hasOwnProperty(i) && (t.shim[i] = n.shim[i]);
+ }
+ }
+ function si(n) {
+ var r = n.account || n.host,
+ t = r.uri,
+ i = r.relativeUri;
+ return (
+ t &&
+ i &&
+ (t[t.length - 1] !== '/' && (t += '/'),
+ i[i.length - 1] !== '/' && (i += '/'),
+ (t = t.substr(0, t.length - i.length))),
+ t
+ );
+ }
+ function y(n, t, i) {
+ var f = this,
+ r,
+ u;
+ if (t >= n.length) {
+ i.call(this);
+ return;
+ }
+ r = document.createElement('script');
+ r.type = 'text/javascript';
+ n[t].source
+ ? ((u = n[t].source),
+ (r.src = u),
+ r.addEventListener('load', function() {
+ y.call(f, n, t + 1, i);
+ }),
+ r.addEventListener('error', function() {
+ ut('Failed to load script: ' + u);
+ }),
+ p(r, 'head'))
+ : n[t].content && ((r.textContent = n[t].content), p(r, 'head'), y.call(this, n, t + 1, i));
+ }
+ function p(n, t) {
+ var i = document.getElementsByTagName(t)[0];
+ i || ((i = document.createElement(t)), document.appendChild(i));
+ i.appendChild(n);
+ }
+ function h(n, t) {
+ var i = (n || '').toLowerCase();
+ return (
+ i.substr(0, 2) !== '//' &&
+ i.substr(0, 5) !== 'http:' &&
+ i.substr(0, 6) !== 'https:' &&
+ (n = t + (i[0] === '/' ? '' : '/') + n),
+ n
+ );
+ }
+ function w() {
+ var t = this,
+ n;
+ s = !0;
+ f &&
+ ((n = f),
+ (f = null),
+ n.forEach(function(n) {
+ n.call(t);
+ }));
+ }
+ var yt;
+ n.VssSDKVersion = 2;
+ n.VssSDKRestVersion = '4.0';
+ var o,
+ b,
+ t,
+ c,
+ k,
+ d,
+ r,
+ l = !1,
+ e,
+ a,
+ s = !1,
+ f,
+ i = XDM.XDMChannelManager.get().addChannel(window.parent),
+ u,
+ lt = !1,
+ g = (function() {
+ function n() {
+ t && t.call(this);
+ }
+ function i() {}
+ var t;
+ return (
+ Object.defineProperties(i.prototype, {
+ getItem: {
+ get: function() {
+ return function(n) {
+ var t = this['' + n];
+ return typeof t == 'undefined' ? null : t;
+ };
+ }
+ },
+ setItem: {
+ get: function() {
+ return function(t, i) {
+ t = '' + t;
+ var u = this[t],
+ r = '' + i;
+ u !== r && ((this[t] = r), n());
+ };
+ }
+ },
+ removeItem: {
+ get: function() {
+ return function(t) {
+ t = '' + t;
+ typeof this[t] != 'undefined' && (delete this[t], n());
+ };
+ }
+ },
+ clear: {
+ get: function() {
+ return function() {
+ var r = Object.keys(this),
+ t,
+ i,
+ u;
+ if (r.length > 0) {
+ for (t = 0, i = r; t < i.length; t++) (u = i[t]), delete this[u];
+ n();
+ }
+ };
+ }
+ },
+ key: {
+ get: function() {
+ return function(n) {
+ return Object.keys(this)[n];
+ };
+ }
+ },
+ length: {
+ get: function() {
+ return Object.keys(this).length;
+ }
+ }
+ }),
+ i
+ );
+ })();
+ if (!window.__vssNoSandboxShim)
+ try {
+ at();
+ } catch (vt) {
+ window.console &&
+ window.console.warn &&
+ window.console.warn(
+ 'Failed to shim support for sandboxed properties: ' +
+ vt.message +
+ '. Set "window.__vssNoSandboxShim = true" in order to bypass the shim of sandboxed properties.'
+ );
+ }
+ (function(n) {
+ n.Dialog = 'ms.vss-web.dialog-service';
+ n.Navigation = 'ms.vss-web.navigation-service';
+ n.ExtensionData = 'ms.vss-web.data-service';
+ })((yt = n.ServiceIds || (n.ServiceIds = {})));
+ n.init = nt;
+ n.require = pt;
+ n.ready = rt;
+ n.notifyLoadSucceeded = wt;
+ n.notifyLoadFailed = ut;
+ n.getWebContext = ft;
+ n.getConfiguration = bt;
+ n.getExtensionContext = et;
+ n.getContribution = kt;
+ n.getService = dt;
+ n.getServiceContribution = ot;
+ n.getServiceContributions = gt;
+ n.register = ni;
+ n.unregister = ti;
+ n.getRegisteredObject = ii;
+ n.getAccessToken = ri;
+ n.getAppToken = ui;
+ n.resize = fi;
+ })(VSS || (VSS = {}));