Compare commits

...

36 Commits

Author SHA1 Message Date
Steve Faulkner
a266fdc89c Create codeql-analysis.yml 2020-10-14 14:58:44 -05:00
victor-meng
39f7ef331a Use RP to read databases for all API (#280)
Since I've cleaned up all the places that use `_rid` and `_self` of a database in my previous commit (d525afa142), we are safe to use RP to read databases for all APIs except Tables.

For Tables API, since RP doesn't support any database level operations, I have to hardcode the read database response. The only property we need in the Tables database is the id which is the string that we display in the resource tree.
2020-10-14 18:10:14 +00:00
victor-meng
9933a4988a Use SDK calls to read and update shared throughput for Tables API (#278)
RP does not supporting reading or updating database level throughput for Tables API so we have to switch back to using SDK calls for these operations.
2020-10-13 22:49:30 +00:00
victor-meng
d525afa142 Refactor code that uses the _rid and _self of a database or collection (#267) 2020-10-13 13:29:39 -07:00
Steve Faulkner
cfb9a0b321 Refactor NotificationsClient (#270) 2020-10-12 22:10:28 -05:00
Tanuj Mittal
3b64d75322 Add Report Abuse dialog for public gallery notebooks (#265)
![image](https://user-images.githubusercontent.com/693092/95408825-3975a680-08d5-11eb-812b-80f922ab9fc8.png)
2020-10-12 23:48:05 +00:00
Steve Faulkner
daba1c4ed4 Remove AutoMerge Github Action (#273)
Instead, we will be trying out the official Microsoft bot https://docs.opensource.microsoft.com/tools/fabricbot/index.html
2020-10-12 21:19:02 +00:00
victor-meng
a698e08638 Remove database offers cache and get offer directly from database (#268)
Currently we maintain a cache of all database offers which can be stale since we have moved to lazy loading the offers. Instead of reading the offer from the cache, we should just find the selected database and get the offer directly by calling `database.offer()`.
2020-10-12 21:00:47 +00:00
Garrett Ausfeldt
88d630fef4 Add summary to each table of the DataTable for narrator context (#238)
The DataTable control creates 3 tables in the DOM, one for the header, one for the body and one for the footer. Because of this when navigating through the tablem it says "leaving table", when it is really the same table. It seems this is not the default and because of the option **dom: "RZlfrtip"**, the DataTable is created this way. 

If I remove that setting, it will only create one table, BUT other things break, because there is a lot of custom DOM manipulation assuming the DOM was the way it was before (gross). This make me question if we wanted this on purpose to maybe solve a cross browser scrolling issue.

Instead I decided to leave it as is, until migrating to Microsoft's Fluent UI is prioritized. However I did add a summary attribute to each table, so that when listening to the narrator, it make more sense when leaving one part of the table into another part of the table. While not optimal, it should at least satisfy accessibility concern of it being confusing.
2020-10-12 20:30:37 +00:00
Armando Trejo Oliver
5ffa746adb Escape quotes in identifiers in CQL queries 2020-10-12 13:00:11 -05:00
Srinath Narayanan
a9a57f4ba9 Added telelmetry for settings v2 and v1 (#269)
* Added telelmetry for settings v2 and v1

* format errors fixed

* renamed actions
2020-10-12 09:01:00 -07:00
Steve Faulkner
47cc6fd7a8 Set default backend endpoint (#271) 2020-10-12 09:23:57 -05:00
Steve Faulkner
14cdf19efb Remove Explorer.isEmulator (#256) 2020-10-09 11:18:50 -05:00
Steve Faulkner
5191ae3f3a All events should trigger and ADO build 2020-10-09 09:47:52 -05:00
Steve Faulkner
ba862a8106 Remove jquery.contextMenu (#248) 2020-10-08 18:19:24 -05:00
Steve Faulkner
fe085b3e5a Restore AppInsights fetch telemetry (#263) 2020-10-08 17:55:02 -05:00
Srinath Narayanan
8028734cb0 Fixed settingsV2 bugs and added experimentation (#264)
* inital commit for flight tests

- FIxed bugs with settingstab v2

* minor edits

* removed console log

* fixed bug with autoscale throughput step increase

* resolved PR comments

* fixed compile error

* Added comment
2020-10-08 14:32:54 -07:00
Armando Trejo Oliver
444f663733 Update README.md (#255) 2020-10-08 15:57:38 -05:00
Laurent Nguyen
8c1ca35420 Fix double-scrollbar bug 2020-10-08 14:04:02 -05:00
Laurent Nguyen
b69174788d Add more Telemetry to Data Explorer (#242)
* Add Telemetry to command bar buttons

* Count and report # of files/notebooks/directories in myNotebook to telemetry

* Add resource tree clicks to Telemetry

* Log to Telemetry: opened notebook cell counts by type, kernelspec name

* Fix unit test

* Move Telemetry processor call in notebook traceNotebookTelemetry action from reducer to epic. Use action to trace other info.

* Fix react duplicate key error

* Log notebook cell context menu actions

* Reformat and cleanup

* Move resource tree tracing code out of render(). Only call once when tree is updated

* Fix build issues
2020-10-08 10:53:01 +02:00
Laurent Nguyen
ff03c79399 Fix horizontal scrollbar in notebook cell input issue (#260)
* Fix horizontal scrollbar in notebook cell input issue

* Cell input overflow visible
2020-10-08 09:17:46 +02:00
victor-meng
0382628249 Move update offers call to RP 2020-10-07 17:25:21 -05:00
Steve Faulkner
d346ebe054 Fix Blackforest Origin check (#261) 2020-10-07 15:11:11 -05:00
Zachary Foster
f5ecb8a04f Fixes e2e test input focus swapping (#262)
* Remove redundant E2E tests

* Remove deps

* Fixes e2e tests hopefully

Co-authored-by: Steve Faulkner <471400+southpolesteve@users.noreply.github.com>
Co-authored-by: Steve Faulkner <southpolesteve@gmail.com>
2020-10-07 15:41:38 -04:00
Laurent Nguyen
7dd046a15d Fix notebook kernel selection and auto-kernel-start (#254)
* Fix bug: cannot select kernel. Properly plug-in kernelspecs middleware to redux store configuration

* Properly auto-launch kernel with nteract's epic

* Keep kernel lazy start for notebook viewer

* Add unit tets
2020-10-07 08:39:04 +02:00
victor-meng
86d3f0d35d Fix database offers not loaded when add collection pane is opened 2020-10-06 22:22:54 -05:00
Tanuj Mittal
c08a1872cc Add correct content-type header to terminals calls (#258) 2020-10-06 16:55:13 -07:00
Jordi Bunster
0cc38868a6 Remove one indirection for ConfigContext
Things like ConfigContext.BACKEND_ENDPOINT are dynamically changed, and assigning them statically like this only bypasses that mechanism :(

Co-authored-by: Steve Faulkner <471400+southpolesteve@users.noreply.github.com>
2020-10-06 15:30:24 -07:00
Steve Faulkner
f5bbd52311 Test Output Cleanup 2020-10-05 23:21:58 -05:00
Steve Faulkner
b83f59ac31 Remove unused external files 2020-10-05 17:25:44 -05:00
Chris-MS-896
6b8c9f6961 [Azure][Screen Reader-CosmosDB – Data Explorer] Alt is not correctly defined for the console image on the data explorer page (#247)
* Update NotificationConsoleComponent.tsx

'Accessibiliy for alt image'

* Update NotificationConsoleComponent.tsx

'meet Unit test problems'

* Update NotificationConsoleComponent.test.tsx.snap

Update for snapshot

Co-authored-by: Chris896 <caoyiqun2017@gmail.com>
2020-10-05 16:14:51 -05:00
Steve Faulkner
2f978bac69 Use Github PAT for automerge 2020-10-05 14:34:17 -05:00
Steve Faulkner
d9d772f29c Add Automerge Action 2020-10-02 23:23:45 +00:00
victor-meng
0f82ed3749 Add checks for serverless account (#244) 2020-10-02 14:44:12 -07:00
Steve Faulkner
3112cf5573 Allow setting of ARM API version via query param (#241) 2020-10-02 10:45:32 -05:00
Laurent Nguyen
0ad5fb465b Switch notebook editors to monaco (#239)
* Upgrade nteract packages and related dependencies to make new stateful-component work

* Switch to new monacoEditor

* Configure store using nteract mythic configuration

* Replace CodeMirror with Monaco editor in NotebookReadOnlyRenderer

* Reformat

* Upgrade d3 to latest to resolve d3-selection conflicts with nteract/data-explorer that broke D3ForceGraph

* Upgrade jupyterlab terminal widget to work with latest version of react. Upgrade jupyterlab services to include latest fix for websocket auth

* Update jest test snapshots

* Upgrade packages to fix build issues

* Remove comment

* Fix unit tests

* Fix unit test snapshot

* Remove useless @types/node-fetch
2020-10-01 14:00:46 +02:00
165 changed files with 7273 additions and 34672 deletions

View File

@@ -105,74 +105,6 @@ jobs:
EMULATOR_ENDPOINT: https://0.0.0.0:8081/ EMULATOR_ENDPOINT: https://0.0.0.0:8081/
NODE_TLS_REJECT_UNAUTHORIZED: 0 NODE_TLS_REJECT_UNAUTHORIZED: 0
CYPRESS_CACHE_FOLDER: ~/.cache/Cypress CYPRESS_CACHE_FOLDER: ~/.cache/Cypress
endtoendsql:
name: "End To End Tests | SQL"
needs: [lint, format, compile, unittest]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Use Node.js 12.x
uses: actions/setup-node@v1
with:
node-version: 12.x
- name: Restore Cypress Binary Cache
uses: actions/cache@v2
with:
path: ~/.cache/Cypress
key: ${{ runner.os }}-cypress-binary-cache
- run: npm ci
- name: End to End Tests
run: |
npm start &
cd cypress
npm ci
node cleanup.js
npm run wait-for-server
npx cypress run --browser chrome --headless --spec "./integration/dataexplorer/SQL/*"
shell: bash
env:
NODE_TLS_REJECT_UNAUTHORIZED: 0
CYPRESS_CACHE_FOLDER: ~/.cache/Cypress
CYPRESS_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_SQL }}
- uses: actions/upload-artifact@v2
name: videos
if: ${{ failure() }}
with:
path: "**/*.mp4"
endtoendmongo:
name: "End To End Tests | Mongo"
needs: [lint, format, compile, unittest]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Use Node.js 12.x
uses: actions/setup-node@v1
with:
node-version: 12.x
- name: Restore Cypress Binary Cache
uses: actions/cache@v2
with:
path: ~/.cache/Cypress
key: ${{ runner.os }}-cypress-binary-cache
- name: End to End Tests
run: |
npm ci
npm start &
cd cypress
npm ci
node cleanup.js
npm run wait-for-server
npx cypress run --browser chrome --headless --spec "./integration/dataexplorer/MONGO/*"
shell: bash
env:
NODE_TLS_REJECT_UNAUTHORIZED: 0
CYPRESS_CACHE_FOLDER: ~/.cache/Cypress
CYPRESS_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_MONGO }}
- uses: actions/upload-artifact@v2
if: ${{ failure() }}
name: videos
with:
path: "**/*.mp4"
accessibility: accessibility:
name: "Accessibility | Hosted" name: "Accessibility | Hosted"
needs: [lint, format, compile, unittest] needs: [lint, format, compile, unittest]
@@ -221,7 +153,7 @@ jobs:
nuget: nuget:
name: Publish Nuget name: Publish Nuget
if: github.ref == 'refs/heads/master' || contains(github.ref, 'hotfix/') || contains(github.ref, 'release/') if: github.ref == 'refs/heads/master' || contains(github.ref, 'hotfix/') || contains(github.ref, 'release/')
needs: [lint, format, compile, build, unittest, endtoendemulator, endtoendsql, endtoendmongo] needs: [lint, format, compile, build, unittest, endtoendemulator, endtoendpuppeteer]
runs-on: ubuntu-latest runs-on: ubuntu-latest
env: env:
NUGET_SOURCE: ${{ secrets.NUGET_SOURCE }} NUGET_SOURCE: ${{ secrets.NUGET_SOURCE }}
@@ -245,7 +177,7 @@ jobs:
nugetmpac: nugetmpac:
name: Publish Nuget MPAC name: Publish Nuget MPAC
if: github.ref == 'refs/heads/master' || contains(github.ref, 'hotfix/') || contains(github.ref, 'release/') if: github.ref == 'refs/heads/master' || contains(github.ref, 'hotfix/') || contains(github.ref, 'release/')
needs: [lint, format, compile, build, unittest, endtoendemulator, endtoendsql, endtoendmongo] needs: [lint, format, compile, build, unittest, endtoendemulator, endtoendpuppeteer]
runs-on: ubuntu-latest runs-on: ubuntu-latest
env: env:
NUGET_SOURCE: ${{ secrets.NUGET_SOURCE }} NUGET_SOURCE: ${{ secrets.NUGET_SOURCE }}

71
.github/workflows/codeql-analysis.yml vendored Normal file
View File

@@ -0,0 +1,71 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
name: "CodeQL"
on:
push:
branches: [master]
pull_request:
# The branches below must be a subset of the branches above
branches: [master]
schedule:
- cron: '0 5 * * 5'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
# Override automatic language detection by changing the below list
# Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python']
language: ['javascript']
# Learn more...
# https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection
steps:
- name: Checkout repository
uses: actions/checkout@v2
with:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.
fetch-depth: 2
# If this run was triggered by a pull request event, then checkout
# the head of the pull request instead of the merge commit.
- run: git checkout HEAD^2
if: ${{ github.event_name == 'pull_request' }}
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

View File

@@ -1,4 +1,4 @@
# CosmosDB Explorer # Cosmos DB Explorer
UI for Azure Cosmos DB. Powers the [Azure Portal](https://portal.azure.com/), https://cosmos.azure.com/, and the [Cosmos DB Emulator](https://docs.microsoft.com/en-us/azure/cosmos-db/local-emulator) UI for Azure Cosmos DB. Powers the [Azure Portal](https://portal.azure.com/), https://cosmos.azure.com/, and the [Cosmos DB Emulator](https://docs.microsoft.com/en-us/azure/cosmos-db/local-emulator)

View File

@@ -3,11 +3,8 @@
# Add steps that build, run tests, deploy, and more: # Add steps that build, run tests, deploy, and more:
# https://aka.ms/yaml # https://aka.ms/yaml
trigger:
- master
pool: pool:
vmImage: 'ubuntu-latest' vmImage: "ubuntu-latest"
steps: steps:
- task: ComponentGovernanceComponentDetection@0 - task: ComponentGovernanceComponentDetection@0

View File

@@ -1,177 +0,0 @@
/*!
* jQuery contextMenu - Plugin for simple contextMenu handling
*
* Version: 1.6.6
*
* Authors: Rodney Rehm, Addy Osmani (patches for FF)
* Web: http://medialize.github.com/jQuery-contextMenu/
*
* Licensed under
* MIT License http://www.opensource.org/licenses/mit-license
* GPL v3 http://opensource.org/licenses/GPL-3.0
*
*/
.context-menu-list {
z-index: 1001;
position: fixed;
background: white;
border: solid 1px gainsboro;
box-shadow: 4px 4px 4px -2px #888888;
padding: 8px 0px 8px 0px;
line-height: 25px;
width: 254px;
list-style: none;
margin-left: -10px;
outline: 0px #fff;
}
.context-menu-item {
padding: 2px 2px 2px 31px;
background-color: #fff;
position: relative;
-webkit-user-select: none;
-moz-user-select: -moz-none;
-ms-user-select: none;
user-select: none;
}
.context-menu-separator {
padding-bottom: 0;
border-bottom: 1px solid #DDD;
}
.context-menu-item>label>input,
.context-menu-item>label>textarea {
-webkit-user-select: text;
-moz-user-select: text;
-ms-user-select: text;
user-select: text;
margin-left: -10px;
}
.context-menu-item:hover {
cursor: pointer;
background-color: #eeeeee;
}
.context-menu-item.disabled {
color: #666;
}
.context-menu-input.hover,
.context-menu-item.disabled.hover {
cursor: default;
background-color: #EEE;
}
.context-menu-submenu:after {
content: ">";
color: #666;
position: absolute;
top: 0;
right: 3px;
z-index: 1;
}
/* icons
#protip:
In case you want to use sprites for icons (which I would suggest you do) have a look at
http://css-tricks.com/13224-pseudo-spriting/ to get an idea of how to implement
.context-menu-item.icon:before {}
*/
.context-menu-item.icon {
min-height: 18px;
background-repeat: no-repeat;
background-position: 10px 7px;
}
.context-menu-item.icon:hover {
min-height: 18px;
background-repeat: no-repeat;
background-position: 10px 7px;
}
/*.context-menu-item.icon-edit {
background-image: url(images/page_white_edit.png);
}
.context-menu-item.icon-cut {
background-image: url(images/cut.png);
}
.context-menu-item.icon-copy {
background-image: url(images/page_white_copy.png);
}
.context-menu-item.icon-paste {
background-image: url(images/page_white_paste.png);
}
.context-menu-item.icon-delete {
background-image: url(images/page_white_delete.png);
}
.context-menu-item.icon-add {
background-image: url(images/page_white_add.png);
}
.context-menu-item.icon-quit {
background-image: url(images/door.png);
}*/
/* vertically align inside labels */
.context-menu-input>label>* {
vertical-align: top;
}
/* position checkboxes and radios as icons */
.context-menu-input>label>input[type="checkbox"],
.context-menu-input>label>input[type="radio"] {
margin-left: -17px;
}
.context-menu-input>label>span {
margin-left: 5px;
}
.context-menu-input>label,
.context-menu-input>label>input[type="text"],
.context-menu-input>label>textarea,
.context-menu-input>label>select {
display: block;
width: 100%;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
-ms-box-sizing: border-box;
-o-box-sizing: border-box;
box-sizing: border-box;
}
.context-menu-input>label>textarea {
height: 100px;
}
.context-menu-item>.context-menu-list {
display: none;
/* re-positioned by js */
right: -5px;
top: 5px;
}
/*.context-menu-item.hover>.context-menu-list {
display: block;
padding-left: 5px;
}*/
.context-menu-accesskey {
text-decoration: underline;
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,163 +0,0 @@
/*!
DataTables 1.10.9
©2008-2015 SpryMedia Ltd - datatables.net/license
*/
(function(Fa,T,k){var S=function(h){function X(a){var b,c,d={};h.each(a,function(e){if((b=e.match(/^([^A-Z]+?)([A-Z])/))&&-1!=="a aa ai ao as b fn i m o s ".indexOf(b[1]+" "))c=e.replace(b[0],b[2].toLowerCase()),d[c]=e,"o"===b[1]&&X(a[e])});a._hungarianMap=d}function I(a,b,c){a._hungarianMap||X(a);var d;h.each(b,function(e){d=a._hungarianMap[e];if(d!==k&&(c||b[d]===k))"o"===d.charAt(0)?(b[d]||(b[d]={}),h.extend(!0,b[d],b[e]),I(a[d],b[d],c)):b[d]=b[e]})}function S(a){var b=m.defaults.oLanguage,c=a.sZeroRecords;
!a.sEmptyTable&&(c&&"No data available in table"===b.sEmptyTable)&&F(a,a,"sZeroRecords","sEmptyTable");!a.sLoadingRecords&&(c&&"Loading..."===b.sLoadingRecords)&&F(a,a,"sZeroRecords","sLoadingRecords");a.sInfoThousands&&(a.sThousands=a.sInfoThousands);(a=a.sDecimal)&&cb(a)}function db(a){A(a,"ordering","bSort");A(a,"orderMulti","bSortMulti");A(a,"orderClasses","bSortClasses");A(a,"orderCellsTop","bSortCellsTop");A(a,"order","aaSorting");A(a,"orderFixed","aaSortingFixed");A(a,"paging","bPaginate");
A(a,"pagingType","sPaginationType");A(a,"pageLength","iDisplayLength");A(a,"searching","bFilter");"boolean"===typeof a.sScrollX&&(a.sScrollX=a.sScrollX?"100%":"");if(a=a.aoSearchCols)for(var b=0,c=a.length;b<c;b++)a[b]&&I(m.models.oSearch,a[b])}function eb(a){A(a,"orderable","bSortable");A(a,"orderData","aDataSort");A(a,"orderSequence","asSorting");A(a,"orderDataType","sortDataType");var b=a.aDataSort;b&&!h.isArray(b)&&(a.aDataSort=[b])}function fb(a){if(!m.__browser){var b={};m.__browser=b;var c=
h("<div/>").css({position:"fixed",top:0,left:0,height:1,width:1,overflow:"hidden"}).append(h("<div/>").css({position:"absolute",top:1,left:1,width:100,overflow:"scroll"}).append(h("<div/>").css({width:"100%",height:10}))).appendTo("body"),d=c.children(),e=d.children();b.barWidth=d[0].offsetWidth-d[0].clientWidth;b.bScrollOversize=100===e[0].offsetWidth&&100!==d[0].clientWidth;b.bScrollbarLeft=1!==Math.round(e.offset().left);b.bBounding=c[0].getBoundingClientRect().width?!0:!1;c.remove()}h.extend(a.oBrowser,
m.__browser);a.oScroll.iBarWidth=m.__browser.barWidth}function gb(a,b,c,d,e,f){var g,i=!1;c!==k&&(g=c,i=!0);for(;d!==e;)a.hasOwnProperty(d)&&(g=i?b(g,a[d],d,a):a[d],i=!0,d+=f);return g}function Ga(a,b){var c=m.defaults.column,d=a.aoColumns.length,c=h.extend({},m.models.oColumn,c,{nTh:b?b:T.createElement("th"),sTitle:c.sTitle?c.sTitle:b?b.innerHTML:"",aDataSort:c.aDataSort?c.aDataSort:[d],mData:c.mData?c.mData:d,idx:d});a.aoColumns.push(c);c=a.aoPreSearchCols;c[d]=h.extend({},m.models.oSearch,c[d]);
la(a,d,h(b).data())}function la(a,b,c){var b=a.aoColumns[b],d=a.oClasses,e=h(b.nTh);if(!b.sWidthOrig){b.sWidthOrig=e.attr("width")||null;var f=(e.attr("style")||"").match(/width:\s*(\d+[pxem%]+)/);f&&(b.sWidthOrig=f[1])}c!==k&&null!==c&&(eb(c),I(m.defaults.column,c),c.mDataProp!==k&&!c.mData&&(c.mData=c.mDataProp),c.sType&&(b._sManualType=c.sType),c.className&&!c.sClass&&(c.sClass=c.className),h.extend(b,c),F(b,c,"sWidth","sWidthOrig"),c.iDataSort!==k&&(b.aDataSort=[c.iDataSort]),F(b,c,"aDataSort"));
var g=b.mData,i=P(g),j=b.mRender?P(b.mRender):null,c=function(a){return"string"===typeof a&&-1!==a.indexOf("@")};b._bAttrSrc=h.isPlainObject(g)&&(c(g.sort)||c(g.type)||c(g.filter));b.fnGetData=function(a,b,c){var d=i(a,b,k,c);return j&&b?j(d,b,a,c):d};b.fnSetData=function(a,b,c){return Q(g)(a,b,c)};"number"!==typeof g&&(a._rowReadObject=!0);a.oFeatures.bSort||(b.bSortable=!1,e.addClass(d.sSortableNone));a=-1!==h.inArray("asc",b.asSorting);c=-1!==h.inArray("desc",b.asSorting);!b.bSortable||!a&&!c?
(b.sSortingClass=d.sSortableNone,b.sSortingClassJUI=""):a&&!c?(b.sSortingClass=d.sSortableAsc,b.sSortingClassJUI=d.sSortJUIAscAllowed):!a&&c?(b.sSortingClass=d.sSortableDesc,b.sSortingClassJUI=d.sSortJUIDescAllowed):(b.sSortingClass=d.sSortable,b.sSortingClassJUI=d.sSortJUI)}function Y(a){if(!1!==a.oFeatures.bAutoWidth){var b=a.aoColumns;Ha(a);for(var c=0,d=b.length;c<d;c++)b[c].nTh.style.width=b[c].sWidth}b=a.oScroll;(""!==b.sY||""!==b.sX)&&Z(a);w(a,null,"column-sizing",[a])}function $(a,b){var c=
aa(a,"bVisible");return"number"===typeof c[b]?c[b]:null}function ba(a,b){var c=aa(a,"bVisible"),c=h.inArray(b,c);return-1!==c?c:null}function ca(a){return aa(a,"bVisible").length}function aa(a,b){var c=[];h.map(a.aoColumns,function(a,e){a[b]&&c.push(e)});return c}function Ia(a){var b=a.aoColumns,c=a.aoData,d=m.ext.type.detect,e,f,g,i,j,h,l,r,q;e=0;for(f=b.length;e<f;e++)if(l=b[e],q=[],!l.sType&&l._sManualType)l.sType=l._sManualType;else if(!l.sType){g=0;for(i=d.length;g<i;g++){j=0;for(h=c.length;j<
h;j++){q[j]===k&&(q[j]=B(a,j,e,"type"));r=d[g](q[j],a);if(!r&&g!==d.length-1)break;if("html"===r)break}if(r){l.sType=r;break}}l.sType||(l.sType="string")}}function hb(a,b,c,d){var e,f,g,i,j,n,l=a.aoColumns;if(b)for(e=b.length-1;0<=e;e--){n=b[e];var r=n.targets!==k?n.targets:n.aTargets;h.isArray(r)||(r=[r]);f=0;for(g=r.length;f<g;f++)if("number"===typeof r[f]&&0<=r[f]){for(;l.length<=r[f];)Ga(a);d(r[f],n)}else if("number"===typeof r[f]&&0>r[f])d(l.length+r[f],n);else if("string"===typeof r[f]){i=0;
for(j=l.length;i<j;i++)("_all"==r[f]||h(l[i].nTh).hasClass(r[f]))&&d(i,n)}}if(c){e=0;for(a=c.length;e<a;e++)d(e,c[e])}}function L(a,b,c,d){var e=a.aoData.length,f=h.extend(!0,{},m.models.oRow,{src:c?"dom":"data",idx:e});f._aData=b;a.aoData.push(f);for(var g=a.aoColumns,i=0,j=g.length;i<j;i++)g[i].sType=null;a.aiDisplayMaster.push(e);b=a.rowIdFn(b);b!==k&&(a.aIds[b]=f);(c||!a.oFeatures.bDeferRender)&&Ja(a,e,c,d);return e}function ma(a,b){var c;b instanceof h||(b=h(b));return b.map(function(b,e){c=
Ka(a,e);return L(a,c.data,e,c.cells)})}function B(a,b,c,d){var e=a.iDraw,f=a.aoColumns[c],g=a.aoData[b]._aData,i=f.sDefaultContent,c=f.fnGetData(g,d,{settings:a,row:b,col:c});if(c===k)return a.iDrawError!=e&&null===i&&(J(a,0,"Requested unknown parameter "+("function"==typeof f.mData?"{function}":"'"+f.mData+"'")+" for row "+b,4),a.iDrawError=e),i;if((c===g||null===c)&&null!==i)c=i;else if("function"===typeof c)return c.call(g);return null===c&&"display"==d?"":c}function ib(a,b,c,d){a.aoColumns[c].fnSetData(a.aoData[b]._aData,
d,{settings:a,row:b,col:c})}function La(a){return h.map(a.match(/(\\.|[^\.])+/g)||[""],function(a){return a.replace(/\\./g,".")})}function P(a){if(h.isPlainObject(a)){var b={};h.each(a,function(a,c){c&&(b[a]=P(c))});return function(a,c,f,g){var i=b[c]||b._;return i!==k?i(a,c,f,g):a}}if(null===a)return function(a){return a};if("function"===typeof a)return function(b,c,f,g){return a(b,c,f,g)};if("string"===typeof a&&(-1!==a.indexOf(".")||-1!==a.indexOf("[")||-1!==a.indexOf("("))){var c=function(a,b,
f){var g,i;if(""!==f){i=La(f);for(var j=0,n=i.length;j<n;j++){f=i[j].match(da);g=i[j].match(U);if(f){i[j]=i[j].replace(da,"");""!==i[j]&&(a=a[i[j]]);g=[];i.splice(0,j+1);i=i.join(".");if(h.isArray(a)){j=0;for(n=a.length;j<n;j++)g.push(c(a[j],b,i))}a=f[0].substring(1,f[0].length-1);a=""===a?g:g.join(a);break}else if(g){i[j]=i[j].replace(U,"");a=a[i[j]]();continue}if(null===a||a[i[j]]===k)return k;a=a[i[j]]}}return a};return function(b,e){return c(b,e,a)}}return function(b){return b[a]}}function Q(a){if(h.isPlainObject(a))return Q(a._);
if(null===a)return function(){};if("function"===typeof a)return function(b,d,e){a(b,"set",d,e)};if("string"===typeof a&&(-1!==a.indexOf(".")||-1!==a.indexOf("[")||-1!==a.indexOf("("))){var b=function(a,d,e){var e=La(e),f;f=e[e.length-1];for(var g,i,j=0,n=e.length-1;j<n;j++){g=e[j].match(da);i=e[j].match(U);if(g){e[j]=e[j].replace(da,"");a[e[j]]=[];f=e.slice();f.splice(0,j+1);g=f.join(".");if(h.isArray(d)){i=0;for(n=d.length;i<n;i++)f={},b(f,d[i],g),a[e[j]].push(f)}else a[e[j]]=d;return}i&&(e[j]=e[j].replace(U,
""),a=a[e[j]](d));if(null===a[e[j]]||a[e[j]]===k)a[e[j]]={};a=a[e[j]]}if(f.match(U))a[f.replace(U,"")](d);else a[f.replace(da,"")]=d};return function(c,d){return b(c,d,a)}}return function(b,d){b[a]=d}}function Ma(a){return D(a.aoData,"_aData")}function na(a){a.aoData.length=0;a.aiDisplayMaster.length=0;a.aiDisplay.length=0;a.aIds={}}function oa(a,b,c){for(var d=-1,e=0,f=a.length;e<f;e++)a[e]==b?d=e:a[e]>b&&a[e]--; -1!=d&&c===k&&a.splice(d,1)}function ea(a,b,c,d){var e=a.aoData[b],f,g=function(c,d){for(;c.childNodes.length;)c.removeChild(c.firstChild);
c.innerHTML=B(a,b,d,"display")};if("dom"===c||(!c||"auto"===c)&&"dom"===e.src)e._aData=Ka(a,e,d,d===k?k:e._aData).data;else{var i=e.anCells;if(i)if(d!==k)g(i[d],d);else{c=0;for(f=i.length;c<f;c++)g(i[c],c)}}e._aSortData=null;e._aFilterData=null;g=a.aoColumns;if(d!==k)g[d].sType=null;else{c=0;for(f=g.length;c<f;c++)g[c].sType=null;Na(a,e)}}function Ka(a,b,c,d){var e=[],f=b.firstChild,g,i,j=0,n,l=a.aoColumns,r=a._rowReadObject,d=d!==k?d:r?{}:[],q=function(a,b){if("string"===typeof a){var c=a.indexOf("@");
-1!==c&&(c=a.substring(c+1),Q(a)(d,b.getAttribute(c)))}},jb=function(a){if(c===k||c===j)i=l[j],n=h.trim(a.innerHTML),i&&i._bAttrSrc?(Q(i.mData._)(d,n),q(i.mData.sort,a),q(i.mData.type,a),q(i.mData.filter,a)):r?(i._setter||(i._setter=Q(i.mData)),i._setter(d,n)):d[j]=n;j++};if(f)for(;f;){g=f.nodeName.toUpperCase();if("TD"==g||"TH"==g)jb(f),e.push(f);f=f.nextSibling}else{e=b.anCells;g=0;for(var o=e.length;g<o;g++)jb(e[g])}if(b=f?b:b.nTr)(b=b.getAttribute("id"))&&Q(a.rowId)(d,b);return{data:d,cells:e}}
function Ja(a,b,c,d){var e=a.aoData[b],f=e._aData,g=[],i,j,h,l,r;if(null===e.nTr){i=c||T.createElement("tr");e.nTr=i;e.anCells=g;i._DT_RowIndex=b;Na(a,e);l=0;for(r=a.aoColumns.length;l<r;l++){h=a.aoColumns[l];j=c?d[l]:T.createElement(h.sCellType);g.push(j);if(!c||h.mRender||h.mData!==l)j.innerHTML=B(a,b,l,"display");h.sClass&&(j.className+=" "+h.sClass);h.bVisible&&!c?i.appendChild(j):!h.bVisible&&c&&j.parentNode.removeChild(j);h.fnCreatedCell&&h.fnCreatedCell.call(a.oInstance,j,B(a,b,l),f,b,l)}w(a,
"aoRowCreatedCallback",null,[i,f,b])}e.nTr.setAttribute("role","row")}function Na(a,b){var c=b.nTr,d=b._aData;if(c){var e=a.rowIdFn(d);e&&(c.id=e);d.DT_RowClass&&(e=d.DT_RowClass.split(" "),b.__rowc=b.__rowc?pa(b.__rowc.concat(e)):e,h(c).removeClass(b.__rowc.join(" ")).addClass(d.DT_RowClass));d.DT_RowAttr&&h(c).attr(d.DT_RowAttr);d.DT_RowData&&h(c).data(d.DT_RowData)}}function kb(a){var b,c,d,e,f,g=a.nTHead,i=a.nTFoot,j=0===h("th, td",g).length,n=a.oClasses,l=a.aoColumns;j&&(e=h("<tr/>").appendTo(g));
b=0;for(c=l.length;b<c;b++)f=l[b],d=h(f.nTh).addClass(f.sClass),j&&d.appendTo(e),a.oFeatures.bSort&&(d.addClass(f.sSortingClass),!1!==f.bSortable&&(d.attr("tabindex",a.iTabIndex).attr("aria-controls",a.sTableId),Oa(a,f.nTh,b))),f.sTitle!=d[0].innerHTML&&d.html(f.sTitle),Pa(a,"header")(a,d,f,n);j&&fa(a.aoHeader,g);h(g).find(">tr").attr("role","row");h(g).find(">tr>th, >tr>td").addClass(n.sHeaderTH);h(i).find(">tr>th, >tr>td").addClass(n.sFooterTH);if(null!==i){a=a.aoFooter[0];b=0;for(c=a.length;b<
c;b++)f=l[b],f.nTf=a[b].cell,f.sClass&&h(f.nTf).addClass(f.sClass)}}function ga(a,b,c){var d,e,f,g=[],i=[],j=a.aoColumns.length,n;if(b){c===k&&(c=!1);d=0;for(e=b.length;d<e;d++){g[d]=b[d].slice();g[d].nTr=b[d].nTr;for(f=j-1;0<=f;f--)!a.aoColumns[f].bVisible&&!c&&g[d].splice(f,1);i.push([])}d=0;for(e=g.length;d<e;d++){if(a=g[d].nTr)for(;f=a.firstChild;)a.removeChild(f);f=0;for(b=g[d].length;f<b;f++)if(n=j=1,i[d][f]===k){a.appendChild(g[d][f].cell);for(i[d][f]=1;g[d+j]!==k&&g[d][f].cell==g[d+j][f].cell;)i[d+
j][f]=1,j++;for(;g[d][f+n]!==k&&g[d][f].cell==g[d][f+n].cell;){for(c=0;c<j;c++)i[d+c][f+n]=1;n++}h(g[d][f].cell).attr("rowspan",j).attr("colspan",n)}}}}function M(a){var b=w(a,"aoPreDrawCallback","preDraw",[a]);if(-1!==h.inArray(!1,b))C(a,!1);else{var b=[],c=0,d=a.asStripeClasses,e=d.length,f=a.oLanguage,g=a.iInitDisplayStart,i="ssp"==y(a),j=a.aiDisplay;a.bDrawing=!0;g!==k&&-1!==g&&(a._iDisplayStart=i?g:g>=a.fnRecordsDisplay()?0:g,a.iInitDisplayStart=-1);var g=a._iDisplayStart,n=a.fnDisplayEnd();
if(a.bDeferLoading)a.bDeferLoading=!1,a.iDraw++,C(a,!1);else if(i){if(!a.bDestroying&&!lb(a))return}else a.iDraw++;if(0!==j.length){f=i?a.aoData.length:n;for(i=i?0:g;i<f;i++){var l=j[i],r=a.aoData[l];null===r.nTr&&Ja(a,l);l=r.nTr;if(0!==e){var q=d[c%e];r._sRowStripe!=q&&(h(l).removeClass(r._sRowStripe).addClass(q),r._sRowStripe=q)}w(a,"aoRowCallback",null,[l,r._aData,c,i]);b.push(l);c++}}else c=f.sZeroRecords,1==a.iDraw&&"ajax"==y(a)?c=f.sLoadingRecords:f.sEmptyTable&&0===a.fnRecordsTotal()&&(c=f.sEmptyTable),
b[0]=h("<tr/>",{"class":e?d[0]:""}).append(h("<td />",{valign:"top",colSpan:ca(a),"class":a.oClasses.sRowEmpty}).html(c))[0];w(a,"aoHeaderCallback","header",[h(a.nTHead).children("tr")[0],Ma(a),g,n,j]);w(a,"aoFooterCallback","footer",[h(a.nTFoot).children("tr")[0],Ma(a),g,n,j]);d=h(a.nTBody);d.children().detach();d.append(h(b));w(a,"aoDrawCallback","draw",[a]);a.bSorted=!1;a.bFiltered=!1;a.bDrawing=!1}}function R(a,b){var c=a.oFeatures,d=c.bFilter;c.bSort&&mb(a);d?ha(a,a.oPreviousSearch):a.aiDisplay=
a.aiDisplayMaster.slice();!0!==b&&(a._iDisplayStart=0);a._drawHold=b;M(a);a._drawHold=!1}function nb(a){var b=a.oClasses,c=h(a.nTable),c=h("<div/>").insertBefore(c),d=a.oFeatures,e=h("<div/>",{id:a.sTableId+"_wrapper","class":b.sWrapper+(a.nTFoot?"":" "+b.sNoFooter)});a.nHolding=c[0];a.nTableWrapper=e[0];a.nTableReinsertBefore=a.nTable.nextSibling;for(var f=a.sDom.split(""),g,i,j,n,l,r,q=0;q<f.length;q++){g=null;i=f[q];if("<"==i){j=h("<div/>")[0];n=f[q+1];if("'"==n||'"'==n){l="";for(r=2;f[q+r]!=n;)l+=
f[q+r],r++;"H"==l?l=b.sJUIHeader:"F"==l&&(l=b.sJUIFooter);-1!=l.indexOf(".")?(n=l.split("."),j.id=n[0].substr(1,n[0].length-1),j.className=n[1]):"#"==l.charAt(0)?j.id=l.substr(1,l.length-1):j.className=l;q+=r}e.append(j);e=h(j)}else if(">"==i)e=e.parent();else if("l"==i&&d.bPaginate&&d.bLengthChange)g=ob(a);else if("f"==i&&d.bFilter)g=pb(a);else if("r"==i&&d.bProcessing)g=qb(a);else if("t"==i)g=rb(a);else if("i"==i&&d.bInfo)g=sb(a);else if("p"==i&&d.bPaginate)g=tb(a);else if(0!==m.ext.feature.length){j=
m.ext.feature;r=0;for(n=j.length;r<n;r++)if(i==j[r].cFeature){g=j[r].fnInit(a);break}}g&&(j=a.aanFeatures,j[i]||(j[i]=[]),j[i].push(g),e.append(g))}c.replaceWith(e);a.nHolding=null}function fa(a,b){var c=h(b).children("tr"),d,e,f,g,i,j,n,l,r,q;a.splice(0,a.length);f=0;for(j=c.length;f<j;f++)a.push([]);f=0;for(j=c.length;f<j;f++){d=c[f];for(e=d.firstChild;e;){if("TD"==e.nodeName.toUpperCase()||"TH"==e.nodeName.toUpperCase()){l=1*e.getAttribute("colspan");r=1*e.getAttribute("rowspan");l=!l||0===l||
1===l?1:l;r=!r||0===r||1===r?1:r;g=0;for(i=a[f];i[g];)g++;n=g;q=1===l?!0:!1;for(i=0;i<l;i++)for(g=0;g<r;g++)a[f+g][n+i]={cell:e,unique:q},a[f+g].nTr=d}e=e.nextSibling}}}function qa(a,b,c){var d=[];c||(c=a.aoHeader,b&&(c=[],fa(c,b)));for(var b=0,e=c.length;b<e;b++)for(var f=0,g=c[b].length;f<g;f++)if(c[b][f].unique&&(!d[f]||!a.bSortCellsTop))d[f]=c[b][f].cell;return d}function ra(a,b,c){w(a,"aoServerParams","serverParams",[b]);if(b&&h.isArray(b)){var d={},e=/(.*?)\[\]$/;h.each(b,function(a,b){var c=
b.name.match(e);c?(c=c[0],d[c]||(d[c]=[]),d[c].push(b.value)):d[b.name]=b.value});b=d}var f,g=a.ajax,i=a.oInstance,j=function(b){w(a,null,"xhr",[a,b,a.jqXHR]);c(b)};if(h.isPlainObject(g)&&g.data){f=g.data;var n=h.isFunction(f)?f(b,a):f,b=h.isFunction(f)&&n?n:h.extend(!0,b,n);delete g.data}n={data:b,success:function(b){var c=b.error||b.sError;c&&J(a,0,c);a.json=b;j(b)},dataType:"json",cache:!1,type:a.sServerMethod,error:function(b,c){var d=w(a,null,"xhr",[a,null,a.jqXHR]);-1===h.inArray(!0,d)&&("parsererror"==
c?J(a,0,"Invalid JSON response",1):4===b.readyState&&J(a,0,"Ajax error",7));C(a,!1)}};a.oAjaxData=b;w(a,null,"preXhr",[a,b]);a.fnServerData?a.fnServerData.call(i,a.sAjaxSource,h.map(b,function(a,b){return{name:b,value:a}}),j,a):a.sAjaxSource||"string"===typeof g?a.jqXHR=h.ajax(h.extend(n,{url:g||a.sAjaxSource})):h.isFunction(g)?a.jqXHR=g.call(i,b,j,a):(a.jqXHR=h.ajax(h.extend(n,g)),g.data=f)}function lb(a){return a.bAjaxDataGet?(a.iDraw++,C(a,!0),ra(a,ub(a),function(b){vb(a,b)}),!1):!0}function ub(a){var b=
a.aoColumns,c=b.length,d=a.oFeatures,e=a.oPreviousSearch,f=a.aoPreSearchCols,g,i=[],j,n,l,r=V(a);g=a._iDisplayStart;j=!1!==d.bPaginate?a._iDisplayLength:-1;var q=function(a,b){i.push({name:a,value:b})};q("sEcho",a.iDraw);q("iColumns",c);q("sColumns",D(b,"sName").join(","));q("iDisplayStart",g);q("iDisplayLength",j);var k={draw:a.iDraw,columns:[],order:[],start:g,length:j,search:{value:e.sSearch,regex:e.bRegex}};for(g=0;g<c;g++)n=b[g],l=f[g],j="function"==typeof n.mData?"function":n.mData,k.columns.push({data:j,
name:n.sName,searchable:n.bSearchable,orderable:n.bSortable,search:{value:l.sSearch,regex:l.bRegex}}),q("mDataProp_"+g,j),d.bFilter&&(q("sSearch_"+g,l.sSearch),q("bRegex_"+g,l.bRegex),q("bSearchable_"+g,n.bSearchable)),d.bSort&&q("bSortable_"+g,n.bSortable);d.bFilter&&(q("sSearch",e.sSearch),q("bRegex",e.bRegex));d.bSort&&(h.each(r,function(a,b){k.order.push({column:b.col,dir:b.dir});q("iSortCol_"+a,b.col);q("sSortDir_"+a,b.dir)}),q("iSortingCols",r.length));b=m.ext.legacy.ajax;return null===b?a.sAjaxSource?
i:k:b?i:k}function vb(a,b){var c=sa(a,b),d=b.sEcho!==k?b.sEcho:b.draw,e=b.iTotalRecords!==k?b.iTotalRecords:b.recordsTotal,f=b.iTotalDisplayRecords!==k?b.iTotalDisplayRecords:b.recordsFiltered;if(d){if(1*d<a.iDraw)return;a.iDraw=1*d}na(a);a._iRecordsTotal=parseInt(e,10);a._iRecordsDisplay=parseInt(f,10);d=0;for(e=c.length;d<e;d++)L(a,c[d]);a.aiDisplay=a.aiDisplayMaster.slice();a.bAjaxDataGet=!1;M(a);a._bInitComplete||ta(a,b);a.bAjaxDataGet=!0;C(a,!1)}function sa(a,b){var c=h.isPlainObject(a.ajax)&&
a.ajax.dataSrc!==k?a.ajax.dataSrc:a.sAjaxDataProp;return"data"===c?b.aaData||b[c]:""!==c?P(c)(b):b}function pb(a){var b=a.oClasses,c=a.sTableId,d=a.oLanguage,e=a.oPreviousSearch,f=a.aanFeatures,g='<input type="search" class="'+b.sFilterInput+'"/>',i=d.sSearch,i=i.match(/_INPUT_/)?i.replace("_INPUT_",g):i+g,b=h("<div/>",{id:!f.f?c+"_filter":null,"class":b.sFilter}).append(h("<label/>").append(i)),f=function(){var b=!this.value?"":this.value;b!=e.sSearch&&(ha(a,{sSearch:b,bRegex:e.bRegex,bSmart:e.bSmart,
bCaseInsensitive:e.bCaseInsensitive}),a._iDisplayStart=0,M(a))},g=null!==a.searchDelay?a.searchDelay:"ssp"===y(a)?400:0,j=h("input",b).val(e.sSearch).attr("placeholder",d.sSearchPlaceholder).bind("keyup.DT search.DT input.DT paste.DT cut.DT",g?ua(f,g):f).bind("keypress.DT",function(a){if(13==a.keyCode)return!1}).attr("aria-controls",c);h(a.nTable).on("search.dt.DT",function(b,c){if(a===c)try{j[0]!==T.activeElement&&j.val(e.sSearch)}catch(d){}});return b[0]}function ha(a,b,c){var d=a.oPreviousSearch,
e=a.aoPreSearchCols,f=function(a){d.sSearch=a.sSearch;d.bRegex=a.bRegex;d.bSmart=a.bSmart;d.bCaseInsensitive=a.bCaseInsensitive};Ia(a);if("ssp"!=y(a)){wb(a,b.sSearch,c,b.bEscapeRegex!==k?!b.bEscapeRegex:b.bRegex,b.bSmart,b.bCaseInsensitive);f(b);for(b=0;b<e.length;b++)xb(a,e[b].sSearch,b,e[b].bEscapeRegex!==k?!e[b].bEscapeRegex:e[b].bRegex,e[b].bSmart,e[b].bCaseInsensitive);yb(a)}else f(b);a.bFiltered=!0;w(a,null,"search",[a])}function yb(a){for(var b=m.ext.search,c=a.aiDisplay,d,e,f=0,g=b.length;f<
g;f++){for(var i=[],j=0,n=c.length;j<n;j++)e=c[j],d=a.aoData[e],b[f](a,d._aFilterData,e,d._aData,j)&&i.push(e);c.length=0;h.merge(c,i)}}function xb(a,b,c,d,e,f){if(""!==b)for(var g=a.aiDisplay,d=Qa(b,d,e,f),e=g.length-1;0<=e;e--)b=a.aoData[g[e]]._aFilterData[c],d.test(b)||g.splice(e,1)}function wb(a,b,c,d,e,f){var d=Qa(b,d,e,f),e=a.oPreviousSearch.sSearch,f=a.aiDisplayMaster,g;0!==m.ext.search.length&&(c=!0);g=zb(a);if(0>=b.length)a.aiDisplay=f.slice();else{if(g||c||e.length>b.length||0!==b.indexOf(e)||
a.bSorted)a.aiDisplay=f.slice();b=a.aiDisplay;for(c=b.length-1;0<=c;c--)d.test(a.aoData[b[c]]._sFilterRow)||b.splice(c,1)}}function Qa(a,b,c,d){a=b?a:va(a);c&&(a="^(?=.*?"+h.map(a.match(/"[^"]+"|[^ ]+/g)||[""],function(a){if('"'===a.charAt(0))var b=a.match(/^"(.*)"$/),a=b?b[1]:a;return a.replace('"',"")}).join(")(?=.*?")+").*$");return RegExp(a,d?"i":"")}function va(a){return a.replace(Yb,"\\$1")}function zb(a){var b=a.aoColumns,c,d,e,f,g,i,j,h,l=m.ext.type.search;c=!1;d=0;for(f=a.aoData.length;d<
f;d++)if(h=a.aoData[d],!h._aFilterData){i=[];e=0;for(g=b.length;e<g;e++)c=b[e],c.bSearchable?(j=B(a,d,e,"filter"),l[c.sType]&&(j=l[c.sType](j)),null===j&&(j=""),"string"!==typeof j&&j.toString&&(j=j.toString())):j="",j.indexOf&&-1!==j.indexOf("&")&&(wa.innerHTML=j,j=Zb?wa.textContent:wa.innerText),j.replace&&(j=j.replace(/[\r\n]/g,"")),i.push(j);h._aFilterData=i;h._sFilterRow=i.join(" ");c=!0}return c}function Ab(a){return{search:a.sSearch,smart:a.bSmart,regex:a.bRegex,caseInsensitive:a.bCaseInsensitive}}
function Bb(a){return{sSearch:a.search,bSmart:a.smart,bRegex:a.regex,bCaseInsensitive:a.caseInsensitive}}function sb(a){var b=a.sTableId,c=a.aanFeatures.i,d=h("<div/>",{"class":a.oClasses.sInfo,id:!c?b+"_info":null});c||(a.aoDrawCallback.push({fn:Cb,sName:"information"}),d.attr("role","status").attr("aria-live","polite"),h(a.nTable).attr("aria-describedby",b+"_info"));return d[0]}function Cb(a){var b=a.aanFeatures.i;if(0!==b.length){var c=a.oLanguage,d=a._iDisplayStart+1,e=a.fnDisplayEnd(),f=a.fnRecordsTotal(),
g=a.fnRecordsDisplay(),i=g?c.sInfo:c.sInfoEmpty;g!==f&&(i+=" "+c.sInfoFiltered);i+=c.sInfoPostFix;i=Db(a,i);c=c.fnInfoCallback;null!==c&&(i=c.call(a.oInstance,a,d,e,f,g,i));h(b).html(i)}}function Db(a,b){var c=a.fnFormatNumber,d=a._iDisplayStart+1,e=a._iDisplayLength,f=a.fnRecordsDisplay(),g=-1===e;return b.replace(/_START_/g,c.call(a,d)).replace(/_END_/g,c.call(a,a.fnDisplayEnd())).replace(/_MAX_/g,c.call(a,a.fnRecordsTotal())).replace(/_TOTAL_/g,c.call(a,f)).replace(/_PAGE_/g,c.call(a,g?1:Math.ceil(d/
e))).replace(/_PAGES_/g,c.call(a,g?1:Math.ceil(f/e)))}function ia(a){var b,c,d=a.iInitDisplayStart,e=a.aoColumns,f;c=a.oFeatures;var g=a.bDeferLoading;if(a.bInitialised){nb(a);kb(a);ga(a,a.aoHeader);ga(a,a.aoFooter);C(a,!0);c.bAutoWidth&&Ha(a);b=0;for(c=e.length;b<c;b++)f=e[b],f.sWidth&&(f.nTh.style.width=u(f.sWidth));w(a,null,"preInit",[a]);R(a);e=y(a);if("ssp"!=e||g)"ajax"==e?ra(a,[],function(c){var f=sa(a,c);for(b=0;b<f.length;b++)L(a,f[b]);a.iInitDisplayStart=d;R(a);C(a,!1);ta(a,c)},a):(C(a,!1),
ta(a))}else setTimeout(function(){ia(a)},200)}function ta(a,b){a._bInitComplete=!0;(b||a.oInit.aaData)&&Y(a);w(a,"aoInitComplete","init",[a,b])}function Ra(a,b){var c=parseInt(b,10);a._iDisplayLength=c;Sa(a);w(a,null,"length",[a,c])}function ob(a){for(var b=a.oClasses,c=a.sTableId,d=a.aLengthMenu,e=h.isArray(d[0]),f=e?d[0]:d,d=e?d[1]:d,e=h("<select/>",{name:c+"_length","aria-controls":c,"class":b.sLengthSelect}),g=0,i=f.length;g<i;g++)e[0][g]=new Option(d[g],f[g]);var j=h("<div><label/></div>").addClass(b.sLength);
a.aanFeatures.l||(j[0].id=c+"_length");j.children().append(a.oLanguage.sLengthMenu.replace("_MENU_",e[0].outerHTML));h("select",j).val(a._iDisplayLength).bind("change.DT",function(){Ra(a,h(this).val());M(a)});h(a.nTable).bind("length.dt.DT",function(b,c,d){a===c&&h("select",j).val(d)});return j[0]}function tb(a){var b=a.sPaginationType,c=m.ext.pager[b],d="function"===typeof c,e=function(a){M(a)},b=h("<div/>").addClass(a.oClasses.sPaging+b)[0],f=a.aanFeatures;d||c.fnInit(a,b,e);f.p||(b.id=a.sTableId+
"_paginate",a.aoDrawCallback.push({fn:function(a){if(d){var b=a._iDisplayStart,j=a._iDisplayLength,h=a.fnRecordsDisplay(),l=-1===j,b=l?0:Math.ceil(b/j),j=l?1:Math.ceil(h/j),h=c(b,j),k,l=0;for(k=f.p.length;l<k;l++)Pa(a,"pageButton")(a,f.p[l],l,h,b,j)}else c.fnUpdate(a,e)},sName:"pagination"}));return b}function Ta(a,b,c){var d=a._iDisplayStart,e=a._iDisplayLength,f=a.fnRecordsDisplay();0===f||-1===e?d=0:"number"===typeof b?(d=b*e,d>f&&(d=0)):"first"==b?d=0:"previous"==b?(d=0<=e?d-e:0,0>d&&(d=0)):"next"==
b?d+e<f&&(d+=e):"last"==b?d=Math.floor((f-1)/e)*e:J(a,0,"Unknown paging action: "+b,5);b=a._iDisplayStart!==d;a._iDisplayStart=d;b&&(w(a,null,"page",[a]),c&&M(a));return b}function qb(a){return h("<div/>",{id:!a.aanFeatures.r?a.sTableId+"_processing":null,"class":a.oClasses.sProcessing}).html(a.oLanguage.sProcessing).insertBefore(a.nTable)[0]}function C(a,b){a.oFeatures.bProcessing&&h(a.aanFeatures.r).css("display",b?"block":"none");w(a,null,"processing",[a,b])}function rb(a){var b=h(a.nTable);b.attr("role",
"grid");var c=a.oScroll;if(""===c.sX&&""===c.sY)return a.nTable;var d=c.sX,e=c.sY,f=a.oClasses,g=b.children("caption"),i=g.length?g[0]._captionSide:null,j=h(b[0].cloneNode(!1)),n=h(b[0].cloneNode(!1)),l=b.children("tfoot");c.sX&&"100%"===b.attr("width")&&b.removeAttr("width");l.length||(l=null);j=h("<div/>",{"class":f.sScrollWrapper}).append(h("<div/>",{"class":f.sScrollHead}).css({overflow:"hidden",position:"relative",border:0,width:d?!d?null:u(d):"100%"}).append(h("<div/>",{"class":f.sScrollHeadInner}).css({"box-sizing":"content-box",
width:c.sXInner||"100%"}).append(j.removeAttr("id").css("margin-left",0).append("top"===i?g:null).append(b.children("thead"))))).append(h("<div/>",{"class":f.sScrollBody}).css({position:"relative",overflow:"auto",width:!d?null:u(d)}).append(b));l&&j.append(h("<div/>",{"class":f.sScrollFoot}).css({overflow:"hidden",border:0,width:d?!d?null:u(d):"100%"}).append(h("<div/>",{"class":f.sScrollFootInner}).append(n.removeAttr("id").css("margin-left",0).append("bottom"===i?g:null).append(b.children("tfoot")))));
var b=j.children(),k=b[0],f=b[1],q=l?b[2]:null;if(d)h(f).on("scroll.DT",function(){var a=this.scrollLeft;k.scrollLeft=a;l&&(q.scrollLeft=a)});h(f).css(e&&c.bCollapse?"max-height":"height",e);a.nScrollHead=k;a.nScrollBody=f;a.nScrollFoot=q;a.aoDrawCallback.push({fn:Z,sName:"scrolling"});return j[0]}function Z(a){var b=a.oScroll,c=b.sX,d=b.sXInner,e=b.sY,b=b.iBarWidth,f=h(a.nScrollHead),g=f[0].style,i=f.children("div"),j=i[0].style,n=i.children("table"),i=a.nScrollBody,l=h(i),k=i.style,q=h(a.nScrollFoot).children("div"),
m=q.children("table"),o=h(a.nTHead),E=h(a.nTable),p=E[0],t=p.style,N=a.nTFoot?h(a.nTFoot):null,Eb=a.oBrowser,w=Eb.bScrollOversize,s,v,O,x,y=[],z=[],A=[],B,C=function(a){a=a.style;a.paddingTop="0";a.paddingBottom="0";a.borderTopWidth="0";a.borderBottomWidth="0";a.height=0};E.children("thead, tfoot").remove();x=o.clone().prependTo(E);o=o.find("tr");v=x.find("tr");x.find("th, td").removeAttr("tabindex");N&&(O=N.clone().prependTo(E),s=N.find("tr"),O=O.find("tr"));c||(k.width="100%",f[0].style.width="100%");
h.each(qa(a,x),function(b,c){B=$(a,b);c.style.width=a.aoColumns[B].sWidth});N&&H(function(a){a.style.width=""},O);f=E.outerWidth();if(""===c){t.width="100%";if(w&&(E.find("tbody").height()>i.offsetHeight||"scroll"==l.css("overflow-y")))t.width=u(E.outerWidth()-b);f=E.outerWidth()}else""!==d&&(t.width=u(d),f=E.outerWidth());H(C,v);H(function(a){A.push(a.innerHTML);y.push(u(h(a).css("width")))},v);H(function(a,b){a.style.width=y[b]},o);h(v).height(0);N&&(H(C,O),H(function(a){z.push(u(h(a).css("width")))},
O),H(function(a,b){a.style.width=z[b]},s),h(O).height(0));H(function(a,b){a.innerHTML='<div class="dataTables_sizing" style="height:0;overflow:hidden;">'+A[b]+"</div>";a.style.width=y[b]},v);N&&H(function(a,b){a.innerHTML="";a.style.width=z[b]},O);if(E.outerWidth()<f){s=i.scrollHeight>i.offsetHeight||"scroll"==l.css("overflow-y")?f+b:f;if(w&&(i.scrollHeight>i.offsetHeight||"scroll"==l.css("overflow-y")))t.width=u(s-b);(""===c||""!==d)&&J(a,1,"Possible column misalignment",6)}else s="100%";k.width=
u(s);g.width=u(s);N&&(a.nScrollFoot.style.width=u(s));!e&&w&&(k.height=u(p.offsetHeight+b));c=E.outerWidth();n[0].style.width=u(c);j.width=u(c);d=E.height()>i.clientHeight||"scroll"==l.css("overflow-y");e="padding"+(Eb.bScrollbarLeft?"Left":"Right");j[e]=d?b+"px":"0px";N&&(m[0].style.width=u(c),q[0].style.width=u(c),q[0].style[e]=d?b+"px":"0px");l.scroll();if((a.bSorted||a.bFiltered)&&!a._drawHold)i.scrollTop=0}function H(a,b,c){for(var d=0,e=0,f=b.length,g,i;e<f;){g=b[e].firstChild;for(i=c?c[e].firstChild:
null;g;)1===g.nodeType&&(c?a(g,i,d):a(g,d),d++),g=g.nextSibling,i=c?i.nextSibling:null;e++}}function Ha(a){var b=a.nTable,c=a.aoColumns,d=a.oScroll,e=d.sY,f=d.sX,g=d.sXInner,i=c.length,j=aa(a,"bVisible"),n=h("th",a.nTHead),l=b.getAttribute("width"),k=b.parentNode,q=!1,m,o,p;p=a.oBrowser;d=p.bScrollOversize;(m=b.style.width)&&-1!==m.indexOf("%")&&(l=m);for(m=0;m<j.length;m++)o=c[j[m]],null!==o.sWidth&&(o.sWidth=Fb(o.sWidthOrig,k),q=!0);if(d||!q&&!f&&!e&&i==ca(a)&&i==n.length)for(m=0;m<i;m++){if(j=
$(a,m))c[j].sWidth=u(n.eq(m).width())}else{i=h(b).clone().css("visibility","hidden").removeAttr("id");i.find("tbody tr").remove();var t=h("<tr/>").appendTo(i.find("tbody"));i.find("thead, tfoot").remove();i.append(h(a.nTHead).clone()).append(h(a.nTFoot).clone());i.find("tfoot th, tfoot td").css("width","");n=qa(a,i.find("thead")[0]);for(m=0;m<j.length;m++)o=c[j[m]],n[m].style.width=null!==o.sWidthOrig&&""!==o.sWidthOrig?u(o.sWidthOrig):"";if(a.aoData.length)for(m=0;m<j.length;m++)q=j[m],o=c[q],h(Gb(a,
q)).clone(!1).append(o.sContentPadding).appendTo(t);q=h("<div/>").css(f||e?{position:"absolute",top:0,left:0,height:1,right:0,overflow:"hidden"}:{}).append(i).appendTo(k);f&&g?i.width(g):f?(i.css("width","auto"),i.width()<k.clientWidth&&i.width(k.clientWidth)):e?i.width(k.clientWidth):l&&i.width(l);if(f){for(m=g=0;m<j.length;m++)o=c[j[m]],e=p.bBounding?n[m].getBoundingClientRect().width:h(n[m]).outerWidth(),g+=null===o.sWidthOrig?e:parseInt(o.sWidth,10)+e-h(n[m]).width();i.width(u(g));b.style.width=
u(g)}for(m=0;m<j.length;m++)if(o=c[j[m]],p=h(n[m]).width())o.sWidth=u(p);b.style.width=u(i.css("width"));q.remove()}l&&(b.style.width=u(l));if((l||f)&&!a._reszEvt)b=function(){h(Fa).bind("resize.DT-"+a.sInstance,ua(function(){Y(a)}))},d?setTimeout(b,1E3):b(),a._reszEvt=!0}function ua(a,b){var c=b!==k?b:200,d,e;return function(){var b=this,g=+new Date,i=arguments;d&&g<d+c?(clearTimeout(e),e=setTimeout(function(){d=k;a.apply(b,i)},c)):(d=g,a.apply(b,i))}}function Fb(a,b){if(!a)return 0;var c=h("<div/>").css("width",
u(a)).appendTo(b||T.body),d=c[0].offsetWidth;c.remove();return d}function Gb(a,b){var c=Hb(a,b);if(0>c)return null;var d=a.aoData[c];return!d.nTr?h("<td/>").html(B(a,c,b,"display"))[0]:d.anCells[b]}function Hb(a,b){for(var c,d=-1,e=-1,f=0,g=a.aoData.length;f<g;f++)c=B(a,f,b,"display")+"",c=c.replace($b,""),c.length>d&&(d=c.length,e=f);return e}function u(a){return null===a?"0px":"number"==typeof a?0>a?"0px":a+"px":a.match(/\d$/)?a+"px":a}function V(a){var b,c,d=[],e=a.aoColumns,f,g,i,j;b=a.aaSortingFixed;
c=h.isPlainObject(b);var n=[];f=function(a){a.length&&!h.isArray(a[0])?n.push(a):h.merge(n,a)};h.isArray(b)&&f(b);c&&b.pre&&f(b.pre);f(a.aaSorting);c&&b.post&&f(b.post);for(a=0;a<n.length;a++){j=n[a][0];f=e[j].aDataSort;b=0;for(c=f.length;b<c;b++)g=f[b],i=e[g].sType||"string",n[a]._idx===k&&(n[a]._idx=h.inArray(n[a][1],e[g].asSorting)),d.push({src:j,col:g,dir:n[a][1],index:n[a]._idx,type:i,formatter:m.ext.type.order[i+"-pre"]})}return d}function mb(a){var b,c,d=[],e=m.ext.type.order,f=a.aoData,g=
0,i,j=a.aiDisplayMaster,h;Ia(a);h=V(a);b=0;for(c=h.length;b<c;b++)i=h[b],i.formatter&&g++,Ib(a,i.col);if("ssp"!=y(a)&&0!==h.length){b=0;for(c=j.length;b<c;b++)d[j[b]]=b;g===h.length?j.sort(function(a,b){var c,e,g,i,j=h.length,k=f[a]._aSortData,m=f[b]._aSortData;for(g=0;g<j;g++)if(i=h[g],c=k[i.col],e=m[i.col],c=c<e?-1:c>e?1:0,0!==c)return"asc"===i.dir?c:-c;c=d[a];e=d[b];return c<e?-1:c>e?1:0}):j.sort(function(a,b){var c,g,i,j,k=h.length,m=f[a]._aSortData,p=f[b]._aSortData;for(i=0;i<k;i++)if(j=h[i],
c=m[j.col],g=p[j.col],j=e[j.type+"-"+j.dir]||e["string-"+j.dir],c=j(c,g),0!==c)return c;c=d[a];g=d[b];return c<g?-1:c>g?1:0})}a.bSorted=!0}function Jb(a){for(var b,c,d=a.aoColumns,e=V(a),a=a.oLanguage.oAria,f=0,g=d.length;f<g;f++){c=d[f];var i=c.asSorting;b=c.sTitle.replace(/<.*?>/g,"");var j=c.nTh;j.removeAttribute("aria-sort");c.bSortable&&(0<e.length&&e[0].col==f?(j.setAttribute("aria-sort","asc"==e[0].dir?"ascending":"descending"),c=i[e[0].index+1]||i[0]):c=i[0],b+="asc"===c?a.sSortAscending:
a.sSortDescending);j.setAttribute("aria-label",b)}}function Ua(a,b,c,d){var e=a.aaSorting,f=a.aoColumns[b].asSorting,g=function(a,b){var c=a._idx;c===k&&(c=h.inArray(a[1],f));return c+1<f.length?c+1:b?null:0};"number"===typeof e[0]&&(e=a.aaSorting=[e]);c&&a.oFeatures.bSortMulti?(c=h.inArray(b,D(e,"0")),-1!==c?(b=g(e[c],!0),null===b&&1===e.length&&(b=0),null===b?e.splice(c,1):(e[c][1]=f[b],e[c]._idx=b)):(e.push([b,f[0],0]),e[e.length-1]._idx=0)):e.length&&e[0][0]==b?(b=g(e[0]),e.length=1,e[0][1]=f[b],
e[0]._idx=b):(e.length=0,e.push([b,f[0]]),e[0]._idx=0);R(a);"function"==typeof d&&d(a)}function Oa(a,b,c,d){var e=a.aoColumns[c];Va(b,{},function(b){!1!==e.bSortable&&(a.oFeatures.bProcessing?(C(a,!0),setTimeout(function(){Ua(a,c,b.shiftKey,d);"ssp"!==y(a)&&C(a,!1)},0)):Ua(a,c,b.shiftKey,d))})}function xa(a){var b=a.aLastSort,c=a.oClasses.sSortColumn,d=V(a),e=a.oFeatures,f,g;if(e.bSort&&e.bSortClasses){e=0;for(f=b.length;e<f;e++)g=b[e].src,h(D(a.aoData,"anCells",g)).removeClass(c+(2>e?e+1:3));e=0;
for(f=d.length;e<f;e++)g=d[e].src,h(D(a.aoData,"anCells",g)).addClass(c+(2>e?e+1:3))}a.aLastSort=d}function Ib(a,b){var c=a.aoColumns[b],d=m.ext.order[c.sSortDataType],e;d&&(e=d.call(a.oInstance,a,b,ba(a,b)));for(var f,g=m.ext.type.order[c.sType+"-pre"],i=0,h=a.aoData.length;i<h;i++)if(c=a.aoData[i],c._aSortData||(c._aSortData=[]),!c._aSortData[b]||d)f=d?e[i]:B(a,i,b,"sort"),c._aSortData[b]=g?g(f):f}function ya(a){if(a.oFeatures.bStateSave&&!a.bDestroying){var b={time:+new Date,start:a._iDisplayStart,
length:a._iDisplayLength,order:h.extend(!0,[],a.aaSorting),search:Ab(a.oPreviousSearch),columns:h.map(a.aoColumns,function(b,d){return{visible:b.bVisible,search:Ab(a.aoPreSearchCols[d])}})};w(a,"aoStateSaveParams","stateSaveParams",[a,b]);a.oSavedState=b;a.fnStateSaveCallback.call(a.oInstance,a,b)}}function Kb(a){var b,c,d=a.aoColumns;if(a.oFeatures.bStateSave){var e=a.fnStateLoadCallback.call(a.oInstance,a);if(e&&e.time&&(b=w(a,"aoStateLoadParams","stateLoadParams",[a,e]),-1===h.inArray(!1,b)&&(b=
a.iStateDuration,!(0<b&&e.time<+new Date-1E3*b)&&d.length===e.columns.length))){a.oLoadedState=h.extend(!0,{},e);e.start!==k&&(a._iDisplayStart=e.start,a.iInitDisplayStart=e.start);e.length!==k&&(a._iDisplayLength=e.length);e.order!==k&&(a.aaSorting=[],h.each(e.order,function(b,c){a.aaSorting.push(c[0]>=d.length?[0,c[1]]:c)}));e.search!==k&&h.extend(a.oPreviousSearch,Bb(e.search));b=0;for(c=e.columns.length;b<c;b++){var f=e.columns[b];f.visible!==k&&(d[b].bVisible=f.visible);f.search!==k&&h.extend(a.aoPreSearchCols[b],
Bb(f.search))}w(a,"aoStateLoaded","stateLoaded",[a,e])}}}function za(a){var b=m.settings,a=h.inArray(a,D(b,"nTable"));return-1!==a?b[a]:null}function J(a,b,c,d){c="DataTables warning: "+(a?"table id="+a.sTableId+" - ":"")+c;d&&(c+=". For more information about this error, please see http://datatables.net/tn/"+d);if(b)Fa.console&&console.log&&console.log(c);else if(b=m.ext,b=b.sErrMode||b.errMode,a&&w(a,null,"error",[a,d,c]),"alert"==b)alert(c);else{if("throw"==b)throw Error(c);"function"==typeof b&&
b(a,d,c)}}function F(a,b,c,d){h.isArray(c)?h.each(c,function(c,d){h.isArray(d)?F(a,b,d[0],d[1]):F(a,b,d)}):(d===k&&(d=c),b[c]!==k&&(a[d]=b[c]))}function Lb(a,b,c){var d,e;for(e in b)b.hasOwnProperty(e)&&(d=b[e],h.isPlainObject(d)?(h.isPlainObject(a[e])||(a[e]={}),h.extend(!0,a[e],d)):a[e]=c&&"data"!==e&&"aaData"!==e&&h.isArray(d)?d.slice():d);return a}function Va(a,b,c){h(a).bind("click.DT",b,function(b){a.blur();c(b)}).bind("keypress.DT",b,function(a){13===a.which&&(a.preventDefault(),c(a))}).bind("selectstart.DT",
function(){return!1})}function z(a,b,c,d){c&&a[b].push({fn:c,sName:d})}function w(a,b,c,d){var e=[];b&&(e=h.map(a[b].slice().reverse(),function(b){return b.fn.apply(a.oInstance,d)}));null!==c&&(b=h.Event(c+".dt"),h(a.nTable).trigger(b,d),e.push(b.result));return e}function Sa(a){var b=a._iDisplayStart,c=a.fnDisplayEnd(),d=a._iDisplayLength;b>=c&&(b=c-d);b-=b%d;if(-1===d||0>b)b=0;a._iDisplayStart=b}function Pa(a,b){var c=a.renderer,d=m.ext.renderer[b];return h.isPlainObject(c)&&c[b]?d[c[b]]||d._:"string"===
typeof c?d[c]||d._:d._}function y(a){return a.oFeatures.bServerSide?"ssp":a.ajax||a.sAjaxSource?"ajax":"dom"}function Aa(a,b){var c=[],c=Mb.numbers_length,d=Math.floor(c/2);b<=c?c=W(0,b):a<=d?(c=W(0,c-2),c.push("ellipsis"),c.push(b-1)):(a>=b-1-d?c=W(b-(c-2),b):(c=W(a-d+2,a+d-1),c.push("ellipsis"),c.push(b-1)),c.splice(0,0,"ellipsis"),c.splice(0,0,0));c.DT_el="span";return c}function cb(a){h.each({num:function(b){return Ba(b,a)},"num-fmt":function(b){return Ba(b,a,Wa)},"html-num":function(b){return Ba(b,
a,Ca)},"html-num-fmt":function(b){return Ba(b,a,Ca,Wa)}},function(b,c){v.type.order[b+a+"-pre"]=c;b.match(/^html\-/)&&(v.type.search[b+a]=v.type.search.html)})}function Nb(a){return function(){var b=[za(this[m.ext.iApiIndex])].concat(Array.prototype.slice.call(arguments));return m.ext.internal[a].apply(this,b)}}var m,v,t,p,s,Xa={},Ob=/[\r\n]/g,Ca=/<.*?>/g,ac=/^[\w\+\-]/,bc=/[\w\+\-]$/,Yb=RegExp("(\\/|\\.|\\*|\\+|\\?|\\||\\(|\\)|\\[|\\]|\\{|\\}|\\\\|\\$|\\^|\\-)","g"),Wa=/[',$£€¥%\u2009\u202F\u20BD\u20a9\u20BArfk]/gi,
K=function(a){return!a||!0===a||"-"===a?!0:!1},Pb=function(a){var b=parseInt(a,10);return!isNaN(b)&&isFinite(a)?b:null},Qb=function(a,b){Xa[b]||(Xa[b]=RegExp(va(b),"g"));return"string"===typeof a&&"."!==b?a.replace(/\./g,"").replace(Xa[b],"."):a},Ya=function(a,b,c){var d="string"===typeof a;if(K(a))return!0;b&&d&&(a=Qb(a,b));c&&d&&(a=a.replace(Wa,""));return!isNaN(parseFloat(a))&&isFinite(a)},Rb=function(a,b,c){return K(a)?!0:!(K(a)||"string"===typeof a)?null:Ya(a.replace(Ca,""),b,c)?!0:null},D=function(a,
b,c){var d=[],e=0,f=a.length;if(c!==k)for(;e<f;e++)a[e]&&a[e][b]&&d.push(a[e][b][c]);else for(;e<f;e++)a[e]&&d.push(a[e][b]);return d},ja=function(a,b,c,d){var e=[],f=0,g=b.length;if(d!==k)for(;f<g;f++)a[b[f]][c]&&e.push(a[b[f]][c][d]);else for(;f<g;f++)e.push(a[b[f]][c]);return e},W=function(a,b){var c=[],d;b===k?(b=0,d=a):(d=b,b=a);for(var e=b;e<d;e++)c.push(e);return c},Sb=function(a){for(var b=[],c=0,d=a.length;c<d;c++)a[c]&&b.push(a[c]);return b},pa=function(a){var b=[],c,d,e=a.length,f,g=0;
d=0;a:for(;d<e;d++){c=a[d];for(f=0;f<g;f++)if(b[f]===c)continue a;b.push(c);g++}return b},A=function(a,b,c){a[b]!==k&&(a[c]=a[b])},da=/\[.*?\]$/,U=/\(\)$/,wa=h("<div>")[0],Zb=wa.textContent!==k,$b=/<.*?>/g;m=function(a){this.$=function(a,b){return this.api(!0).$(a,b)};this._=function(a,b){return this.api(!0).rows(a,b).data()};this.api=function(a){return a?new t(za(this[v.iApiIndex])):new t(this)};this.fnAddData=function(a,b){var c=this.api(!0),d=h.isArray(a)&&(h.isArray(a[0])||h.isPlainObject(a[0]))?
c.rows.add(a):c.row.add(a);(b===k||b)&&c.draw();return d.flatten().toArray()};this.fnAdjustColumnSizing=function(a){var b=this.api(!0).columns.adjust(),c=b.settings()[0],d=c.oScroll;a===k||a?b.draw(!1):(""!==d.sX||""!==d.sY)&&Z(c)};this.fnClearTable=function(a){var b=this.api(!0).clear();(a===k||a)&&b.draw()};this.fnClose=function(a){this.api(!0).row(a).child.hide()};this.fnDeleteRow=function(a,b,c){var d=this.api(!0),a=d.rows(a),e=a.settings()[0],h=e.aoData[a[0][0]];a.remove();b&&b.call(this,e,h);
(c===k||c)&&d.draw();return h};this.fnDestroy=function(a){this.api(!0).destroy(a)};this.fnDraw=function(a){this.api(!0).draw(a)};this.fnFilter=function(a,b,c,d,e,h){e=this.api(!0);null===b||b===k?e.search(a,c,d,h):e.column(b).search(a,c,d,h);e.draw()};this.fnGetData=function(a,b){var c=this.api(!0);if(a!==k){var d=a.nodeName?a.nodeName.toLowerCase():"";return b!==k||"td"==d||"th"==d?c.cell(a,b).data():c.row(a).data()||null}return c.data().toArray()};this.fnGetNodes=function(a){var b=this.api(!0);
return a!==k?b.row(a).node():b.rows().nodes().flatten().toArray()};this.fnGetPosition=function(a){var b=this.api(!0),c=a.nodeName.toUpperCase();return"TR"==c?b.row(a).index():"TD"==c||"TH"==c?(a=b.cell(a).index(),[a.row,a.columnVisible,a.column]):null};this.fnIsOpen=function(a){return this.api(!0).row(a).child.isShown()};this.fnOpen=function(a,b,c){return this.api(!0).row(a).child(b,c).show().child()[0]};this.fnPageChange=function(a,b){var c=this.api(!0).page(a);(b===k||b)&&c.draw(!1)};this.fnSetColumnVis=
function(a,b,c){a=this.api(!0).column(a).visible(b);(c===k||c)&&a.columns.adjust().draw()};this.fnSettings=function(){return za(this[v.iApiIndex])};this.fnSort=function(a){this.api(!0).order(a).draw()};this.fnSortListener=function(a,b,c){this.api(!0).order.listener(a,b,c)};this.fnUpdate=function(a,b,c,d,e){var h=this.api(!0);c===k||null===c?h.row(b).data(a):h.cell(b,c).data(a);(e===k||e)&&h.columns.adjust();(d===k||d)&&h.draw();return 0};this.fnVersionCheck=v.fnVersionCheck;var b=this,c=a===k,d=this.length;
c&&(a={});this.oApi=this.internal=v.internal;for(var e in m.ext.internal)e&&(this[e]=Nb(e));this.each(function(){var e={},e=1<d?Lb(e,a,!0):a,g=0,i,j=this.getAttribute("id"),n=!1,l=m.defaults,r=h(this);if("table"!=this.nodeName.toLowerCase())J(null,0,"Non-table node initialisation ("+this.nodeName+")",2);else{db(l);eb(l.column);I(l,l,!0);I(l.column,l.column,!0);I(l,h.extend(e,r.data()));var q=m.settings,g=0;for(i=q.length;g<i;g++){var p=q[g];if(p.nTable==this||p.nTHead.parentNode==this||p.nTFoot&&
p.nTFoot.parentNode==this){g=e.bRetrieve!==k?e.bRetrieve:l.bRetrieve;if(c||g)return p.oInstance;if(e.bDestroy!==k?e.bDestroy:l.bDestroy){p.oInstance.fnDestroy();break}else{J(p,0,"Cannot reinitialise DataTable",3);return}}if(p.sTableId==this.id){q.splice(g,1);break}}if(null===j||""===j)this.id=j="DataTables_Table_"+m.ext._unique++;var o=h.extend(!0,{},m.models.oSettings,{sDestroyWidth:r[0].style.width,sInstance:j,sTableId:j});o.nTable=this;o.oApi=b.internal;o.oInit=e;q.push(o);o.oInstance=1===b.length?
b:r.dataTable();db(e);e.oLanguage&&S(e.oLanguage);e.aLengthMenu&&!e.iDisplayLength&&(e.iDisplayLength=h.isArray(e.aLengthMenu[0])?e.aLengthMenu[0][0]:e.aLengthMenu[0]);e=Lb(h.extend(!0,{},l),e);F(o.oFeatures,e,"bPaginate bLengthChange bFilter bSort bSortMulti bInfo bProcessing bAutoWidth bSortClasses bServerSide bDeferRender".split(" "));F(o,e,["asStripeClasses","ajax","fnServerData","fnFormatNumber","sServerMethod","aaSorting","aaSortingFixed","aLengthMenu","sPaginationType","sAjaxSource","sAjaxDataProp",
"iStateDuration","sDom","bSortCellsTop","iTabIndex","fnStateLoadCallback","fnStateSaveCallback","renderer","searchDelay","rowId",["iCookieDuration","iStateDuration"],["oSearch","oPreviousSearch"],["aoSearchCols","aoPreSearchCols"],["iDisplayLength","_iDisplayLength"],["bJQueryUI","bJUI"]]);F(o.oScroll,e,[["sScrollX","sX"],["sScrollXInner","sXInner"],["sScrollY","sY"],["bScrollCollapse","bCollapse"]]);F(o.oLanguage,e,"fnInfoCallback");z(o,"aoDrawCallback",e.fnDrawCallback,"user");z(o,"aoServerParams",
e.fnServerParams,"user");z(o,"aoStateSaveParams",e.fnStateSaveParams,"user");z(o,"aoStateLoadParams",e.fnStateLoadParams,"user");z(o,"aoStateLoaded",e.fnStateLoaded,"user");z(o,"aoRowCallback",e.fnRowCallback,"user");z(o,"aoRowCreatedCallback",e.fnCreatedRow,"user");z(o,"aoHeaderCallback",e.fnHeaderCallback,"user");z(o,"aoFooterCallback",e.fnFooterCallback,"user");z(o,"aoInitComplete",e.fnInitComplete,"user");z(o,"aoPreDrawCallback",e.fnPreDrawCallback,"user");o.rowIdFn=P(e.rowId);fb(o);j=o.oClasses;
e.bJQueryUI?(h.extend(j,m.ext.oJUIClasses,e.oClasses),e.sDom===l.sDom&&"lfrtip"===l.sDom&&(o.sDom='<"H"lfr>t<"F"ip>'),o.renderer)?h.isPlainObject(o.renderer)&&!o.renderer.header&&(o.renderer.header="jqueryui"):o.renderer="jqueryui":h.extend(j,m.ext.classes,e.oClasses);r.addClass(j.sTable);o.iInitDisplayStart===k&&(o.iInitDisplayStart=e.iDisplayStart,o._iDisplayStart=e.iDisplayStart);null!==e.iDeferLoading&&(o.bDeferLoading=!0,g=h.isArray(e.iDeferLoading),o._iRecordsDisplay=g?e.iDeferLoading[0]:e.iDeferLoading,
o._iRecordsTotal=g?e.iDeferLoading[1]:e.iDeferLoading);var t=o.oLanguage;h.extend(!0,t,e.oLanguage);""!==t.sUrl&&(h.ajax({dataType:"json",url:t.sUrl,success:function(a){S(a);I(l.oLanguage,a);h.extend(true,t,a);ia(o)},error:function(){ia(o)}}),n=!0);null===e.asStripeClasses&&(o.asStripeClasses=[j.sStripeOdd,j.sStripeEven]);var g=o.asStripeClasses,s=r.children("tbody").find("tr").eq(0);-1!==h.inArray(!0,h.map(g,function(a){return s.hasClass(a)}))&&(h("tbody tr",this).removeClass(g.join(" ")),o.asDestroyStripes=
g.slice());q=[];g=this.getElementsByTagName("thead");0!==g.length&&(fa(o.aoHeader,g[0]),q=qa(o));if(null===e.aoColumns){p=[];g=0;for(i=q.length;g<i;g++)p.push(null)}else p=e.aoColumns;g=0;for(i=p.length;g<i;g++)Ga(o,q?q[g]:null);hb(o,e.aoColumnDefs,p,function(a,b){la(o,a,b)});if(s.length){var u=function(a,b){return a.getAttribute("data-"+b)!==null?b:null};h(s[0]).children("th, td").each(function(a,b){var c=o.aoColumns[a];if(c.mData===a){var d=u(b,"sort")||u(b,"order"),e=u(b,"filter")||u(b,"search");
if(d!==null||e!==null){c.mData={_:a+".display",sort:d!==null?a+".@data-"+d:k,type:d!==null?a+".@data-"+d:k,filter:e!==null?a+".@data-"+e:k};la(o,a)}}})}var v=o.oFeatures;e.bStateSave&&(v.bStateSave=!0,Kb(o,e),z(o,"aoDrawCallback",ya,"state_save"));if(e.aaSorting===k){q=o.aaSorting;g=0;for(i=q.length;g<i;g++)q[g][1]=o.aoColumns[g].asSorting[0]}xa(o);v.bSort&&z(o,"aoDrawCallback",function(){if(o.bSorted){var a=V(o),b={};h.each(a,function(a,c){b[c.src]=c.dir});w(o,null,"order",[o,a,b]);Jb(o)}});z(o,
"aoDrawCallback",function(){(o.bSorted||y(o)==="ssp"||v.bDeferRender)&&xa(o)},"sc");g=r.children("caption").each(function(){this._captionSide=r.css("caption-side")});i=r.children("thead");0===i.length&&(i=h("<thead/>").appendTo(this));o.nTHead=i[0];i=r.children("tbody");0===i.length&&(i=h("<tbody/>").appendTo(this));o.nTBody=i[0];i=r.children("tfoot");if(0===i.length&&0<g.length&&(""!==o.oScroll.sX||""!==o.oScroll.sY))i=h("<tfoot/>").appendTo(this);0===i.length||0===i.children().length?r.addClass(j.sNoFooter):
0<i.length&&(o.nTFoot=i[0],fa(o.aoFooter,o.nTFoot));if(e.aaData)for(g=0;g<e.aaData.length;g++)L(o,e.aaData[g]);else(o.bDeferLoading||"dom"==y(o))&&ma(o,h(o.nTBody).children("tr"));o.aiDisplay=o.aiDisplayMaster.slice();o.bInitialised=!0;!1===n&&ia(o)}});b=null;return this};var Tb=[],x=Array.prototype,cc=function(a){var b,c,d=m.settings,e=h.map(d,function(a){return a.nTable});if(a){if(a.nTable&&a.oApi)return[a];if(a.nodeName&&"table"===a.nodeName.toLowerCase())return b=h.inArray(a,e),-1!==b?[d[b]]:
null;if(a&&"function"===typeof a.settings)return a.settings().toArray();"string"===typeof a?c=h(a):a instanceof h&&(c=a)}else return[];if(c)return c.map(function(){b=h.inArray(this,e);return-1!==b?d[b]:null}).toArray()};t=function(a,b){if(!(this instanceof t))return new t(a,b);var c=[],d=function(a){(a=cc(a))&&(c=c.concat(a))};if(h.isArray(a))for(var e=0,f=a.length;e<f;e++)d(a[e]);else d(a);this.context=pa(c);b&&h.merge(this,b);this.selector={rows:null,cols:null,opts:null};t.extend(this,this,Tb)};
m.Api=t;h.extend(t.prototype,{any:function(){return 0!==this.count()},concat:x.concat,context:[],count:function(){return this.flatten().length},each:function(a){for(var b=0,c=this.length;b<c;b++)a.call(this,this[b],b,this);return this},eq:function(a){var b=this.context;return b.length>a?new t(b[a],this[a]):null},filter:function(a){var b=[];if(x.filter)b=x.filter.call(this,a,this);else for(var c=0,d=this.length;c<d;c++)a.call(this,this[c],c,this)&&b.push(this[c]);return new t(this.context,b)},flatten:function(){var a=
[];return new t(this.context,a.concat.apply(a,this.toArray()))},join:x.join,indexOf:x.indexOf||function(a,b){for(var c=b||0,d=this.length;c<d;c++)if(this[c]===a)return c;return-1},iterator:function(a,b,c,d){var e=[],f,g,h,j,n,l=this.context,m,q,p=this.selector;"string"===typeof a&&(d=c,c=b,b=a,a=!1);g=0;for(h=l.length;g<h;g++){var o=new t(l[g]);if("table"===b)f=c.call(o,l[g],g),f!==k&&e.push(f);else if("columns"===b||"rows"===b)f=c.call(o,l[g],this[g],g),f!==k&&e.push(f);else if("column"===b||"column-rows"===
b||"row"===b||"cell"===b){q=this[g];"column-rows"===b&&(m=Da(l[g],p.opts));j=0;for(n=q.length;j<n;j++)f=q[j],f="cell"===b?c.call(o,l[g],f.row,f.column,g,j):c.call(o,l[g],f,g,j,m),f!==k&&e.push(f)}}return e.length||d?(a=new t(l,a?e.concat.apply([],e):e),b=a.selector,b.rows=p.rows,b.cols=p.cols,b.opts=p.opts,a):this},lastIndexOf:x.lastIndexOf||function(a,b){return this.indexOf.apply(this.toArray.reverse(),arguments)},length:0,map:function(a){var b=[];if(x.map)b=x.map.call(this,a,this);else for(var c=
0,d=this.length;c<d;c++)b.push(a.call(this,this[c],c));return new t(this.context,b)},pluck:function(a){return this.map(function(b){return b[a]})},pop:x.pop,push:x.push,reduce:x.reduce||function(a,b){return gb(this,a,b,0,this.length,1)},reduceRight:x.reduceRight||function(a,b){return gb(this,a,b,this.length-1,-1,-1)},reverse:x.reverse,selector:null,shift:x.shift,sort:x.sort,splice:x.splice,toArray:function(){return x.slice.call(this)},to$:function(){return h(this)},toJQuery:function(){return h(this)},
unique:function(){return new t(this.context,pa(this))},unshift:x.unshift});t.extend=function(a,b,c){if(c.length&&b&&(b instanceof t||b.__dt_wrapper)){var d,e,f,g=function(a,b,c){return function(){var d=b.apply(a,arguments);t.extend(d,d,c.methodExt);return d}};d=0;for(e=c.length;d<e;d++)f=c[d],b[f.name]="function"===typeof f.val?g(a,f.val,f):h.isPlainObject(f.val)?{}:f.val,b[f.name].__dt_wrapper=!0,t.extend(a,b[f.name],f.propExt)}};t.register=p=function(a,b){if(h.isArray(a))for(var c=0,d=a.length;c<
d;c++)t.register(a[c],b);else for(var e=a.split("."),f=Tb,g,i,c=0,d=e.length;c<d;c++){g=(i=-1!==e[c].indexOf("()"))?e[c].replace("()",""):e[c];var j;a:{j=0;for(var n=f.length;j<n;j++)if(f[j].name===g){j=f[j];break a}j=null}j||(j={name:g,val:{},methodExt:[],propExt:[]},f.push(j));c===d-1?j.val=b:f=i?j.methodExt:j.propExt}};t.registerPlural=s=function(a,b,c){t.register(a,c);t.register(b,function(){var a=c.apply(this,arguments);return a===this?this:a instanceof t?a.length?h.isArray(a[0])?new t(a.context,
a[0]):a[0]:k:a})};p("tables()",function(a){var b;if(a){b=t;var c=this.context;if("number"===typeof a)a=[c[a]];else var d=h.map(c,function(a){return a.nTable}),a=h(d).filter(a).map(function(){var a=h.inArray(this,d);return c[a]}).toArray();b=new b(a)}else b=this;return b});p("table()",function(a){var a=this.tables(a),b=a.context;return b.length?new t(b[0]):a});s("tables().nodes()","table().node()",function(){return this.iterator("table",function(a){return a.nTable},1)});s("tables().body()","table().body()",
function(){return this.iterator("table",function(a){return a.nTBody},1)});s("tables().header()","table().header()",function(){return this.iterator("table",function(a){return a.nTHead},1)});s("tables().footer()","table().footer()",function(){return this.iterator("table",function(a){return a.nTFoot},1)});s("tables().containers()","table().container()",function(){return this.iterator("table",function(a){return a.nTableWrapper},1)});p("draw()",function(a){return this.iterator("table",function(b){"page"===
a?M(b):("string"===typeof a&&(a="full-hold"===a?!1:!0),R(b,!1===a))})});p("page()",function(a){return a===k?this.page.info().page:this.iterator("table",function(b){Ta(b,a)})});p("page.info()",function(){if(0===this.context.length)return k;var a=this.context[0],b=a._iDisplayStart,c=a._iDisplayLength,d=a.fnRecordsDisplay(),e=-1===c;return{page:e?0:Math.floor(b/c),pages:e?1:Math.ceil(d/c),start:b,end:a.fnDisplayEnd(),length:c,recordsTotal:a.fnRecordsTotal(),recordsDisplay:d,serverSide:"ssp"===y(a)}});
p("page.len()",function(a){return a===k?0!==this.context.length?this.context[0]._iDisplayLength:k:this.iterator("table",function(b){Ra(b,a)})});var Ub=function(a,b,c){if(c){var d=new t(a);d.one("draw",function(){c(d.ajax.json())})}if("ssp"==y(a))R(a,b);else{C(a,!0);var e=a.jqXHR;e&&4!==e.readyState&&e.abort();ra(a,[],function(c){na(a);for(var c=sa(a,c),d=0,e=c.length;d<e;d++)L(a,c[d]);R(a,b);C(a,!1)})}};p("ajax.json()",function(){var a=this.context;if(0<a.length)return a[0].json});p("ajax.params()",
function(){var a=this.context;if(0<a.length)return a[0].oAjaxData});p("ajax.reload()",function(a,b){return this.iterator("table",function(c){Ub(c,!1===b,a)})});p("ajax.url()",function(a){var b=this.context;if(a===k){if(0===b.length)return k;b=b[0];return b.ajax?h.isPlainObject(b.ajax)?b.ajax.url:b.ajax:b.sAjaxSource}return this.iterator("table",function(b){h.isPlainObject(b.ajax)?b.ajax.url=a:b.ajax=a})});p("ajax.url().load()",function(a,b){return this.iterator("table",function(c){Ub(c,!1===b,a)})});
var Za=function(a,b,c,d,e){var f=[],g,i,j,n,l,m;j=typeof b;if(!b||"string"===j||"function"===j||b.length===k)b=[b];j=0;for(n=b.length;j<n;j++){i=b[j]&&b[j].split?b[j].split(","):[b[j]];l=0;for(m=i.length;l<m;l++)(g=c("string"===typeof i[l]?h.trim(i[l]):i[l]))&&g.length&&(f=f.concat(g))}a=v.selector[a];if(a.length){j=0;for(n=a.length;j<n;j++)f=a[j](d,e,f)}return pa(f)},$a=function(a){a||(a={});a.filter&&a.search===k&&(a.search=a.filter);return h.extend({search:"none",order:"current",page:"all"},a)},
ab=function(a){for(var b=0,c=a.length;b<c;b++)if(0<a[b].length)return a[0]=a[b],a[0].length=1,a.length=1,a.context=[a.context[b]],a;a.length=0;return a},Da=function(a,b){var c,d,e,f=[],g=a.aiDisplay;c=a.aiDisplayMaster;var i=b.search;d=b.order;e=b.page;if("ssp"==y(a))return"removed"===i?[]:W(0,c.length);if("current"==e){c=a._iDisplayStart;for(d=a.fnDisplayEnd();c<d;c++)f.push(g[c])}else if("current"==d||"applied"==d)f="none"==i?c.slice():"applied"==i?g.slice():h.map(c,function(a){return-1===h.inArray(a,
g)?a:null});else if("index"==d||"original"==d){c=0;for(d=a.aoData.length;c<d;c++)"none"==i?f.push(c):(e=h.inArray(c,g),(-1===e&&"removed"==i||0<=e&&"applied"==i)&&f.push(c))}return f};p("rows()",function(a,b){a===k?a="":h.isPlainObject(a)&&(b=a,a="");var b=$a(b),c=this.iterator("table",function(c){var e=b;return Za("row",a,function(a){var b=Pb(a);if(b!==null&&!e)return[b];var i=Da(c,e);if(b!==null&&h.inArray(b,i)!==-1)return[b];if(!a)return i;if(typeof a==="function")return h.map(i,function(b){var e=
c.aoData[b];return a(b,e._aData,e.nTr)?b:null});b=Sb(ja(c.aoData,i,"nTr"));if(a.nodeName&&h.inArray(a,b)!==-1)return[a._DT_RowIndex];if(typeof a==="string"&&a.charAt(0)==="#"){i=c.aIds[a.replace(/^#/,"")];if(i!==k)return[i.idx]}return h(b).filter(a).map(function(){return this._DT_RowIndex}).toArray()},c,e)},1);c.selector.rows=a;c.selector.opts=b;return c});p("rows().nodes()",function(){return this.iterator("row",function(a,b){return a.aoData[b].nTr||k},1)});p("rows().data()",function(){return this.iterator(!0,
"rows",function(a,b){return ja(a.aoData,b,"_aData")},1)});s("rows().cache()","row().cache()",function(a){return this.iterator("row",function(b,c){var d=b.aoData[c];return"search"===a?d._aFilterData:d._aSortData},1)});s("rows().invalidate()","row().invalidate()",function(a){return this.iterator("row",function(b,c){ea(b,c,a)})});s("rows().indexes()","row().index()",function(){return this.iterator("row",function(a,b){return b},1)});s("rows().ids()","row().id()",function(a){for(var b=[],c=this.context,
d=0,e=c.length;d<e;d++)for(var f=0,g=this[d].length;f<g;f++){var h=c[d].rowIdFn(c[d].aoData[this[d][f]]._aData);b.push((!0===a?"#":"")+h)}return new t(c,b)});s("rows().remove()","row().remove()",function(){var a=this;this.iterator("row",function(b,c,d){var e=b.aoData,f=e[c];e.splice(c,1);for(var g=0,h=e.length;g<h;g++)null!==e[g].nTr&&(e[g].nTr._DT_RowIndex=g);oa(b.aiDisplayMaster,c);oa(b.aiDisplay,c);oa(a[d],c,!1);Sa(b);c=b.rowIdFn(f._aData);c!==k&&delete b.aIds[c]});this.iterator("table",function(a){for(var c=
0,d=a.aoData.length;c<d;c++)a.aoData[c].idx=c});return this});p("rows.add()",function(a){var b=this.iterator("table",function(b){var c,f,g,h=[];f=0;for(g=a.length;f<g;f++)c=a[f],c.nodeName&&"TR"===c.nodeName.toUpperCase()?h.push(ma(b,c)[0]):h.push(L(b,c));return h},1),c=this.rows(-1);c.pop();h.merge(c,b);return c});p("row()",function(a,b){return ab(this.rows(a,b))});p("row().data()",function(a){var b=this.context;if(a===k)return b.length&&this.length?b[0].aoData[this[0]]._aData:k;b[0].aoData[this[0]]._aData=
a;ea(b[0],this[0],"data");return this});p("row().node()",function(){var a=this.context;return a.length&&this.length?a[0].aoData[this[0]].nTr||null:null});p("row.add()",function(a){a instanceof h&&a.length&&(a=a[0]);var b=this.iterator("table",function(b){return a.nodeName&&"TR"===a.nodeName.toUpperCase()?ma(b,a)[0]:L(b,a)});return this.row(b[0])});var bb=function(a,b){var c=a.context;if(c.length&&(c=c[0].aoData[b!==k?b:a[0]])&&c._details)c._details.remove(),c._detailsShow=k,c._details=k},Vb=function(a,
b){var c=a.context;if(c.length&&a.length){var d=c[0].aoData[a[0]];if(d._details){(d._detailsShow=b)?d._details.insertAfter(d.nTr):d._details.detach();var e=c[0],f=new t(e),g=e.aoData;f.off("draw.dt.DT_details column-visibility.dt.DT_details destroy.dt.DT_details");0<D(g,"_details").length&&(f.on("draw.dt.DT_details",function(a,b){e===b&&f.rows({page:"current"}).eq(0).each(function(a){a=g[a];a._detailsShow&&a._details.insertAfter(a.nTr)})}),f.on("column-visibility.dt.DT_details",function(a,b){if(e===
b)for(var c,d=ca(b),f=0,h=g.length;f<h;f++)c=g[f],c._details&&c._details.children("td[colspan]").attr("colspan",d)}),f.on("destroy.dt.DT_details",function(a,b){if(e===b)for(var c=0,d=g.length;c<d;c++)g[c]._details&&bb(f,c)}))}}};p("row().child()",function(a,b){var c=this.context;if(a===k)return c.length&&this.length?c[0].aoData[this[0]]._details:k;if(!0===a)this.child.show();else if(!1===a)bb(this);else if(c.length&&this.length){var d=c[0],c=c[0].aoData[this[0]],e=[],f=function(a,b){if(h.isArray(a)||
a instanceof h)for(var c=0,k=a.length;c<k;c++)f(a[c],b);else a.nodeName&&"tr"===a.nodeName.toLowerCase()?e.push(a):(c=h("<tr><td/></tr>").addClass(b),h("td",c).addClass(b).html(a)[0].colSpan=ca(d),e.push(c[0]))};f(a,b);c._details&&c._details.remove();c._details=h(e);c._detailsShow&&c._details.insertAfter(c.nTr)}return this});p(["row().child.show()","row().child().show()"],function(){Vb(this,!0);return this});p(["row().child.hide()","row().child().hide()"],function(){Vb(this,!1);return this});p(["row().child.remove()",
"row().child().remove()"],function(){bb(this);return this});p("row().child.isShown()",function(){var a=this.context;return a.length&&this.length?a[0].aoData[this[0]]._detailsShow||!1:!1});var dc=/^(.+):(name|visIdx|visible)$/,Wb=function(a,b,c,d,e){for(var c=[],d=0,f=e.length;d<f;d++)c.push(B(a,e[d],b));return c};p("columns()",function(a,b){a===k?a="":h.isPlainObject(a)&&(b=a,a="");var b=$a(b),c=this.iterator("table",function(c){var e=a,f=b,g=c.aoColumns,i=D(g,"sName"),j=D(g,"nTh");return Za("column",
e,function(a){var b=Pb(a);if(a==="")return W(g.length);if(b!==null)return[b>=0?b:g.length+b];if(typeof a==="function"){var e=Da(c,f);return h.map(g,function(b,f){return a(f,Wb(c,f,0,0,e),j[f])?f:null})}var k=typeof a==="string"?a.match(dc):"";if(k)switch(k[2]){case "visIdx":case "visible":b=parseInt(k[1],10);if(b<0){var m=h.map(g,function(a,b){return a.bVisible?b:null});return[m[m.length+b]]}return[$(c,b)];case "name":return h.map(i,function(a,b){return a===k[1]?b:null})}else return h(j).filter(a).map(function(){return h.inArray(this,
j)}).toArray()},c,f)},1);c.selector.cols=a;c.selector.opts=b;return c});s("columns().header()","column().header()",function(){return this.iterator("column",function(a,b){return a.aoColumns[b].nTh},1)});s("columns().footer()","column().footer()",function(){return this.iterator("column",function(a,b){return a.aoColumns[b].nTf},1)});s("columns().data()","column().data()",function(){return this.iterator("column-rows",Wb,1)});s("columns().dataSrc()","column().dataSrc()",function(){return this.iterator("column",
function(a,b){return a.aoColumns[b].mData},1)});s("columns().cache()","column().cache()",function(a){return this.iterator("column-rows",function(b,c,d,e,f){return ja(b.aoData,f,"search"===a?"_aFilterData":"_aSortData",c)},1)});s("columns().nodes()","column().nodes()",function(){return this.iterator("column-rows",function(a,b,c,d,e){return ja(a.aoData,e,"anCells",b)},1)});s("columns().visible()","column().visible()",function(a,b){return this.iterator("column",function(c,d){if(a===k)return c.aoColumns[d].bVisible;
var e=c.aoColumns,f=e[d],g=c.aoData,i,j,m;if(a!==k&&f.bVisible!==a){if(a){var l=h.inArray(!0,D(e,"bVisible"),d+1);i=0;for(j=g.length;i<j;i++)m=g[i].nTr,e=g[i].anCells,m&&m.insertBefore(e[d],e[l]||null)}else h(D(c.aoData,"anCells",d)).detach();f.bVisible=a;ga(c,c.aoHeader);ga(c,c.aoFooter);if(b===k||b)Y(c),(c.oScroll.sX||c.oScroll.sY)&&Z(c);w(c,null,"column-visibility",[c,d,a]);ya(c)}})});s("columns().indexes()","column().index()",function(a){return this.iterator("column",function(b,c){return"visible"===
a?ba(b,c):c},1)});p("columns.adjust()",function(){return this.iterator("table",function(a){Y(a)},1)});p("column.index()",function(a,b){if(0!==this.context.length){var c=this.context[0];if("fromVisible"===a||"toData"===a)return $(c,b);if("fromData"===a||"toVisible"===a)return ba(c,b)}});p("column()",function(a,b){return ab(this.columns(a,b))});p("cells()",function(a,b,c){h.isPlainObject(a)&&(a.row===k?(c=a,a=null):(c=b,b=null));h.isPlainObject(b)&&(c=b,b=null);if(null===b||b===k)return this.iterator("table",
function(b){var d=a,e=$a(c),f=b.aoData,g=Da(b,e),i=Sb(ja(f,g,"anCells")),j=h([].concat.apply([],i)),l,m=b.aoColumns.length,n,p,t,s,u,v;return Za("cell",d,function(a){var c=typeof a==="function";if(a===null||a===k||c){n=[];p=0;for(t=g.length;p<t;p++){l=g[p];for(s=0;s<m;s++){u={row:l,column:s};if(c){v=f[l];a(u,B(b,l,s),v.anCells?v.anCells[s]:null)&&n.push(u)}else n.push(u)}}return n}return h.isPlainObject(a)?[a]:j.filter(a).map(function(a,b){if(b.parentNode)l=b.parentNode._DT_RowIndex;else{a=0;for(t=
f.length;a<t;a++)if(h.inArray(b,f[a].anCells)!==-1){l=a;break}}return{row:l,column:h.inArray(b,f[l].anCells)}}).toArray()},b,e)});var d=this.columns(b,c),e=this.rows(a,c),f,g,i,j,m,l=this.iterator("table",function(a,b){f=[];g=0;for(i=e[b].length;g<i;g++){j=0;for(m=d[b].length;j<m;j++)f.push({row:e[b][g],column:d[b][j]})}return f},1);h.extend(l.selector,{cols:b,rows:a,opts:c});return l});s("cells().nodes()","cell().node()",function(){return this.iterator("cell",function(a,b,c){return(a=a.aoData[b].anCells)?
a[c]:k},1)});p("cells().data()",function(){return this.iterator("cell",function(a,b,c){return B(a,b,c)},1)});s("cells().cache()","cell().cache()",function(a){a="search"===a?"_aFilterData":"_aSortData";return this.iterator("cell",function(b,c,d){return b.aoData[c][a][d]},1)});s("cells().render()","cell().render()",function(a){return this.iterator("cell",function(b,c,d){return B(b,c,d,a)},1)});s("cells().indexes()","cell().index()",function(){return this.iterator("cell",function(a,b,c){return{row:b,
column:c,columnVisible:ba(a,c)}},1)});s("cells().invalidate()","cell().invalidate()",function(a){return this.iterator("cell",function(b,c,d){ea(b,c,a,d)})});p("cell()",function(a,b,c){return ab(this.cells(a,b,c))});p("cell().data()",function(a){var b=this.context,c=this[0];if(a===k)return b.length&&c.length?B(b[0],c[0].row,c[0].column):k;ib(b[0],c[0].row,c[0].column,a);ea(b[0],c[0].row,"data",c[0].column);return this});p("order()",function(a,b){var c=this.context;if(a===k)return 0!==c.length?c[0].aaSorting:
k;"number"===typeof a?a=[[a,b]]:h.isArray(a[0])||(a=Array.prototype.slice.call(arguments));return this.iterator("table",function(b){b.aaSorting=a.slice()})});p("order.listener()",function(a,b,c){return this.iterator("table",function(d){Oa(d,a,b,c)})});p(["columns().order()","column().order()"],function(a){var b=this;return this.iterator("table",function(c,d){var e=[];h.each(b[d],function(b,c){e.push([c,a])});c.aaSorting=e})});p("search()",function(a,b,c,d){var e=this.context;return a===k?0!==e.length?
e[0].oPreviousSearch.sSearch:k:this.iterator("table",function(e){e.oFeatures.bFilter&&ha(e,h.extend({},e.oPreviousSearch,{sSearch:a+"",bRegex:null===b?!1:b,bSmart:null===c?!0:c,bCaseInsensitive:null===d?!0:d}),1)})});s("columns().search()","column().search()",function(a,b,c,d){return this.iterator("column",function(e,f){var g=e.aoPreSearchCols;if(a===k)return g[f].sSearch;e.oFeatures.bFilter&&(h.extend(g[f],{sSearch:a+"",bRegex:null===b?!1:b,bSmart:null===c?!0:c,bCaseInsensitive:null===d?!0:d}),ha(e,
e.oPreviousSearch,1))})});p("state()",function(){return this.context.length?this.context[0].oSavedState:null});p("state.clear()",function(){return this.iterator("table",function(a){a.fnStateSaveCallback.call(a.oInstance,a,{})})});p("state.loaded()",function(){return this.context.length?this.context[0].oLoadedState:null});p("state.save()",function(){return this.iterator("table",function(a){ya(a)})});m.versionCheck=m.fnVersionCheck=function(a){for(var b=m.version.split("."),a=a.split("."),c,d,e=0,f=
a.length;e<f;e++)if(c=parseInt(b[e],10)||0,d=parseInt(a[e],10)||0,c!==d)return c>d;return!0};m.isDataTable=m.fnIsDataTable=function(a){var b=h(a).get(0),c=!1;h.each(m.settings,function(a,e){var f=e.nScrollHead?h("table",e.nScrollHead)[0]:null,g=e.nScrollFoot?h("table",e.nScrollFoot)[0]:null;if(e.nTable===b||f===b||g===b)c=!0});return c};m.tables=m.fnTables=function(a){var b=!1;h.isPlainObject(a)&&(b=a.api,a=a.visible);var c=h.map(m.settings,function(b){if(!a||a&&h(b.nTable).is(":visible"))return b.nTable});
return b?new t(c):c};m.util={throttle:ua,escapeRegex:va};m.camelToHungarian=I;p("$()",function(a,b){var c=this.rows(b).nodes(),c=h(c);return h([].concat(c.filter(a).toArray(),c.find(a).toArray()))});h.each(["on","one","off"],function(a,b){p(b+"()",function(){var a=Array.prototype.slice.call(arguments);a[0].match(/\.dt\b/)||(a[0]+=".dt");var d=h(this.tables().nodes());d[b].apply(d,a);return this})});p("clear()",function(){return this.iterator("table",function(a){na(a)})});p("settings()",function(){return new t(this.context,
this.context)});p("init()",function(){var a=this.context;return a.length?a[0].oInit:null});p("data()",function(){return this.iterator("table",function(a){return D(a.aoData,"_aData")}).flatten()});p("destroy()",function(a){a=a||!1;return this.iterator("table",function(b){var c=b.nTableWrapper.parentNode,d=b.oClasses,e=b.nTable,f=b.nTBody,g=b.nTHead,i=b.nTFoot,j=h(e),f=h(f),k=h(b.nTableWrapper),l=h.map(b.aoData,function(a){return a.nTr}),p;b.bDestroying=!0;w(b,"aoDestroyCallback","destroy",[b]);a||
(new t(b)).columns().visible(!0);k.unbind(".DT").find(":not(tbody *)").unbind(".DT");h(Fa).unbind(".DT-"+b.sInstance);e!=g.parentNode&&(j.children("thead").detach(),j.append(g));i&&e!=i.parentNode&&(j.children("tfoot").detach(),j.append(i));b.aaSorting=[];b.aaSortingFixed=[];xa(b);h(l).removeClass(b.asStripeClasses.join(" "));h("th, td",g).removeClass(d.sSortable+" "+d.sSortableAsc+" "+d.sSortableDesc+" "+d.sSortableNone);b.bJUI&&(h("th span."+d.sSortIcon+", td span."+d.sSortIcon,g).detach(),h("th, td",
g).each(function(){var a=h("div."+d.sSortJUIWrapper,this);h(this).append(a.contents());a.detach()}));f.children().detach();f.append(l);g=a?"remove":"detach";j[g]();k[g]();!a&&c&&(c.insertBefore(e,b.nTableReinsertBefore),j.css("width",b.sDestroyWidth).removeClass(d.sTable),(p=b.asDestroyStripes.length)&&f.children().each(function(a){h(this).addClass(b.asDestroyStripes[a%p])}));c=h.inArray(b,m.settings);-1!==c&&m.settings.splice(c,1)})});h.each(["column","row","cell"],function(a,b){p(b+"s().every()",
function(a){return this.iterator(b,function(d,e,f,g,h){a.call((new t(d))[b](e,"cell"===b?f:k),e,f,g,h)})})});p("i18n()",function(a,b,c){var d=this.context[0],a=P(a)(d.oLanguage);a===k&&(a=b);c!==k&&h.isPlainObject(a)&&(a=a[c]!==k?a[c]:a._);return a.replace("%d",c)});m.version="1.10.9";m.settings=[];m.models={};m.models.oSearch={bCaseInsensitive:!0,sSearch:"",bRegex:!1,bSmart:!0};m.models.oRow={nTr:null,anCells:null,_aData:[],_aSortData:null,_aFilterData:null,_sFilterRow:null,_sRowStripe:"",src:null,
idx:-1};m.models.oColumn={idx:null,aDataSort:null,asSorting:null,bSearchable:null,bSortable:null,bVisible:null,_sManualType:null,_bAttrSrc:!1,fnCreatedCell:null,fnGetData:null,fnSetData:null,mData:null,mRender:null,nTh:null,nTf:null,sClass:null,sContentPadding:null,sDefaultContent:null,sName:null,sSortDataType:"std",sSortingClass:null,sSortingClassJUI:null,sTitle:null,sType:null,sWidth:null,sWidthOrig:null};m.defaults={aaData:null,aaSorting:[[0,"asc"]],aaSortingFixed:[],ajax:null,aLengthMenu:[10,
25,50,100],aoColumns:null,aoColumnDefs:null,aoSearchCols:[],asStripeClasses:null,bAutoWidth:!0,bDeferRender:!1,bDestroy:!1,bFilter:!0,bInfo:!0,bJQueryUI:!1,bLengthChange:!0,bPaginate:!0,bProcessing:!1,bRetrieve:!1,bScrollCollapse:!1,bServerSide:!1,bSort:!0,bSortMulti:!0,bSortCellsTop:!1,bSortClasses:!0,bStateSave:!1,fnCreatedRow:null,fnDrawCallback:null,fnFooterCallback:null,fnFormatNumber:function(a){return a.toString().replace(/\B(?=(\d{3})+(?!\d))/g,this.oLanguage.sThousands)},fnHeaderCallback:null,
fnInfoCallback:null,fnInitComplete:null,fnPreDrawCallback:null,fnRowCallback:null,fnServerData:null,fnServerParams:null,fnStateLoadCallback:function(a){try{return JSON.parse((-1===a.iStateDuration?sessionStorage:localStorage).getItem("DataTables_"+a.sInstance+"_"+location.pathname))}catch(b){}},fnStateLoadParams:null,fnStateLoaded:null,fnStateSaveCallback:function(a,b){try{(-1===a.iStateDuration?sessionStorage:localStorage).setItem("DataTables_"+a.sInstance+"_"+location.pathname,JSON.stringify(b))}catch(c){}},
fnStateSaveParams:null,iStateDuration:7200,iDeferLoading:null,iDisplayLength:10,iDisplayStart:0,iTabIndex:0,oClasses:{},oLanguage:{oAria:{sSortAscending:": activate to sort column ascending",sSortDescending:": activate to sort column descending"},oPaginate:{sFirst:"First",sLast:"Last",sNext:"Next",sPrevious:"Previous"},sEmptyTable:"No data available in table",sInfo:"Showing _START_ to _END_ of _TOTAL_ entries",sInfoEmpty:"Showing 0 to 0 of 0 entries",sInfoFiltered:"(filtered from _MAX_ total entries)",
sInfoPostFix:"",sDecimal:"",sThousands:",",sLengthMenu:"Show _MENU_ entries",sLoadingRecords:"Loading...",sProcessing:"Processing...",sSearch:"Search:",sSearchPlaceholder:"",sUrl:"",sZeroRecords:"No matching records found"},oSearch:h.extend({},m.models.oSearch),sAjaxDataProp:"data",sAjaxSource:null,sDom:"lfrtip",searchDelay:null,sPaginationType:"simple_numbers",sScrollX:"",sScrollXInner:"",sScrollY:"",sServerMethod:"GET",renderer:null,rowId:"DT_RowId"};X(m.defaults);m.defaults.column={aDataSort:null,
iDataSort:-1,asSorting:["asc","desc"],bSearchable:!0,bSortable:!0,bVisible:!0,fnCreatedCell:null,mData:null,mRender:null,sCellType:"td",sClass:"",sContentPadding:"",sDefaultContent:null,sName:"",sSortDataType:"std",sTitle:null,sType:null,sWidth:null};X(m.defaults.column);m.models.oSettings={oFeatures:{bAutoWidth:null,bDeferRender:null,bFilter:null,bInfo:null,bLengthChange:null,bPaginate:null,bProcessing:null,bServerSide:null,bSort:null,bSortMulti:null,bSortClasses:null,bStateSave:null},oScroll:{bCollapse:null,
iBarWidth:0,sX:null,sXInner:null,sY:null},oLanguage:{fnInfoCallback:null},oBrowser:{bScrollOversize:!1,bScrollbarLeft:!1,bBounding:!1,barWidth:0},ajax:null,aanFeatures:[],aoData:[],aiDisplay:[],aiDisplayMaster:[],aIds:{},aoColumns:[],aoHeader:[],aoFooter:[],oPreviousSearch:{},aoPreSearchCols:[],aaSorting:null,aaSortingFixed:[],asStripeClasses:null,asDestroyStripes:[],sDestroyWidth:0,aoRowCallback:[],aoHeaderCallback:[],aoFooterCallback:[],aoDrawCallback:[],aoRowCreatedCallback:[],aoPreDrawCallback:[],
aoInitComplete:[],aoStateSaveParams:[],aoStateLoadParams:[],aoStateLoaded:[],sTableId:"",nTable:null,nTHead:null,nTFoot:null,nTBody:null,nTableWrapper:null,bDeferLoading:!1,bInitialised:!1,aoOpenRows:[],sDom:null,searchDelay:null,sPaginationType:"two_button",iStateDuration:0,aoStateSave:[],aoStateLoad:[],oSavedState:null,oLoadedState:null,sAjaxSource:null,sAjaxDataProp:null,bAjaxDataGet:!0,jqXHR:null,json:k,oAjaxData:k,fnServerData:null,aoServerParams:[],sServerMethod:null,fnFormatNumber:null,aLengthMenu:null,
iDraw:0,bDrawing:!1,iDrawError:-1,_iDisplayLength:10,_iDisplayStart:0,_iRecordsTotal:0,_iRecordsDisplay:0,bJUI:null,oClasses:{},bFiltered:!1,bSorted:!1,bSortCellsTop:null,oInit:null,aoDestroyCallback:[],fnRecordsTotal:function(){return"ssp"==y(this)?1*this._iRecordsTotal:this.aiDisplayMaster.length},fnRecordsDisplay:function(){return"ssp"==y(this)?1*this._iRecordsDisplay:this.aiDisplay.length},fnDisplayEnd:function(){var a=this._iDisplayLength,b=this._iDisplayStart,c=b+a,d=this.aiDisplay.length,e=
this.oFeatures,f=e.bPaginate;return e.bServerSide?!1===f||-1===a?b+d:Math.min(b+a,this._iRecordsDisplay):!f||c>d||-1===a?d:c},oInstance:null,sInstance:null,iTabIndex:0,nScrollHead:null,nScrollFoot:null,aLastSort:[],oPlugins:{},rowIdFn:null,rowId:null};m.ext=v={buttons:{},classes:{},errMode:"alert",feature:[],search:[],selector:{cell:[],column:[],row:[]},internal:{},legacy:{ajax:null},pager:{},renderer:{pageButton:{},header:{}},order:{},type:{detect:[],search:{},order:{}},_unique:0,fnVersionCheck:m.fnVersionCheck,
iApiIndex:0,oJUIClasses:{},sVersion:m.version};h.extend(v,{afnFiltering:v.search,aTypes:v.type.detect,ofnSearch:v.type.search,oSort:v.type.order,afnSortData:v.order,aoFeatures:v.feature,oApi:v.internal,oStdClasses:v.classes,oPagination:v.pager});h.extend(m.ext.classes,{sTable:"dataTable",sNoFooter:"no-footer",sPageButton:"paginate_button",sPageButtonActive:"current",sPageButtonDisabled:"disabled",sStripeOdd:"odd",sStripeEven:"even",sRowEmpty:"dataTables_empty",sWrapper:"dataTables_wrapper",sFilter:"dataTables_filter",
sInfo:"dataTables_info",sPaging:"dataTables_paginate paging_",sLength:"dataTables_length",sProcessing:"dataTables_processing",sSortAsc:"sorting_asc",sSortDesc:"sorting_desc",sSortable:"sorting",sSortableAsc:"sorting_asc_disabled",sSortableDesc:"sorting_desc_disabled",sSortableNone:"sorting_disabled",sSortColumn:"sorting_",sFilterInput:"",sLengthSelect:"",sScrollWrapper:"dataTables_scroll",sScrollHead:"dataTables_scrollHead",sScrollHeadInner:"dataTables_scrollHeadInner",sScrollBody:"dataTables_scrollBody",
sScrollFoot:"dataTables_scrollFoot",sScrollFootInner:"dataTables_scrollFootInner",sHeaderTH:"",sFooterTH:"",sSortJUIAsc:"",sSortJUIDesc:"",sSortJUI:"",sSortJUIAscAllowed:"",sSortJUIDescAllowed:"",sSortJUIWrapper:"",sSortIcon:"",sJUIHeader:"",sJUIFooter:""});var Ea="",Ea="",G=Ea+"ui-state-default",ka=Ea+"css_right ui-icon ui-icon-",Xb=Ea+"fg-toolbar ui-toolbar ui-widget-header ui-helper-clearfix";h.extend(m.ext.oJUIClasses,m.ext.classes,{sPageButton:"fg-button ui-button "+G,sPageButtonActive:"ui-state-disabled",
sPageButtonDisabled:"ui-state-disabled",sPaging:"dataTables_paginate fg-buttonset ui-buttonset fg-buttonset-multi ui-buttonset-multi paging_",sSortAsc:G+" sorting_asc",sSortDesc:G+" sorting_desc",sSortable:G+" sorting",sSortableAsc:G+" sorting_asc_disabled",sSortableDesc:G+" sorting_desc_disabled",sSortableNone:G+" sorting_disabled",sSortJUIAsc:ka+"triangle-1-n",sSortJUIDesc:ka+"triangle-1-s",sSortJUI:ka+"carat-2-n-s",sSortJUIAscAllowed:ka+"carat-1-n",sSortJUIDescAllowed:ka+"carat-1-s",sSortJUIWrapper:"DataTables_sort_wrapper",
sSortIcon:"DataTables_sort_icon",sScrollHead:"dataTables_scrollHead "+G,sScrollFoot:"dataTables_scrollFoot "+G,sHeaderTH:G,sFooterTH:G,sJUIHeader:Xb+" ui-corner-tl ui-corner-tr",sJUIFooter:Xb+" ui-corner-bl ui-corner-br"});var Mb=m.ext.pager;h.extend(Mb,{simple:function(){return["previous","next"]},full:function(){return["first","previous","next","last"]},numbers:function(a,b){return[Aa(a,b)]},simple_numbers:function(a,b){return["previous",Aa(a,b),"next"]},full_numbers:function(a,b){return["first",
"previous",Aa(a,b),"next","last"]},_numbers:Aa,numbers_length:7});h.extend(!0,m.ext.renderer,{pageButton:{_:function(a,b,c,d,e,f){var g=a.oClasses,i=a.oLanguage.oPaginate,j,k,l=0,m=function(b,d){var p,q,t,s,u=function(b){Ta(a,b.data.action,true)};p=0;for(q=d.length;p<q;p++){s=d[p];if(h.isArray(s)){t=h("<"+(s.DT_el||"div")+"/>").appendTo(b);m(t,s)}else{j=null;k="";switch(s){case "ellipsis":b.append('<span class="ellipsis">&#x2026;</span>');break;case "first":j=i.sFirst;k=s+(e>0?"":" "+g.sPageButtonDisabled);
break;case "previous":j=i.sPrevious;k=s+(e>0?"":" "+g.sPageButtonDisabled);break;case "next":j=i.sNext;k=s+(e<f-1?"":" "+g.sPageButtonDisabled);break;case "last":j=i.sLast;k=s+(e<f-1?"":" "+g.sPageButtonDisabled);break;default:j=s+1;k=e===s?g.sPageButtonActive:""}if(j!==null){t=h("<a>",{"class":g.sPageButton+" "+k,"aria-controls":a.sTableId,"data-dt-idx":l,tabindex:a.iTabIndex,id:c===0&&typeof s==="string"?a.sTableId+"_"+s:null}).html(j).appendTo(b);Va(t,{action:s},u);l++}}}},p;try{p=h(b).find(T.activeElement).data("dt-idx")}catch(t){}m(h(b).empty(),
d);p&&h(b).find("[data-dt-idx="+p+"]").focus()}}});h.extend(m.ext.type.detect,[function(a,b){var c=b.oLanguage.sDecimal;return Ya(a,c)?"num"+c:null},function(a){if(a&&!(a instanceof Date)&&(!ac.test(a)||!bc.test(a)))return null;var b=Date.parse(a);return null!==b&&!isNaN(b)||K(a)?"date":null},function(a,b){var c=b.oLanguage.sDecimal;return Ya(a,c,!0)?"num-fmt"+c:null},function(a,b){var c=b.oLanguage.sDecimal;return Rb(a,c)?"html-num"+c:null},function(a,b){var c=b.oLanguage.sDecimal;return Rb(a,c,
!0)?"html-num-fmt"+c:null},function(a){return K(a)||"string"===typeof a&&-1!==a.indexOf("<")?"html":null}]);h.extend(m.ext.type.search,{html:function(a){return K(a)?a:"string"===typeof a?a.replace(Ob," ").replace(Ca,""):""},string:function(a){return K(a)?a:"string"===typeof a?a.replace(Ob," "):a}});var Ba=function(a,b,c,d){if(0!==a&&(!a||"-"===a))return-Infinity;b&&(a=Qb(a,b));a.replace&&(c&&(a=a.replace(c,"")),d&&(a=a.replace(d,"")));return 1*a};h.extend(v.type.order,{"date-pre":function(a){return Date.parse(a)||
0},"html-pre":function(a){return K(a)?"":a.replace?a.replace(/<.*?>/g,"").toLowerCase():a+""},"string-pre":function(a){return K(a)?"":"string"===typeof a?a.toLowerCase():!a.toString?"":a.toString()},"string-asc":function(a,b){return a<b?-1:a>b?1:0},"string-desc":function(a,b){return a<b?1:a>b?-1:0}});cb("");h.extend(!0,m.ext.renderer,{header:{_:function(a,b,c,d){h(a.nTable).on("order.dt.DT",function(e,f,g,h){if(a===f){e=c.idx;b.removeClass(c.sSortingClass+" "+d.sSortAsc+" "+d.sSortDesc).addClass(h[e]==
"asc"?d.sSortAsc:h[e]=="desc"?d.sSortDesc:c.sSortingClass)}})},jqueryui:function(a,b,c,d){h("<div/>").addClass(d.sSortJUIWrapper).append(b.contents()).append(h("<span/>").addClass(d.sSortIcon+" "+c.sSortingClassJUI)).appendTo(b);h(a.nTable).on("order.dt.DT",function(e,f,g,h){if(a===f){e=c.idx;b.removeClass(d.sSortAsc+" "+d.sSortDesc).addClass(h[e]=="asc"?d.sSortAsc:h[e]=="desc"?d.sSortDesc:c.sSortingClass);b.find("span."+d.sSortIcon).removeClass(d.sSortJUIAsc+" "+d.sSortJUIDesc+" "+d.sSortJUI+" "+
d.sSortJUIAscAllowed+" "+d.sSortJUIDescAllowed).addClass(h[e]=="asc"?d.sSortJUIAsc:h[e]=="desc"?d.sSortJUIDesc:c.sSortingClassJUI)}})}}});m.render={number:function(a,b,c,d,e){return{display:function(f){if("number"!==typeof f&&"string"!==typeof f)return f;var g=0>f?"-":"",f=Math.abs(parseFloat(f)),h=parseInt(f,10),f=c?b+(f-h).toFixed(c).substring(2):"";return g+(d||"")+h.toString().replace(/\B(?=(\d{3})+(?!\d))/g,a)+f+(e||"")}}}};h.extend(m.ext.internal,{_fnExternApiFunc:Nb,_fnBuildAjax:ra,_fnAjaxUpdate:lb,
_fnAjaxParameters:ub,_fnAjaxUpdateDraw:vb,_fnAjaxDataSrc:sa,_fnAddColumn:Ga,_fnColumnOptions:la,_fnAdjustColumnSizing:Y,_fnVisibleToColumnIndex:$,_fnColumnIndexToVisible:ba,_fnVisbleColumns:ca,_fnGetColumns:aa,_fnColumnTypes:Ia,_fnApplyColumnDefs:hb,_fnHungarianMap:X,_fnCamelToHungarian:I,_fnLanguageCompat:S,_fnBrowserDetect:fb,_fnAddData:L,_fnAddTr:ma,_fnNodeToDataIndex:function(a,b){return b._DT_RowIndex!==k?b._DT_RowIndex:null},_fnNodeToColumnIndex:function(a,b,c){return h.inArray(c,a.aoData[b].anCells)},
_fnGetCellData:B,_fnSetCellData:ib,_fnSplitObjNotation:La,_fnGetObjectDataFn:P,_fnSetObjectDataFn:Q,_fnGetDataMaster:Ma,_fnClearTable:na,_fnDeleteIndex:oa,_fnInvalidate:ea,_fnGetRowElements:Ka,_fnCreateTr:Ja,_fnBuildHead:kb,_fnDrawHead:ga,_fnDraw:M,_fnReDraw:R,_fnAddOptionsHtml:nb,_fnDetectHeader:fa,_fnGetUniqueThs:qa,_fnFeatureHtmlFilter:pb,_fnFilterComplete:ha,_fnFilterCustom:yb,_fnFilterColumn:xb,_fnFilter:wb,_fnFilterCreateSearch:Qa,_fnEscapeRegex:va,_fnFilterData:zb,_fnFeatureHtmlInfo:sb,_fnUpdateInfo:Cb,
_fnInfoMacros:Db,_fnInitialise:ia,_fnInitComplete:ta,_fnLengthChange:Ra,_fnFeatureHtmlLength:ob,_fnFeatureHtmlPaginate:tb,_fnPageChange:Ta,_fnFeatureHtmlProcessing:qb,_fnProcessingDisplay:C,_fnFeatureHtmlTable:rb,_fnScrollDraw:Z,_fnApplyToChildren:H,_fnCalculateColumnWidths:Ha,_fnThrottle:ua,_fnConvertToWidth:Fb,_fnGetWidestNode:Gb,_fnGetMaxLenString:Hb,_fnStringToCss:u,_fnSortFlatten:V,_fnSort:mb,_fnSortAria:Jb,_fnSortListener:Ua,_fnSortAttachListener:Oa,_fnSortingClasses:xa,_fnSortData:Ib,_fnSaveState:ya,
_fnLoadState:Kb,_fnSettingsFromNode:za,_fnLog:J,_fnMap:F,_fnBindAction:Va,_fnCallbackReg:z,_fnCallbackFire:w,_fnLengthOverflow:Sa,_fnRenderer:Pa,_fnDataSource:y,_fnRowAttributes:Na,_fnCalculateEnd:function(){}});h.fn.dataTable=m;h.fn.dataTableSettings=m.settings;h.fn.dataTableExt=m.ext;h.fn.DataTable=function(a){return h(this).dataTable(a).api()};h.each(m,function(a,b){h.fn.DataTable[a]=b});return h.fn.dataTable};"function"===typeof define&&define.amd?define("datatables",["jquery"],S):"object"===
typeof exports?module.exports=S(require("jquery")):jQuery&&!jQuery.fn.dataTable&&S(jQuery)})(window,document);

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -1,544 +0,0 @@
.typeahead__container {
/**
* Restore the font weight unset by the previous rule.
*/
/**
* Show the overflow in IE.
* 1. Show the overflow in Edge.
*/
/**
* Remove the inheritance of text transform in Edge, Firefox, and IE.
* 1. Remove the inheritance of text transform in Firefox.
*/
/**
* 1. Prevent a WebKit bug where (2) destroys native `audio` and `video`
* controls in Android 4.
* 2. Correct the inability to style clickable types in iOS and Safari.
*/
/**
* Remove the inner border and padding in Firefox.
*/
/**
* Restore the focus styles unset by the previous rule.
*/
/**
* Change the border, margin, and padding in all browsers (opinionated).
*/
/**
* 1. Correct the text wrapping in Edge and IE.
* 2. Correct the color inheritance from `fieldset` elements in IE.
* 3. Remove the padding so developers are not caught out when they zero out
* `fieldset` elements in all browsers.
*/
/**
* Remove the default vertical scrollbar in IE.
*/
/**
* 1. Add the correct box sizing in IE 10-.
* 2. Remove the padding in IE 10-.
*/
/**
* Correct the cursor style of increment and decrement buttons in Chrome.
*/
/**
* 1. Correct the odd appearance in Chrome and Safari.
* 2. Correct the outline style in Safari.
*/
/**
* Remove the inner padding and cancel buttons in Chrome and Safari on OS X.
*/
/**
* Correct the text style of placeholders in Chrome, Edge, and Safari.
*/
/**
* 1. Correct the inability to style clickable types in iOS and Safari.
* 2. Change font properties to `inherit` in Safari.
*/
}
.typeahead__container button,
.typeahead__container input,
.typeahead__container optgroup,
.typeahead__container select,
.typeahead__container textarea {
font: inherit;
/* 1 */
margin: 0;
/* 2 */
}
.typeahead__container optgroup {
font-weight: bold;
}
.typeahead__container button,
.typeahead__container input {
/* 1 */
overflow: visible;
}
.typeahead__container button,
.typeahead__container select {
/* 1 */
text-transform: none;
}
.typeahead__container button,
.typeahead__container html [type="button"],
.typeahead__container [type="reset"],
.typeahead__container [type="submit"] {
-webkit-appearance: button;
/* 2 */
}
.typeahead__container button::-moz-focus-inner,
.typeahead__container [type="button"]::-moz-focus-inner,
.typeahead__container [type="reset"]::-moz-focus-inner,
.typeahead__container [type="submit"]::-moz-focus-inner {
border-style: none;
padding: 0;
}
.typeahead__container button:-moz-focusring,
.typeahead__container [type="button"]:-moz-focusring,
.typeahead__container [type="reset"]:-moz-focusring,
.typeahead__container [type="submit"]:-moz-focusring {
outline: 1px dotted ButtonText;
}
.typeahead__container fieldset {
border: 1px solid #c0c0c0;
margin: 0 2px;
padding: 0.35em 0.625em 0.75em;
}
.typeahead__container legend {
box-sizing: border-box;
/* 1 */
color: inherit;
/* 2 */
display: table;
/* 1 */
max-width: 100%;
/* 1 */
padding: 0;
/* 3 */
white-space: normal;
/* 1 */
}
.typeahead__container textarea {
overflow: auto;
}
.typeahead__container [type="checkbox"],
.typeahead__container [type="radio"] {
box-sizing: border-box;
/* 1 */
padding: 0;
/* 2 */
}
.typeahead__container [type="number"]::-webkit-inner-spin-button,
.typeahead__container [type="number"]::-webkit-outer-spin-button {
height: auto;
}
.typeahead__container [type="search"] {
-webkit-appearance: textfield;
/* 1 */
outline-offset: -2px;
/* 2 */
}
.typeahead__container [type="search"]::-webkit-search-cancel-button,
.typeahead__container [type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}
.typeahead__container ::-webkit-input-placeholder {
color: inherit;
opacity: 0.54;
}
.typeahead__container ::-webkit-file-upload-button {
-webkit-appearance: button;
/* 1 */
font: inherit;
/* 2 */
}
.typeahead__container {
position: relative;
font: 14px Lato, "Helvetica Neue", Arial, Helvetica, sans-serif;
}
.typeahead__container * {
box-sizing: border-box;
outline: 0;
}
.typeahead__query {
position: relative;
z-index: 2;
width: 100%;
}
.typeahead__filter {
position: relative;
}
.typeahead__filter button {
min-width: 100%;
white-space: nowrap;
}
.typeahead__filter button:after {
display: inline-block;
margin-left: 4px;
width: 0;
height: 0;
vertical-align: -2px;
content: "";
border: 4px solid;
border-right-color: transparent;
border-bottom-color: transparent;
border-left-color: transparent;
}
.typeahead__field {
font-size: 0;
position: relative;
display: table;
border-collapse: collapse;
width: 100%;
}
.typeahead__field > * {
display: table-cell;
vertical-align: top;
}
.typeahead__query, .typeahead__filter, .typeahead__button {
font-size: 14px;
}
.typeahead__button {
position: relative;
font-size: 0;
width: 1%;
vertical-align: middle;
}
.typeahead__button button {
border-top-right-radius: 2px;
border-bottom-right-radius: 2px;
}
.typeahead__field {
color: #555;
}
.typeahead__field input {
display: block;
width: 100%;
height: 32px;
padding: 6px 12px;
background: #fff;
border: 1px solid #ccc;
border-radius: 2px 0 0 2px;
transition: all ease-in-out .15s;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
box-sizing: border-box;
}
.typeahead__field input:focus, .typeahead__field input:active {
border-color: #66afe9;
}
.typeahead__field input[type="search"],
.typeahead__field input[type="search"]::-webkit-search-cancel-button {
-webkit-appearance: none;
appearance: none;
}
.typeahead__field input[type="search"]::-ms-clear {
display: none;
width: 0;
height: 0;
}
.typeahead__container.hint .typeahead__field input {
background: transparent;
}
.typeahead__container.hint .typeahead__field input:last-child, .typeahead__hint {
background: #fff;
}
.typeahead__container button {
display: inline-block;
margin-bottom: 0;
text-align: center;
vertical-align: middle;
-ms-touch-action: manipulation;
touch-action: manipulation;
cursor: pointer;
background-color: #fff;
border: 1px solid #ccc;
height: 32px;
padding: 6px 12px;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
color: #555;
}
.typeahead__container button:hover, .typeahead__container button:focus {
color: #3c3c3c;
background-color: #f5f5f5;
border-color: #b3b3b3;
}
.typeahead__container button:active, .typeahead__container button.active {
background-image: none;
}
.typeahead__container button:focus, .typeahead__container button:active {
border-color: #66afe9;
}
.typeahead__container input.disabled,
.typeahead__container input[disabled],
.typeahead__container button.disabled,
.typeahead__container button[disabled] {
cursor: not-allowed;
pointer-events: none;
opacity: 0.65;
box-shadow: none;
background-color: #fff;
border-color: #ccc;
}
.typeahead__filter, .typeahead__button {
z-index: 1;
}
.typeahead__filter button, .typeahead__button button {
margin-left: -1px;
border-bottom-left-radius: 0;
border-top-left-radius: 0;
}
.typeahead__filter:hover, .typeahead__filter:active, .typeahead__filter:focus, .typeahead__button:hover, .typeahead__button:active, .typeahead__button:focus {
z-index: 1001;
}
.typeahead__filter:hover button:focus, .typeahead__filter:hover button:active, .typeahead__filter:active button:focus, .typeahead__filter:active button:active, .typeahead__filter:focus button:focus, .typeahead__filter:focus button:active, .typeahead__button:hover button:focus, .typeahead__button:hover button:active, .typeahead__button:active button:focus, .typeahead__button:active button:active, .typeahead__button:focus button:focus, .typeahead__button:focus button:active {
z-index: 1001;
}
.typeahead__filter + .typeahead__button button {
margin-left: -2px;
}
.typeahead__container.filter .typeahead__filter {
z-index: 1001;
}
.typeahead__list, .typeahead__dropdown {
position: absolute;
top: 100%;
left: 0;
z-index: 1000;
width: 100%;
min-width: 160px;
padding: 5px 0;
margin: 2px 0 0;
list-style: none;
text-align: left;
background-color: #fff;
border: 1px solid #ccc;
border-radius: 2px;
background-clip: padding-box;
}
.typeahead__result.detached .typeahead__list {
position: relative;
z-index: 1041;
top: initial;
left: initial;
}
.typeahead__dropdown {
right: 0;
left: initial;
z-index: 1001;
}
.typeahead__list > li {
position: relative;
border-top: solid 1px #ccc;
}
.typeahead__list > li:first-child {
border-top: none;
}
.typeahead__list > li > a,
.typeahead__dropdown > li > a {
display: block;
padding: 6px 12px;
clear: both;
color: #333333;
text-decoration: none;
}
.typeahead__list > li > a:hover,
.typeahead__list > li > a:focus,
.typeahead__list > li.active > a,
.typeahead__dropdown > li > a:hover,
.typeahead__dropdown > li > a:focus,
.typeahead__dropdown > li.active > a {
background-color: #f5f5f5;
color: #3c3c3c;
}
.typeahead__list.empty > li > a {
cursor: default;
}
.typeahead__list.empty > li > a:hover,
.typeahead__list.empty > li > a:focus,
.typeahead__list.empty > li.active > a {
background-color: transparent;
}
.typeahead__list > li.typeahead__group {
border-color: #bfdef6;
font-weight: bold;
}
.typeahead__list > li.typeahead__group:first-child {
border-top: solid 1px #bfdef6;
}
.typeahead__list > li.typeahead__group > a,
.typeahead__list > li.typeahead__group > a:hover,
.typeahead__list > li.typeahead__group > a:focus,
.typeahead__list > li.typeahead__group.active > a {
cursor: default;
color: #17639f;
background: #ecf5fc;
}
.typeahead__list > li.typeahead__group + li.typeahead__item {
border-color: #bfdef6;
}
.typeahead__container.result .typeahead__list,
.typeahead__container.filter .typeahead__dropdown,
.typeahead__container.hint .typeahead__hint,
.typeahead__container.backdrop + .typeahead__backdrop {
display: block !important;
}
.typeahead__container .typeahead__list,
.typeahead__container .typeahead__dropdown,
.typeahead__container .typeahead__hint,
.typeahead__container + .typeahead__backdrop {
display: none !important;
}
.typeahead__dropdown li:last-child {
margin-top: 5px;
padding-top: 5px;
border-top: solid 1px #ccc;
}
.typeahead__cancel-button {
visibility: hidden;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
border-radius: 50%;
width: 16px;
height: 16px;
position: absolute;
top: 8px;
right: .8em;
cursor: pointer;
background: url(data:image/svg+xml;charset=utf8;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pgo8IS0tIEdlbmVyYXRvcjogQWRvYmUgSWxsdXN0cmF0b3IgMTYuMC4wLCBTVkcgRXhwb3J0IFBsdWctSW4gLiBTVkcgVmVyc2lvbjogNi4wMCBCdWlsZCAwKSAgLS0+CjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0iQ2FwYV8xIiB4PSIwcHgiIHk9IjBweCIgd2lkdGg9IjE2cHgiIGhlaWdodD0iMTZweCIgdmlld0JveD0iMCAwIDQzOC41MzMgNDM4LjUzMyIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgNDM4LjUzMyA0MzguNTMzOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+CjxnPgoJPHBhdGggZD0iTTQwOS4xMzMsMTA5LjIwM2MtMTkuNjA4LTMzLjU5Mi00Ni4yMDUtNjAuMTg5LTc5Ljc5OC03OS43OTZDMjk1LjczNiw5LjgwMSwyNTkuMDU4LDAsMjE5LjI3MywwICAgYy0zOS43ODEsMC03Ni40Nyw5LjgwMS0xMTAuMDYzLDI5LjQwN2MtMzMuNTk1LDE5LjYwNC02MC4xOTIsNDYuMjAxLTc5LjgsNzkuNzk2QzkuODAxLDE0Mi44LDAsMTc5LjQ4OSwwLDIxOS4yNjcgICBjMCwzOS43OCw5LjgwNCw3Ni40NjMsMjkuNDA3LDExMC4wNjJjMTkuNjA3LDMzLjU5Miw0Ni4yMDQsNjAuMTg5LDc5Ljc5OSw3OS43OThjMzMuNTk3LDE5LjYwNSw3MC4yODMsMjkuNDA3LDExMC4wNjMsMjkuNDA3ICAgczc2LjQ3LTkuODAyLDExMC4wNjUtMjkuNDA3YzMzLjU5My0xOS42MDIsNjAuMTg5LTQ2LjIwNiw3OS43OTUtNzkuNzk4YzE5LjYwMy0zMy41OTYsMjkuNDAzLTcwLjI4NCwyOS40MDMtMTEwLjA2MiAgIEM0MzguNTMzLDE3OS40ODUsNDI4LjczMiwxNDIuNzk1LDQwOS4xMzMsMTA5LjIwM3ogTTMyMi42MjEsMjcwLjkzOWMzLjYxNywzLjYxMyw1LjQyOCw3LjkwNSw1LjQyOCwxMi44NTQgICBjMCw1LjEzMy0xLjgxMSw5LjUxNC01LjQyOCwxMy4xMjdsLTI1LjY5MywyNS43MDFjLTMuNjE0LDMuNjEzLTcuOTk0LDUuNDItMTMuMTM1LDUuNDJjLTQuOTQ4LDAtOS4yMzYtMS44MDctMTIuODQ3LTUuNDIgICBsLTUxLjY3Ni01MS42ODJsLTUxLjY3OCw1MS42ODJjLTMuNjE2LDMuNjEzLTcuODk4LDUuNDItMTIuODQ3LDUuNDJjLTUuMTQsMC05LjUxNy0xLjgwNy0xMy4xMzQtNS40MmwtMjUuNjk3LTI1LjcwMSAgIGMtMy42MTYtMy42MTMtNS40MjQtNy45OTQtNS40MjQtMTMuMTI3YzAtNC45NDgsMS44MDktOS4yNCw1LjQyNC0xMi44NTRsNTEuNjc4LTUxLjY3M2wtNTEuNjc4LTUxLjY3OCAgIGMtMy42MTYtMy42MTItNS40MjQtNy44OTgtNS40MjQtMTIuODQ3YzAtNS4xNCwxLjgwOS05LjUxNyw1LjQyNC0xMy4xMzRsMjUuNjk3LTI1LjY5M2MzLjYxNy0zLjYxNiw3Ljk5NC01LjQyNCwxMy4xMzQtNS40MjQgICBjNC45NDksMCw5LjIzMSwxLjgwOSwxMi44NDcsNS40MjRsNTEuNjc4LDUxLjY3NGw1MS42NzYtNTEuNjc0YzMuNjEtMy42MTYsNy44OTgtNS40MjQsMTIuODQ3LTUuNDI0ICAgYzUuMTQxLDAsOS41MjEsMS44MDksMTMuMTM1LDUuNDI0bDI1LjY5MywyNS42OTNjMy42MTcsMy42MTcsNS40MjgsNy45OTQsNS40MjgsMTMuMTM0YzAsNC45NDgtMS44MTEsOS4yMzUtNS40MjgsMTIuODQ3ICAgbC01MS42NzUsNTEuNjc4TDMyMi42MjEsMjcwLjkzOXoiIGZpbGw9IiM1NTU1NTUiLz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8L3N2Zz4K) no-repeat scroll center center transparent;
}
.typeahead__container.cancel:not(.loading) .typeahead__cancel-button {
visibility: visible;
opacity: .25;
}
.typeahead__container.cancel:not(.loading) .typeahead__cancel-button:hover {
opacity: .4;
}
.typeahead__search-icon {
padding: 0 1.25rem;
width: 16px;
height: 16px;
display: block;
background: url(data:image/svg+xml;charset=utf8;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pgo8IS0tIEdlbmVyYXRvcjogQWRvYmUgSWxsdXN0cmF0b3IgMTguMS4xLCBTVkcgRXhwb3J0IFBsdWctSW4gLiBTVkcgVmVyc2lvbjogNi4wMCBCdWlsZCAwKSAgLS0+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0iQ2FwYV8xIiB4PSIwcHgiIHk9IjBweCIgdmlld0JveD0iMCAwIDI1MC4zMTMgMjUwLjMxMyIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgMjUwLjMxMyAyNTAuMzEzOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSIgd2lkdGg9IjE2cHgiIGhlaWdodD0iMTZweCI+CjxnIGlkPSJTZWFyY2giPgoJPHBhdGggc3R5bGU9ImZpbGwtcnVsZTpldmVub2RkO2NsaXAtcnVsZTpldmVub2RkOyIgZD0iTTI0NC4xODYsMjE0LjYwNGwtNTQuMzc5LTU0LjM3OGMtMC4yODktMC4yODktMC42MjgtMC40OTEtMC45My0wLjc2ICAgYzEwLjctMTYuMjMxLDE2Ljk0NS0zNS42NiwxNi45NDUtNTYuNTU0QzIwNS44MjIsNDYuMDc1LDE1OS43NDcsMCwxMDIuOTExLDBTMCw0Ni4wNzUsMCwxMDIuOTExICAgYzAsNTYuODM1LDQ2LjA3NCwxMDIuOTExLDEwMi45MSwxMDIuOTExYzIwLjg5NSwwLDQwLjMyMy02LjI0NSw1Ni41NTQtMTYuOTQ1YzAuMjY5LDAuMzAxLDAuNDcsMC42NCwwLjc1OSwwLjkyOWw1NC4zOCw1NC4zOCAgIGM4LjE2OSw4LjE2OCwyMS40MTMsOC4xNjgsMjkuNTgzLDBDMjUyLjM1NCwyMzYuMDE3LDI1Mi4zNTQsMjIyLjc3MywyNDQuMTg2LDIxNC42MDR6IE0xMDIuOTExLDE3MC4xNDYgICBjLTM3LjEzNCwwLTY3LjIzNi0zMC4xMDItNjcuMjM2LTY3LjIzNWMwLTM3LjEzNCwzMC4xMDMtNjcuMjM2LDY3LjIzNi02Ny4yMzZjMzcuMTMyLDAsNjcuMjM1LDMwLjEwMyw2Ny4yMzUsNjcuMjM2ICAgQzE3MC4xNDYsMTQwLjA0NCwxNDAuMDQzLDE3MC4xNDYsMTAyLjkxMSwxNzAuMTQ2eiIgZmlsbD0iIzU1NTU1NSIvPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+Cjwvc3ZnPgo=) no-repeat scroll center center transparent;
}
.typeahead__container.loading .typeahead__query:before, .typeahead__container.loading .typeahead__query:after {
transition: all 0s linear, opacity 0.2s ease;
position: absolute;
z-index: 3;
content: '';
top: 50%;
right: .55em;
margin-top: -10.5px;
width: 21px;
height: 21px;
box-sizing: border-box;
border-radius: 500rem;
border-style: solid;
border-width: .1em;
}
.typeahead__container.loading .typeahead__query:before {
border-color: rgba(0, 0, 0, 0.35);
}
.typeahead__container.loading .typeahead__query:after {
-webkit-animation: button-spin 0.6s linear;
animation: button-spin 0.6s linear;
-webkit-animation-iteration-count: infinite;
animation-iteration-count: infinite;
border-color: #fff transparent transparent;
box-shadow: 0 0 0 1px transparent;
}
@-webkit-keyframes button-spin {
from {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
to {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@keyframes button-spin {
from {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
to {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,497 +0,0 @@
/*!
* jQuery UI Position v1.10.0
* http://jqueryui.com
*
* Copyright 2013 jQuery Foundation and other contributors
* Released under the MIT license.
* http://jquery.org/license
*
* http://api.jqueryui.com/position/
*/
(function( $, undefined ) {
$.ui = $.ui || {};
var cachedScrollbarWidth,
max = Math.max,
abs = Math.abs,
round = Math.round,
rhorizontal = /left|center|right/,
rvertical = /top|center|bottom/,
roffset = /[\+\-]\d+%?/,
rposition = /^\w+/,
rpercent = /%$/,
_position = $.fn.position;
function getOffsets( offsets, width, height ) {
return [
parseInt( offsets[ 0 ], 10 ) * ( rpercent.test( offsets[ 0 ] ) ? width / 100 : 1 ),
parseInt( offsets[ 1 ], 10 ) * ( rpercent.test( offsets[ 1 ] ) ? height / 100 : 1 )
];
}
function parseCss( element, property ) {
return parseInt( $.css( element, property ), 10 ) || 0;
}
function getDimensions( elem ) {
var raw = elem[0];
if ( raw.nodeType === 9 ) {
return {
width: elem.width(),
height: elem.height(),
offset: { top: 0, left: 0 }
};
}
if ( $.isWindow( raw ) ) {
return {
width: elem.width(),
height: elem.height(),
offset: { top: elem.scrollTop(), left: elem.scrollLeft() }
};
}
if ( raw.preventDefault ) {
return {
width: 0,
height: 0,
offset: { top: raw.pageY, left: raw.pageX }
};
}
return {
width: elem.outerWidth(),
height: elem.outerHeight(),
offset: elem.offset()
};
}
$.position = {
scrollbarWidth: function() {
if ( cachedScrollbarWidth !== undefined ) {
return cachedScrollbarWidth;
}
var w1, w2,
div = $( "<div style='display:block;width:50px;height:50px;overflow:hidden;'><div style='height:100px;width:auto;'></div></div>" ),
innerDiv = div.children()[0];
$( "body" ).append( div );
w1 = innerDiv.offsetWidth;
div.css( "overflow", "scroll" );
w2 = innerDiv.offsetWidth;
if ( w1 === w2 ) {
w2 = div[0].clientWidth;
}
div.remove();
return (cachedScrollbarWidth = w1 - w2);
},
getScrollInfo: function( within ) {
var overflowX = within.isWindow ? "" : within.element.css( "overflow-x" ),
overflowY = within.isWindow ? "" : within.element.css( "overflow-y" ),
hasOverflowX = overflowX === "scroll" ||
( overflowX === "auto" && within.width < within.element[0].scrollWidth ),
hasOverflowY = overflowY === "scroll" ||
( overflowY === "auto" && within.height < within.element[0].scrollHeight );
return {
width: hasOverflowX ? $.position.scrollbarWidth() : 0,
height: hasOverflowY ? $.position.scrollbarWidth() : 0
};
},
getWithinInfo: function( element ) {
var withinElement = $( element || window ),
isWindow = $.isWindow( withinElement[0] );
return {
element: withinElement,
isWindow: isWindow,
offset: withinElement.offset() || { left: 0, top: 0 },
scrollLeft: withinElement.scrollLeft(),
scrollTop: withinElement.scrollTop(),
width: isWindow ? withinElement.width() : withinElement.outerWidth(),
height: isWindow ? withinElement.height() : withinElement.outerHeight()
};
}
};
$.fn.position = function( options ) {
if ( !options || !options.of ) {
return _position.apply( this, arguments );
}
// make a copy, we don't want to modify arguments
options = $.extend( {}, options );
var atOffset, targetWidth, targetHeight, targetOffset, basePosition, dimensions,
target = $( options.of ),
within = $.position.getWithinInfo( options.within ),
scrollInfo = $.position.getScrollInfo( within ),
collision = ( options.collision || "flip" ).split( " " ),
offsets = {};
dimensions = getDimensions( target );
if ( target[0].preventDefault ) {
// force left top to allow flipping
options.at = "left top";
}
targetWidth = dimensions.width;
targetHeight = dimensions.height;
targetOffset = dimensions.offset;
// clone to reuse original targetOffset later
basePosition = $.extend( {}, targetOffset );
// force my and at to have valid horizontal and vertical positions
// if a value is missing or invalid, it will be converted to center
$.each( [ "my", "at" ], function() {
var pos = ( options[ this ] || "" ).split( " " ),
horizontalOffset,
verticalOffset;
if ( pos.length === 1) {
pos = rhorizontal.test( pos[ 0 ] ) ?
pos.concat( [ "center" ] ) :
rvertical.test( pos[ 0 ] ) ?
[ "center" ].concat( pos ) :
[ "center", "center" ];
}
pos[ 0 ] = rhorizontal.test( pos[ 0 ] ) ? pos[ 0 ] : "center";
pos[ 1 ] = rvertical.test( pos[ 1 ] ) ? pos[ 1 ] : "center";
// calculate offsets
horizontalOffset = roffset.exec( pos[ 0 ] );
verticalOffset = roffset.exec( pos[ 1 ] );
offsets[ this ] = [
horizontalOffset ? horizontalOffset[ 0 ] : 0,
verticalOffset ? verticalOffset[ 0 ] : 0
];
// reduce to just the positions without the offsets
options[ this ] = [
rposition.exec( pos[ 0 ] )[ 0 ],
rposition.exec( pos[ 1 ] )[ 0 ]
];
});
// normalize collision option
if ( collision.length === 1 ) {
collision[ 1 ] = collision[ 0 ];
}
if ( options.at[ 0 ] === "right" ) {
basePosition.left += targetWidth;
} else if ( options.at[ 0 ] === "center" ) {
basePosition.left += targetWidth / 2;
}
if ( options.at[ 1 ] === "bottom" ) {
basePosition.top += targetHeight;
} else if ( options.at[ 1 ] === "center" ) {
basePosition.top += targetHeight / 2;
}
atOffset = getOffsets( offsets.at, targetWidth, targetHeight );
basePosition.left += atOffset[ 0 ];
basePosition.top += atOffset[ 1 ];
return this.each(function() {
var collisionPosition, using,
elem = $( this ),
elemWidth = elem.outerWidth(),
elemHeight = elem.outerHeight(),
marginLeft = parseCss( this, "marginLeft" ),
marginTop = parseCss( this, "marginTop" ),
collisionWidth = elemWidth + marginLeft + parseCss( this, "marginRight" ) + scrollInfo.width,
collisionHeight = elemHeight + marginTop + parseCss( this, "marginBottom" ) + scrollInfo.height,
position = $.extend( {}, basePosition ),
myOffset = getOffsets( offsets.my, elem.outerWidth(), elem.outerHeight() );
if ( options.my[ 0 ] === "right" ) {
position.left -= elemWidth;
} else if ( options.my[ 0 ] === "center" ) {
position.left -= elemWidth / 2;
}
if ( options.my[ 1 ] === "bottom" ) {
position.top -= elemHeight;
} else if ( options.my[ 1 ] === "center" ) {
position.top -= elemHeight / 2;
}
position.left += myOffset[ 0 ];
position.top += myOffset[ 1 ];
// if the browser doesn't support fractions, then round for consistent results
if ( !$.support.offsetFractions ) {
position.left = round( position.left );
position.top = round( position.top );
}
collisionPosition = {
marginLeft: marginLeft,
marginTop: marginTop
};
$.each( [ "left", "top" ], function( i, dir ) {
if ( $.ui.position[ collision[ i ] ] ) {
$.ui.position[ collision[ i ] ][ dir ]( position, {
targetWidth: targetWidth,
targetHeight: targetHeight,
elemWidth: elemWidth,
elemHeight: elemHeight,
collisionPosition: collisionPosition,
collisionWidth: collisionWidth,
collisionHeight: collisionHeight,
offset: [ atOffset[ 0 ] + myOffset[ 0 ], atOffset [ 1 ] + myOffset[ 1 ] ],
my: options.my,
at: options.at,
within: within,
elem : elem
});
}
});
if ( options.using ) {
// adds feedback as second argument to using callback, if present
using = function( props ) {
var left = targetOffset.left - position.left,
right = left + targetWidth - elemWidth,
top = targetOffset.top - position.top,
bottom = top + targetHeight - elemHeight,
feedback = {
target: {
element: target,
left: targetOffset.left,
top: targetOffset.top,
width: targetWidth,
height: targetHeight
},
element: {
element: elem,
left: position.left,
top: position.top,
width: elemWidth,
height: elemHeight
},
horizontal: right < 0 ? "left" : left > 0 ? "right" : "center",
vertical: bottom < 0 ? "top" : top > 0 ? "bottom" : "middle"
};
if ( targetWidth < elemWidth && abs( left + right ) < targetWidth ) {
feedback.horizontal = "center";
}
if ( targetHeight < elemHeight && abs( top + bottom ) < targetHeight ) {
feedback.vertical = "middle";
}
if ( max( abs( left ), abs( right ) ) > max( abs( top ), abs( bottom ) ) ) {
feedback.important = "horizontal";
} else {
feedback.important = "vertical";
}
options.using.call( this, props, feedback );
};
}
elem.offset( $.extend( position, { using: using } ) );
});
};
$.ui.position = {
fit: {
left: function( position, data ) {
var within = data.within,
withinOffset = within.isWindow ? within.scrollLeft : within.offset.left,
outerWidth = within.width,
collisionPosLeft = position.left - data.collisionPosition.marginLeft,
overLeft = withinOffset - collisionPosLeft,
overRight = collisionPosLeft + data.collisionWidth - outerWidth - withinOffset,
newOverRight;
// element is wider than within
if ( data.collisionWidth > outerWidth ) {
// element is initially over the left side of within
if ( overLeft > 0 && overRight <= 0 ) {
newOverRight = position.left + overLeft + data.collisionWidth - outerWidth - withinOffset;
position.left += overLeft - newOverRight;
// element is initially over right side of within
} else if ( overRight > 0 && overLeft <= 0 ) {
position.left = withinOffset;
// element is initially over both left and right sides of within
} else {
if ( overLeft > overRight ) {
position.left = withinOffset + outerWidth - data.collisionWidth;
} else {
position.left = withinOffset;
}
}
// too far left -> align with left edge
} else if ( overLeft > 0 ) {
position.left += overLeft;
// too far right -> align with right edge
} else if ( overRight > 0 ) {
position.left -= overRight;
// adjust based on position and margin
} else {
position.left = max( position.left - collisionPosLeft, position.left );
}
},
top: function( position, data ) {
var within = data.within,
withinOffset = within.isWindow ? within.scrollTop : within.offset.top,
outerHeight = data.within.height,
collisionPosTop = position.top - data.collisionPosition.marginTop,
overTop = withinOffset - collisionPosTop,
overBottom = collisionPosTop + data.collisionHeight - outerHeight - withinOffset,
newOverBottom;
// element is taller than within
if ( data.collisionHeight > outerHeight ) {
// element is initially over the top of within
if ( overTop > 0 && overBottom <= 0 ) {
newOverBottom = position.top + overTop + data.collisionHeight - outerHeight - withinOffset;
position.top += overTop - newOverBottom;
// element is initially over bottom of within
} else if ( overBottom > 0 && overTop <= 0 ) {
position.top = withinOffset;
// element is initially over both top and bottom of within
} else {
if ( overTop > overBottom ) {
position.top = withinOffset + outerHeight - data.collisionHeight;
} else {
position.top = withinOffset;
}
}
// too far up -> align with top
} else if ( overTop > 0 ) {
position.top += overTop;
// too far down -> align with bottom edge
} else if ( overBottom > 0 ) {
position.top -= overBottom;
// adjust based on position and margin
} else {
position.top = max( position.top - collisionPosTop, position.top );
}
}
},
flip: {
left: function( position, data ) {
var within = data.within,
withinOffset = within.offset.left + within.scrollLeft,
outerWidth = within.width,
offsetLeft = within.isWindow ? within.scrollLeft : within.offset.left,
collisionPosLeft = position.left - data.collisionPosition.marginLeft,
overLeft = collisionPosLeft - offsetLeft,
overRight = collisionPosLeft + data.collisionWidth - outerWidth - offsetLeft,
myOffset = data.my[ 0 ] === "left" ?
-data.elemWidth :
data.my[ 0 ] === "right" ?
data.elemWidth :
0,
atOffset = data.at[ 0 ] === "left" ?
data.targetWidth :
data.at[ 0 ] === "right" ?
-data.targetWidth :
0,
offset = -2 * data.offset[ 0 ],
newOverRight,
newOverLeft;
if ( overLeft < 0 ) {
newOverRight = position.left + myOffset + atOffset + offset + data.collisionWidth - outerWidth - withinOffset;
if ( newOverRight < 0 || newOverRight < abs( overLeft ) ) {
position.left += myOffset + atOffset + offset;
}
}
else if ( overRight > 0 ) {
newOverLeft = position.left - data.collisionPosition.marginLeft + myOffset + atOffset + offset - offsetLeft;
if ( newOverLeft > 0 || abs( newOverLeft ) < overRight ) {
position.left += myOffset + atOffset + offset;
}
}
},
top: function( position, data ) {
var within = data.within,
withinOffset = within.offset.top + within.scrollTop,
outerHeight = within.height,
offsetTop = within.isWindow ? within.scrollTop : within.offset.top,
collisionPosTop = position.top - data.collisionPosition.marginTop,
overTop = collisionPosTop - offsetTop,
overBottom = collisionPosTop + data.collisionHeight - outerHeight - offsetTop,
top = data.my[ 1 ] === "top",
myOffset = top ?
-data.elemHeight :
data.my[ 1 ] === "bottom" ?
data.elemHeight :
0,
atOffset = data.at[ 1 ] === "top" ?
data.targetHeight :
data.at[ 1 ] === "bottom" ?
-data.targetHeight :
0,
offset = -2 * data.offset[ 1 ],
newOverTop,
newOverBottom;
if ( overTop < 0 ) {
newOverBottom = position.top + myOffset + atOffset + offset + data.collisionHeight - outerHeight - withinOffset;
if ( ( position.top + myOffset + atOffset + offset) > overTop && ( newOverBottom < 0 || newOverBottom < abs( overTop ) ) ) {
position.top += myOffset + atOffset + offset;
}
}
else if ( overBottom > 0 ) {
newOverTop = position.top - data.collisionPosition.marginTop + myOffset + atOffset + offset - offsetTop;
if ( ( position.top + myOffset + atOffset + offset) > overBottom && ( newOverTop > 0 || abs( newOverTop ) < overBottom ) ) {
position.top += myOffset + atOffset + offset;
}
}
}
},
flipfit: {
left: function() {
$.ui.position.flip.left.apply( this, arguments );
$.ui.position.fit.left.apply( this, arguments );
},
top: function() {
$.ui.position.flip.top.apply( this, arguments );
$.ui.position.fit.top.apply( this, arguments );
}
}
};
// fraction support test
(function () {
var testElement, testElementParent, testElementStyle, offsetLeft, i,
body = document.getElementsByTagName( "body" )[ 0 ],
div = document.createElement( "div" );
//Create a "fake body" for testing based on method used in jQuery.support
testElement = document.createElement( body ? "div" : "body" );
testElementStyle = {
visibility: "hidden",
width: 0,
height: 0,
border: 0,
margin: 0,
background: "none"
};
if ( body ) {
$.extend( testElementStyle, {
position: "absolute",
left: "-1000px",
top: "-1000px"
});
}
for ( i in testElementStyle ) {
testElement.style[ i ] = testElementStyle[ i ];
}
testElement.appendChild( div );
testElementParent = body || document.documentElement;
testElementParent.insertBefore( testElement, testElementParent.firstChild );
div.style.cssText = "position: absolute; left: 10.7432222px;";
offsetLeft = $( div ).offset().left;
$.support.offsetFractions = offsetLeft > 10 && offsetLeft < 11;
testElement.innerHTML = "";
testElementParent.removeChild( testElement );
})();
}( jQuery ) );

View File

@@ -3,7 +3,7 @@ const isCI = require("is-ci");
module.exports = { module.exports = {
launch: { launch: {
headless: isCI, headless: isCI,
slowMo: 30, slowMo: 55,
defaultViewport: null, defaultViewport: null,
ignoreHTTPSErrors: true, ignoreHTTPSErrors: true,
args: ["--disable-web-security"] args: ["--disable-web-security"]

View File

@@ -2082,7 +2082,8 @@ a:link {
.resourceTreeAndTabs { .resourceTreeAndTabs {
display: flex; display: flex;
flex: 1 1 auto; flex: 1 1 auto;
overflow: auto; overflow-x: auto;
overflow-y: hidden;
height: 100%; height: 100%;
} }

16421
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -6,26 +6,26 @@
"dependencies": { "dependencies": {
"@azure/cosmos": "3.9.0", "@azure/cosmos": "3.9.0",
"@azure/cosmos-language-service": "0.0.4", "@azure/cosmos-language-service": "0.0.4",
"@jupyterlab/services": "4.2.0", "@jupyterlab/services": "6.0.0-rc.2",
"@jupyterlab/terminal": "1.2.1", "@jupyterlab/terminal": "3.0.0-rc.2",
"@microsoft/applicationinsights-web": "2.5.8", "@microsoft/applicationinsights-web": "2.5.9",
"@nteract/commutable": "7.1.4", "@nteract/commutable": "7.3.2",
"@nteract/connected-components": "6.7.8", "@nteract/connected-components": "6.8.2",
"@nteract/core": "13.0.0", "@nteract/core": "15.1.0",
"@nteract/data-explorer": "8.0.3", "@nteract/data-explorer": "8.0.3",
"@nteract/directory-listing": "2.0.6", "@nteract/directory-listing": "2.0.6",
"@nteract/dropdown-menu": "1.0.1", "@nteract/dropdown-menu": "1.0.1",
"@nteract/editor": "9.6.6", "@nteract/editor": "10.1.2",
"@nteract/fixtures": "2.3.0", "@nteract/fixtures": "2.3.0",
"@nteract/iron-icons": "1.0.0", "@nteract/iron-icons": "1.0.0",
"@nteract/jupyter-widgets": "2.0.0", "@nteract/jupyter-widgets": "2.0.0",
"@nteract/logos": "1.0.0", "@nteract/logos": "1.0.0",
"@nteract/markdown": "4.4.0", "@nteract/markdown": "4.4.0",
"@nteract/monaco-editor": "3.0.3", "@nteract/monaco-editor": "3.2.0",
"@nteract/octicons": "2.0.0", "@nteract/octicons": "2.0.0",
"@nteract/outputs": "3.0.9", "@nteract/outputs": "3.0.9",
"@nteract/presentational-components": "3.0.7", "@nteract/presentational-components": "3.0.7",
"@nteract/stateful-components": "1.4.0", "@nteract/stateful-components": "1.7.0",
"@nteract/styles": "2.0.2", "@nteract/styles": "2.0.2",
"@nteract/transform-geojson": "5.1.8", "@nteract/transform-geojson": "5.1.8",
"@nteract/transform-model-debug": "5.0.1", "@nteract/transform-model-debug": "5.0.1",
@@ -47,6 +47,7 @@
"copy-webpack-plugin": "6.0.2", "copy-webpack-plugin": "6.0.2",
"crossroads": "0.12.2", "crossroads": "0.12.2",
"css-element-queries": "1.1.1", "css-element-queries": "1.1.1",
"d3": "6.1.1",
"datatables.net-colreorder-dt": "1.5.1", "datatables.net-colreorder-dt": "1.5.1",
"datatables.net-dt": "1.10.19", "datatables.net-dt": "1.10.19",
"date-fns": "1.29.0", "date-fns": "1.29.0",
@@ -73,7 +74,7 @@
"promise-polyfill": "8.1.0", "promise-polyfill": "8.1.0",
"promise.prototype.finally": "3.1.0", "promise.prototype.finally": "3.1.0",
"q": "1.5.1", "q": "1.5.1",
"react": "16.9.0", "react": "16.13.1",
"react-animate-height": "2.0.8", "react-animate-height": "2.0.8",
"react-dnd": "9.4.0", "react-dnd": "9.4.0",
"react-dnd-html5-backend": "9.4.0", "react-dnd-html5-backend": "9.4.0",
@@ -82,12 +83,13 @@
"react-notification-system": "0.2.17", "react-notification-system": "0.2.17",
"react-redux": "7.1.3", "react-redux": "7.1.3",
"redux": "4.0.4", "redux": "4.0.4",
"rx-jupyter": "5.5.2", "rx-jupyter": "5.5.12",
"rxjs": "6.5.3", "rxjs": "6.6.3",
"styled-components": "4.3.2", "styled-components": "4.3.2",
"text-encoding": "0.7.0", "text-encoding": "0.7.0",
"underscore": "1.9.1", "underscore": "1.9.1",
"url-polyfill": "1.1.7", "url-polyfill": "1.1.7",
"utility-types": "3.10.0",
"webcrypto-liner": "1.1.4", "webcrypto-liner": "1.1.4",
"webfontloader": "1.6.28", "webfontloader": "1.6.28",
"whatwg-fetch": "3.0.0" "whatwg-fetch": "3.0.0"
@@ -100,9 +102,9 @@
"@types/applicationinsights-js": "1.0.7", "@types/applicationinsights-js": "1.0.7",
"@types/codemirror": "0.0.56", "@types/codemirror": "0.0.56",
"@types/crossroads": "0.0.30", "@types/crossroads": "0.0.30",
"@types/d3": "4.13.2", "@types/d3": "5.9.2",
"@types/enzyme": "3.10.3", "@types/enzyme": "3.10.7",
"@types/enzyme-adapter-react-16": "1.0.5", "@types/enzyme-adapter-react-16": "1.0.6",
"@types/expect-puppeteer": "4.4.3", "@types/expect-puppeteer": "4.4.3",
"@types/hasher": "0.0.31", "@types/hasher": "0.0.31",
"@types/jest": "23.3.10", "@types/jest": "23.3.10",
@@ -113,7 +115,7 @@
"@types/prop-types": "15.5.8", "@types/prop-types": "15.5.8",
"@types/puppeteer": "3.0.1", "@types/puppeteer": "3.0.1",
"@types/q": "1.5.1", "@types/q": "1.5.1",
"@types/react": "16.8.25", "@types/react": "16.9.49",
"@types/react-dom": "16.0.7", "@types/react-dom": "16.0.7",
"@types/react-notification-system": "0.2.39", "@types/react-notification-system": "0.2.39",
"@types/react-redux": "7.1.7", "@types/react-redux": "7.1.7",
@@ -132,10 +134,9 @@
"case-sensitive-paths-webpack-plugin": "2.3.0", "case-sensitive-paths-webpack-plugin": "2.3.0",
"create-file-webpack": "1.0.2", "create-file-webpack": "1.0.2",
"css-loader": "1.0.0", "css-loader": "1.0.0",
"d3": "4.13.0", "enzyme": "3.11.0",
"enzyme": "3.10.0", "enzyme-adapter-react-16": "1.15.5",
"enzyme-adapter-react-16": "1.15.1", "enzyme-to-json": "3.6.1",
"enzyme-to-json": "3.4.3",
"eslint": "7.8.1", "eslint": "7.8.1",
"eslint-cli": "1.1.1", "eslint-cli": "1.1.1",
"eslint-plugin-no-null": "1.0.2", "eslint-plugin-no-null": "1.0.2",
@@ -156,7 +157,7 @@
"less-vars-loader": "1.1.0", "less-vars-loader": "1.1.0",
"mini-css-extract-plugin": "0.4.3", "mini-css-extract-plugin": "0.4.3",
"monaco-editor-webpack-plugin": "1.7.0", "monaco-editor-webpack-plugin": "1.7.0",
"node-fetch": "2.6.0", "node-fetch": "2.6.1",
"prettier": "1.19.1", "prettier": "1.19.1",
"puppeteer": "4.0.0", "puppeteer": "4.0.0",
"raw-loader": "0.5.1", "raw-loader": "0.5.1",

View File

@@ -1,5 +1,4 @@
import { AutopilotTier } from "../Contracts/DataModels"; import { AutopilotTier } from "../Contracts/DataModels";
import { configContext } from "../ConfigContext";
import { HashMap } from "./HashMap"; import { HashMap } from "./HashMap";
export class AuthorizationEndpoints { export class AuthorizationEndpoints {
@@ -13,12 +12,6 @@ export class CodeOfConductEndpoints {
public static termsOfUse: string = "https://aka.ms/ms-terms-of-use"; public static termsOfUse: string = "https://aka.ms/ms-terms-of-use";
} }
export class BackendEndpoints {
public static localhost: string = "https://localhost:12900";
public static dev: string = "https://ext.documents-dev.windows-int.net";
public static productionPortal: string = configContext.BACKEND_ENDPOINT || "https://main.documentdb.ext.azure.com";
}
export class EndpointsRegex { export class EndpointsRegex {
public static readonly cassandra = [ public static readonly cassandra = [
"AccountEndpoint=(.*).cassandra.cosmosdb.azure.com", "AccountEndpoint=(.*).cassandra.cosmosdb.azure.com",
@@ -124,7 +117,6 @@ export class Features {
public static readonly enableGalleryPublish = "enablegallerypublish"; public static readonly enableGalleryPublish = "enablegallerypublish";
public static readonly enableCodeOfConduct = "enablecodeofconduct"; public static readonly enableCodeOfConduct = "enablecodeofconduct";
public static readonly enableLinkInjection = "enablelinkinjection"; public static readonly enableLinkInjection = "enablelinkinjection";
public static readonly enableSettingsV2 = "enablesettingsv2";
public static readonly enableSpark = "enablespark"; public static readonly enableSpark = "enablespark";
public static readonly livyEndpoint = "livyendpoint"; public static readonly livyEndpoint = "livyendpoint";
public static readonly notebookServerUrl = "notebookserverurl"; public static readonly notebookServerUrl = "notebookserverurl";
@@ -138,6 +130,11 @@ export class Features {
public static readonly enableSDKoperations = "enablesdkoperations"; public static readonly enableSDKoperations = "enablesdkoperations";
} }
// flight names returned from the portal are always lowercase
export class Flights {
public static readonly SettingsV2 = "settingsv2";
}
export class AfecFeatures { export class AfecFeatures {
public static readonly Spark = "spark-public-preview"; public static readonly Spark = "spark-public-preview";
public static readonly Notebooks = "sparknotebooks-public-preview"; public static readonly Notebooks = "sparknotebooks-public-preview";

View File

@@ -14,7 +14,9 @@ describe("tokenProvider", () => {
}; };
beforeEach(() => { beforeEach(() => {
window.dataExplorer = { extensionEndpoint: () => "https://main.documentdb.ext.azure.com" } as any; updateConfigContext({
BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com"
});
window.fetch = jest.fn().mockImplementation(() => { window.fetch = jest.fn().mockImplementation(() => {
return { return {
json: () => "{}", json: () => "{}",
@@ -58,7 +60,9 @@ describe("getTokenFromAuthService", () => {
}); });
it("builds the correct URL in production", () => { it("builds the correct URL in production", () => {
window.dataExplorer = { extensionEndpoint: () => "https://main.documentdb.ext.azure.com" } as any; updateConfigContext({
BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com"
});
getTokenFromAuthService("GET", "dbs", "foo"); getTokenFromAuthService("GET", "dbs", "foo");
expect(window.fetch).toHaveBeenCalledWith( expect(window.fetch).toHaveBeenCalledWith(
"https://main.documentdb.ext.azure.com/api/guest/runtimeproxy/authorizationTokens", "https://main.documentdb.ext.azure.com/api/guest/runtimeproxy/authorizationTokens",

View File

@@ -52,7 +52,7 @@ export const endpoint = () => {
export async function getTokenFromAuthService(verb: string, resourceType: string, resourceId?: string): Promise<any> { export async function getTokenFromAuthService(verb: string, resourceType: string, resourceId?: string): Promise<any> {
try { try {
const host = configContext.BACKEND_ENDPOINT || _global.dataExplorer.extensionEndpoint(); const host = configContext.BACKEND_ENDPOINT;
const response = await _global.fetch(host + "/api/guest/runtimeproxy/authorizationTokens", { const response = await _global.fetch(host + "/api/guest/runtimeproxy/authorizationTokens", {
method: "POST", method: "POST",
headers: { headers: {
@@ -85,8 +85,7 @@ export function client(): Cosmos.CosmosClient {
userAgentSuffix: "Azure Portal" userAgentSuffix: "Azure Portal"
}; };
// In development we proxy requests to the backend via webpack. This is removed in production bundles. if (configContext.PROXY_PATH !== undefined) {
if (process.env.NODE_ENV === "development") {
(options as any).plugins = [{ on: "request", plugin: requestPlugin }]; (options as any).plugins = [{ on: "request", plugin: requestPlugin }];
} }
return new Cosmos.CosmosClient(options); return new Cosmos.CosmosClient(options);

View File

@@ -72,22 +72,6 @@ export function getPartitionKeyHeader(partitionKeyDefinition: DataModels.Partiti
return [partitionKeyValue]; return [partitionKeyValue];
} }
export function updateOffer(
offer: DataModels.Offer,
newOffer: DataModels.Offer,
options?: RequestOptions
): Q.Promise<DataModels.Offer> {
return Q(
client()
.offer(offer.id)
// TODO Remove casting when SDK types are fixed (https://github.com/Azure/azure-sdk-for-js/issues/10660)
.replace((newOffer as unknown) as OfferDefinition, options)
.then(response => {
return Promise.all([refreshCachedOffers(), refreshCachedResources()]).then(() => response.resource);
})
);
}
export function updateDocument( export function updateDocument(
collection: ViewModels.CollectionBase, collection: ViewModels.CollectionBase,
documentId: DocumentId, documentId: DocumentId,

View File

@@ -157,41 +157,6 @@ export function updateDocument(
return deferred.promise; return deferred.promise;
} }
export function updateOffer(
offer: DataModels.Offer,
newOffer: DataModels.Offer,
options: RequestOptions
): Q.Promise<DataModels.Offer> {
var deferred = Q.defer<any>();
const clearMessage = logConsoleProgress(`Updating offer for resource ${offer.resource}`);
DataAccessUtilityBase.updateOffer(offer, newOffer, options)
.then(
(replacedOffer: DataModels.Offer) => {
logConsoleInfo(`Successfully updated offer for resource ${offer.resource}`);
deferred.resolve(replacedOffer);
},
(error: any) => {
logConsoleError(`Error updating offer for resource ${offer.resource}: ${JSON.stringify(error)}`);
Logger.logError(
JSON.stringify({
oldOffer: offer,
newOffer: newOffer,
error: error
}),
"UpdateOffer",
error.code
);
sendNotificationForError(error);
deferred.reject(error);
}
)
.finally(() => {
clearMessage();
});
return deferred.promise;
}
export function createDocument(collection: ViewModels.CollectionBase, newDocument: any): Q.Promise<any> { export function createDocument(collection: ViewModels.CollectionBase, newDocument: any): Q.Promise<any> {
var deferred = Q.defer<any>(); var deferred = Q.defer<any>();
const entityName = getEntityName(); const entityName = getEntityName();

View File

@@ -63,10 +63,9 @@ describe("MongoProxyClient", () => {
updateUserContext({ updateUserContext({
databaseAccount databaseAccount
}); });
window.dataExplorer = { updateConfigContext({
extensionEndpoint: () => "https://main.documentdb.ext.azure.com", BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com"
serverId: () => "" });
} as any;
window.fetch = jest.fn().mockImplementation(fetchMock); window.fetch = jest.fn().mockImplementation(fetchMock);
}); });
afterEach(() => { afterEach(() => {
@@ -96,10 +95,9 @@ describe("MongoProxyClient", () => {
updateUserContext({ updateUserContext({
databaseAccount databaseAccount
}); });
window.dataExplorer = { updateConfigContext({
extensionEndpoint: () => "https://main.documentdb.ext.azure.com", BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com"
serverId: () => "" });
} as any;
window.fetch = jest.fn().mockImplementation(fetchMock); window.fetch = jest.fn().mockImplementation(fetchMock);
}); });
afterEach(() => { afterEach(() => {
@@ -129,10 +127,9 @@ describe("MongoProxyClient", () => {
updateUserContext({ updateUserContext({
databaseAccount databaseAccount
}); });
window.dataExplorer = { updateConfigContext({
extensionEndpoint: () => "https://main.documentdb.ext.azure.com", BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com"
serverId: () => "" });
} as any;
window.fetch = jest.fn().mockImplementation(fetchMock); window.fetch = jest.fn().mockImplementation(fetchMock);
}); });
afterEach(() => { afterEach(() => {
@@ -162,10 +159,9 @@ describe("MongoProxyClient", () => {
updateUserContext({ updateUserContext({
databaseAccount databaseAccount
}); });
window.dataExplorer = { updateConfigContext({
extensionEndpoint: () => "https://main.documentdb.ext.azure.com", BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com"
serverId: () => "" });
} as any;
window.fetch = jest.fn().mockImplementation(fetchMock); window.fetch = jest.fn().mockImplementation(fetchMock);
}); });
afterEach(() => { afterEach(() => {
@@ -195,10 +191,9 @@ describe("MongoProxyClient", () => {
updateUserContext({ updateUserContext({
databaseAccount databaseAccount
}); });
window.dataExplorer = { updateConfigContext({
extensionEndpoint: () => "https://main.documentdb.ext.azure.com", BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com"
serverId: () => "" });
} as any;
window.fetch = jest.fn().mockImplementation(fetchMock); window.fetch = jest.fn().mockImplementation(fetchMock);
}); });
afterEach(() => { afterEach(() => {
@@ -229,10 +224,9 @@ describe("MongoProxyClient", () => {
updateUserContext({ updateUserContext({
databaseAccount databaseAccount
}); });
window.dataExplorer = { updateConfigContext({
extensionEndpoint: () => "https://main.documentdb.ext.azure.com", BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com"
serverId: () => "" });
} as any;
}); });
it("returns a production endpoint", () => { it("returns a production endpoint", () => {

View File

@@ -327,8 +327,7 @@ export function createMongoCollectionWithProxy(
} }
export function getEndpoint(): string { export function getEndpoint(): string {
const extensionEndpoint = window.dataExplorer.extensionEndpoint(); let url = (configContext.MONGO_BACKEND_ENDPOINT || configContext.BACKEND_ENDPOINT) + "/api/mongo/explorer";
let url = (configContext.MONGO_BACKEND_ENDPOINT || extensionEndpoint) + "/api/mongo/explorer";
if (window.authType === AuthType.EncryptedToken) { if (window.authType === AuthType.EncryptedToken) {
url = url.replace("api/mongo", "api/guest/mongo"); url = url.replace("api/mongo", "api/guest/mongo");

View File

@@ -1,46 +0,0 @@
import "jquery";
import * as Q from "q";
import * as DataModels from "../Contracts/DataModels";
import * as ViewModels from "../Contracts/ViewModels";
import { getAuthorizationHeader } from "../Utils/AuthorizationUtils";
import { userContext } from "../UserContext";
export class NotificationsClientBase {
private _extensionEndpoint: string;
private _notificationsApiSuffix: string;
protected constructor(notificationsApiSuffix: string) {
this._notificationsApiSuffix = notificationsApiSuffix;
}
public fetchNotifications(): Q.Promise<DataModels.Notification[]> {
const deferred: Q.Deferred<DataModels.Notification[]> = Q.defer<DataModels.Notification[]>();
const databaseAccount = userContext.databaseAccount;
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const url = `${this._extensionEndpoint}${this._notificationsApiSuffix}?accountName=${databaseAccount.name}&subscriptionId=${subscriptionId}&resourceGroup=${resourceGroup}`;
const authorizationHeader: ViewModels.AuthorizationTokenHeaderMetadata = getAuthorizationHeader();
const headers: any = {};
headers[authorizationHeader.header] = authorizationHeader.token;
$.ajax({
url: url,
type: "GET",
headers: headers,
cache: false
}).then(
(notifications: DataModels.Notification[], textStatus: string, xhr: JQueryXHR<any>) => {
deferred.resolve(notifications);
},
(xhr: JQueryXHR<any>, textStatus: string, error: any) => {
deferred.reject(xhr.responseText);
}
);
return deferred.promise;
}
public setExtensionEndpoint(extensionEndpoint: string): void {
this._extensionEndpoint = extensionEndpoint;
}
}

View File

@@ -0,0 +1,41 @@
import * as DataModels from "../Contracts/DataModels";
import * as ViewModels from "../Contracts/ViewModels";
import { getAuthorizationHeader } from "../Utils/AuthorizationUtils";
import { userContext } from "../UserContext";
import { configContext, Platform } from "../ConfigContext";
const notificationsPath = () => {
switch (configContext.platform) {
case Platform.Hosted:
return "/api/guest/notifications";
case Platform.Portal:
return "/api/notifications";
default:
throw new Error(`Unknown platform: ${configContext.platform}`);
}
};
export const fetchPortalNotifications = async (): Promise<DataModels.Notification[]> => {
if (configContext.platform === Platform.Emulator) {
return [];
}
const databaseAccount = userContext.databaseAccount;
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const url = `${configContext.BACKEND_ENDPOINT}${notificationsPath()}?accountName=${
databaseAccount.name
}&subscriptionId=${subscriptionId}&resourceGroup=${resourceGroup}`;
const authorizationHeader: ViewModels.AuthorizationTokenHeaderMetadata = getAuthorizationHeader();
const headers = { [authorizationHeader.header]: authorizationHeader.token };
const response = await window.fetch(url, {
headers
});
if (!response.ok) {
throw new Error(await response.text());
}
return (await response.json()) as DataModels.Notification[];
};

View File

@@ -20,26 +20,14 @@ export const readDatabaseOffer = async (
const clearMessage = logConsoleProgress(`Querying offer for database ${params.databaseId}`); const clearMessage = logConsoleProgress(`Querying offer for database ${params.databaseId}`);
let offerId = params.offerId; let offerId = params.offerId;
if (!offerId) { if (!offerId) {
if ( offerId = await (window.authType === AuthType.AAD &&
window.authType === AuthType.AAD && !userContext.useSDKOperations &&
!userContext.useSDKOperations && userContext.defaultExperience !== DefaultAccountExperienceType.Table
userContext.defaultExperience !== DefaultAccountExperienceType.Table ? getDatabaseOfferIdWithARM(params.databaseId)
) { : getDatabaseOfferIdWithSDK(params.databaseResourceId));
try { if (!offerId) {
offerId = await getDatabaseOfferIdWithARM(params.databaseId); clearMessage();
} catch (error) { return undefined;
clearMessage();
if (error.code !== "NotFound") {
throw new error();
}
return undefined;
}
} else {
offerId = await getDatabaseOfferIdWithSDK(params.databaseResourceId);
if (!offerId) {
clearMessage();
return undefined;
}
} }
} }
@@ -75,24 +63,32 @@ const getDatabaseOfferIdWithARM = async (databaseId: string): Promise<string> =>
const resourceGroup = userContext.resourceGroup; const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name; const accountName = userContext.databaseAccount.name;
const defaultExperience = userContext.defaultExperience; const defaultExperience = userContext.defaultExperience;
switch (defaultExperience) {
case DefaultAccountExperienceType.DocumentDB:
rpResponse = await getSqlDatabaseThroughput(subscriptionId, resourceGroup, accountName, databaseId);
break;
case DefaultAccountExperienceType.MongoDB:
rpResponse = await getMongoDBDatabaseThroughput(subscriptionId, resourceGroup, accountName, databaseId);
break;
case DefaultAccountExperienceType.Cassandra:
rpResponse = await getCassandraKeyspaceThroughput(subscriptionId, resourceGroup, accountName, databaseId);
break;
case DefaultAccountExperienceType.Graph:
rpResponse = await getGremlinDatabaseThroughput(subscriptionId, resourceGroup, accountName, databaseId);
break;
default:
throw new Error(`Unsupported default experience type: ${defaultExperience}`);
}
return rpResponse?.name; try {
switch (defaultExperience) {
case DefaultAccountExperienceType.DocumentDB:
rpResponse = await getSqlDatabaseThroughput(subscriptionId, resourceGroup, accountName, databaseId);
break;
case DefaultAccountExperienceType.MongoDB:
rpResponse = await getMongoDBDatabaseThroughput(subscriptionId, resourceGroup, accountName, databaseId);
break;
case DefaultAccountExperienceType.Cassandra:
rpResponse = await getCassandraKeyspaceThroughput(subscriptionId, resourceGroup, accountName, databaseId);
break;
case DefaultAccountExperienceType.Graph:
rpResponse = await getGremlinDatabaseThroughput(subscriptionId, resourceGroup, accountName, databaseId);
break;
default:
throw new Error(`Unsupported default experience type: ${defaultExperience}`);
}
return rpResponse?.name;
} catch (error) {
if (error.code !== "NotFound") {
throw error;
}
return undefined;
}
}; };
const getDatabaseOfferIdWithSDK = async (databaseResourceId: string): Promise<string> => { const getDatabaseOfferIdWithSDK = async (databaseResourceId: string): Promise<string> => {

View File

@@ -12,16 +12,14 @@ import { sendNotificationForError } from "./sendNotificationForError";
import { userContext } from "../../UserContext"; import { userContext } from "../../UserContext";
export async function readDatabases(): Promise<DataModels.Database[]> { export async function readDatabases(): Promise<DataModels.Database[]> {
if (userContext.defaultExperience === DefaultAccountExperienceType.Table) {
return [{ id: "TablesDB" } as DataModels.Database];
}
let databases: DataModels.Database[]; let databases: DataModels.Database[];
const clearMessage = logConsoleProgress(`Querying databases`); const clearMessage = logConsoleProgress(`Querying databases`);
try { try {
if ( if (window.authType === AuthType.AAD && !userContext.useSDKOperations) {
window.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience !== DefaultAccountExperienceType.MongoDB &&
userContext.defaultExperience !== DefaultAccountExperienceType.Table &&
userContext.defaultExperience !== DefaultAccountExperienceType.Cassandra
) {
databases = await readDatabasesWithARM(); databases = await readDatabasesWithARM();
} else { } else {
const sdkResponse = await client() const sdkResponse = await client()

View File

@@ -41,6 +41,7 @@ export async function updateCollection(
try { try {
if ( if (
window.authType === AuthType.AAD && window.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience !== DefaultAccountExperienceType.MongoDB && userContext.defaultExperience !== DefaultAccountExperienceType.MongoDB &&
userContext.defaultExperience !== DefaultAccountExperienceType.Table userContext.defaultExperience !== DefaultAccountExperienceType.Table
) { ) {
@@ -52,16 +53,18 @@ export async function updateCollection(
.replace(newCollection as ContainerDefinition, options); .replace(newCollection as ContainerDefinition, options);
collection = sdkResponse.resource as Collection; collection = sdkResponse.resource as Collection;
} }
logConsoleInfo(`Successfully updated container ${collectionId}`);
await refreshCachedResources();
return collection;
} catch (error) { } catch (error) {
logConsoleError(`Failed to update container ${collectionId}: ${JSON.stringify(error)}`); logConsoleError(`Failed to update container ${collectionId}: ${JSON.stringify(error)}`);
logError(JSON.stringify(error), "UpdateCollection", error.code); logError(JSON.stringify(error), "UpdateCollection", error.code);
sendNotificationForError(error); sendNotificationForError(error);
throw error; throw error;
} finally {
clearMessage();
} }
logConsoleInfo(`Successfully updated container ${collectionId}`);
clearMessage();
await refreshCachedResources();
return collection;
} }
async function updateCollectionWithARM( async function updateCollectionWithARM(

View File

@@ -0,0 +1,421 @@
import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { HttpHeaders } from "../Constants";
import { Offer, UpdateOfferParams } from "../../Contracts/DataModels";
import { OfferDefinition } from "@azure/cosmos";
import { RequestOptions } from "@azure/cosmos/dist-esm";
import { ThroughputSettingsUpdateParameters } from "../../Utils/arm/generatedClients/2020-04-01/types";
import { client } from "../CosmosClient";
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { logError } from "../Logger";
import { readCollectionOffer } from "./readCollectionOffer";
import { readDatabaseOffer } from "./readDatabaseOffer";
import { refreshCachedOffers, refreshCachedResources } from "../DataAccessUtilityBase";
import { sendNotificationForError } from "./sendNotificationForError";
import {
updateSqlDatabaseThroughput,
migrateSqlDatabaseToAutoscale,
migrateSqlDatabaseToManualThroughput,
migrateSqlContainerToAutoscale,
migrateSqlContainerToManualThroughput,
updateSqlContainerThroughput
} from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import {
updateCassandraKeyspaceThroughput,
migrateCassandraKeyspaceToAutoscale,
migrateCassandraKeyspaceToManualThroughput,
migrateCassandraTableToAutoscale,
migrateCassandraTableToManualThroughput,
updateCassandraTableThroughput
} from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
import {
updateMongoDBDatabaseThroughput,
migrateMongoDBDatabaseToAutoscale,
migrateMongoDBDatabaseToManualThroughput,
migrateMongoDBCollectionToAutoscale,
migrateMongoDBCollectionToManualThroughput,
updateMongoDBCollectionThroughput
} from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import {
updateGremlinDatabaseThroughput,
migrateGremlinDatabaseToAutoscale,
migrateGremlinDatabaseToManualThroughput,
migrateGremlinGraphToAutoscale,
migrateGremlinGraphToManualThroughput,
updateGremlinGraphThroughput
} from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
import { userContext } from "../../UserContext";
import {
migrateTableToAutoscale,
migrateTableToManualThroughput,
updateTableThroughput
} from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
export const updateOffer = async (params: UpdateOfferParams): Promise<Offer> => {
let updatedOffer: Offer;
const offerResourceText: string = params.collectionId
? `collection ${params.collectionId}`
: `database ${params.databaseId}`;
const clearMessage = logConsoleProgress(`Updating offer for ${offerResourceText}`);
try {
if (window.authType === AuthType.AAD && !userContext.useSDKOperations) {
if (params.collectionId) {
updatedOffer = await updateCollectionOfferWithARM(params);
} else if (userContext.defaultExperience === DefaultAccountExperienceType.Table) {
// update table's database offer with SDK since RP doesn't support it
updatedOffer = await updateOfferWithSDK(params);
} else {
updatedOffer = await updateDatabaseOfferWithARM(params);
}
} else {
updatedOffer = await updateOfferWithSDK(params);
}
await refreshCachedOffers();
await refreshCachedResources();
logConsoleInfo(`Successfully updated offer for ${offerResourceText}`);
return updatedOffer;
} catch (error) {
logConsoleError(`Error updating offer for ${offerResourceText}: ${JSON.stringify(error)}`);
logError(JSON.stringify(error), "UpdateCollection", error.code);
sendNotificationForError(error);
throw error;
} finally {
clearMessage();
}
};
const updateCollectionOfferWithARM = async (params: UpdateOfferParams): Promise<Offer> => {
try {
switch (userContext.defaultExperience) {
case DefaultAccountExperienceType.DocumentDB:
await updateSqlContainerOffer(params);
break;
case DefaultAccountExperienceType.MongoDB:
await updateMongoCollectionOffer(params);
break;
case DefaultAccountExperienceType.Cassandra:
await updateCassandraTableOffer(params);
break;
case DefaultAccountExperienceType.Graph:
await updateGremlinGraphOffer(params);
break;
case DefaultAccountExperienceType.Table:
await updateTableOffer(params);
break;
default:
throw new Error(`Unsupported default experience type: ${userContext.defaultExperience}`);
}
} catch (error) {
if (error.code !== "MethodNotAllowed") {
throw error;
}
}
return await readCollectionOffer({
collectionId: params.collectionId,
databaseId: params.databaseId,
offerId: params.currentOffer.id
});
};
const updateDatabaseOfferWithARM = async (params: UpdateOfferParams): Promise<Offer> => {
try {
switch (userContext.defaultExperience) {
case DefaultAccountExperienceType.DocumentDB:
await updateSqlDatabaseOffer(params);
break;
case DefaultAccountExperienceType.MongoDB:
await updateMongoDatabaseOffer(params);
break;
case DefaultAccountExperienceType.Cassandra:
await updateCassandraKeyspaceOffer(params);
break;
case DefaultAccountExperienceType.Graph:
await updateGremlinDatabaseOffer(params);
break;
default:
throw new Error(`Unsupported default experience type: ${userContext.defaultExperience}`);
}
} catch (error) {
if (error.code !== "MethodNotAllowed") {
throw error;
}
}
return await readDatabaseOffer({
databaseId: params.databaseId,
offerId: params.currentOffer.id
});
};
const updateSqlContainerOffer = async (params: UpdateOfferParams): Promise<void> => {
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
if (params.migrateToAutoPilot) {
await migrateSqlContainerToAutoscale(
subscriptionId,
resourceGroup,
accountName,
params.databaseId,
params.collectionId
);
} else if (params.migrateToManual) {
await migrateSqlContainerToManualThroughput(
subscriptionId,
resourceGroup,
accountName,
params.databaseId,
params.collectionId
);
} else {
const body: ThroughputSettingsUpdateParameters = createUpdateOfferBody(params);
await updateSqlContainerThroughput(
subscriptionId,
resourceGroup,
accountName,
params.databaseId,
params.collectionId,
body
);
}
};
const updateMongoCollectionOffer = async (params: UpdateOfferParams): Promise<void> => {
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
if (params.migrateToAutoPilot) {
await migrateMongoDBCollectionToAutoscale(
subscriptionId,
resourceGroup,
accountName,
params.databaseId,
params.collectionId
);
} else if (params.migrateToManual) {
await migrateMongoDBCollectionToManualThroughput(
subscriptionId,
resourceGroup,
accountName,
params.databaseId,
params.collectionId
);
} else {
const body: ThroughputSettingsUpdateParameters = createUpdateOfferBody(params);
await updateMongoDBCollectionThroughput(
subscriptionId,
resourceGroup,
accountName,
params.databaseId,
params.collectionId,
body
);
}
};
const updateCassandraTableOffer = async (params: UpdateOfferParams): Promise<void> => {
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
if (params.migrateToAutoPilot) {
await migrateCassandraTableToAutoscale(
subscriptionId,
resourceGroup,
accountName,
params.databaseId,
params.collectionId
);
} else if (params.migrateToManual) {
await migrateCassandraTableToManualThroughput(
subscriptionId,
resourceGroup,
accountName,
params.databaseId,
params.collectionId
);
} else {
const body: ThroughputSettingsUpdateParameters = createUpdateOfferBody(params);
await updateCassandraTableThroughput(
subscriptionId,
resourceGroup,
accountName,
params.databaseId,
params.collectionId,
body
);
}
};
const updateGremlinGraphOffer = async (params: UpdateOfferParams): Promise<void> => {
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
if (params.migrateToAutoPilot) {
await migrateGremlinGraphToAutoscale(
subscriptionId,
resourceGroup,
accountName,
params.databaseId,
params.collectionId
);
} else if (params.migrateToManual) {
await migrateGremlinGraphToManualThroughput(
subscriptionId,
resourceGroup,
accountName,
params.databaseId,
params.collectionId
);
} else {
const body: ThroughputSettingsUpdateParameters = createUpdateOfferBody(params);
await updateGremlinGraphThroughput(
subscriptionId,
resourceGroup,
accountName,
params.databaseId,
params.collectionId,
body
);
}
};
const updateTableOffer = async (params: UpdateOfferParams): Promise<void> => {
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
if (params.migrateToAutoPilot) {
await migrateTableToAutoscale(subscriptionId, resourceGroup, accountName, params.collectionId);
} else if (params.migrateToManual) {
await migrateTableToManualThroughput(subscriptionId, resourceGroup, accountName, params.collectionId);
} else {
const body: ThroughputSettingsUpdateParameters = createUpdateOfferBody(params);
await updateTableThroughput(subscriptionId, resourceGroup, accountName, params.collectionId, body);
}
};
const updateSqlDatabaseOffer = async (params: UpdateOfferParams): Promise<void> => {
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
if (params.migrateToAutoPilot) {
await migrateSqlDatabaseToAutoscale(subscriptionId, resourceGroup, accountName, params.databaseId);
} else if (params.migrateToManual) {
await migrateSqlDatabaseToManualThroughput(subscriptionId, resourceGroup, accountName, params.databaseId);
} else {
const body: ThroughputSettingsUpdateParameters = createUpdateOfferBody(params);
await updateSqlDatabaseThroughput(subscriptionId, resourceGroup, accountName, params.databaseId, body);
}
};
const updateMongoDatabaseOffer = async (params: UpdateOfferParams): Promise<void> => {
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
if (params.migrateToAutoPilot) {
await migrateMongoDBDatabaseToAutoscale(subscriptionId, resourceGroup, accountName, params.databaseId);
} else if (params.migrateToManual) {
await migrateMongoDBDatabaseToManualThroughput(subscriptionId, resourceGroup, accountName, params.databaseId);
} else {
const body: ThroughputSettingsUpdateParameters = createUpdateOfferBody(params);
await updateMongoDBDatabaseThroughput(subscriptionId, resourceGroup, accountName, params.databaseId, body);
}
};
const updateCassandraKeyspaceOffer = async (params: UpdateOfferParams): Promise<void> => {
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
if (params.migrateToAutoPilot) {
await migrateCassandraKeyspaceToAutoscale(subscriptionId, resourceGroup, accountName, params.databaseId);
} else if (params.migrateToManual) {
await migrateCassandraKeyspaceToManualThroughput(subscriptionId, resourceGroup, accountName, params.databaseId);
} else {
const body: ThroughputSettingsUpdateParameters = createUpdateOfferBody(params);
await updateCassandraKeyspaceThroughput(subscriptionId, resourceGroup, accountName, params.databaseId, body);
}
};
const updateGremlinDatabaseOffer = async (params: UpdateOfferParams): Promise<void> => {
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
if (params.migrateToAutoPilot) {
await migrateGremlinDatabaseToAutoscale(subscriptionId, resourceGroup, accountName, params.databaseId);
} else if (params.migrateToManual) {
await migrateGremlinDatabaseToManualThroughput(subscriptionId, resourceGroup, accountName, params.databaseId);
} else {
const body: ThroughputSettingsUpdateParameters = createUpdateOfferBody(params);
await updateGremlinDatabaseThroughput(subscriptionId, resourceGroup, accountName, params.databaseId, body);
}
};
const createUpdateOfferBody = (params: UpdateOfferParams): ThroughputSettingsUpdateParameters => {
const body: ThroughputSettingsUpdateParameters = {
properties: {
resource: {}
}
};
if (params.autopilotThroughput) {
body.properties.resource.autoscaleSettings = {
maxThroughput: params.autopilotThroughput
};
} else {
body.properties.resource.throughput = params.manualThroughput;
}
return body;
};
const updateOfferWithSDK = async (params: UpdateOfferParams): Promise<Offer> => {
const currentOffer = params.currentOffer;
const newOffer: Offer = {
content: {
offerThroughput: undefined,
offerIsRUPerMinuteThroughputEnabled: false
},
_etag: undefined,
_ts: undefined,
_rid: currentOffer._rid,
_self: currentOffer._self,
id: currentOffer.id,
offerResourceId: currentOffer.offerResourceId,
offerVersion: currentOffer.offerVersion,
offerType: currentOffer.offerType,
resource: currentOffer.resource
};
if (params.autopilotThroughput) {
newOffer.content.offerAutopilotSettings = {
maxThroughput: params.autopilotThroughput
};
} else {
newOffer.content.offerThroughput = params.manualThroughput;
}
const options: RequestOptions = {};
if (params.migrateToAutoPilot) {
options.initialHeaders[HttpHeaders.migrateOfferToAutopilot] = "true";
delete newOffer.content.offerAutopilotSettings;
} else if (params.migrateToManual) {
options.initialHeaders[HttpHeaders.migrateOfferToManualThroughput] = "true";
newOffer.content.offerAutopilotSettings = { maxThroughput: 0 };
}
const sdkResponse = await client()
.offer(params.currentOffer.id)
// TODO Remove casting when SDK types are fixed (https://github.com/Azure/azure-sdk-for-js/issues/10660)
.replace((newOffer as unknown) as OfferDefinition, options);
return sdkResponse?.resource;
};

View File

@@ -9,8 +9,7 @@ describe("updateOfferThroughputBeyondLimit", () => {
}); });
window.dataExplorer = { window.dataExplorer = {
logConsoleData: jest.fn(), logConsoleData: jest.fn(),
deleteInProgressConsoleDataWithId: jest.fn(), deleteInProgressConsoleDataWithId: jest.fn()
extensionEndpoint: jest.fn()
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any; } as any;
await updateOfferThroughputBeyondLimit({ await updateOfferThroughputBeyondLimit({

View File

@@ -28,8 +28,7 @@ export async function updateOfferThroughputBeyondLimit(request: UpdateOfferThrou
`Requesting increase in throughput to ${request.throughput} for ${resourceDescriptionInfo}` `Requesting increase in throughput to ${request.throughput} for ${resourceDescriptionInfo}`
); );
const explorer = window.dataExplorer; const url = `${configContext.BACKEND_ENDPOINT}/api/offerthroughputrequest/updatebeyondspecifiedlimit`;
const url = `${explorer.extensionEndpoint()}/api/offerthroughputrequest/updatebeyondspecifiedlimit`;
const authorizationHeader = getAuthorizationHeader(); const authorizationHeader = getAuthorizationHeader();
const response = await fetch(url, { const response = await fetch(url, {

View File

@@ -25,6 +25,7 @@ interface ConfigContext {
GITHUB_CLIENT_ID: string; GITHUB_CLIENT_ID: string;
GITHUB_CLIENT_SECRET?: string; // No need to inject secret for prod. Juno already knows it. GITHUB_CLIENT_SECRET?: string; // No need to inject secret for prod. Juno already knows it.
hostedExplorerURL: string; hostedExplorerURL: string;
armAPIVersion?: string;
} }
// Default configuration // Default configuration
@@ -33,6 +34,7 @@ let configContext: Readonly<ConfigContext> = {
allowedParentFrameOrigins: [ allowedParentFrameOrigins: [
`^https:\\/\\/cosmos\\.azure\\.(com|cn|us)$`, `^https:\\/\\/cosmos\\.azure\\.(com|cn|us)$`,
`^https:\\/\\/[\\.\\w]*portal\\.azure\\.(com|cn|us)$`, `^https:\\/\\/[\\.\\w]*portal\\.azure\\.(com|cn|us)$`,
`^https:\\/\\/[\\.\\w]*portal\\.microsoftazure.de$`,
`^https:\\/\\/[\\.\\w]*ext\\.azure\\.(com|cn|us)$`, `^https:\\/\\/[\\.\\w]*ext\\.azure\\.(com|cn|us)$`,
`^https:\\/\\/[\\.\\w]*\\.ext\\.microsoftazure\\.de$`, `^https:\\/\\/[\\.\\w]*\\.ext\\.microsoftazure\\.de$`,
`^https://cosmos-db-dataexplorer-germanycentral.azurewebsites.de$` `^https://cosmos-db-dataexplorer-germanycentral.azurewebsites.de$`
@@ -49,7 +51,8 @@ let configContext: Readonly<ConfigContext> = {
ARCADIA_ENDPOINT: "https://workspaceartifacts.projectarcadia.net", ARCADIA_ENDPOINT: "https://workspaceartifacts.projectarcadia.net",
ARCADIA_LIVY_ENDPOINT_DNS_ZONE: "dev.azuresynapse.net", ARCADIA_LIVY_ENDPOINT_DNS_ZONE: "dev.azuresynapse.net",
GITHUB_CLIENT_ID: "6cb2f63cf6f7b5cbdeca", // Registered OAuth app: https://github.com/settings/applications/1189306 GITHUB_CLIENT_ID: "6cb2f63cf6f7b5cbdeca", // Registered OAuth app: https://github.com/settings/applications/1189306
JUNO_ENDPOINT: "https://tools.cosmos.azure.com" JUNO_ENDPOINT: "https://tools.cosmos.azure.com",
BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com"
}; };
export function resetConfigContext(): void { export function resetConfigContext(): void {
@@ -93,6 +96,10 @@ export async function initializeConfiguration(): Promise<ConfigContext> {
} }
// Allow override of platform value with URL query parameter // Allow override of platform value with URL query parameter
const params = new URLSearchParams(window.location.search); const params = new URLSearchParams(window.location.search);
if (params.has("armAPIVersion")) {
const armAPIVersion = params.get("armAPIVersion") || "";
updateConfigContext({ armAPIVersion });
}
if (params.has("platform")) { if (params.has("platform")) {
const platform = params.get("platform"); const platform = params.get("platform");
switch (platform) { switch (platform) {

View File

@@ -303,6 +303,16 @@ export interface ReadCollectionOfferParams {
offerId?: string; offerId?: string;
} }
export interface UpdateOfferParams {
currentOffer: Offer;
databaseId: string;
autopilotThroughput: number;
manualThroughput: number;
collectionId?: string;
migrateToAutoPilot?: boolean;
migrateToManual?: boolean;
}
export interface Notification { export interface Notification {
id: string; id: string;
kind: string; kind: string;

View File

@@ -85,7 +85,7 @@ export interface Database extends TreeNode {
collapseDatabase(): void; collapseDatabase(): void;
loadCollections(): Promise<void>; loadCollections(): Promise<void>;
findCollectionWithId(collectionRid: string): Collection; findCollectionWithId(collectionId: string): Collection;
openAddCollection(database: Database, event: MouseEvent): void; openAddCollection(database: Database, event: MouseEvent): void;
onDeleteDatabaseContextMenuClick(source: Database, event: MouseEvent | KeyboardEvent): void; onDeleteDatabaseContextMenuClick(source: Database, event: MouseEvent | KeyboardEvent): void;
onSettingsClick: () => void; onSettingsClick: () => void;
@@ -266,7 +266,6 @@ export interface TabOptions {
tabKind: CollectionTabKind; tabKind: CollectionTabKind;
title: string; title: string;
tabPath: string; tabPath: string;
selfLink: string;
isActive: ko.Observable<boolean>; isActive: ko.Observable<boolean>;
hashLocation: string; hashLocation: string;
onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]) => void; onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]) => void;
@@ -305,7 +304,6 @@ export interface QueryTabOptions extends TabOptions {
export interface ScriptTabOption extends TabOptions { export interface ScriptTabOption extends TabOptions {
resource: any; resource: any;
isNew: boolean; isNew: boolean;
collectionSelfLink?: string;
partitionKey?: DataModels.PartitionKey; partitionKey?: DataModels.PartitionKey;
} }
@@ -388,6 +386,7 @@ export interface DataExplorerInputsFrame {
dataExplorerVersion?: string; dataExplorerVersion?: string;
isAuthWithresourceToken?: boolean; isAuthWithresourceToken?: boolean;
defaultCollectionThroughput?: CollectionCreationDefaults; defaultCollectionThroughput?: CollectionCreationDefaults;
flights?: readonly string[];
} }
export interface CollectionCreationDefaults { export interface CollectionCreationDefaults {

View File

@@ -1,42 +0,0 @@
// Type definitions for jQuery contextMenu 1.7.0
// Project: http://medialize.github.com/jQuery-contextMenu/
// Definitions by: Natan Vivo <https://github.com/nvivo/>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
/// <reference path="jquery.d.ts" />
interface JQueryContextMenuOptions {
selector: string;
appendTo?: string;
trigger?: string;
autoHide?: boolean;
delay?: number;
determinePosition?: (menu: JQuery) => void;
position?: (opt: JQuery, x: number, y: number) => void;
positionSubmenu?: (menu: JQuery) => void;
zIndex?: number;
animation?: {
duration?: number;
show?: string;
hide?: string;
};
events?: {
show?: () => void;
hide?: () => void;
};
callback?: (key: any, options: any) => any;
items?: any;
build?: (triggerElement: JQuery, e: Event) => any;
reposition?: boolean;
className?: string;
itemClickEvent?: string;
}
interface JQueryStatic {
contextMenu(options?: JQueryContextMenuOptions): JQuery;
contextMenu(type: string, selector?: any): JQuery;
}
interface JQuery {
contextMenu(options?: any): JQuery;
}

View File

@@ -3,7 +3,7 @@ import { Dialog, DialogType, DialogFooter, IDialogProps } from "office-ui-fabric
import { IButtonProps, PrimaryButton, DefaultButton } from "office-ui-fabric-react/lib/Button"; import { IButtonProps, PrimaryButton, DefaultButton } from "office-ui-fabric-react/lib/Button";
import { ITextFieldProps, TextField } from "office-ui-fabric-react/lib/TextField"; import { ITextFieldProps, TextField } from "office-ui-fabric-react/lib/TextField";
import { Link } from "office-ui-fabric-react/lib/Link"; import { Link } from "office-ui-fabric-react/lib/Link";
import { FontIcon } from "office-ui-fabric-react"; import { ChoiceGroup, FontIcon, IChoiceGroupProps } from "office-ui-fabric-react";
export interface TextFieldProps extends ITextFieldProps { export interface TextFieldProps extends ITextFieldProps {
label: string; label: string;
@@ -24,6 +24,7 @@ export interface DialogProps {
subText: string; subText: string;
isModal: boolean; isModal: boolean;
visible: boolean; visible: boolean;
choiceGroupProps?: IChoiceGroupProps;
textFieldProps?: TextFieldProps; textFieldProps?: TextFieldProps;
linkProps?: LinkProps; linkProps?: LinkProps;
primaryButtonText: string; primaryButtonText: string;
@@ -65,6 +66,7 @@ export class DialogComponent extends React.Component<DialogProps, {}> {
minWidth: DIALOG_MIN_WIDTH, minWidth: DIALOG_MIN_WIDTH,
maxWidth: DIALOG_MAX_WIDTH maxWidth: DIALOG_MAX_WIDTH
}; };
const choiceGroupProps: IChoiceGroupProps = this.props.choiceGroupProps;
const textFieldProps: ITextFieldProps = this.props.textFieldProps; const textFieldProps: ITextFieldProps = this.props.textFieldProps;
const linkProps: LinkProps = this.props.linkProps; const linkProps: LinkProps = this.props.linkProps;
const primaryButtonProps: IButtonProps = { const primaryButtonProps: IButtonProps = {
@@ -82,6 +84,7 @@ export class DialogComponent extends React.Component<DialogProps, {}> {
return ( return (
<Dialog {...dialogProps}> <Dialog {...dialogProps}>
{choiceGroupProps && <ChoiceGroup {...choiceGroupProps} />}
{textFieldProps && <TextField {...textFieldProps} />} {textFieldProps && <TextField {...textFieldProps} />}
{linkProps && ( {linkProps && (
<Link href={linkProps.linkUrl} target="_blank"> <Link href={linkProps.linkUrl} target="_blank">

View File

@@ -55,7 +55,6 @@ export const FeaturePanelComponent: React.FunctionComponent = () => {
label: "Enable Injecting Notebook Viewer Link into the first cell", label: "Enable Injecting Notebook Viewer Link into the first cell",
value: "true" value: "true"
}, },
{ key: "feature.enablesettingsv2", label: "Enable SettingsV2 Tab", value: "true" },
{ key: "feature.canexceedmaximumvalue", label: "Can exceed max value", value: "true" }, { key: "feature.canexceedmaximumvalue", label: "Can exceed max value", value: "true" },
{ {
key: "feature.enablefixedcollectionwithsharedthroughput", key: "feature.enablefixedcollectionwithsharedthroughput",

View File

@@ -178,12 +178,6 @@ exports[`Feature panel renders all flags 1`] = `
className="checkboxRow" className="checkboxRow"
horizontalAlign="space-between" horizontalAlign="space-between"
> >
<StyledCheckboxBase
checked={false}
key="feature.enablesettingsv2"
label="Enable SettingsV2 Tab"
onChange={[Function]}
/>
<StyledCheckboxBase <StyledCheckboxBase
checked={false} checked={false}
key="feature.canexceedmaximumvalue" key="feature.canexceedmaximumvalue"

View File

@@ -26,7 +26,7 @@ exports[`GalleryCardComponent renders 1`] = `
/> />
</CardItem> </CardItem>
<CardItem> <CardItem>
<Memo(StyledImageBase) <StyledImageBase
alt="name cover image" alt="name cover image"
height={144} height={144}
imageFit={2} imageFit={2}
@@ -95,7 +95,7 @@ exports[`GalleryCardComponent renders 1`] = `
} }
variant="tiny" variant="tiny"
> >
<Memo(StyledIconBase) <StyledIconBase
iconName="RedEye" iconName="RedEye"
styles={ styles={
Object { Object {
@@ -119,7 +119,7 @@ exports[`GalleryCardComponent renders 1`] = `
} }
variant="tiny" variant="tiny"
> >
<Memo(StyledIconBase) <StyledIconBase
iconName="Download" iconName="Download"
styles={ styles={
Object { Object {
@@ -143,7 +143,7 @@ exports[`GalleryCardComponent renders 1`] = `
} }
variant="tiny" variant="tiny"
> >
<Memo(StyledIconBase) <StyledIconBase
iconName="Heart" iconName="Heart"
styles={ styles={
Object { Object {

View File

@@ -1,31 +1,25 @@
jest.mock("../../../Juno/JunoClient");
import { shallow } from "enzyme"; import { shallow } from "enzyme";
import * as sinon from "sinon";
import React from "react"; import React from "react";
import { CodeOfConductComponent, CodeOfConductComponentProps } from "./CodeOfConductComponent"; import { CodeOfConductComponent, CodeOfConductComponentProps } from "./CodeOfConductComponent";
import { IJunoResponse, JunoClient } from "../../../Juno/JunoClient"; import { JunoClient } from "../../../Juno/JunoClient";
import { HttpStatusCodes } from "../../../Common/Constants"; import { HttpStatusCodes } from "../../../Common/Constants";
describe("CodeOfConductComponent", () => { describe("CodeOfConductComponent", () => {
let sandbox: sinon.SinonSandbox;
let codeOfConductProps: CodeOfConductComponentProps; let codeOfConductProps: CodeOfConductComponentProps;
beforeEach(() => { beforeEach(() => {
sandbox = sinon.sandbox.create(); const junoClient = new JunoClient(undefined);
sandbox.stub(JunoClient.prototype, "acceptCodeOfConduct").returns({ junoClient.acceptCodeOfConduct = jest.fn().mockReturnValue({
status: HttpStatusCodes.OK, status: HttpStatusCodes.OK,
data: true data: true
} as IJunoResponse<boolean>); });
const junoClient = new JunoClient(undefined);
codeOfConductProps = { codeOfConductProps = {
junoClient: junoClient, junoClient: junoClient,
onAcceptCodeOfConduct: jest.fn() onAcceptCodeOfConduct: jest.fn()
}; };
}); });
afterEach(() => {
sandbox.restore();
});
it("renders", () => { it("renders", () => {
const wrapper = shallow(<CodeOfConductComponent {...codeOfConductProps} />); const wrapper = shallow(<CodeOfConductComponent {...codeOfConductProps} />);
expect(wrapper).toMatchSnapshot(); expect(wrapper).toMatchSnapshot();

View File

@@ -227,7 +227,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
<Stack.Item styles={{ root: { minWidth: 200 } }}> <Stack.Item styles={{ root: { minWidth: 200 } }}>
<Dropdown options={this.sortingOptions} selectedKey={this.state.sortBy} onChange={this.onDropdownChange} /> <Dropdown options={this.sortingOptions} selectedKey={this.state.sortBy} onChange={this.onDropdownChange} />
</Stack.Item> </Stack.Item>
{this.props.container?.isGalleryPublishEnabled() && ( {(!this.props.container || this.props.container.isGalleryPublishEnabled()) && (
<Stack.Item> <Stack.Item>
<InfoComponent /> <InfoComponent />
</Stack.Item> </Stack.Item>

View File

@@ -3,10 +3,14 @@ import { Icon, Label, Stack, HoverCard, HoverCardType, Link } from "office-ui-fa
import { CodeOfConductEndpoints } from "../../../../Common/Constants"; import { CodeOfConductEndpoints } from "../../../../Common/Constants";
import "./InfoComponent.less"; import "./InfoComponent.less";
export class InfoComponent extends React.Component { export interface InfoComponentProps {
private getInfoPanel = (iconName: string, labelText: string, url: string): JSX.Element => { onReportAbuseClick?: () => void;
}
export class InfoComponent extends React.Component<InfoComponentProps> {
private getInfoPanel = (iconName: string, labelText: string, url?: string, onClick?: () => void): JSX.Element => {
return ( return (
<Link href={url} target="_blank"> <Link href={url} target={url && "_blank"} onClick={onClick}>
<div className="infoPanel"> <div className="infoPanel">
<Icon iconName={iconName} styles={{ root: { verticalAlign: "middle" } }} /> <Icon iconName={iconName} styles={{ root: { verticalAlign: "middle" } }} />
<Label className="infoLabel">{labelText}</Label> <Label className="infoLabel">{labelText}</Label>
@@ -25,6 +29,11 @@ export class InfoComponent extends React.Component {
<Stack.Item> <Stack.Item>
{this.getInfoPanel("KnowledgeArticle", "Microsoft Terms of Use", CodeOfConductEndpoints.termsOfUse)} {this.getInfoPanel("KnowledgeArticle", "Microsoft Terms of Use", CodeOfConductEndpoints.termsOfUse)}
</Stack.Item> </Stack.Item>
{this.props.onReportAbuseClick !== undefined && (
<Stack.Item>
{this.getInfoPanel("ReportHacked", "Report Abuse", undefined, () => this.props.onReportAbuseClick())}
</Stack.Item>
)}
</Stack> </Stack>
); );
}; };

View File

@@ -13,7 +13,7 @@ exports[`InfoComponent renders 1`] = `
<div <div
className="infoPanelMain" className="infoPanelMain"
> >
<Memo(StyledIconBase) <StyledIconBase
className="infoIconMain" className="infoIconMain"
iconName="Help" iconName="Help"
styles={ styles={

View File

@@ -77,6 +77,9 @@ exports[`GalleryViewerComponent renders 1`] = `
selectedKey={0} selectedKey={0}
/> />
</StackItem> </StackItem>
<StackItem>
<InfoComponent />
</StackItem>
</Stack> </Stack>
</Stack> </Stack>
</PivotItem> </PivotItem>

View File

@@ -25,7 +25,8 @@ describe("NotebookMetadataComponent", () => {
onTagClick: undefined, onTagClick: undefined,
onDownloadClick: undefined, onDownloadClick: undefined,
onFavoriteClick: undefined, onFavoriteClick: undefined,
onUnfavoriteClick: undefined onUnfavoriteClick: undefined,
onReportAbuseClick: undefined
}; };
const wrapper = shallow(<NotebookMetadataComponent {...props} />); const wrapper = shallow(<NotebookMetadataComponent {...props} />);
@@ -54,7 +55,8 @@ describe("NotebookMetadataComponent", () => {
onTagClick: undefined, onTagClick: undefined,
onDownloadClick: undefined, onDownloadClick: undefined,
onFavoriteClick: undefined, onFavoriteClick: undefined,
onUnfavoriteClick: undefined onUnfavoriteClick: undefined,
onReportAbuseClick: undefined
}; };
const wrapper = shallow(<NotebookMetadataComponent {...props} />); const wrapper = shallow(<NotebookMetadataComponent {...props} />);

View File

@@ -17,6 +17,7 @@ import { IGalleryItem } from "../../../Juno/JunoClient";
import { FileSystemUtil } from "../../Notebook/FileSystemUtil"; import { FileSystemUtil } from "../../Notebook/FileSystemUtil";
import "./NotebookViewerComponent.less"; import "./NotebookViewerComponent.less";
import CosmosDBLogo from "../../../../images/CosmosDB-logo.svg"; import CosmosDBLogo from "../../../../images/CosmosDB-logo.svg";
import { InfoComponent } from "../NotebookGallery/InfoComponent/InfoComponent";
export interface NotebookMetadataComponentProps { export interface NotebookMetadataComponentProps {
data: IGalleryItem; data: IGalleryItem;
@@ -26,6 +27,7 @@ export interface NotebookMetadataComponentProps {
onFavoriteClick: () => void; onFavoriteClick: () => void;
onUnfavoriteClick: () => void; onUnfavoriteClick: () => void;
onDownloadClick: () => void; onDownloadClick: () => void;
onReportAbuseClick: () => void;
} }
export class NotebookMetadataComponent extends React.Component<NotebookMetadataComponentProps> { export class NotebookMetadataComponent extends React.Component<NotebookMetadataComponentProps> {
@@ -41,24 +43,39 @@ export class NotebookMetadataComponent extends React.Component<NotebookMetadataC
return ( return (
<Stack tokens={{ childrenGap: 10 }}> <Stack tokens={{ childrenGap: 10 }}>
<Stack horizontal verticalAlign="center" tokens={{ childrenGap: 30 }}> <Stack horizontal verticalAlign="center" tokens={{ childrenGap: 30 }}>
<Text variant="xxLarge" nowrap> <Stack.Item>
{FileSystemUtil.stripExtension(this.props.data.name, "ipynb")} <Text variant="xxLarge" nowrap>
</Text> {FileSystemUtil.stripExtension(this.props.data.name, "ipynb")}
<Text> </Text>
{this.props.isFavorite !== undefined && ( </Stack.Item>
<>
<IconButton <Stack.Item>
iconProps={{ iconName: this.props.isFavorite ? "HeartFill" : "Heart" }} <Text>
onClick={this.props.isFavorite ? this.props.onUnfavoriteClick : this.props.onFavoriteClick} {this.props.isFavorite !== undefined && (
/> <>
{this.props.data.favorites} likes <IconButton
</> iconProps={{ iconName: this.props.isFavorite ? "HeartFill" : "Heart" }}
)} onClick={this.props.isFavorite ? this.props.onUnfavoriteClick : this.props.onFavoriteClick}
</Text> />
{this.props.data.favorites} likes
</>
)}
</Text>
</Stack.Item>
{this.props.downloadButtonText && ( {this.props.downloadButtonText && (
<PrimaryButton text={this.props.downloadButtonText} onClick={this.props.onDownloadClick} /> <Stack.Item>
<PrimaryButton text={this.props.downloadButtonText} onClick={this.props.onDownloadClick} />
</Stack.Item>
)} )}
<Stack.Item grow>
<></>
</Stack.Item>
<Stack.Item>
<InfoComponent onReportAbuseClick={this.props.onReportAbuseClick} />
</Stack.Item>
</Stack> </Stack>
<Stack horizontal verticalAlign="center" tokens={{ childrenGap: 10 }}> <Stack horizontal verticalAlign="center" tokens={{ childrenGap: 10 }}>

View File

@@ -3,11 +3,10 @@
*/ */
import { Notebook } from "@nteract/commutable"; import { Notebook } from "@nteract/commutable";
import { createContentRef } from "@nteract/core"; import { createContentRef } from "@nteract/core";
import { Icon, Link, ProgressIndicator } from "office-ui-fabric-react"; import { IChoiceGroupProps, Icon, Link, ProgressIndicator } from "office-ui-fabric-react";
import * as React from "react"; import * as React from "react";
import { contents } from "rx-jupyter"; import { contents } from "rx-jupyter";
import * as Logger from "../../../Common/Logger"; import * as Logger from "../../../Common/Logger";
import * as ViewModels from "../../../Contracts/ViewModels";
import { IGalleryItem, JunoClient } from "../../../Juno/JunoClient"; import { IGalleryItem, JunoClient } from "../../../Juno/JunoClient";
import * as GalleryUtils from "../../../Utils/GalleryUtils"; import * as GalleryUtils from "../../../Utils/GalleryUtils";
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils"; import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
@@ -15,12 +14,13 @@ import { ConsoleDataType } from "../../Menus/NotificationConsole/NotificationCon
import { NotebookClientV2 } from "../../Notebook/NotebookClientV2"; import { NotebookClientV2 } from "../../Notebook/NotebookClientV2";
import { NotebookComponentBootstrapper } from "../../Notebook/NotebookComponent/NotebookComponentBootstrapper"; import { NotebookComponentBootstrapper } from "../../Notebook/NotebookComponent/NotebookComponentBootstrapper";
import NotebookReadOnlyRenderer from "../../Notebook/NotebookRenderer/NotebookReadOnlyRenderer"; import NotebookReadOnlyRenderer from "../../Notebook/NotebookRenderer/NotebookReadOnlyRenderer";
import { DialogComponent, DialogProps } from "../DialogReactComponent/DialogComponent"; import { DialogComponent, DialogProps, TextFieldProps } from "../DialogReactComponent/DialogComponent";
import { NotebookMetadataComponent } from "./NotebookMetadataComponent"; import { NotebookMetadataComponent } from "./NotebookMetadataComponent";
import "./NotebookViewerComponent.less"; import "./NotebookViewerComponent.less";
import Explorer from "../../Explorer"; import Explorer from "../../Explorer";
import { NotebookV4 } from "@nteract/commutable/lib/v4"; import { NotebookV4 } from "@nteract/commutable/lib/v4";
import { SessionStorageUtility } from "../../../Shared/StorageUtility"; import { SessionStorageUtility } from "../../../Shared/StorageUtility";
import { DialogHost } from "../../../Utils/GalleryUtils";
export interface NotebookViewerComponentProps { export interface NotebookViewerComponentProps {
container?: Explorer; container?: Explorer;
@@ -43,10 +43,8 @@ interface NotebookViewerComponentState {
showProgressBar: boolean; showProgressBar: boolean;
} }
export class NotebookViewerComponent extends React.Component< export class NotebookViewerComponent extends React.Component<NotebookViewerComponentProps, NotebookViewerComponentState>
NotebookViewerComponentProps, implements DialogHost {
NotebookViewerComponentState
> {
private clientManager: NotebookClientV2; private clientManager: NotebookClientV2;
private notebookComponentBootstrapper: NotebookComponentBootstrapper; private notebookComponentBootstrapper: NotebookComponentBootstrapper;
@@ -58,7 +56,7 @@ export class NotebookViewerComponent extends React.Component<
databaseAccountName: undefined, databaseAccountName: undefined,
defaultExperience: "NotebookViewer", defaultExperience: "NotebookViewer",
isReadOnly: true, isReadOnly: true,
cellEditorType: "codemirror", cellEditorType: "monaco",
autoSaveInterval: 365 * 24 * 3600 * 1000, // There is no way to turn off auto-save, set to 1 year autoSaveInterval: 365 * 24 * 3600 * 1000, // There is no way to turn off auto-save, set to 1 year
contentProvider: contents.JupyterContentProvider // NotebookViewer only knows how to talk to Jupyter contents API contentProvider: contents.JupyterContentProvider // NotebookViewer only knows how to talk to Jupyter contents API
}); });
@@ -140,6 +138,7 @@ export class NotebookViewerComponent extends React.Component<
onFavoriteClick={this.favoriteItem} onFavoriteClick={this.favoriteItem}
onUnfavoriteClick={this.unfavoriteItem} onUnfavoriteClick={this.unfavoriteItem}
onDownloadClick={this.downloadItem} onDownloadClick={this.downloadItem}
onReportAbuseClick={this.state.galleryItem.isSample ? undefined : this.reportAbuse}
/> />
</div> </div>
) : ( ) : (
@@ -179,6 +178,39 @@ export class NotebookViewerComponent extends React.Component<
}; };
} }
// DialogHost
showOkCancelModalDialog(
title: string,
msg: string,
okLabel: string,
onOk: () => void,
cancelLabel: string,
onCancel: () => void,
choiceGroupProps?: IChoiceGroupProps,
textFieldProps?: TextFieldProps
): void {
this.setState({
dialogProps: {
isModal: true,
visible: true,
title,
subText: msg,
primaryButtonText: okLabel,
secondaryButtonText: cancelLabel,
onPrimaryButtonClick: () => {
this.setState({ dialogProps: undefined });
onOk && onOk();
},
onSecondaryButtonClick: () => {
this.setState({ dialogProps: undefined });
onCancel && onCancel();
},
choiceGroupProps,
textFieldProps
}
});
}
private favoriteItem = async (): Promise<void> => { private favoriteItem = async (): Promise<void> => {
GalleryUtils.favoriteItem(this.props.container, this.props.junoClient, this.state.galleryItem, item => GalleryUtils.favoriteItem(this.props.container, this.props.junoClient, this.state.galleryItem, item =>
this.setState({ galleryItem: item, isFavorite: true }) this.setState({ galleryItem: item, isFavorite: true })
@@ -196,4 +228,8 @@ export class NotebookViewerComponent extends React.Component<
this.setState({ galleryItem: item }) this.setState({ galleryItem: item })
); );
}; };
private reportAbuse = (): void => {
GalleryUtils.reportAbuse(this.props.junoClient, this.state.galleryItem, this, () => {});
};
} }

View File

@@ -17,26 +17,38 @@ exports[`NotebookMetadataComponent renders liked notebook 1`] = `
} }
verticalAlign="center" verticalAlign="center"
> >
<Text <StackItem>
nowrap={true} <Text
variant="xxLarge" nowrap={true}
> variant="xxLarge"
name >
</Text> name
<Text> </Text>
<CustomizedIconButton </StackItem>
iconProps={ <StackItem>
Object { <Text>
"iconName": "HeartFill", <CustomizedIconButton
iconProps={
Object {
"iconName": "HeartFill",
}
} }
} />
0
likes
</Text>
</StackItem>
<StackItem>
<CustomizedPrimaryButton
text="Download"
/> />
0 </StackItem>
likes <StackItem
</Text> grow={true}
<CustomizedPrimaryButton
text="Download"
/> />
<StackItem>
<InfoComponent />
</StackItem>
</Stack> </Stack>
<Stack <Stack
horizontal={true} horizontal={true}
@@ -56,14 +68,14 @@ exports[`NotebookMetadataComponent renders liked notebook 1`] = `
Invalid Date Invalid Date
</Text> </Text>
<Text> <Text>
<Memo(StyledIconBase) <StyledIconBase
iconName="RedEye" iconName="RedEye"
/> />
0 0
</Text> </Text>
<Text> <Text>
<Memo(StyledIconBase) <StyledIconBase
iconName="Download" iconName="Download"
/> />
0 0
@@ -117,26 +129,38 @@ exports[`NotebookMetadataComponent renders un-liked notebook 1`] = `
} }
verticalAlign="center" verticalAlign="center"
> >
<Text <StackItem>
nowrap={true} <Text
variant="xxLarge" nowrap={true}
> variant="xxLarge"
name >
</Text> name
<Text> </Text>
<CustomizedIconButton </StackItem>
iconProps={ <StackItem>
Object { <Text>
"iconName": "Heart", <CustomizedIconButton
iconProps={
Object {
"iconName": "Heart",
}
} }
} />
0
likes
</Text>
</StackItem>
<StackItem>
<CustomizedPrimaryButton
text="Download"
/> />
0 </StackItem>
likes <StackItem
</Text> grow={true}
<CustomizedPrimaryButton
text="Download"
/> />
<StackItem>
<InfoComponent />
</StackItem>
</Stack> </Stack>
<Stack <Stack
horizontal={true} horizontal={true}
@@ -156,14 +180,14 @@ exports[`NotebookMetadataComponent renders un-liked notebook 1`] = `
Invalid Date Invalid Date
</Text> </Text>
<Text> <Text>
<Memo(StyledIconBase) <StyledIconBase
iconName="RedEye" iconName="RedEye"
/> />
0 0
</Text> </Text>
<Text> <Text>
<Memo(StyledIconBase) <StyledIconBase
iconName="Download" iconName="Download"
/> />
0 0

View File

@@ -6,7 +6,7 @@ import SettingsTabV2 from "../../Tabs/SettingsTabV2";
import { collection } from "./TestUtils"; import { collection } from "./TestUtils";
import * as DataModels from "../../../Contracts/DataModels"; import * as DataModels from "../../../Contracts/DataModels";
import ko from "knockout"; import ko from "knockout";
import { TtlType, isDirty, TtlOnNoDefault, TtlOn, TtlOff } from "./SettingsUtils"; import { TtlType, isDirty } from "./SettingsUtils";
import Explorer from "../../Explorer"; import Explorer from "../../Explorer";
import { updateCollection } from "../../../Common/dataAccess/updateCollection"; import { updateCollection } from "../../../Common/dataAccess/updateCollection";
jest.mock("../../../Common/dataAccess/updateCollection", () => ({ jest.mock("../../../Common/dataAccess/updateCollection", () => ({
@@ -20,8 +20,8 @@ jest.mock("../../../Common/dataAccess/updateCollection", () => ({
geospatialConfig: undefined geospatialConfig: undefined
} as DataModels.Collection) } as DataModels.Collection)
})); }));
import { updateOffer } from "../../../Common/DocumentClientUtilityBase"; import { updateOffer } from "../../../Common/dataAccess/updateOffer";
jest.mock("../../../Common/DocumentClientUtilityBase", () => ({ jest.mock("../../../Common/dataAccess/updateOffer", () => ({
updateOffer: jest.fn().mockReturnValue({} as DataModels.Offer) updateOffer: jest.fn().mockReturnValue({} as DataModels.Offer)
})); }));
@@ -33,7 +33,6 @@ describe("SettingsComponent", () => {
title: "Scale & Settings", title: "Scale & Settings",
tabPath: "", tabPath: "",
node: undefined, node: undefined,
selfLink: undefined,
hashLocation: "settings", hashLocation: "settings",
isActive: ko.observable(false), isActive: ko.observable(false),
onUpdateTabsButtons: undefined onUpdateTabsButtons: undefined
@@ -103,10 +102,7 @@ describe("SettingsComponent", () => {
let settingsComponentInstance = new SettingsComponent(baseProps); let settingsComponentInstance = new SettingsComponent(baseProps);
expect(settingsComponentInstance.shouldShowKeyspaceSharedThroughputMessage()).toEqual(false); expect(settingsComponentInstance.shouldShowKeyspaceSharedThroughputMessage()).toEqual(false);
const newContainer = new Explorer({ const newContainer = new Explorer();
notificationsClient: undefined,
isEmulator: false
});
newContainer.isPreferredApiCassandra = ko.computed(() => true); newContainer.isPreferredApiCassandra = ko.computed(() => true);
const newCollection = { ...collection }; const newCollection = { ...collection };
@@ -147,10 +143,7 @@ describe("SettingsComponent", () => {
let settingsComponentInstance = new SettingsComponent(baseProps); let settingsComponentInstance = new SettingsComponent(baseProps);
expect(settingsComponentInstance.hasConflictResolution()).toEqual(undefined); expect(settingsComponentInstance.hasConflictResolution()).toEqual(undefined);
const newContainer = new Explorer({ const newContainer = new Explorer();
notificationsClient: undefined,
isEmulator: false
});
newContainer.databaseAccount = ko.observable({ newContainer.databaseAccount = ko.observable({
id: undefined, id: undefined,
name: undefined, name: undefined,
@@ -220,13 +213,6 @@ describe("SettingsComponent", () => {
expect(isDirty(state.throughput, state.throughputBaseline)).toEqual(false); expect(isDirty(state.throughput, state.throughputBaseline)).toEqual(false);
}); });
it("getTtlValue", async () => {
const settingsComponentInstance = new SettingsComponent(baseProps);
expect(settingsComponentInstance.getTtlValue(TtlType.OnNoDefault)).toEqual(TtlOnNoDefault);
expect(settingsComponentInstance.getTtlValue(TtlType.On)).toEqual(TtlOn);
expect(settingsComponentInstance.getTtlValue(TtlType.Off)).toEqual(TtlOff);
});
it("getAnalyticalStorageTtl", () => { it("getAnalyticalStorageTtl", () => {
const newCollection = { ...collection }; const newCollection = { ...collection };
newCollection.analyticalStorageTtl = ko.observable(10); newCollection.analyticalStorageTtl = ko.observable(10);

View File

@@ -6,11 +6,11 @@ import * as SharedConstants from "../../../Shared/Constants";
import * as ViewModels from "../../../Contracts/ViewModels"; import * as ViewModels from "../../../Contracts/ViewModels";
import DiscardIcon from "../../../../images/discard.svg"; import DiscardIcon from "../../../../images/discard.svg";
import SaveIcon from "../../../../images/save-cosmos.svg"; import SaveIcon from "../../../../images/save-cosmos.svg";
import { traceStart, traceFailure, traceSuccess } from "../../../Shared/Telemetry/TelemetryProcessor"; import { traceStart, traceFailure, traceSuccess, trace } from "../../../Shared/Telemetry/TelemetryProcessor";
import { Action } from "../../../Shared/Telemetry/TelemetryConstants"; import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
import { RequestOptions } from "@azure/cosmos/dist-esm"; import { RequestOptions } from "@azure/cosmos/dist-esm";
import Explorer from "../../Explorer"; import Explorer from "../../Explorer";
import { updateOffer } from "../../../Common/DocumentClientUtilityBase"; import { updateOffer } from "../../../Common/dataAccess/updateOffer";
import { updateCollection } from "../../../Common/dataAccess/updateCollection"; import { updateCollection } from "../../../Common/dataAccess/updateCollection";
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent"; import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
import { userContext } from "../../../UserContext"; import { userContext } from "../../../UserContext";
@@ -27,9 +27,6 @@ import {
SettingsV2TabTypes, SettingsV2TabTypes,
getTabTitle, getTabTitle,
isDirty, isDirty,
TtlOff,
TtlOn,
TtlOnNoDefault,
parseConflictResolutionMode, parseConflictResolutionMode,
parseConflictResolutionProcedure parseConflictResolutionProcedure
} from "./SettingsUtils"; } from "./SettingsUtils";
@@ -38,7 +35,7 @@ import {
ConflictResolutionComponentProps ConflictResolutionComponentProps
} from "./SettingsSubComponents/ConflictResolutionComponent"; } from "./SettingsSubComponents/ConflictResolutionComponent";
import { SubSettingsComponent, SubSettingsComponentProps } from "./SettingsSubComponents/SubSettingsComponent"; import { SubSettingsComponent, SubSettingsComponentProps } from "./SettingsSubComponents/SubSettingsComponent";
import { Pivot, PivotItem, IPivotProps, IPivotItemProps, IChoiceGroupOption } from "office-ui-fabric-react"; import { Pivot, PivotItem, IPivotProps, IPivotItemProps } from "office-ui-fabric-react";
import "./SettingsComponent.less"; import "./SettingsComponent.less";
import { IndexingPolicyComponent, IndexingPolicyComponentProps } from "./SettingsSubComponents/IndexingPolicyComponent"; import { IndexingPolicyComponent, IndexingPolicyComponentProps } from "./SettingsSubComponents/IndexingPolicyComponent";
@@ -85,7 +82,6 @@ export interface SettingsComponentState {
indexingPolicyContent: DataModels.IndexingPolicy; indexingPolicyContent: DataModels.IndexingPolicy;
indexingPolicyContentBaseline: DataModels.IndexingPolicy; indexingPolicyContentBaseline: DataModels.IndexingPolicy;
shouldDiscardIndexingPolicy: boolean; shouldDiscardIndexingPolicy: boolean;
indexingPolicyElementFocussed: boolean;
isIndexingPolicyDirty: boolean; isIndexingPolicyDirty: boolean;
conflictResolutionPolicyMode: DataModels.ConflictResolutionMode; conflictResolutionPolicyMode: DataModels.ConflictResolutionMode;
@@ -102,7 +98,6 @@ export interface SettingsComponentState {
export class SettingsComponent extends React.Component<SettingsComponentProps, SettingsComponentState> { export class SettingsComponent extends React.Component<SettingsComponentProps, SettingsComponentState> {
private static readonly sixMonthsInSeconds = 15768000; private static readonly sixMonthsInSeconds = 15768000;
private static readonly zeroSeconds = 0;
public saveSettingsButton: ButtonV2; public saveSettingsButton: ButtonV2;
public discardSettingsChangesButton: ButtonV2; public discardSettingsChangesButton: ButtonV2;
@@ -127,8 +122,8 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
this.changeFeedPolicyVisible = this.collection?.container.isFeatureEnabled( this.changeFeedPolicyVisible = this.collection?.container.isFeatureEnabled(
Constants.Features.enableChangeFeedPolicy Constants.Features.enableChangeFeedPolicy
); );
// Mongo container with system partition key still treat as "Fixed"
// Mongo container with system partition key still treat as "Fixed"
this.isFixedContainer = this.isFixedContainer =
!this.collection.partitionKey || !this.collection.partitionKey ||
(this.container.isPreferredApiMongoDB() && this.collection.partitionKey.systemKey); (this.container.isPreferredApiMongoDB() && this.collection.partitionKey.systemKey);
@@ -160,7 +155,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
indexingPolicyContent: undefined, indexingPolicyContent: undefined,
indexingPolicyContentBaseline: undefined, indexingPolicyContentBaseline: undefined,
indexingPolicyElementFocussed: false,
shouldDiscardIndexingPolicy: false, shouldDiscardIndexingPolicy: false,
isIndexingPolicyDirty: false, isIndexingPolicyDirty: false,
@@ -270,7 +264,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
this.props.settingsTab.isExecutionError(false); this.props.settingsTab.isExecutionError(false);
this.props.settingsTab.isExecuting(true); this.props.settingsTab.isExecuting(true);
const startKey: number = traceStart(Action.UpdateSettings, { const startKey: number = traceStart(Action.SettingsV2Updated, {
databaseAccountName: this.container.databaseAccount()?.name, databaseAccountName: this.container.databaseAccount()?.name,
defaultExperience: this.container.defaultExperience(), defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab, dataExplorerArea: Constants.Areas.Tab,
@@ -411,7 +405,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
this.setState({ isScaleSaveable: false, isScaleDiscardable: false }); this.setState({ isScaleSaveable: false, isScaleDiscardable: false });
} catch (error) { } catch (error) {
traceFailure( traceFailure(
Action.UpdateSettings, Action.SettingsV2Updated,
{ {
databaseAccountName: this.container.databaseAccount().name, databaseAccountName: this.container.databaseAccount().name,
databaseName: this.collection && this.collection.databaseId, databaseName: this.collection && this.collection.databaseId,
@@ -426,7 +420,21 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
throw error; throw error;
} }
} else { } else {
const updatedOffer: DataModels.Offer = await updateOffer(this.collection.offer(), newOffer, headerOptions); const updateOfferParams: DataModels.UpdateOfferParams = {
databaseId: this.collection.databaseId,
collectionId: this.collection.id(),
currentOffer: this.collection.offer(),
autopilotThroughput: this.state.isAutoPilotSelected ? this.state.autoPilotThroughput : undefined,
manualThroughput: this.state.isAutoPilotSelected ? undefined : newThroughput
};
if (this.hasProvisioningTypeChanged()) {
if (this.state.isAutoPilotSelected) {
updateOfferParams.migrateToAutoPilot = true;
} else {
updateOfferParams.migrateToManual = true;
}
}
const updatedOffer: DataModels.Offer = await updateOffer(updateOfferParams);
this.collection.offer(updatedOffer); this.collection.offer(updatedOffer);
this.setState({ isScaleSaveable: false, isScaleDiscardable: false }); this.setState({ isScaleSaveable: false, isScaleDiscardable: false });
if (this.state.isAutoPilotSelected) { if (this.state.isAutoPilotSelected) {
@@ -446,7 +454,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
this.setBaseline(); this.setBaseline();
this.setState({ wasAutopilotOriginallySet: this.state.isAutoPilotSelected }); this.setState({ wasAutopilotOriginallySet: this.state.isAutoPilotSelected });
traceSuccess( traceSuccess(
Action.UpdateSettings, Action.SettingsV2Updated,
{ {
databaseAccountName: this.container.databaseAccount()?.name, databaseAccountName: this.container.databaseAccount()?.name,
defaultExperience: this.container.defaultExperience(), defaultExperience: this.container.defaultExperience(),
@@ -460,7 +468,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
this.props.settingsTab.isExecutionError(true); this.props.settingsTab.isExecutionError(true);
console.error(reason); console.error(reason);
traceFailure( traceFailure(
Action.UpdateSettings, Action.SettingsV2Updated,
{ {
databaseAccountName: this.container.databaseAccount()?.name, databaseAccountName: this.container.databaseAccount()?.name,
defaultExperience: this.container.defaultExperience(), defaultExperience: this.container.defaultExperience(),
@@ -474,6 +482,10 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
}; };
public onRevertClick = (): void => { public onRevertClick = (): void => {
trace(Action.SettingsV2Discarded, ActionModifiers.Mark, {
message: "Settings Discarded"
});
this.setState({ this.setState({
throughput: this.state.throughputBaseline, throughput: this.state.throughputBaseline,
timeToLive: this.state.timeToLiveBaseline, timeToLive: this.state.timeToLiveBaseline,
@@ -504,9 +516,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
private onScaleDiscardableChange = (isScaleDiscardable: boolean): void => private onScaleDiscardableChange = (isScaleDiscardable: boolean): void =>
this.setState({ isScaleDiscardable: isScaleDiscardable }); this.setState({ isScaleDiscardable: isScaleDiscardable });
private onIndexingPolicyElementFocusChange = (indexingPolicyElementFocussed: boolean): void =>
this.setState({ indexingPolicyElementFocussed: indexingPolicyElementFocussed });
private onIndexingPolicyContentChange = (newIndexingPolicy: DataModels.IndexingPolicy): void => private onIndexingPolicyContentChange = (newIndexingPolicy: DataModels.IndexingPolicy): void =>
this.setState({ indexingPolicyContent: newIndexingPolicy }); this.setState({ indexingPolicyContent: newIndexingPolicy });
@@ -530,79 +539,34 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
} }
}; };
private onConflictResolutionPolicyModeChange = ( private onConflictResolutionPolicyModeChange = (newMode: DataModels.ConflictResolutionMode): void =>
event?: React.FormEvent<HTMLElement | HTMLInputElement>, this.setState({ conflictResolutionPolicyMode: newMode });
option?: IChoiceGroupOption
): void =>
this.setState({
conflictResolutionPolicyMode:
DataModels.ConflictResolutionMode[option.key as keyof typeof DataModels.ConflictResolutionMode]
});
private onConflictResolutionPolicyPathChange = ( private onConflictResolutionPolicyPathChange = (newPath: string): void =>
event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>, this.setState({ conflictResolutionPolicyPath: newPath });
newValue?: string
): void => this.setState({ conflictResolutionPolicyPath: newValue });
private onConflictResolutionPolicyProcedureChange = ( private onConflictResolutionPolicyProcedureChange = (newProcedure: string): void =>
event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>, this.setState({ conflictResolutionPolicyProcedure: newProcedure });
newValue?: string
): void => this.setState({ conflictResolutionPolicyProcedure: newValue });
private onConflictResolutionDirtyChange = (isConflictResolutionDirty: boolean): void => private onConflictResolutionDirtyChange = (isConflictResolutionDirty: boolean): void =>
this.setState({ isConflictResolutionDirty: isConflictResolutionDirty }); this.setState({ isConflictResolutionDirty: isConflictResolutionDirty });
public getTtlValue = (value: string): TtlType => { private onTtlChange = (newTtl: TtlType): void => this.setState({ timeToLive: newTtl });
switch (value) {
case TtlOn:
return TtlType.On;
case TtlOff:
return TtlType.Off;
case TtlOnNoDefault:
return TtlType.OnNoDefault;
}
return undefined;
};
private onTtlChange = (ev?: React.FormEvent<HTMLElement | HTMLInputElement>, option?: IChoiceGroupOption): void => private onTimeToLiveSecondsChange = (newTimeToLiveSeconds: number): void =>
this.setState({ timeToLive: this.getTtlValue(option.key) });
private onTimeToLiveSecondsChange = (
event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
newValue?: string
): void => {
let newTimeToLiveSeconds = parseInt(newValue);
newTimeToLiveSeconds = isNaN(newTimeToLiveSeconds) ? SettingsComponent.zeroSeconds : newTimeToLiveSeconds;
this.setState({ timeToLiveSeconds: newTimeToLiveSeconds }); this.setState({ timeToLiveSeconds: newTimeToLiveSeconds });
};
private onGeoSpatialConfigTypeChange = ( private onGeoSpatialConfigTypeChange = (newGeoSpatialConfigType: GeospatialConfigType): void =>
ev?: React.FormEvent<HTMLElement | HTMLInputElement>, this.setState({ geospatialConfigType: newGeoSpatialConfigType });
option?: IChoiceGroupOption
): void =>
this.setState({ geospatialConfigType: GeospatialConfigType[option.key as keyof typeof GeospatialConfigType] });
private onAnalyticalStorageTtlSelectionChange = ( private onAnalyticalStorageTtlSelectionChange = (newAnalyticalStorageTtlSelection: TtlType): void =>
ev?: React.FormEvent<HTMLElement | HTMLInputElement>, this.setState({ analyticalStorageTtlSelection: newAnalyticalStorageTtlSelection });
option?: IChoiceGroupOption
): void => this.setState({ analyticalStorageTtlSelection: this.getTtlValue(option.key) });
private onAnalyticalStorageTtlSecondsChange = ( private onAnalyticalStorageTtlSecondsChange = (newAnalyticalStorageTtlSeconds: number): void =>
event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
newValue?: string
): void => {
let newAnalyticalStorageTtlSeconds = parseInt(newValue);
newAnalyticalStorageTtlSeconds = isNaN(newAnalyticalStorageTtlSeconds)
? SettingsComponent.zeroSeconds
: newAnalyticalStorageTtlSeconds;
this.setState({ analyticalStorageTtlSeconds: newAnalyticalStorageTtlSeconds }); this.setState({ analyticalStorageTtlSeconds: newAnalyticalStorageTtlSeconds });
};
private onChangeFeedPolicyChange = ( private onChangeFeedPolicyChange = (newChangeFeedPolicy: ChangeFeedPolicyState): void =>
ev?: React.FormEvent<HTMLElement | HTMLInputElement>, this.setState({ changeFeedPolicy: newChangeFeedPolicy });
option?: IChoiceGroupOption
): void =>
this.setState({ changeFeedPolicy: ChangeFeedPolicyState[option.key as keyof typeof ChangeFeedPolicyState] });
private onSubSettingsSaveableChange = (isSubSettingsSaveable: boolean): void => private onSubSettingsSaveableChange = (isSubSettingsSaveable: boolean): void =>
this.setState({ isSubSettingsSaveable: isSubSettingsSaveable }); this.setState({ isSubSettingsSaveable: isSubSettingsSaveable });
@@ -830,7 +794,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
resetShouldDiscardIndexingPolicy: this.resetShouldDiscardIndexingPolicy, resetShouldDiscardIndexingPolicy: this.resetShouldDiscardIndexingPolicy,
indexingPolicyContent: this.state.indexingPolicyContent, indexingPolicyContent: this.state.indexingPolicyContent,
indexingPolicyContentBaseline: this.state.indexingPolicyContentBaseline, indexingPolicyContentBaseline: this.state.indexingPolicyContentBaseline,
onIndexingPolicyElementFocusChange: this.onIndexingPolicyElementFocusChange,
onIndexingPolicyContentChange: this.onIndexingPolicyContentChange, onIndexingPolicyContentChange: this.onIndexingPolicyContentChange,
logIndexingPolicySuccessMessage: this.logIndexingPolicySuccessMessage, logIndexingPolicySuccessMessage: this.logIndexingPolicySuccessMessage,
onIndexingPolicyDirtyChange: this.onIndexingPolicyDirtyChange onIndexingPolicyDirtyChange: this.onIndexingPolicyDirtyChange

View File

@@ -18,24 +18,15 @@ export interface ConflictResolutionComponentProps {
container: Explorer; container: Explorer;
conflictResolutionPolicyMode: DataModels.ConflictResolutionMode; conflictResolutionPolicyMode: DataModels.ConflictResolutionMode;
conflictResolutionPolicyModeBaseline: DataModels.ConflictResolutionMode; conflictResolutionPolicyModeBaseline: DataModels.ConflictResolutionMode;
onConflictResolutionPolicyModeChange: ( onConflictResolutionPolicyModeChange: (newMode: DataModels.ConflictResolutionMode) => void;
event?: React.FormEvent<HTMLElement | HTMLInputElement>,
option?: IChoiceGroupOption
) => void;
conflictResolutionPolicyPath: string; conflictResolutionPolicyPath: string;
conflictResolutionPolicyPathBaseline: string; conflictResolutionPolicyPathBaseline: string;
onConflictResolutionPolicyPathChange: ( onConflictResolutionPolicyPathChange: (newPath: string) => void;
event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
newValue?: string
) => void;
conflictResolutionPolicyProcedure: string; conflictResolutionPolicyProcedure: string;
conflictResolutionPolicyProcedureBaseline: string; conflictResolutionPolicyProcedureBaseline: string;
onConflictResolutionPolicyProcedureChange: ( onConflictResolutionPolicyProcedureChange: (newProcedure: string) => void;
event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
newValue?: string
) => void;
onConflictResolutionDirtyChange: (isConflictResolutionDirty: boolean) => void; onConflictResolutionDirtyChange: (isConflictResolutionDirty: boolean) => void;
} }
@@ -77,12 +68,30 @@ export class ConflictResolutionComponent extends React.Component<ConflictResolut
return false; return false;
}; };
private onConflictResolutionPolicyModeChange = (
event?: React.FormEvent<HTMLElement | HTMLInputElement>,
option?: IChoiceGroupOption
): void =>
this.props.onConflictResolutionPolicyModeChange(
DataModels.ConflictResolutionMode[option.key as keyof typeof DataModels.ConflictResolutionMode]
);
private onConflictResolutionPolicyPathChange = (
event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
newValue?: string
): void => this.props.onConflictResolutionPolicyPathChange(newValue);
private onConflictResolutionPolicyProcedureChange = (
event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
newValue?: string
): void => this.props.onConflictResolutionPolicyProcedureChange(newValue);
private getConflictResolutionModeComponent = (): JSX.Element => ( private getConflictResolutionModeComponent = (): JSX.Element => (
<ChoiceGroup <ChoiceGroup
label="Mode" label="Mode"
selectedKey={this.props.conflictResolutionPolicyMode} selectedKey={this.props.conflictResolutionPolicyMode}
options={this.conflictResolutionChoiceGroupOptions} options={this.conflictResolutionChoiceGroupOptions}
onChange={this.props.onConflictResolutionPolicyModeChange} onChange={this.onConflictResolutionPolicyModeChange}
styles={getChoiceGroupStyles( styles={getChoiceGroupStyles(
this.props.conflictResolutionPolicyMode, this.props.conflictResolutionPolicyMode,
this.props.conflictResolutionPolicyModeBaseline this.props.conflictResolutionPolicyModeBaseline
@@ -104,7 +113,7 @@ export class ConflictResolutionComponent extends React.Component<ConflictResolut
this.props.conflictResolutionPolicyPathBaseline this.props.conflictResolutionPolicyPathBaseline
)} )}
value={this.props.conflictResolutionPolicyPath} value={this.props.conflictResolutionPolicyPath}
onChange={this.props.onConflictResolutionPolicyPathChange} onChange={this.onConflictResolutionPolicyPathChange}
/> />
); );
@@ -122,7 +131,7 @@ export class ConflictResolutionComponent extends React.Component<ConflictResolut
this.props.conflictResolutionPolicyProcedureBaseline this.props.conflictResolutionPolicyProcedureBaseline
)} )}
value={this.props.conflictResolutionPolicyProcedure} value={this.props.conflictResolutionPolicyProcedure}
onChange={this.props.onConflictResolutionPolicyProcedureChange} onChange={this.onConflictResolutionPolicyProcedureChange}
/> />
); );

View File

@@ -17,9 +17,6 @@ describe("IndexingPolicyComponent", () => {
}, },
indexingPolicyContent: initialIndexingPolicyContent, indexingPolicyContent: initialIndexingPolicyContent,
indexingPolicyContentBaseline: initialIndexingPolicyContent, indexingPolicyContentBaseline: initialIndexingPolicyContent,
onIndexingPolicyElementFocusChange: () => {
return;
},
onIndexingPolicyContentChange: () => { onIndexingPolicyContentChange: () => {
return; return;
}, },

View File

@@ -10,7 +10,6 @@ export interface IndexingPolicyComponentProps {
resetShouldDiscardIndexingPolicy: () => void; resetShouldDiscardIndexingPolicy: () => void;
indexingPolicyContent: DataModels.IndexingPolicy; indexingPolicyContent: DataModels.IndexingPolicy;
indexingPolicyContentBaseline: DataModels.IndexingPolicy; indexingPolicyContentBaseline: DataModels.IndexingPolicy;
onIndexingPolicyElementFocusChange: (indexingPolicyContentFocussed: boolean) => void;
onIndexingPolicyContentChange: (newIndexingPolicy: DataModels.IndexingPolicy) => void; onIndexingPolicyContentChange: (newIndexingPolicy: DataModels.IndexingPolicy) => void;
logIndexingPolicySuccessMessage: () => void; logIndexingPolicySuccessMessage: () => void;
onIndexingPolicyDirtyChange: (isIndexingPolicyDirty: boolean) => void; onIndexingPolicyDirtyChange: (isIndexingPolicyDirty: boolean) => void;
@@ -89,8 +88,6 @@ export class IndexingPolicyComponent extends React.Component<
ariaLabel: "Indexing Policy" ariaLabel: "Indexing Policy"
}); });
if (this.indexingPolicyEditor) { if (this.indexingPolicyEditor) {
this.indexingPolicyEditor.onDidFocusEditorText(() => this.props.onIndexingPolicyElementFocusChange(true));
this.indexingPolicyEditor.onDidBlurEditorText(() => this.props.onIndexingPolicyElementFocusChange(false));
const indexingPolicyEditorModel = this.indexingPolicyEditor.getModel(); const indexingPolicyEditorModel = this.indexingPolicyEditor.getModel();
indexingPolicyEditorModel.onDidChangeContent(this.onEditorContentChange.bind(this)); indexingPolicyEditorModel.onDidChangeContent(this.onEditorContentChange.bind(this));
this.props.logIndexingPolicySuccessMessage(); this.props.logIndexingPolicySuccessMessage();

View File

@@ -12,10 +12,7 @@ import * as SharedConstants from "../../../../Shared/Constants";
import ko from "knockout"; import ko from "knockout";
describe("ScaleComponent", () => { describe("ScaleComponent", () => {
const nonNationalCloudContainer = new Explorer({ const nonNationalCloudContainer = new Explorer();
notificationsClient: undefined,
isEmulator: false
});
nonNationalCloudContainer.getPlatformType = () => PlatformType.Portal; nonNationalCloudContainer.getPlatformType = () => PlatformType.Portal;
nonNationalCloudContainer.isRunningOnNationalCloud = () => false; nonNationalCloudContainer.isRunningOnNationalCloud = () => false;
@@ -88,10 +85,7 @@ describe("ScaleComponent", () => {
}); });
it("autoScale enabled", () => { it("autoScale enabled", () => {
const newContainer = new Explorer({ const newContainer = new Explorer();
notificationsClient: undefined,
isEmulator: false
});
newContainer.databaseAccount({ newContainer.databaseAccount({
id: undefined, id: undefined,

View File

@@ -19,6 +19,7 @@ import {
import { getMaxRUs, getMinRUs, hasDatabaseSharedThroughput } from "../SettingsUtils"; import { getMaxRUs, getMinRUs, hasDatabaseSharedThroughput } from "../SettingsUtils";
import * as AutoPilotUtils from "../../../../Utils/AutoPilotUtils"; import * as AutoPilotUtils from "../../../../Utils/AutoPilotUtils";
import { Text, TextField, Stack, Label, MessageBar, MessageBarType } from "office-ui-fabric-react"; import { Text, TextField, Stack, Label, MessageBar, MessageBarType } from "office-ui-fabric-react";
import { configContext, Platform } from "../../../../ConfigContext";
export interface ScaleComponentProps { export interface ScaleComponentProps {
collection: ViewModels.Collection; collection: ViewModels.Collection;
@@ -43,7 +44,7 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
private isEmulator: boolean; private isEmulator: boolean;
constructor(props: ScaleComponentProps) { constructor(props: ScaleComponentProps) {
super(props); super(props);
this.isEmulator = this.props.container.isEmulator; this.isEmulator = configContext.platform === Platform.Emulator;
} }
public isAutoScaleEnabled = (): boolean => { public isAutoScaleEnabled = (): boolean => {

View File

@@ -2,7 +2,7 @@ import { shallow } from "enzyme";
import React from "react"; import React from "react";
import { SubSettingsComponent, SubSettingsComponentProps } from "./SubSettingsComponent"; import { SubSettingsComponent, SubSettingsComponentProps } from "./SubSettingsComponent";
import { container, collection } from "../TestUtils"; import { container, collection } from "../TestUtils";
import { TtlType, GeospatialConfigType, ChangeFeedPolicyState } from "../SettingsUtils"; import { TtlType, GeospatialConfigType, ChangeFeedPolicyState, TtlOnNoDefault, TtlOn, TtlOff } from "../SettingsUtils";
import ko from "knockout"; import ko from "knockout";
import Explorer from "../../../Explorer"; import Explorer from "../../../Explorer";
@@ -105,10 +105,7 @@ describe("SubSettingsComponent", () => {
}); });
it("partitionKey not visible", () => { it("partitionKey not visible", () => {
const newContainer = new Explorer({ const newContainer = new Explorer();
notificationsClient: undefined,
isEmulator: false
});
newContainer.isPreferredApiCassandra = ko.computed(() => true); newContainer.isPreferredApiCassandra = ko.computed(() => true);
const props = { ...baseProps, container: newContainer }; const props = { ...baseProps, container: newContainer };
@@ -133,4 +130,11 @@ describe("SubSettingsComponent", () => {
expect(isComponentDirtyResult.isSaveable).toEqual(true); expect(isComponentDirtyResult.isSaveable).toEqual(true);
expect(isComponentDirtyResult.isDiscardable).toEqual(true); expect(isComponentDirtyResult.isDiscardable).toEqual(true);
}); });
it("getTtlValue", async () => {
const subSettingsComponentInstance = new SubSettingsComponent(baseProps);
expect(subSettingsComponentInstance.getTtlValue(TtlType.OnNoDefault)).toEqual(TtlOnNoDefault);
expect(subSettingsComponentInstance.getTtlValue(TtlType.On)).toEqual(TtlOn);
expect(subSettingsComponentInstance.getTtlValue(TtlType.Off)).toEqual(TtlOff);
});
}); });

View File

@@ -5,7 +5,11 @@ import {
TtlType, TtlType,
ChangeFeedPolicyState, ChangeFeedPolicyState,
isDirty, isDirty,
IsComponentDirtyResult IsComponentDirtyResult,
TtlOn,
TtlOff,
TtlOnNoDefault,
getSanitizedInputValue
} from "../SettingsUtils"; } from "../SettingsUtils";
import Explorer from "../../../Explorer"; import Explorer from "../../../Explorer";
import { Int32 } from "../../../Panes/Tables/Validators/EntityPropertyValidationCommon"; import { Int32 } from "../../../Panes/Tables/Validators/EntityPropertyValidationCommon";
@@ -37,40 +41,28 @@ export interface SubSettingsComponentProps {
timeToLive: TtlType; timeToLive: TtlType;
timeToLiveBaseline: TtlType; timeToLiveBaseline: TtlType;
onTtlChange: (ev?: React.FormEvent<HTMLElement | HTMLInputElement>, option?: IChoiceGroupOption) => void; onTtlChange: (newTtl: TtlType) => void;
timeToLiveSeconds: number; timeToLiveSeconds: number;
timeToLiveSecondsBaseline: number; timeToLiveSecondsBaseline: number;
onTimeToLiveSecondsChange: ( onTimeToLiveSecondsChange: (newTimeToLiveSeconds: number) => void;
event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
newValue?: string
) => void;
geospatialConfigType: GeospatialConfigType; geospatialConfigType: GeospatialConfigType;
geospatialConfigTypeBaseline: GeospatialConfigType; geospatialConfigTypeBaseline: GeospatialConfigType;
onGeoSpatialConfigTypeChange: ( onGeoSpatialConfigTypeChange: (newGeoSpatialConfigType: GeospatialConfigType) => void;
ev?: React.FormEvent<HTMLElement | HTMLInputElement>,
option?: IChoiceGroupOption
) => void;
isAnalyticalStorageEnabled: boolean; isAnalyticalStorageEnabled: boolean;
analyticalStorageTtlSelection: TtlType; analyticalStorageTtlSelection: TtlType;
analyticalStorageTtlSelectionBaseline: TtlType; analyticalStorageTtlSelectionBaseline: TtlType;
onAnalyticalStorageTtlSelectionChange: ( onAnalyticalStorageTtlSelectionChange: (newAnalyticalStorageTtlSelection: TtlType) => void;
ev?: React.FormEvent<HTMLElement | HTMLInputElement>,
option?: IChoiceGroupOption
) => void;
analyticalStorageTtlSeconds: number; analyticalStorageTtlSeconds: number;
analyticalStorageTtlSecondsBaseline: number; analyticalStorageTtlSecondsBaseline: number;
onAnalyticalStorageTtlSecondsChange: ( onAnalyticalStorageTtlSecondsChange: (newAnalyticalStorageTtlSeconds: number) => void;
event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
newValue?: string
) => void;
changeFeedPolicyVisible: boolean; changeFeedPolicyVisible: boolean;
changeFeedPolicy: ChangeFeedPolicyState; changeFeedPolicy: ChangeFeedPolicyState;
changeFeedPolicyBaseline: ChangeFeedPolicyState; changeFeedPolicyBaseline: ChangeFeedPolicyState;
onChangeFeedPolicyChange: (ev?: React.FormEvent<HTMLElement | HTMLInputElement>, option?: IChoiceGroupOption) => void; onChangeFeedPolicyChange: (newChangeFeedPolicyState: ChangeFeedPolicyState) => void;
onSubSettingsSaveableChange: (isSubSettingsSaveable: boolean) => void; onSubSettingsSaveableChange: (isSubSettingsSaveable: boolean) => void;
onSubSettingsDiscardableChange: (isSubSettingsDiscardable: boolean) => void; onSubSettingsDiscardableChange: (isSubSettingsDiscardable: boolean) => void;
} }
@@ -139,6 +131,54 @@ export class SubSettingsComponent extends React.Component<SubSettingsComponentPr
{ key: TtlType.On, text: "On" } { key: TtlType.On, text: "On" }
]; ];
public getTtlValue = (value: string): TtlType => {
switch (value) {
case TtlOn:
return TtlType.On;
case TtlOff:
return TtlType.Off;
case TtlOnNoDefault:
return TtlType.OnNoDefault;
}
return undefined;
};
private onTtlChange = (ev?: React.FormEvent<HTMLElement | HTMLInputElement>, option?: IChoiceGroupOption): void =>
this.props.onTtlChange(this.getTtlValue(option.key));
private onTimeToLiveSecondsChange = (
event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
newValue?: string
): void => {
const newTimeToLiveSeconds = getSanitizedInputValue(newValue, Int32.Max);
this.props.onTimeToLiveSecondsChange(newTimeToLiveSeconds);
};
private onGeoSpatialConfigTypeChange = (
ev?: React.FormEvent<HTMLElement | HTMLInputElement>,
option?: IChoiceGroupOption
): void =>
this.props.onGeoSpatialConfigTypeChange(GeospatialConfigType[option.key as keyof typeof GeospatialConfigType]);
private onAnalyticalStorageTtlSelectionChange = (
ev?: React.FormEvent<HTMLElement | HTMLInputElement>,
option?: IChoiceGroupOption
): void => this.props.onAnalyticalStorageTtlSelectionChange(this.getTtlValue(option.key));
private onAnalyticalStorageTtlSecondsChange = (
event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
newValue?: string
): void => {
const newAnalyticalStorageTtlSeconds = getSanitizedInputValue(newValue, Int32.Max);
this.props.onAnalyticalStorageTtlSecondsChange(newAnalyticalStorageTtlSeconds);
};
private onChangeFeedPolicyChange = (
ev?: React.FormEvent<HTMLElement | HTMLInputElement>,
option?: IChoiceGroupOption
): void =>
this.props.onChangeFeedPolicyChange(ChangeFeedPolicyState[option.key as keyof typeof ChangeFeedPolicyState]);
private getTtlComponent = (): JSX.Element => ( private getTtlComponent = (): JSX.Element => (
<Stack {...titleAndInputStackProps}> <Stack {...titleAndInputStackProps}>
<ChoiceGroup <ChoiceGroup
@@ -146,7 +186,7 @@ export class SubSettingsComponent extends React.Component<SubSettingsComponentPr
label="Time to Live" label="Time to Live"
selectedKey={this.props.timeToLive} selectedKey={this.props.timeToLive}
options={this.ttlChoiceGroupOptions} options={this.ttlChoiceGroupOptions}
onChange={this.props.onTtlChange} onChange={this.onTtlChange}
styles={getChoiceGroupStyles(this.props.timeToLive, this.props.timeToLiveBaseline)} styles={getChoiceGroupStyles(this.props.timeToLive, this.props.timeToLiveBaseline)}
/> />
{isDirty(this.props.timeToLive, this.props.timeToLiveBaseline) && this.props.timeToLive === TtlType.On && ( {isDirty(this.props.timeToLive, this.props.timeToLiveBaseline) && this.props.timeToLive === TtlType.On && (
@@ -163,7 +203,7 @@ export class SubSettingsComponent extends React.Component<SubSettingsComponentPr
min={1} min={1}
max={Int32.Max} max={Int32.Max}
value={this.props.timeToLiveSeconds?.toString()} value={this.props.timeToLiveSeconds?.toString()}
onChange={this.props.onTimeToLiveSecondsChange} onChange={this.onTimeToLiveSecondsChange}
suffix="second(s)" suffix="second(s)"
/> />
)} )}
@@ -183,7 +223,7 @@ export class SubSettingsComponent extends React.Component<SubSettingsComponentPr
label="Analytical Storage Time to Live" label="Analytical Storage Time to Live"
selectedKey={this.props.analyticalStorageTtlSelection} selectedKey={this.props.analyticalStorageTtlSelection}
options={this.analyticalTtlChoiceGroupOptions} options={this.analyticalTtlChoiceGroupOptions}
onChange={this.props.onAnalyticalStorageTtlSelectionChange} onChange={this.onAnalyticalStorageTtlSelectionChange}
styles={getChoiceGroupStyles( styles={getChoiceGroupStyles(
this.props.analyticalStorageTtlSelection, this.props.analyticalStorageTtlSelection,
this.props.analyticalStorageTtlSelectionBaseline this.props.analyticalStorageTtlSelectionBaseline
@@ -202,7 +242,7 @@ export class SubSettingsComponent extends React.Component<SubSettingsComponentPr
max={Int32.Max} max={Int32.Max}
value={this.props.analyticalStorageTtlSeconds?.toString()} value={this.props.analyticalStorageTtlSeconds?.toString()}
suffix="second(s)" suffix="second(s)"
onChange={this.props.onAnalyticalStorageTtlSecondsChange} onChange={this.onAnalyticalStorageTtlSecondsChange}
/> />
)} )}
</Stack> </Stack>
@@ -219,7 +259,7 @@ export class SubSettingsComponent extends React.Component<SubSettingsComponentPr
label="Geospatial Configuration" label="Geospatial Configuration"
selectedKey={this.props.geospatialConfigType} selectedKey={this.props.geospatialConfigType}
options={this.geoSpatialConfigTypeChoiceGroupOptions} options={this.geoSpatialConfigTypeChoiceGroupOptions}
onChange={this.props.onGeoSpatialConfigTypeChange} onChange={this.onGeoSpatialConfigTypeChange}
styles={getChoiceGroupStyles(this.props.geospatialConfigType, this.props.geospatialConfigTypeBaseline)} styles={getChoiceGroupStyles(this.props.geospatialConfigType, this.props.geospatialConfigTypeBaseline)}
/> />
); );
@@ -241,7 +281,7 @@ export class SubSettingsComponent extends React.Component<SubSettingsComponentPr
id="changeFeedPolicy" id="changeFeedPolicy"
selectedKey={this.props.changeFeedPolicy} selectedKey={this.props.changeFeedPolicy}
options={this.changeFeedChoiceGroupOptions} options={this.changeFeedChoiceGroupOptions}
onChange={this.props.onChangeFeedPolicyChange} onChange={this.onChangeFeedPolicyChange}
styles={getChoiceGroupStyles(this.props.changeFeedPolicy, this.props.changeFeedPolicyBaseline)} styles={getChoiceGroupStyles(this.props.changeFeedPolicy, this.props.changeFeedPolicyBaseline)}
aria-labelledby={labelId} aria-labelledby={labelId}
/> />

View File

@@ -26,9 +26,10 @@ import {
MessageBarType MessageBarType
} from "office-ui-fabric-react"; } from "office-ui-fabric-react";
import { ToolTipLabelComponent } from "../ToolTipLabelComponent"; import { ToolTipLabelComponent } from "../ToolTipLabelComponent";
import { IsComponentDirtyResult, isDirty } from "../../SettingsUtils"; import { getSanitizedInputValue, IsComponentDirtyResult, isDirty } from "../../SettingsUtils";
import * as SharedConstants from "../../../../../Shared/Constants"; import * as SharedConstants from "../../../../../Shared/Constants";
import * as DataModels from "../../../../../Contracts/DataModels"; import * as DataModels from "../../../../../Contracts/DataModels";
import { Int32 } from "../../../../Panes/Tables/Validators/EntityPropertyValidationCommon";
export interface ThroughputInputAutoPilotV3Props { export interface ThroughputInputAutoPilotV3Props {
databaseAccount: DataModels.DatabaseAccount; databaseAccount: DataModels.DatabaseAccount;
@@ -71,9 +72,9 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
> { > {
private shouldCheckComponentIsDirty = true; private shouldCheckComponentIsDirty = true;
private static readonly defaultStep = 100; private static readonly defaultStep = 100;
private static readonly zeroThroughput = 0;
private step: number; private step: number;
private choiceGroupFixedStyle = getChoiceGroupStyles(undefined, undefined); private throughputInputMaxValue: number;
private autoPilotInputMaxValue: number;
private options: IChoiceGroupOption[] = [ private options: IChoiceGroupOption[] = [
{ key: "true", text: "Autoscale" }, { key: "true", text: "Autoscale" },
{ key: "false", text: "Manual" } { key: "false", text: "Manual" }
@@ -140,6 +141,8 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
}; };
this.step = this.props.step ?? ThroughputInputAutoPilotV3Component.defaultStep; this.step = this.props.step ?? ThroughputInputAutoPilotV3Component.defaultStep;
this.throughputInputMaxValue = this.props.canExceedMaximumValue ? Int32.Max : this.props.maximum;
this.autoPilotInputMaxValue = Int32.Max;
} }
public hasProvisioningTypeChanged = (): boolean => public hasProvisioningTypeChanged = (): boolean =>
@@ -200,8 +203,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>, event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
newValue?: string newValue?: string
): void => { ): void => {
let newThroughput = parseInt(newValue); const newThroughput = getSanitizedInputValue(newValue, this.autoPilotInputMaxValue);
newThroughput = isNaN(newThroughput) ? ThroughputInputAutoPilotV3Component.zeroThroughput : newThroughput;
this.props.onMaxAutoPilotThroughputChange(newThroughput); this.props.onMaxAutoPilotThroughputChange(newThroughput);
}; };
@@ -209,9 +211,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>, event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
newValue?: string newValue?: string
): void => { ): void => {
let newThroughput = parseInt(newValue); const newThroughput = getSanitizedInputValue(newValue, this.throughputInputMaxValue);
newThroughput = isNaN(newThroughput) ? ThroughputInputAutoPilotV3Component.zeroThroughput : newThroughput;
if (this.overrideWithAutoPilotSettings()) { if (this.overrideWithAutoPilotSettings()) {
this.props.onMaxAutoPilotThroughputChange(newThroughput); this.props.onMaxAutoPilotThroughputChange(newThroughput);
} else { } else {
@@ -245,7 +245,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
onChange={this.onChoiceGroupChange} onChange={this.onChoiceGroupChange}
required={this.props.showAsMandatory} required={this.props.showAsMandatory}
ariaLabelledBy={labelId} ariaLabelledBy={labelId}
styles={this.choiceGroupFixedStyle} styles={getChoiceGroupStyles(this.props.wasAutopilotOriginallySet, this.props.isAutoPilotSelected)}
/> />
</Stack> </Stack>
); );
@@ -270,8 +270,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
key="auto pilot throughput input" key="auto pilot throughput input"
styles={getTextFieldStyles(this.props.maxAutoPilotThroughput, this.props.maxAutoPilotThroughputBaseline)} styles={getTextFieldStyles(this.props.maxAutoPilotThroughput, this.props.maxAutoPilotThroughputBaseline)}
disabled={this.overrideWithProvisionedThroughputSettings()} disabled={this.overrideWithProvisionedThroughputSettings()}
step={this.step} step={AutoPilotUtils.autoPilotIncrementStep}
min={AutoPilotUtils.minAutoPilotThroughput}
value={this.overrideWithProvisionedThroughputSettings() ? "" : this.props.maxAutoPilotThroughput?.toString()} value={this.overrideWithProvisionedThroughputSettings() ? "" : this.props.maxAutoPilotThroughput?.toString()}
onChange={this.onAutoPilotThroughputChange} onChange={this.onAutoPilotThroughputChange}
/> />
@@ -298,8 +297,6 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
styles={getTextFieldStyles(this.props.throughput, this.props.throughputBaseline)} styles={getTextFieldStyles(this.props.throughput, this.props.throughputBaseline)}
disabled={this.overrideWithAutoPilotSettings()} disabled={this.overrideWithAutoPilotSettings()}
step={this.step} step={this.step}
min={this.props.minimum}
max={this.props.canExceedMaximumValue ? undefined : this.props.maximum}
value={ value={
this.overrideWithAutoPilotSettings() this.overrideWithAutoPilotSettings()
? this.props.maxAutoPilotThroughputBaseline?.toString() ? this.props.maxAutoPilotThroughputBaseline?.toString()

View File

@@ -81,10 +81,10 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = `
Object { Object {
"selectors": Object { "selectors": Object {
".ms-ChoiceField-field.is-checked::after": Object { ".ms-ChoiceField-field.is-checked::after": Object {
"borderColor": "", "borderColor": undefined,
}, },
".ms-ChoiceField-field.is-checked::before": Object { ".ms-ChoiceField-field.is-checked::before": Object {
"borderColor": "", "borderColor": undefined,
}, },
".ms-ChoiceField-wrapper label": Object { ".ms-ChoiceField-wrapper label": Object {
"fontFamily": undefined, "fontFamily": undefined,
@@ -113,10 +113,9 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = `
id="autopilotInput" id="autopilotInput"
key="auto pilot throughput input" key="auto pilot throughput input"
label="Max RU/s" label="Max RU/s"
min={4000}
onChange={[Function]} onChange={[Function]}
required={true} required={true}
step={100} step={1000}
styles={ styles={
Object { Object {
"fieldGroup": Object { "fieldGroup": Object {
@@ -219,7 +218,6 @@ exports[`ThroughputInputAutoPilotV3Component spendAck checkbox visible 1`] = `
disabled={false} disabled={false}
id="throughputInput" id="throughputInput"
key="provisioned throughput input" key="provisioned throughput input"
min={10000}
onChange={[Function]} onChange={[Function]}
required={true} required={true}
step={100} step={100}
@@ -375,7 +373,6 @@ exports[`ThroughputInputAutoPilotV3Component throughput input visible 1`] = `
disabled={false} disabled={false}
id="throughputInput" id="throughputInput"
key="provisioned throughput input" key="provisioned throughput input"
min={10000}
onChange={[Function]} onChange={[Function]}
required={true} required={true}
step={100} step={100}

View File

@@ -41,7 +41,7 @@ exports[`ToolTipLabelComponent renders 1`] = `
} }
} }
> >
<Memo(StyledIconBase) <StyledIconBase
ariaLabel="Info" ariaLabel="Info"
iconName="Info" iconName="Info"
styles={ styles={

View File

@@ -2,6 +2,7 @@ import { collection, container } from "./TestUtils";
import { import {
getMaxRUs, getMaxRUs,
getMinRUs, getMinRUs,
getSanitizedInputValue,
hasDatabaseSharedThroughput, hasDatabaseSharedThroughput,
isDirty, isDirty,
isDirtyTypes, isDirtyTypes,
@@ -86,4 +87,11 @@ describe("SettingsUtils", () => {
expect(isDirty(baseline, current)).toEqual(true); expect(isDirty(baseline, current)).toEqual(true);
}); });
}); });
it("getSanitizedInputValue", () => {
const max = 100;
expect(getSanitizedInputValue("", max)).toEqual(0);
expect(getSanitizedInputValue("999", max)).toEqual(99);
expect(getSanitizedInputValue("10", max)).toEqual(10);
});
}); });

View File

@@ -6,6 +6,7 @@ import * as PricingUtils from "../../../Utils/PricingUtils";
import Explorer from "../../Explorer"; import Explorer from "../../Explorer";
const zeroValue = 0;
export type isDirtyTypes = boolean | string | number | DataModels.IndexingPolicy; export type isDirtyTypes = boolean | string | number | DataModels.IndexingPolicy;
export const TtlOff = "off"; export const TtlOff = "off";
export const TtlOn = "on"; export const TtlOn = "on";
@@ -129,6 +130,16 @@ export const parseConflictResolutionProcedure = (procedureFromBackEnd: string):
return procedureFromBackEnd; return procedureFromBackEnd;
}; };
export const getSanitizedInputValue = (newValueString: string, max: number): number => {
let newValue = parseInt(newValueString);
if (isNaN(newValue)) {
newValue = zeroValue;
} else if (newValue > max) {
newValue = Math.floor(newValue / 10);
}
return newValue;
};
export const isDirty = (current: isDirtyTypes, baseline: isDirtyTypes): boolean => { export const isDirty = (current: isDirtyTypes, baseline: isDirtyTypes): boolean => {
const currentType = typeof current; const currentType = typeof current;
const baselineType = typeof baseline; const baselineType = typeof baseline;

View File

@@ -3,10 +3,7 @@ import * as ViewModels from "../../../Contracts/ViewModels";
import Explorer from "../../Explorer"; import Explorer from "../../Explorer";
import ko from "knockout"; import ko from "knockout";
export const container = new Explorer({ export const container = new Explorer();
notificationsClient: undefined,
isEmulator: false
});
export const collection = ({ export const collection = ({
container: container, container: container,

View File

@@ -84,9 +84,6 @@ exports[`SettingsComponent renders 1`] = `
"visible": [Function], "visible": [Function],
}, },
AddCollectionPane { AddCollectionPane {
"_databaseOffers": HashMap {
"container": Object {},
},
"_isSynapseLinkEnabled": [Function], "_isSynapseLinkEnabled": [Function],
"autoPilotThroughput": [Function], "autoPilotThroughput": [Function],
"autoPilotTiersList": [Function], "autoPilotTiersList": [Function],
@@ -586,9 +583,6 @@ exports[`SettingsComponent renders 1`] = `
"_refreshSparkEnabledStateForAccount": [Function], "_refreshSparkEnabledStateForAccount": [Function],
"_resetNotebookWorkspace": [Function], "_resetNotebookWorkspace": [Function],
"addCollectionPane": AddCollectionPane { "addCollectionPane": AddCollectionPane {
"_databaseOffers": HashMap {
"container": Object {},
},
"_isSynapseLinkEnabled": [Function], "_isSynapseLinkEnabled": [Function],
"autoPilotThroughput": [Function], "autoPilotThroughput": [Function],
"autoPilotTiersList": [Function], "autoPilotTiersList": [Function],
@@ -953,7 +947,6 @@ exports[`SettingsComponent renders 1`] = `
"validPartitionKeyValue": [Function], "validPartitionKeyValue": [Function],
"visible": [Function], "visible": [Function],
}, },
"extensionEndpoint": [Function],
"features": [Function], "features": [Function],
"flight": [Function], "flight": [Function],
"graphStylingPane": GraphStylingPane { "graphStylingPane": GraphStylingPane {
@@ -983,7 +976,6 @@ exports[`SettingsComponent renders 1`] = `
"isAuthWithResourceToken": [Function], "isAuthWithResourceToken": [Function],
"isCodeOfConductEnabled": [Function], "isCodeOfConductEnabled": [Function],
"isCopyNotebookPaneEnabled": [Function], "isCopyNotebookPaneEnabled": [Function],
"isEmulator": false,
"isEnableMongoCapabilityPresent": [Function], "isEnableMongoCapabilityPresent": [Function],
"isFixedCollectionWithSharedThroughputSupported": [Function], "isFixedCollectionWithSharedThroughputSupported": [Function],
"isGalleryPublishEnabled": [Function], "isGalleryPublishEnabled": [Function],
@@ -1059,7 +1051,6 @@ exports[`SettingsComponent renders 1`] = `
"parameters": [Function], "parameters": [Function],
}, },
"notificationConsoleData": [Function], "notificationConsoleData": [Function],
"notificationsClient": undefined,
"onRefreshDatabasesKeyPress": [Function], "onRefreshDatabasesKeyPress": [Function],
"onRefreshResourcesClick": [Function], "onRefreshResourcesClick": [Function],
"onSwitchToConnectionString": [Function], "onSwitchToConnectionString": [Function],
@@ -1399,9 +1390,6 @@ exports[`SettingsComponent renders 1`] = `
"visible": [Function], "visible": [Function],
}, },
AddCollectionPane { AddCollectionPane {
"_databaseOffers": HashMap {
"container": Object {},
},
"_isSynapseLinkEnabled": [Function], "_isSynapseLinkEnabled": [Function],
"autoPilotThroughput": [Function], "autoPilotThroughput": [Function],
"autoPilotTiersList": [Function], "autoPilotTiersList": [Function],
@@ -1901,9 +1889,6 @@ exports[`SettingsComponent renders 1`] = `
"_refreshSparkEnabledStateForAccount": [Function], "_refreshSparkEnabledStateForAccount": [Function],
"_resetNotebookWorkspace": [Function], "_resetNotebookWorkspace": [Function],
"addCollectionPane": AddCollectionPane { "addCollectionPane": AddCollectionPane {
"_databaseOffers": HashMap {
"container": Object {},
},
"_isSynapseLinkEnabled": [Function], "_isSynapseLinkEnabled": [Function],
"autoPilotThroughput": [Function], "autoPilotThroughput": [Function],
"autoPilotTiersList": [Function], "autoPilotTiersList": [Function],
@@ -2268,7 +2253,6 @@ exports[`SettingsComponent renders 1`] = `
"validPartitionKeyValue": [Function], "validPartitionKeyValue": [Function],
"visible": [Function], "visible": [Function],
}, },
"extensionEndpoint": [Function],
"features": [Function], "features": [Function],
"flight": [Function], "flight": [Function],
"graphStylingPane": GraphStylingPane { "graphStylingPane": GraphStylingPane {
@@ -2298,7 +2282,6 @@ exports[`SettingsComponent renders 1`] = `
"isAuthWithResourceToken": [Function], "isAuthWithResourceToken": [Function],
"isCodeOfConductEnabled": [Function], "isCodeOfConductEnabled": [Function],
"isCopyNotebookPaneEnabled": [Function], "isCopyNotebookPaneEnabled": [Function],
"isEmulator": false,
"isEnableMongoCapabilityPresent": [Function], "isEnableMongoCapabilityPresent": [Function],
"isFixedCollectionWithSharedThroughputSupported": [Function], "isFixedCollectionWithSharedThroughputSupported": [Function],
"isGalleryPublishEnabled": [Function], "isGalleryPublishEnabled": [Function],
@@ -2374,7 +2357,6 @@ exports[`SettingsComponent renders 1`] = `
"parameters": [Function], "parameters": [Function],
}, },
"notificationConsoleData": [Function], "notificationConsoleData": [Function],
"notificationsClient": undefined,
"onRefreshDatabasesKeyPress": [Function], "onRefreshDatabasesKeyPress": [Function],
"onRefreshResourcesClick": [Function], "onRefreshResourcesClick": [Function],
"onSwitchToConnectionString": [Function], "onSwitchToConnectionString": [Function],
@@ -2727,9 +2709,6 @@ exports[`SettingsComponent renders 1`] = `
"visible": [Function], "visible": [Function],
}, },
AddCollectionPane { AddCollectionPane {
"_databaseOffers": HashMap {
"container": Object {},
},
"_isSynapseLinkEnabled": [Function], "_isSynapseLinkEnabled": [Function],
"autoPilotThroughput": [Function], "autoPilotThroughput": [Function],
"autoPilotTiersList": [Function], "autoPilotTiersList": [Function],
@@ -3229,9 +3208,6 @@ exports[`SettingsComponent renders 1`] = `
"_refreshSparkEnabledStateForAccount": [Function], "_refreshSparkEnabledStateForAccount": [Function],
"_resetNotebookWorkspace": [Function], "_resetNotebookWorkspace": [Function],
"addCollectionPane": AddCollectionPane { "addCollectionPane": AddCollectionPane {
"_databaseOffers": HashMap {
"container": Object {},
},
"_isSynapseLinkEnabled": [Function], "_isSynapseLinkEnabled": [Function],
"autoPilotThroughput": [Function], "autoPilotThroughput": [Function],
"autoPilotTiersList": [Function], "autoPilotTiersList": [Function],
@@ -3596,7 +3572,6 @@ exports[`SettingsComponent renders 1`] = `
"validPartitionKeyValue": [Function], "validPartitionKeyValue": [Function],
"visible": [Function], "visible": [Function],
}, },
"extensionEndpoint": [Function],
"features": [Function], "features": [Function],
"flight": [Function], "flight": [Function],
"graphStylingPane": GraphStylingPane { "graphStylingPane": GraphStylingPane {
@@ -3626,7 +3601,6 @@ exports[`SettingsComponent renders 1`] = `
"isAuthWithResourceToken": [Function], "isAuthWithResourceToken": [Function],
"isCodeOfConductEnabled": [Function], "isCodeOfConductEnabled": [Function],
"isCopyNotebookPaneEnabled": [Function], "isCopyNotebookPaneEnabled": [Function],
"isEmulator": false,
"isEnableMongoCapabilityPresent": [Function], "isEnableMongoCapabilityPresent": [Function],
"isFixedCollectionWithSharedThroughputSupported": [Function], "isFixedCollectionWithSharedThroughputSupported": [Function],
"isGalleryPublishEnabled": [Function], "isGalleryPublishEnabled": [Function],
@@ -3702,7 +3676,6 @@ exports[`SettingsComponent renders 1`] = `
"parameters": [Function], "parameters": [Function],
}, },
"notificationConsoleData": [Function], "notificationConsoleData": [Function],
"notificationsClient": undefined,
"onRefreshDatabasesKeyPress": [Function], "onRefreshDatabasesKeyPress": [Function],
"onRefreshResourcesClick": [Function], "onRefreshResourcesClick": [Function],
"onSwitchToConnectionString": [Function], "onSwitchToConnectionString": [Function],
@@ -4042,9 +4015,6 @@ exports[`SettingsComponent renders 1`] = `
"visible": [Function], "visible": [Function],
}, },
AddCollectionPane { AddCollectionPane {
"_databaseOffers": HashMap {
"container": Object {},
},
"_isSynapseLinkEnabled": [Function], "_isSynapseLinkEnabled": [Function],
"autoPilotThroughput": [Function], "autoPilotThroughput": [Function],
"autoPilotTiersList": [Function], "autoPilotTiersList": [Function],
@@ -4544,9 +4514,6 @@ exports[`SettingsComponent renders 1`] = `
"_refreshSparkEnabledStateForAccount": [Function], "_refreshSparkEnabledStateForAccount": [Function],
"_resetNotebookWorkspace": [Function], "_resetNotebookWorkspace": [Function],
"addCollectionPane": AddCollectionPane { "addCollectionPane": AddCollectionPane {
"_databaseOffers": HashMap {
"container": Object {},
},
"_isSynapseLinkEnabled": [Function], "_isSynapseLinkEnabled": [Function],
"autoPilotThroughput": [Function], "autoPilotThroughput": [Function],
"autoPilotTiersList": [Function], "autoPilotTiersList": [Function],
@@ -4911,7 +4878,6 @@ exports[`SettingsComponent renders 1`] = `
"validPartitionKeyValue": [Function], "validPartitionKeyValue": [Function],
"visible": [Function], "visible": [Function],
}, },
"extensionEndpoint": [Function],
"features": [Function], "features": [Function],
"flight": [Function], "flight": [Function],
"graphStylingPane": GraphStylingPane { "graphStylingPane": GraphStylingPane {
@@ -4941,7 +4907,6 @@ exports[`SettingsComponent renders 1`] = `
"isAuthWithResourceToken": [Function], "isAuthWithResourceToken": [Function],
"isCodeOfConductEnabled": [Function], "isCodeOfConductEnabled": [Function],
"isCopyNotebookPaneEnabled": [Function], "isCopyNotebookPaneEnabled": [Function],
"isEmulator": false,
"isEnableMongoCapabilityPresent": [Function], "isEnableMongoCapabilityPresent": [Function],
"isFixedCollectionWithSharedThroughputSupported": [Function], "isFixedCollectionWithSharedThroughputSupported": [Function],
"isGalleryPublishEnabled": [Function], "isGalleryPublishEnabled": [Function],
@@ -5017,7 +4982,6 @@ exports[`SettingsComponent renders 1`] = `
"parameters": [Function], "parameters": [Function],
}, },
"notificationConsoleData": [Function], "notificationConsoleData": [Function],
"notificationsClient": undefined,
"onRefreshDatabasesKeyPress": [Function], "onRefreshDatabasesKeyPress": [Function],
"onRefreshResourcesClick": [Function], "onRefreshResourcesClick": [Function],
"onSwitchToConnectionString": [Function], "onSwitchToConnectionString": [Function],
@@ -5329,7 +5293,6 @@ exports[`SettingsComponent renders 1`] = `
logIndexingPolicySuccessMessage={[Function]} logIndexingPolicySuccessMessage={[Function]}
onIndexingPolicyContentChange={[Function]} onIndexingPolicyContentChange={[Function]}
onIndexingPolicyDirtyChange={[Function]} onIndexingPolicyDirtyChange={[Function]}
onIndexingPolicyElementFocusChange={[Function]}
resetShouldDiscardIndexingPolicy={[Function]} resetShouldDiscardIndexingPolicy={[Function]}
shouldDiscardIndexingPolicy={false} shouldDiscardIndexingPolicy={false}
/> />

View File

@@ -18,6 +18,8 @@ import {
import TriangleDownIcon from "../../../../images/Triangle-down.svg"; import TriangleDownIcon from "../../../../images/Triangle-down.svg";
import TriangleRightIcon from "../../../../images/Triangle-right.svg"; import TriangleRightIcon from "../../../../images/Triangle-right.svg";
import LoadingIndicator_3Squares from "../../../../images/LoadingIndicator_3Squares.gif"; import LoadingIndicator_3Squares from "../../../../images/LoadingIndicator_3Squares.gif";
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
export interface TreeNodeMenuItem { export interface TreeNodeMenuItem {
label: string; label: string;
@@ -276,7 +278,12 @@ export class TreeNodeComponent extends React.Component<TreeNodeComponentProps, T
text: menuItem.label, text: menuItem.label,
disabled: menuItem.isDisabled, disabled: menuItem.isDisabled,
className: menuItem.styleClass, className: menuItem.styleClass,
onClick: menuItem.onClick, onClick: () => {
menuItem.onClick();
TelemetryProcessor.trace(Action.ClickResourceTreeNodeContextMenuItem, ActionModifiers.Mark, {
label: menuItem.label
});
},
onRenderIcon: (props: any) => <img src={menuItem.iconSrc} alt="" /> onRenderIcon: (props: any) => <img src={menuItem.iconSrc} alt="" />
})) }))
}} }}

View File

@@ -191,7 +191,7 @@ exports[`TreeNodeComponent renders a simple node (sorted children, expanded) 1`]
"className": undefined, "className": undefined,
"disabled": true, "disabled": true,
"key": "menuLabel", "key": "menuLabel",
"onClick": undefined, "onClick": [Function],
"onRenderIcon": [Function], "onRenderIcon": [Function],
"text": "menuLabel", "text": "menuLabel",
}, },

View File

@@ -1,12 +1,11 @@
jest.mock("../../Common/DocumentClientUtilityBase"); jest.mock("../../Common/DocumentClientUtilityBase");
jest.mock("../Graph/GraphExplorerComponent/GremlinClient");
jest.mock("../../Common/dataAccess/createCollection"); jest.mock("../../Common/dataAccess/createCollection");
import * as ko from "knockout"; import * as ko from "knockout";
import * as sinon from "sinon";
import * as ViewModels from "../../Contracts/ViewModels"; import * as ViewModels from "../../Contracts/ViewModels";
import Q from "q"; import Q from "q";
import { ContainerSampleGenerator } from "./ContainerSampleGenerator"; import { ContainerSampleGenerator } from "./ContainerSampleGenerator";
import * as DocumentClientUtility from "../../Common/DocumentClientUtilityBase"; import { createDocument } from "../../Common/DocumentClientUtilityBase";
import { GremlinClient } from "../Graph/GraphExplorerComponent/GremlinClient";
import Explorer from "../Explorer"; import Explorer from "../Explorer";
import { updateUserContext } from "../../UserContext"; import { updateUserContext } from "../../UserContext";
@@ -26,6 +25,10 @@ describe("ContainerSampleGenerator", () => {
return explorerStub; return explorerStub;
}; };
beforeEach(() => {
(createDocument as jest.Mock).mockResolvedValue(undefined);
});
it("should insert documents for sql API account", async () => { it("should insert documents for sql API account", async () => {
const sampleCollectionId = "SampleCollection"; const sampleCollectionId = "SampleCollection";
const sampleDatabaseId = "SampleDB"; const sampleDatabaseId = "SampleDB";
@@ -69,13 +72,10 @@ describe("ContainerSampleGenerator", () => {
await generator.createSampleContainerAsync(); await generator.createSampleContainerAsync();
expect(DocumentClientUtility.createDocument).toHaveBeenCalled(); expect(createDocument).toHaveBeenCalled();
}); });
it("should send gremlin queries for Graph API account", async () => { it("should send gremlin queries for Graph API account", async () => {
sinon.stub(GremlinClient.prototype, "initialize").callsFake(() => {});
const executeStub = sinon.stub(GremlinClient.prototype, "execute").returns(Q.resolve());
updateUserContext({ updateUserContext({
databaseAccount: { databaseAccount: {
id: "foo", id: "foo",
@@ -121,9 +121,6 @@ describe("ContainerSampleGenerator", () => {
generator.setData(sampleData); generator.setData(sampleData);
await generator.createSampleContainerAsync(); await generator.createSampleContainerAsync();
expect(DocumentClientUtility.createDocument).toHaveBeenCalled();
expect(executeStub.called).toBe(true);
}); });
it("should not create any sample for Mongo API account", async () => { it("should not create any sample for Mongo API account", async () => {
@@ -133,7 +130,7 @@ describe("ContainerSampleGenerator", () => {
explorerStub.defaultExperience = ko.observable<string>(experience); explorerStub.defaultExperience = ko.observable<string>(experience);
// Rejects with error that contains experience // Rejects with error that contains experience
await expect(ContainerSampleGenerator.createSampleGeneratorAsync(explorerStub)).rejects.toMatch(experience); expect(ContainerSampleGenerator.createSampleGeneratorAsync(explorerStub)).rejects.toMatch(experience);
}); });
it("should not create any sample for Table API account", async () => { it("should not create any sample for Table API account", async () => {

View File

@@ -1,7 +1,6 @@
import * as DataModels from "../../Contracts/DataModels"; import * as DataModels from "../../Contracts/DataModels";
import * as ViewModels from "../../Contracts/ViewModels"; import * as ViewModels from "../../Contracts/ViewModels";
import GraphTab from ".././Tabs/GraphTab"; import GraphTab from ".././Tabs/GraphTab";
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
import { GremlinClient } from "../Graph/GraphExplorerComponent/GremlinClient"; import { GremlinClient } from "../Graph/GraphExplorerComponent/GremlinClient";
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils"; import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
import Explorer from "../Explorer"; import Explorer from "../Explorer";
@@ -96,9 +95,9 @@ export class ContainerSampleGenerator {
.reduce((previous, current) => previous.then(current), Promise.resolve()); .reduce((previous, current) => previous.then(current), Promise.resolve());
} else { } else {
// For SQL all queries are executed at the same time // For SQL all queries are executed at the same time
this.sampleDataFile.data.forEach(doc => { this.sampleDataFile.data.map(doc => {
const subPromise = createDocument(collection, doc); const subPromise = createDocument(collection, doc);
subPromise.catch(reason => NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, reason)); subPromise.catch(reason => NotificationConsoleUtils.logConsoleError(reason));
promises.push(subPromise); promises.push(subPromise);
}); });
await Promise.all(promises); await Promise.all(promises);

View File

@@ -82,12 +82,12 @@ import { toRawContentUri, fromContentUri } from "../Utils/GitHubUtils";
import UserDefinedFunction from "./Tree/UserDefinedFunction"; import UserDefinedFunction from "./Tree/UserDefinedFunction";
import StoredProcedure from "./Tree/StoredProcedure"; import StoredProcedure from "./Tree/StoredProcedure";
import Trigger from "./Tree/Trigger"; import Trigger from "./Tree/Trigger";
import { NotificationsClientBase } from "../Common/NotificationsClientBase";
import { ContextualPaneBase } from "./Panes/ContextualPaneBase"; import { ContextualPaneBase } from "./Panes/ContextualPaneBase";
import TabsBase from "./Tabs/TabsBase"; import TabsBase from "./Tabs/TabsBase";
import { CommandButtonComponentProps } from "./Controls/CommandButton/CommandButtonComponent"; import { CommandButtonComponentProps } from "./Controls/CommandButton/CommandButtonComponent";
import { updateUserContext, userContext } from "../UserContext"; import { updateUserContext, userContext } from "../UserContext";
import { stringToBlob } from "../Utils/BlobUtils"; import { stringToBlob } from "../Utils/BlobUtils";
import { IChoiceGroupProps } from "office-ui-fabric-react";
BindingHandlersRegisterer.registerBindingHandlers(); BindingHandlersRegisterer.registerBindingHandlers();
// Hold a reference to ComponentRegisterer to prevent transpiler to ignore import // Hold a reference to ComponentRegisterer to prevent transpiler to ignore import
@@ -98,10 +98,6 @@ enum ShareAccessToggleState {
Read Read
} }
interface ExplorerOptions {
notificationsClient: NotificationsClientBase;
isEmulator: boolean;
}
interface AdHocAccessData { interface AdHocAccessData {
readWriteUrl: string; readWriteUrl: string;
readUrl: string; readUrl: string;
@@ -135,15 +131,12 @@ export default class Explorer {
public isFixedCollectionWithSharedThroughputSupported: ko.Computed<boolean>; public isFixedCollectionWithSharedThroughputSupported: ko.Computed<boolean>;
public isEnableMongoCapabilityPresent: ko.Computed<boolean>; public isEnableMongoCapabilityPresent: ko.Computed<boolean>;
public isServerlessEnabled: ko.Computed<boolean>; public isServerlessEnabled: ko.Computed<boolean>;
public isEmulator: boolean;
public isAccountReady: ko.Observable<boolean>; public isAccountReady: ko.Observable<boolean>;
public canSaveQueries: ko.Computed<boolean>; public canSaveQueries: ko.Computed<boolean>;
public features: ko.Observable<any>; public features: ko.Observable<any>;
public serverId: ko.Observable<string>; public serverId: ko.Observable<string>;
public extensionEndpoint: ko.Observable<string>;
public armEndpoint: ko.Observable<string>; public armEndpoint: ko.Observable<string>;
public isTryCosmosDBSubscription: ko.Observable<boolean>; public isTryCosmosDBSubscription: ko.Observable<boolean>;
public notificationsClient: NotificationsClientBase;
public queriesClient: QueriesClient; public queriesClient: QueriesClient;
public tableDataClient: TableDataClient; public tableDataClient: TableDataClient;
public splitter: Splitter; public splitter: Splitter;
@@ -213,7 +206,7 @@ export default class Explorer {
public isGalleryPublishEnabled: ko.Computed<boolean>; public isGalleryPublishEnabled: ko.Computed<boolean>;
public isCodeOfConductEnabled: ko.Computed<boolean>; public isCodeOfConductEnabled: ko.Computed<boolean>;
public isLinkInjectionEnabled: ko.Computed<boolean>; public isLinkInjectionEnabled: ko.Computed<boolean>;
public isSettingsV2Enabled: ko.Computed<boolean>; public isSettingsV2Enabled: ko.Observable<boolean>;
public isGitHubPaneEnabled: ko.Observable<boolean>; public isGitHubPaneEnabled: ko.Observable<boolean>;
public isPublishNotebookPaneEnabled: ko.Observable<boolean>; public isPublishNotebookPaneEnabled: ko.Observable<boolean>;
public isCopyNotebookPaneEnabled: ko.Observable<boolean>; public isCopyNotebookPaneEnabled: ko.Observable<boolean>;
@@ -272,7 +265,7 @@ export default class Explorer {
private static readonly MaxNbDatabasesToAutoExpand = 5; private static readonly MaxNbDatabasesToAutoExpand = 5;
constructor(options: ExplorerOptions) { constructor() {
const startKey: number = TelemetryProcessor.traceStart(Action.InitializeDataExplorer, { const startKey: number = TelemetryProcessor.traceStart(Action.InitializeDataExplorer, {
dataExplorerArea: Constants.Areas.ResourceTree dataExplorerArea: Constants.Areas.ResourceTree
}); });
@@ -378,12 +371,9 @@ export default class Explorer {
} }
}); });
this.memoryUsageInfo = ko.observable<DataModels.MemoryUsageInfo>(); this.memoryUsageInfo = ko.observable<DataModels.MemoryUsageInfo>();
this.notificationsClient = options.notificationsClient;
this.isEmulator = options.isEmulator;
this.features = ko.observable(); this.features = ko.observable();
this.serverId = ko.observable<string>(); this.serverId = ko.observable<string>();
this.extensionEndpoint = ko.observable<string>(undefined);
this.armEndpoint = ko.observable<string>(undefined); this.armEndpoint = ko.observable<string>(undefined);
this.queriesClient = new QueriesClient(this); this.queriesClient = new QueriesClient(this);
this.isTryCosmosDBSubscription = ko.observable<boolean>(false); this.isTryCosmosDBSubscription = ko.observable<boolean>(false);
@@ -423,7 +413,8 @@ export default class Explorer {
this.isLinkInjectionEnabled = ko.computed<boolean>(() => this.isLinkInjectionEnabled = ko.computed<boolean>(() =>
this.isFeatureEnabled(Constants.Features.enableLinkInjection) this.isFeatureEnabled(Constants.Features.enableLinkInjection)
); );
this.isSettingsV2Enabled = ko.computed<boolean>(() => this.isFeatureEnabled(Constants.Features.enableSettingsV2)); //this.isSettingsV2Enabled = ko.computed<boolean>(() => this.isFeatureEnabled(Constants.Features.enableSettingsV2));
this.isSettingsV2Enabled = ko.observable(false);
this.isGitHubPaneEnabled = ko.observable<boolean>(false); this.isGitHubPaneEnabled = ko.observable<boolean>(false);
this.isPublishNotebookPaneEnabled = ko.observable<boolean>(false); this.isPublishNotebookPaneEnabled = ko.observable<boolean>(false);
this.isCopyNotebookPaneEnabled = ko.observable<boolean>(false); this.isCopyNotebookPaneEnabled = ko.observable<boolean>(false);
@@ -1870,7 +1861,7 @@ export default class Explorer {
return null; return null;
} }
if (this.selectedNode().nodeKind === "Database") { if (this.selectedNode().nodeKind === "Database") {
return _.find(this.databases(), (database: ViewModels.Database) => database.rid === this.selectedNode().rid); return _.find(this.databases(), (database: ViewModels.Database) => database.id() === this.selectedNode().id());
} }
return this.findSelectedCollection().database; return this.findSelectedCollection().database;
} }
@@ -1912,9 +1903,7 @@ export default class Explorer {
} }
this.features(inputs.features); this.features(inputs.features);
this.serverId(inputs.serverId); this.serverId(inputs.serverId);
this.extensionEndpoint(inputs.extensionEndpoint || "");
this.armEndpoint(EnvironmentUtility.normalizeArmEndpointUri(inputs.csmEndpoint || configContext.ARM_ENDPOINT)); this.armEndpoint(EnvironmentUtility.normalizeArmEndpointUri(inputs.csmEndpoint || configContext.ARM_ENDPOINT));
this.notificationsClient.setExtensionEndpoint(this.extensionEndpoint());
this.databaseAccount(databaseAccount); this.databaseAccount(databaseAccount);
this.subscriptionType(inputs.subscriptionType); this.subscriptionType(inputs.subscriptionType);
this.quotaId(inputs.quotaId); this.quotaId(inputs.quotaId);
@@ -1922,6 +1911,7 @@ export default class Explorer {
this.flight(inputs.addCollectionDefaultFlight); this.flight(inputs.addCollectionDefaultFlight);
this.isTryCosmosDBSubscription(inputs.isTryCosmosDBSubscription); this.isTryCosmosDBSubscription(inputs.isTryCosmosDBSubscription);
this.isAuthWithResourceToken(inputs.isAuthWithresourceToken); this.isAuthWithResourceToken(inputs.isAuthWithresourceToken);
this.setFeatureFlagsFromFlights(inputs.flights);
if (!!inputs.dataExplorerVersion) { if (!!inputs.dataExplorerVersion) {
this.parentFrameDataExplorerVersion(inputs.dataExplorerVersion); this.parentFrameDataExplorerVersion(inputs.dataExplorerVersion);
@@ -1930,6 +1920,7 @@ export default class Explorer {
this._importExplorerConfigComplete = true; this._importExplorerConfigComplete = true;
updateConfigContext({ updateConfigContext({
BACKEND_ENDPOINT: inputs.extensionEndpoint || "",
ARM_ENDPOINT: this.armEndpoint() ARM_ENDPOINT: this.armEndpoint()
}); });
@@ -1955,12 +1946,20 @@ export default class Explorer {
return Q(); return Q();
} }
public findSelectedCollection(): ViewModels.Collection { public setFeatureFlagsFromFlights(flights: readonly string[]): void {
if (this.selectedNode().nodeKind === "Collection") { if (!flights) {
return this.findSelectedCollectionForSelectedNode(); return;
} else {
return this.findSelectedCollectionForSubNode();
} }
if (flights.indexOf(Constants.Flights.SettingsV2) !== -1) {
this.isSettingsV2Enabled(true);
}
}
public findSelectedCollection(): ViewModels.Collection {
return (this.selectedNode().nodeKind === "Collection"
? this.selectedNode()
: this.selectedNode().collection) as ViewModels.Collection;
} }
// TODO: Refactor below methods, minimize dependencies and add unit tests where necessary // TODO: Refactor below methods, minimize dependencies and add unit tests where necessary
@@ -2077,11 +2076,11 @@ export default class Explorer {
}); });
databasesToLoad.forEach(async (database: ViewModels.Database) => { databasesToLoad.forEach(async (database: ViewModels.Database) => {
await database.loadCollections(); await database.loadCollections();
const isNewDatabase: boolean = _.some(newDatabases, (db: ViewModels.Database) => db.rid === database.rid); const isNewDatabase: boolean = _.some(newDatabases, (db: ViewModels.Database) => db.id() === database.id());
if (isNewDatabase) { if (isNewDatabase) {
database.expandDatabase(); database.expandDatabase();
} }
this.tabsManager.refreshActiveTab(tab => tab.collection && tab.collection.getDatabase().rid === database.rid); this.tabsManager.refreshActiveTab(tab => tab.collection && tab.collection.getDatabase().id() === database.id());
}); });
Q.all(loadCollectionPromises).done( Q.all(loadCollectionPromises).done(
@@ -2193,21 +2192,11 @@ export default class Explorer {
} }
} }
private findSelectedCollectionForSelectedNode(): ViewModels.Collection { public findCollection(databaseId: string, collectionId: string): ViewModels.Collection {
return this.findCollection(this.selectedNode().rid); const database: ViewModels.Database = this.databases().find(
} (database: ViewModels.Database) => database.id() === databaseId
);
public findCollection(rid: string): ViewModels.Collection { return database?.collections().find((collection: ViewModels.Collection) => collection.id() === collectionId);
for (let i = 0; i < this.databases().length; i++) {
const database = this.databases()[i];
for (let j = 0; j < database.collections().length; j++) {
const collection = database.collections()[j];
if (collection.rid === rid) {
return collection;
}
}
}
return null;
} }
public isLastCollection(): boolean { public isLastCollection(): boolean {
@@ -2231,7 +2220,7 @@ export default class Explorer {
const newDatabases: DataModels.Database[] = _.filter(updatedDatabaseList, (database: DataModels.Database) => { const newDatabases: DataModels.Database[] = _.filter(updatedDatabaseList, (database: DataModels.Database) => {
const databaseExists = _.some( const databaseExists = _.some(
this.databases(), this.databases(),
(existingDatabase: ViewModels.Database) => existingDatabase.rid === database._rid (existingDatabase: ViewModels.Database) => existingDatabase.id() === database.id
); );
return !databaseExists; return !databaseExists;
}); });
@@ -2243,7 +2232,7 @@ export default class Explorer {
ko.utils.arrayForEach(this.databases(), (database: ViewModels.Database) => { ko.utils.arrayForEach(this.databases(), (database: ViewModels.Database) => {
const databasePresentInUpdatedList = _.some( const databasePresentInUpdatedList = _.some(
updatedDatabaseList, updatedDatabaseList,
(db: DataModels.Database) => db._rid === database.rid (db: DataModels.Database) => db.id === database.id()
); );
if (!databasePresentInUpdatedList) { if (!databasePresentInUpdatedList) {
databasesToDelete.push(database); databasesToDelete.push(database);
@@ -2265,7 +2254,7 @@ export default class Explorer {
const databasesToKeep: ViewModels.Database[] = []; const databasesToKeep: ViewModels.Database[] = [];
ko.utils.arrayForEach(this.databases(), (database: ViewModels.Database) => { ko.utils.arrayForEach(this.databases(), (database: ViewModels.Database) => {
const shouldRemoveDatabase = _.some(databasesToRemove, (db: ViewModels.Database) => db.rid === database.rid); const shouldRemoveDatabase = _.some(databasesToRemove, (db: ViewModels.Database) => db.id === database.id);
if (!shouldRemoveDatabase) { if (!shouldRemoveDatabase) {
databasesToKeep.push(database); databasesToKeep.push(database);
} }
@@ -2274,19 +2263,6 @@ export default class Explorer {
this.databases(databasesToKeep); this.databases(databasesToKeep);
} }
private findSelectedCollectionForSubNode(): ViewModels.Collection {
for (let i = 0; i < this.databases().length; i++) {
const database = this.databases()[i];
for (let j = 0; j < database.collections().length; j++) {
const collection = database.collections()[j];
if (this.selectedNode().collection && collection.rid === this.selectedNode().collection.rid) {
return collection;
}
}
}
return null;
}
public uploadFile(name: string, content: string, parent: NotebookContentItem): Promise<NotebookContentItem> { public uploadFile(name: string, content: string, parent: NotebookContentItem): Promise<NotebookContentItem> {
if (!this.isNotebookEnabled() || !this.notebookManager?.notebookContentClient) { if (!this.isNotebookEnabled() || !this.notebookManager?.notebookContentClient) {
const error = "Attempt to upload notebook, but notebook is not enabled"; const error = "Attempt to upload notebook, but notebook is not enabled";
@@ -2378,42 +2354,16 @@ export default class Explorer {
} }
public showOkCancelModalDialog( public showOkCancelModalDialog(
title: string,
msg: string,
okLabel: string,
onOk: () => void,
cancelLabel: string,
onCancel: () => void
): void {
this._dialogProps({
isModal: true,
visible: true,
title,
subText: msg,
primaryButtonText: okLabel,
secondaryButtonText: cancelLabel,
onPrimaryButtonClick: () => {
this._closeModalDialog();
onOk && onOk();
},
onSecondaryButtonClick: () => {
this._closeModalDialog();
onCancel && onCancel();
}
});
}
public showOkCancelTextFieldModalDialog(
title: string, title: string,
msg: string, msg: string,
okLabel: string, okLabel: string,
onOk: () => void, onOk: () => void,
cancelLabel: string, cancelLabel: string,
onCancel: () => void, onCancel: () => void,
textFieldProps: TextFieldProps, choiceGroupProps?: IChoiceGroupProps,
textFieldProps?: TextFieldProps,
isPrimaryButtonDisabled?: boolean isPrimaryButtonDisabled?: boolean
): void { ): void {
let textFieldValue: string = null;
this._dialogProps({ this._dialogProps({
isModal: true, isModal: true,
visible: true, visible: true,
@@ -2429,8 +2379,9 @@ export default class Explorer {
this._closeModalDialog(); this._closeModalDialog();
onCancel && onCancel(); onCancel && onCancel();
}, },
primaryButtonDisabled: isPrimaryButtonDisabled, choiceGroupProps,
textFieldProps textFieldProps,
primaryButtonDisabled: isPrimaryButtonDisabled
}); });
} }
@@ -2471,7 +2422,6 @@ export default class Explorer {
title: notebookContentItem.name, title: notebookContentItem.name,
tabPath: notebookContentItem.path, tabPath: notebookContentItem.path,
collection: null, collection: null,
selfLink: null,
masterKey: userContext.masterKey || "", masterKey: userContext.masterKey || "",
hashLocation: "notebooks", hashLocation: "notebooks",
isActive: ko.observable(false), isActive: ko.observable(false),
@@ -2633,7 +2583,7 @@ export default class Explorer {
const databaseAccount = this.databaseAccount(); const databaseAccount = this.databaseAccount();
const databaseAccountLocation = databaseAccount && databaseAccount.location.toLowerCase(); const databaseAccountLocation = databaseAccount && databaseAccount.location.toLowerCase();
const disallowedLocationsUri = `${this.extensionEndpoint()}/api/disallowedLocations`; const disallowedLocationsUri = `${configContext.BACKEND_ENDPOINT}/api/disallowedLocations`;
const authorizationHeader = getAuthorizationHeader(); const authorizationHeader = getAuthorizationHeader();
try { try {
const response = await fetch(disallowedLocationsUri, { const response = await fetch(disallowedLocationsUri, {
@@ -2923,7 +2873,6 @@ export default class Explorer {
title: title, title: title,
tabPath: title, tabPath: title,
collection: null, collection: null,
selfLink: null,
hashLocation: hashLocation, hashLocation: hashLocation,
isActive: ko.observable(false), isActive: ko.observable(false),
isTabsContentExpanded: ko.observable(true), isTabsContentExpanded: ko.observable(true),
@@ -2967,7 +2916,6 @@ export default class Explorer {
title: title, title: title,
tabPath: title, tabPath: title,
documentClientUtility: null, documentClientUtility: null,
selfLink: null,
isActive: ko.observable(false), isActive: ko.observable(false),
hashLocation: hashLocation, hashLocation: hashLocation,
onUpdateTabsButtons: this.onUpdateTabsButtons, onUpdateTabsButtons: this.onUpdateTabsButtons,
@@ -3010,7 +2958,6 @@ export default class Explorer {
tabPath: title, tabPath: title,
documentClientUtility: null, documentClientUtility: null,
collection: null, collection: null,
selfLink: null,
hashLocation: hashLocation, hashLocation: hashLocation,
isActive: ko.observable(false), isActive: ko.observable(false),
isTabsContentExpanded: ko.observable(true), isTabsContentExpanded: ko.observable(true),
@@ -3130,8 +3077,10 @@ export default class Explorer {
} }
public async loadDatabaseOffers(): Promise<void> { public async loadDatabaseOffers(): Promise<void> {
this.databases()?.forEach(async (database: ViewModels.Database) => { await Promise.all(
await database.loadOffer(); this.databases()?.map(async (database: ViewModels.Database) => {
}); await database.loadOffer();
})
);
} }
} }

View File

@@ -32,13 +32,11 @@ export class ArraysByKeyCache<T> {
this.cache[key] = elements; this.cache[key] = elements;
if (index < 0) { if (index < 0) {
console.error("Inserting with negative index is not allowed by ArraysByCache");
return; return;
} }
// Check that previous index is populated, if not, ignore // Check that previous index is populated, if not, ignore
if (index > elements.length) { if (index > elements.length) {
console.error("Inserting non-contiguous element is not allowed by ArraysByCache");
return; return;
} }

View File

@@ -1,13 +1,13 @@
import * as ko from "knockout"; import * as ko from "knockout";
import Q from "q"; import Q from "q";
import { schemeCategory20 } from "d3"; import { schemeCategory10 } from "d3-scale-chromatic";
import { event as d3Event, selectAll, select } from "d3-selection"; import { selectAll, select } from "d3-selection";
import { zoom, zoomIdentity } from "d3-zoom"; import { zoom, zoomIdentity } from "d3-zoom";
import { scaleOrdinal } from "d3-scale"; import { scaleOrdinal } from "d3-scale";
import { forceSimulation, forceLink, forceCollide, forceManyBody } from "d3-force"; import { forceSimulation, forceLink, forceCollide, forceManyBody } from "d3-force";
import { interpolateNumber, interpolate } from "d3-interpolate"; import { interpolateNumber, interpolate } from "d3-interpolate";
import { map as d3Map } from "d3-collection"; import { map as d3Map } from "d3-collection";
import { drag } from "d3-drag"; import { drag, D3DragEvent } from "d3-drag";
import _ from "underscore"; import _ from "underscore";
import { NeighborType } from "../../../Contracts/ViewModels"; import { NeighborType } from "../../../Contracts/ViewModels";
@@ -89,7 +89,7 @@ export class D3ForceGraph implements GraphRenderer {
private static readonly PAGINATION_LINE2_Y_OFFSET_PX = 14; private static readonly PAGINATION_LINE2_Y_OFFSET_PX = 14;
// We limit the number of different colors to 20 // We limit the number of different colors to 20
private static readonly COLOR_SCHEME_20 = scaleOrdinal(schemeCategory20); private static readonly COLOR_SCHEME = scaleOrdinal(schemeCategory10);
private static readonly MAX_COLOR_NB = 20; private static readonly MAX_COLOR_NB = 20;
// Some state variables // Some state variables
@@ -344,13 +344,13 @@ export class D3ForceGraph implements GraphRenderer {
} while ((el = el && el.parentNode)); } while ((el = el && el.parentNode));
} }
private zoomed() { private zoomed(event: any) {
this.zoomTransform = { this.zoomTransform = {
x: d3Event.transform.x, x: event.transform.x,
y: d3Event.transform.y, y: event.transform.y,
k: d3Event.transform.k k: event.transform.k
}; };
this.g.attr("transform", d3Event.transform); this.g.attr("transform", event.transform);
} }
private instantiateSimulation() { private instantiateSimulation() {
@@ -719,17 +719,17 @@ export class D3ForceGraph implements GraphRenderer {
}) })
.call( .call(
drag() drag()
.on("start", (d: D3Node) => { .on("start", ((e: D3DragEvent<SVGGElement, D3Node, unknown>, d: D3Node) => {
return this.dragstarted(d); return this.dragstarted(d, e);
}) }) as any)
.on("drag", (d: D3Node) => { .on("drag", ((e: D3DragEvent<SVGGElement, D3Node, unknown>, d: D3Node) => {
return this.dragged(d); return this.dragged(d, e);
}) }) as any)
.on("end", (d: D3Node) => { .on("end", ((e: D3DragEvent<SVGGElement, D3Node, unknown>, d: D3Node) => {
return this.dragended(d); return this.dragended(d, e);
}) }) as any)
) )
.on("mouseover", (d: D3Node) => { .on("mouseover", (_: MouseEvent, d: D3Node) => {
if (this.isHighlightDisabled || this.selectedNode || this.isDragging) { if (this.isHighlightDisabled || this.selectedNode || this.isDragging) {
return; return;
} }
@@ -737,7 +737,7 @@ export class D3ForceGraph implements GraphRenderer {
this.highlightNode(this, d); this.highlightNode(this, d);
this.simulation.stop(); this.simulation.stop();
}) })
.on("mouseout", (d: D3Node) => { .on("mouseout", (_: MouseEvent, d: D3Node) => {
if (this.isHighlightDisabled || this.selectedNode || this.isDragging) { if (this.isHighlightDisabled || this.selectedNode || this.isDragging) {
return; return;
} }
@@ -765,17 +765,17 @@ export class D3ForceGraph implements GraphRenderer {
.attr("aria-label", (d: D3Node) => { .attr("aria-label", (d: D3Node) => {
return this.retrieveNodeCaption(d); return this.retrieveNodeCaption(d);
}) })
.on("dblclick", function(d: D3Node) { .on("dblclick", function(_: MouseEvent, d: D3Node) {
// this is the <g> element // this is the <g> element
self.onNodeClicked(this.parentNode, d); self.onNodeClicked(this.parentNode, d);
}) })
.on("click", function(d: D3Node) { .on("click", function(_: MouseEvent, d: D3Node) {
// this is the <g> element // this is the <g> element
self.onNodeClicked(this.parentNode, d); self.onNodeClicked(this.parentNode, d);
}) })
.on("keypress", function(d: D3Node) { .on("keypress", function(event: KeyboardEvent, d: D3Node) {
if (d3Event.charCode === Constants.KeyCodes.Space || d3Event.charCode === Constants.KeyCodes.Enter) { if (event.charCode === Constants.KeyCodes.Space || event.charCode === Constants.KeyCodes.Enter) {
d3Event.stopPropagation(); event.stopPropagation();
// this is the <g> element // this is the <g> element
self.onNodeClicked(this.parentNode, d); self.onNodeClicked(this.parentNode, d);
} }
@@ -850,24 +850,24 @@ export class D3ForceGraph implements GraphRenderer {
return `Next page of nodes for ${this.retrieveNodeCaption(d)}`; return `Next page of nodes for ${this.retrieveNodeCaption(d)}`;
}) })
.attr("tabindex", 0) .attr("tabindex", 0)
.on("click", function(d: D3Node) { .on("click", ((_: MouseEvent, d: D3Node) => {
self.loadNeighbors(d, PAGE_ACTION.NEXT_PAGE); self.loadNeighbors(d, PAGE_ACTION.NEXT_PAGE);
}) }) as any)
.on("dblclick", function(d: D3Node) { .on("dblclick", ((_: MouseEvent, d: D3Node) => {
self.loadNeighbors(d, PAGE_ACTION.NEXT_PAGE); self.loadNeighbors(d, PAGE_ACTION.NEXT_PAGE);
}) }) as any)
.on("keypress", function(d: D3Node) { .on("keypress", ((event: KeyboardEvent, d: D3Node) => {
if (d3Event.charCode === Constants.KeyCodes.Space || d3Event.charCode === Constants.KeyCodes.Enter) { if (event.charCode === Constants.KeyCodes.Space || event.charCode === Constants.KeyCodes.Enter) {
d3Event.stopPropagation(); event.stopPropagation();
self.loadNeighbors(d, PAGE_ACTION.NEXT_PAGE); self.loadNeighbors(d, PAGE_ACTION.NEXT_PAGE);
} }
}) }) as any)
.on("mouseover", function(d: D3Node) { .on("mouseover", ((e: MouseEvent, d: D3Node) => {
select(this).classed("active", true); select(e.target as any).classed("active", true);
}) }) as any)
.on("mouseout", function(d: D3Node) { .on("mouseout", ((e: MouseEvent, d: D3Node) => {
select(this).classed("active", false); select(e.target as any).classed("active", false);
}) }) as any)
.attr("visibility", (d: D3Node) => (!d._outEAllLoaded || !d._inEAllLoaded ? "visible" : "hidden")); .attr("visibility", (d: D3Node) => (!d._outEAllLoaded || !d._inEAllLoaded ? "visible" : "hidden"));
parent parent
.append("use") .append("use")
@@ -879,24 +879,24 @@ export class D3ForceGraph implements GraphRenderer {
return `Previous page of nodes for ${this.retrieveNodeCaption(d)}`; return `Previous page of nodes for ${this.retrieveNodeCaption(d)}`;
}) })
.attr("tabindex", 0) .attr("tabindex", 0)
.on("click", function(d: D3Node) { .on("click", ((_: MouseEvent, d: D3Node) => {
self.loadNeighbors(d, PAGE_ACTION.PREVIOUS_PAGE); self.loadNeighbors(d, PAGE_ACTION.PREVIOUS_PAGE);
}) }) as any)
.on("dblclick", function(d: D3Node) { .on("dblclick", ((_: MouseEvent, d: D3Node) => {
self.loadNeighbors(d, PAGE_ACTION.PREVIOUS_PAGE); self.loadNeighbors(d, PAGE_ACTION.PREVIOUS_PAGE);
}) }) as any)
.on("keypress", function(d: D3Node) { .on("keypress", ((event: KeyboardEvent, d: D3Node) => {
if (d3Event.charCode === Constants.KeyCodes.Space || d3Event.charCode === Constants.KeyCodes.Enter) { if (event.charCode === Constants.KeyCodes.Space || event.charCode === Constants.KeyCodes.Enter) {
d3Event.stopPropagation(); event.stopPropagation();
self.loadNeighbors(d, PAGE_ACTION.PREVIOUS_PAGE); self.loadNeighbors(d, PAGE_ACTION.PREVIOUS_PAGE);
} }
}) }) as any)
.on("mouseover", function(d: D3Node) { .on("mouseover", ((e: MouseEvent, d: D3Node) => {
select(this).classed("active", true); select(e.target as any).classed("active", true);
}) }) as any)
.on("mouseout", function(d: D3Node) { .on("mouseout", ((e: MouseEvent, d: D3Node) => {
select(this).classed("active", false); select(e.target as any).classed("active", false);
}) }) as any)
.attr("visibility", (d: D3Node) => .attr("visibility", (d: D3Node) =>
!d._pagination || d._pagination.currentPage.start !== 0 ? "visible" : "hidden" !d._pagination || d._pagination.currentPage.start !== 0 ? "visible" : "hidden"
); );
@@ -975,24 +975,24 @@ export class D3ForceGraph implements GraphRenderer {
return `Load adjacent nodes for ${this.retrieveNodeCaption(d)}`; return `Load adjacent nodes for ${this.retrieveNodeCaption(d)}`;
}) })
.attr("tabindex", 0) .attr("tabindex", 0)
.on("click", function(d: D3Node) { .on("click", ((_: MouseEvent, d: D3Node) => {
self.loadNeighbors(d, PAGE_ACTION.FIRST_PAGE); self.loadNeighbors(d, PAGE_ACTION.FIRST_PAGE);
}) }) as any)
.on("dblclick", function(d: D3Node) { .on("dblclick", ((_: MouseEvent, d: D3Node) => {
self.loadNeighbors(d, PAGE_ACTION.FIRST_PAGE); self.loadNeighbors(d, PAGE_ACTION.FIRST_PAGE);
}) }) as any)
.on("keypress", function(d: D3Node) { .on("keypress", ((event: KeyboardEvent, d: D3Node) => {
if (d3Event.charCode === Constants.KeyCodes.Space || d3Event.charCode === Constants.KeyCodes.Enter) { if (event.charCode === Constants.KeyCodes.Space || event.charCode === Constants.KeyCodes.Enter) {
d3Event.stopPropagation(); event.stopPropagation();
self.loadNeighbors(d, PAGE_ACTION.FIRST_PAGE); self.loadNeighbors(d, PAGE_ACTION.FIRST_PAGE);
} }
}) }) as any)
.on("mouseover", function(d: D3Node) { .on("mouseover", ((e: MouseEvent, d: D3Node) => {
select(this).classed("active", true); select(e.target as any).classed("active", true);
}) }) as any)
.on("mouseout", function(d: D3Node) { .on("mouseout", ((e: MouseEvent, d: D3Node) => {
select(this).classed("active", false); select(e.target as any).classed("active", false);
}); }) as any);
} }
/** /**
@@ -1053,7 +1053,7 @@ export class D3ForceGraph implements GraphRenderer {
if (index < 0 || index >= D3ForceGraph.MAX_COLOR_NB) { if (index < 0 || index >= D3ForceGraph.MAX_COLOR_NB) {
index = D3ForceGraph.MAX_COLOR_NB - 1; index = D3ForceGraph.MAX_COLOR_NB - 1;
} }
return D3ForceGraph.COLOR_SCHEME_20(index.toString()); return D3ForceGraph.COLOR_SCHEME(index.toString());
} }
/** /**
@@ -1071,23 +1071,23 @@ export class D3ForceGraph implements GraphRenderer {
} }
} }
private dragstarted(d: D3Node) { private dragstarted(d: D3Node, event: D3DragEvent<SVGGElement, D3Node, unknown>) {
this.isDragging = true; this.isDragging = true;
if (!d3Event.active) { if (!event.active) {
this.simulation.alphaTarget(0.3).restart(); this.simulation.alphaTarget(0.3).restart();
} }
d.fx = d.x; d.fx = d.x;
d.fy = d.y; d.fy = d.y;
} }
private dragged(d: D3Node) { private dragged(d: D3Node, event: D3DragEvent<SVGGElement, D3Node, unknown>) {
d.fx = d3Event.x; d.fx = event.x;
d.fy = d3Event.y; d.fy = event.y;
} }
private dragended(d: D3Node) { private dragended(d: D3Node, event: D3DragEvent<SVGGElement, D3Node, unknown>) {
this.isDragging = false; this.isDragging = false;
if (!d3Event.active) { if (!event.active) {
this.simulation.alphaTarget(0); this.simulation.alphaTarget(0);
} }

View File

@@ -141,8 +141,6 @@ describe("getPkIdFromDocumentId", () => {
}); });
describe("GraphExplorer", () => { describe("GraphExplorer", () => {
const COLLECTION_RID = "collectionRid";
const COLLECTION_SELF_LINK = "collectionSelfLink";
const gremlinRU = 789.12; const gremlinRU = 789.12;
const createMockProps = (): GraphExplorerProps => { const createMockProps = (): GraphExplorerProps => {
@@ -160,8 +158,6 @@ describe("GraphExplorer", () => {
onIsValidQueryChange: (isValidQuery: boolean): void => {}, onIsValidQueryChange: (isValidQuery: boolean): void => {},
collectionPartitionKeyProperty: "collectionPartitionKeyProperty", collectionPartitionKeyProperty: "collectionPartitionKeyProperty",
collectionRid: COLLECTION_RID,
collectionSelfLink: COLLECTION_SELF_LINK,
graphBackendEndpoint: "graphBackendEndpoint", graphBackendEndpoint: "graphBackendEndpoint",
databaseId: "databaseId", databaseId: "databaseId",
collectionId: "collectionId", collectionId: "collectionId",

View File

@@ -47,8 +47,6 @@ export interface GraphExplorerProps {
onIsValidQueryChange: (isValidQuery: boolean) => void; onIsValidQueryChange: (isValidQuery: boolean) => void;
collectionPartitionKeyProperty: string; collectionPartitionKeyProperty: string;
collectionRid: string;
collectionSelfLink: string;
graphBackendEndpoint: string; graphBackendEndpoint: string;
databaseId: string; databaseId: string;
collectionId: string; collectionId: string;
@@ -1761,7 +1759,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
const id = GraphExplorer.reportToConsole(ConsoleDataType.InProgress, `Executing: ${queryInfoStr}`); const id = GraphExplorer.reportToConsole(ConsoleDataType.InProgress, `Executing: ${queryInfoStr}`);
return queryDocumentsPage( return queryDocumentsPage(
this.props.collectionRid, this.props.collectionId,
this.currentDocDBQueryInfo.iterator, this.currentDocDBQueryInfo.iterator,
this.currentDocDBQueryInfo.index, this.currentDocDBQueryInfo.index,
{ {

View File

@@ -17,8 +17,6 @@ interface Parameter {
graphConfig?: GraphConfig; graphConfig?: GraphConfig;
collectionPartitionKeyProperty: string; collectionPartitionKeyProperty: string;
collectionRid: string;
collectionSelfLink: string;
graphBackendEndpoint: string; graphBackendEndpoint: string;
databaseId: string; databaseId: string;
collectionId: string; collectionId: string;
@@ -49,8 +47,6 @@ export class GraphExplorerAdapter implements ReactAdapter {
onIsGraphDisplayed={this.params.onIsGraphDisplayed} onIsGraphDisplayed={this.params.onIsGraphDisplayed}
onResetDefaultGraphConfigValues={this.params.onResetDefaultGraphConfigValues} onResetDefaultGraphConfigValues={this.params.onResetDefaultGraphConfigValues}
collectionPartitionKeyProperty={this.params.collectionPartitionKeyProperty} collectionPartitionKeyProperty={this.params.collectionPartitionKeyProperty}
collectionRid={this.params.collectionRid}
collectionSelfLink={this.params.collectionSelfLink}
graphBackendEndpoint={this.params.graphBackendEndpoint} graphBackendEndpoint={this.params.graphBackendEndpoint}
databaseId={this.params.databaseId} databaseId={this.params.databaseId}
collectionId={this.params.collectionId} collectionId={this.params.collectionId}

View File

@@ -192,7 +192,6 @@ export class GremlinClient {
} }
private static reportError(msg: string): void { private static reportError(msg: string): void {
console.error(msg);
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, msg); NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, msg);
Logger.logError(msg, GremlinClient.LOG_AREA); Logger.logError(msg, GremlinClient.LOG_AREA);
} }

View File

@@ -136,7 +136,6 @@ export class GremlinSimpleClient {
const data = typeof msg.data === "string" ? msg.data : new TextDecoder("utf-8").decode(msg.data); const data = typeof msg.data === "string" ? msg.data : new TextDecoder("utf-8").decode(msg.data);
return JSON.parse(data); return JSON.parse(data);
} catch (e) { } catch (e) {
console.error(e, msg);
if (this.params.failureCallback) { if (this.params.failureCallback) {
this.params.failureCallback( this.params.failureCallback(
null, null,

View File

@@ -0,0 +1,6 @@
/* eslint-disable */
export class GremlinClient {
constructor() {}
initialize() {}
execute() {}
}

View File

@@ -10,7 +10,7 @@ import * as ViewModels from "../../../Contracts/ViewModels";
import { CommandBarComponentButtonFactory } from "./CommandBarComponentButtonFactory"; import { CommandBarComponentButtonFactory } from "./CommandBarComponentButtonFactory";
import { CommandBar, ICommandBarItemProps } from "office-ui-fabric-react/lib/CommandBar"; import { CommandBar, ICommandBarItemProps } from "office-ui-fabric-react/lib/CommandBar";
import { StyleConstants } from "../../../Common/Constants"; import { StyleConstants } from "../../../Common/Constants";
import { CommandBarUtil } from "./CommandBarUtil"; import * as CommandBarUtil from "./CommandBarUtil";
import Explorer from "../../Explorer"; import Explorer from "../../Explorer";
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent"; import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";

View File

@@ -194,7 +194,7 @@ export class CommandBarComponentButtonFactory {
buttons.push(fullScreenButton); buttons.push(fullScreenButton);
} }
if (!container.hasOwnProperty("isEmulator") || !container.isEmulator) { if (configContext.platform !== Platform.Emulator) {
const label = "Feedback"; const label = "Feedback";
const feedbackButtonOptions: CommandButtonComponentProps = { const feedbackButtonOptions: CommandButtonComponentProps = {
iconSrc: FeedbackIcon, iconSrc: FeedbackIcon,

View File

@@ -1,4 +1,4 @@
import { CommandBarUtil } from "./CommandBarUtil"; import * as CommandBarUtil from "./CommandBarUtil";
import * as ViewModels from "../../../Contracts/ViewModels"; import * as ViewModels from "../../../Contracts/ViewModels";
import { ICommandBarItemProps } from "office-ui-fabric-react/lib/CommandBar"; import { ICommandBarItemProps } from "office-ui-fabric-react/lib/CommandBar";
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent"; import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
@@ -8,7 +8,7 @@ describe("CommandBarUtil tests", () => {
return { return {
iconSrc: "icon", iconSrc: "icon",
iconAlt: "label", iconAlt: "label",
onCommandClick: (e: React.SyntheticEvent): void => {}, onCommandClick: jest.fn(),
commandButtonLabel: "label", commandButtonLabel: "label",
ariaLabel: "ariaLabel", ariaLabel: "ariaLabel",
hasPopup: true, hasPopup: true,
@@ -29,11 +29,14 @@ describe("CommandBarUtil tests", () => {
expect(!converted.split); expect(!converted.split);
expect(converted.iconProps.imageProps.src).toEqual(btn.iconSrc); expect(converted.iconProps.imageProps.src).toEqual(btn.iconSrc);
expect(converted.iconProps.imageProps.alt).toEqual(btn.iconAlt); expect(converted.iconProps.imageProps.alt).toEqual(btn.iconAlt);
expect(converted.onClick).toEqual(btn.onCommandClick);
expect(converted.text).toEqual(btn.commandButtonLabel); expect(converted.text).toEqual(btn.commandButtonLabel);
expect(converted.ariaLabel).toEqual(btn.ariaLabel); expect(converted.ariaLabel).toEqual(btn.ariaLabel);
expect(converted.disabled).toEqual(btn.disabled); expect(converted.disabled).toEqual(btn.disabled);
expect(converted.className).toEqual(btn.className); expect(converted.className).toEqual(btn.className);
// Click gets called
converted.onClick();
expect(btn.onCommandClick).toBeCalled();
}); });
it("should convert NavbarButtonConfig to split button", () => { it("should convert NavbarButtonConfig to split button", () => {

View File

@@ -11,177 +11,187 @@ import ChevronDownIcon from "../../../../images/Chevron_down.svg";
import { ArcadiaMenuPicker } from "../../Controls/Arcadia/ArcadiaMenuPicker"; import { ArcadiaMenuPicker } from "../../Controls/Arcadia/ArcadiaMenuPicker";
import { MemoryTrackerComponent } from "./MemoryTrackerComponent"; import { MemoryTrackerComponent } from "./MemoryTrackerComponent";
import { MemoryUsageInfo } from "../../../Contracts/DataModels"; import { MemoryUsageInfo } from "../../../Contracts/DataModels";
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
/** /**
* Utilities for CommandBar * Convert our NavbarButtonConfig to UI Fabric buttons
* @param btns
*/ */
export class CommandBarUtil { export const convertButton = (btns: CommandButtonComponentProps[], backgroundColor: string): ICommandBarItemProps[] => {
/** const buttonHeightPx = StyleConstants.CommandBarButtonHeight;
* Convert our NavbarButtonConfig to UI Fabric buttons
* @param btns
*/
public static convertButton(btns: CommandButtonComponentProps[], backgroundColor: string): ICommandBarItemProps[] {
const buttonHeightPx = StyleConstants.CommandBarButtonHeight;
return btns return btns
.filter(btn => btn) .filter(btn => btn)
.map( .map(
(btn: CommandButtonComponentProps, index: number): ICommandBarItemProps => { (btn: CommandButtonComponentProps, index: number): ICommandBarItemProps => {
if (btn.isDivider) { if (btn.isDivider) {
return CommandBarUtil.createDivider(btn.commandButtonLabel); return createDivider(btn.commandButtonLabel);
} }
const isSplit = !!btn.children && btn.children.length > 0; const isSplit = !!btn.children && btn.children.length > 0;
const label = btn.commandButtonLabel || btn.tooltipText;
const result: ICommandBarItemProps = { const result: ICommandBarItemProps = {
iconProps: { iconProps: {
style: { style: {
width: StyleConstants.CommandBarIconWidth, // 16 width: StyleConstants.CommandBarIconWidth, // 16
alignSelf: btn.iconName ? "baseline" : undefined alignSelf: btn.iconName ? "baseline" : undefined
},
imageProps: btn.iconSrc ? { src: btn.iconSrc, alt: btn.iconAlt } : undefined,
iconName: btn.iconName
}, },
onClick: btn.onCommandClick, imageProps: btn.iconSrc ? { src: btn.iconSrc, alt: btn.iconAlt } : undefined,
key: `${btn.commandButtonLabel}${index}`, iconName: btn.iconName
text: btn.commandButtonLabel || btn.tooltipText, },
"data-test": btn.commandButtonLabel || btn.tooltipText, onClick: (ev?: React.MouseEvent<HTMLElement, MouseEvent> | React.KeyboardEvent<HTMLElement>) => {
title: btn.tooltipText, btn.onCommandClick(ev);
name: btn.commandButtonLabel || btn.tooltipText, TelemetryProcessor.trace(Action.ClickCommandBarButton, ActionModifiers.Mark, { label });
disabled: btn.disabled, },
ariaLabel: btn.ariaLabel, key: `${btn.commandButtonLabel}${index}`,
buttonStyles: { text: label,
root: { "data-test": label,
backgroundColor: backgroundColor, title: btn.tooltipText,
height: buttonHeightPx, name: label,
paddingRight: 0, disabled: btn.disabled,
paddingLeft: 0, ariaLabel: btn.ariaLabel,
minWidth: 24, buttonStyles: {
marginLeft: isSplit ? 0 : 5, root: {
marginRight: isSplit ? 0 : 5 backgroundColor: backgroundColor,
height: buttonHeightPx,
paddingRight: 0,
paddingLeft: 0,
minWidth: 24,
marginLeft: isSplit ? 0 : 5,
marginRight: isSplit ? 0 : 5
},
rootDisabled: {
backgroundColor: backgroundColor,
pointerEvents: "auto"
},
splitButtonMenuButton: {
backgroundColor: backgroundColor,
selectors: {
":hover": { backgroundColor: StyleConstants.AccentLight }
}, },
rootDisabled: { width: 16
backgroundColor: backgroundColor, },
pointerEvents: "auto" label: { fontSize: StyleConstants.mediumFontSize },
}, rootHovered: { backgroundColor: StyleConstants.AccentLight },
splitButtonMenuButton: { rootPressed: { backgroundColor: StyleConstants.AccentLight },
backgroundColor: backgroundColor, splitButtonMenuButtonExpanded: {
selectors: { backgroundColor: StyleConstants.AccentExtra,
":hover": { backgroundColor: StyleConstants.AccentLight } selectors: {
}, ":hover": { backgroundColor: StyleConstants.AccentLight }
width: 16
},
label: { fontSize: StyleConstants.mediumFontSize },
rootHovered: { backgroundColor: StyleConstants.AccentLight },
rootPressed: { backgroundColor: StyleConstants.AccentLight },
splitButtonMenuButtonExpanded: {
backgroundColor: StyleConstants.AccentExtra,
selectors: {
":hover": { backgroundColor: StyleConstants.AccentLight }
}
},
splitButtonDivider: {
display: "none"
},
icon: {
paddingLeft: 0,
paddingRight: 0
},
splitButtonContainer: {
marginLeft: 5,
marginRight: 5
} }
}, },
className: btn.className, splitButtonDivider: {
id: btn.id display: "none"
},
icon: {
paddingLeft: 0,
paddingRight: 0
},
splitButtonContainer: {
marginLeft: 5,
marginRight: 5
}
},
className: btn.className,
id: btn.id
};
if (isSplit) {
// It's a split button
result.split = true;
result.subMenuProps = {
items: convertButton(btn.children, backgroundColor),
styles: {
list: {
// TODO Figure out how to do it the proper way with subComponentStyles.
// TODO Remove all this crazy styling once we adopt Ui-Fabric Azure themes
selectors: {
".ms-ContextualMenu-itemText": { fontSize: StyleConstants.mediumFontSize },
".ms-ContextualMenu-link:hover": { backgroundColor: StyleConstants.AccentLight },
".ms-ContextualMenu-icon": { width: 16, height: 16 }
}
}
}
}; };
if (isSplit) { result.menuIconProps = {
// It's a split button iconType: IconType.image,
result.split = true; style: {
width: 12,
result.subMenuProps = { paddingLeft: 1,
items: CommandBarUtil.convertButton(btn.children, backgroundColor), paddingTop: 6
styles: { },
list: { imageProps: { src: ChevronDownIcon, alt: btn.iconAlt }
// TODO Figure out how to do it the proper way with subComponentStyles. };
// TODO Remove all this crazy styling once we adopt Ui-Fabric Azure themes
selectors: {
".ms-ContextualMenu-itemText": { fontSize: StyleConstants.mediumFontSize },
".ms-ContextualMenu-link:hover": { backgroundColor: StyleConstants.AccentLight },
".ms-ContextualMenu-icon": { width: 16, height: 16 }
}
}
}
};
result.menuIconProps = {
iconType: IconType.image,
style: {
width: 12,
paddingLeft: 1,
paddingTop: 6
},
imageProps: { src: ChevronDownIcon, alt: btn.iconAlt }
};
}
if (btn.isDropdown) {
const selectedChild = _.find(btn.children, child => child.dropdownItemKey === btn.dropdownSelectedKey);
result.name = selectedChild?.commandButtonLabel || btn.dropdownPlaceholder;
const dropdownStyles: Partial<IDropdownStyles> = {
root: { margin: 5 },
dropdown: { width: btn.dropdownWidth },
title: { fontSize: 12, height: 30, lineHeight: 28 },
dropdownItem: { fontSize: 12, lineHeight: 28, minHeight: 30 },
dropdownItemSelected: { fontSize: 12, lineHeight: 28, minHeight: 30 }
};
result.commandBarButtonAs = (props: IComponentAsProps<ICommandBarItemProps>) => {
return (
<Dropdown
placeholder={btn.dropdownPlaceholder}
defaultSelectedKey={btn.dropdownSelectedKey}
onChange={(event: React.FormEvent<HTMLDivElement>, option?: IDropdownOption, index?: number): void =>
btn.children[index].onCommandClick(event)
}
options={btn.children.map((child: CommandButtonComponentProps) => ({
key: child.dropdownItemKey,
text: child.commandButtonLabel
}))}
styles={dropdownStyles}
/>
);
};
}
if (btn.isArcadiaPicker && btn.arcadiaProps) {
result.commandBarButtonAs = () => <ArcadiaMenuPicker {...btn.arcadiaProps} />;
}
return result;
} }
);
}
public static createDivider(key: string): ICommandBarItemProps { if (btn.isDropdown) {
return { const selectedChild = _.find(btn.children, child => child.dropdownItemKey === btn.dropdownSelectedKey);
onRender: () => ( result.name = selectedChild?.commandButtonLabel || btn.dropdownPlaceholder;
<div className="dividerContainer">
<span />
</div>
),
iconOnly: true,
disabled: true,
key: key
};
}
public static createMemoryTracker(key: string, memoryUsageInfo: Observable<MemoryUsageInfo>): ICommandBarItemProps { const dropdownStyles: Partial<IDropdownStyles> = {
return { root: { margin: 5 },
key, dropdown: { width: btn.dropdownWidth },
onRender: () => <MemoryTrackerComponent memoryUsageInfo={memoryUsageInfo} /> title: { fontSize: 12, height: 30, lineHeight: 28 },
}; dropdownItem: { fontSize: 12, lineHeight: 28, minHeight: 30 },
} dropdownItemSelected: { fontSize: 12, lineHeight: 28, minHeight: 30 }
} };
const onDropdownChange = (
event: React.FormEvent<HTMLDivElement>,
option?: IDropdownOption,
index?: number
): void => {
btn.children[index].onCommandClick(event);
TelemetryProcessor.trace(Action.ClickCommandBarButton, ActionModifiers.Mark, { label: option.text });
};
result.commandBarButtonAs = (props: IComponentAsProps<ICommandBarItemProps>) => {
return (
<Dropdown
placeholder={btn.dropdownPlaceholder}
defaultSelectedKey={btn.dropdownSelectedKey}
onChange={onDropdownChange}
options={btn.children.map((child: CommandButtonComponentProps) => ({
key: child.dropdownItemKey,
text: child.commandButtonLabel
}))}
styles={dropdownStyles}
/>
);
};
}
if (btn.isArcadiaPicker && btn.arcadiaProps) {
result.commandBarButtonAs = () => <ArcadiaMenuPicker {...btn.arcadiaProps} />;
}
return result;
}
);
};
export const createDivider = (key: string): ICommandBarItemProps => {
return {
onRender: () => (
<div className="dividerContainer">
<span />
</div>
),
iconOnly: true,
disabled: true,
key: key
};
};
export const createMemoryTracker = (
key: string,
memoryUsageInfo: Observable<MemoryUsageInfo>
): ICommandBarItemProps => {
return {
key,
onRender: () => <MemoryTrackerComponent memoryUsageInfo={memoryUsageInfo} />
};
};

View File

@@ -134,10 +134,13 @@ export class NotificationConsoleComponent extends React.Component<
className="expandCollapseButton" className="expandCollapseButton"
role="button" role="button"
tabIndex={0} tabIndex={0}
aria-label={this.state.isExpanded ? "collapse console" : "expand console"} aria-label={"console button" + (this.state.isExpanded ? " collapsed" : " expanded")}
aria-expanded={this.state.isExpanded} aria-expanded={!this.state.isExpanded}
> >
<img src={this.state.isExpanded ? ChevronDownIcon : ChevronUpIcon} alt="" /> <img
src={this.state.isExpanded ? ChevronDownIcon : ChevronUpIcon}
alt={this.state.isExpanded ? "ChevronDownIcon" : "ChevronUpIcon"}
/>
</div> </div>
</div> </div>
<AnimateHeight <AnimateHeight

View File

@@ -68,14 +68,14 @@ exports[`NotificationConsoleComponent renders the console (expanded) 1`] = `
</span> </span>
</div> </div>
<div <div
aria-expanded={true} aria-expanded={false}
aria-label="collapse console" aria-label="console button collapsed"
className="expandCollapseButton" className="expandCollapseButton"
role="button" role="button"
tabIndex={0} tabIndex={0}
> >
<img <img
alt="" alt="ChevronDownIcon"
src="" src=""
/> />
</div> </div>

View File

@@ -15,7 +15,10 @@ export interface OpenNotebookItem {
path: string; path: string;
} }
export type OpenCollectionItem = string; export interface OpenCollectionItem {
databaseId: string;
collectionId: string;
}
export interface Item { export interface Item {
type: Type; type: Type;
@@ -121,7 +124,11 @@ export class MostRecentActivity {
public onItemClicked(item: Item) { public onItemClicked(item: Item) {
switch (item.type) { switch (item.type) {
case Type.OpenCollection: { case Type.OpenCollection: {
const collection = this.container.findCollection(item.data as OpenCollectionItem); const openCollectionitem = item.data as OpenCollectionItem;
const collection = this.container.findCollection(
openCollectionitem.databaseId,
openCollectionitem.collectionId
);
if (collection) { if (collection) {
collection.openTab(); collection.openTab();
} }

View File

@@ -13,6 +13,7 @@ import {
makeAppRecord, makeAppRecord,
makeCommsRecord, makeCommsRecord,
makeContentsRecord, makeContentsRecord,
makeEditorsRecord,
makeEntitiesRecord, makeEntitiesRecord,
makeHostsRecord, makeHostsRecord,
makeJupyterHostRecord, makeJupyterHostRecord,
@@ -34,6 +35,7 @@ import configureStore from "./NotebookComponent/store";
import { Notification } from "react-notification-system"; import { Notification } from "react-notification-system";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor"; import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { Action } from "../../Shared/Telemetry/TelemetryConstants"; import { Action } from "../../Shared/Telemetry/TelemetryConstants";
import { configOption, createConfigCollection, defineConfigOption } from "@nteract/mythic-configuration";
export type KernelSpecsDisplay = { name: string; displayName: string }; export type KernelSpecsDisplay = { name: string; displayName: string };
@@ -112,20 +114,15 @@ export class NotebookClientV2 {
host: jupyterHostRecord host: jupyterHostRecord
// TODO: tamitta: notificationSystem.addNotification was removed, do we need a substitute? // TODO: tamitta: notificationSystem.addNotification was removed, do we need a substitute?
}), }),
comms: makeCommsRecord(),
config: Immutable.Map({
theme: "light",
editorType: params.cellEditorType || "codemirror",
autoSaveInterval: params.autoSaveInterval || Constants.Notebook.autoSaveIntervalMs
}),
core: makeStateRecord({ core: makeStateRecord({
currentKernelspecsRef: kernelspecsRef, currentKernelspecsRef: kernelspecsRef,
entities: makeEntitiesRecord({ entities: makeEntitiesRecord({
editors: makeEditorsRecord({}),
hosts: makeHostsRecord({ hosts: makeHostsRecord({
byRef: Immutable.Map<string, HostRecord>().set(this.contentHostRef, jupyterHostRecord) byRef: Immutable.Map<string, HostRecord>().set(this.contentHostRef, jupyterHostRecord)
}), }),
comms: makeCommsRecord(),
contents: makeContentsRecord({ contents: makeContentsRecord({
// byRef: Immutable.Map<string, ContentRecord>().set(this.contentRef, record)
byRef: Immutable.Map<string, ContentRecord>() byRef: Immutable.Map<string, ContentRecord>()
}), }),
transforms: makeTransformsRecord({ transforms: makeTransformsRecord({
@@ -237,7 +234,31 @@ export class NotebookClientV2 {
console.error(`${title}: ${message}`); console.error(`${title}: ${message}`);
}; };
this.store = configureStore(initialState, params.contentProvider, traceErrorFct, [cacheKernelSpecsMiddleware]); this.store = configureStore(
initialState,
params.contentProvider,
traceErrorFct,
[cacheKernelSpecsMiddleware],
!params.isReadOnly
);
// Additional configuration
this.store.dispatch(configOption("editorType").action(params.cellEditorType ?? "monaco"));
this.store.dispatch(
configOption("autoSaveInterval").action(params.autoSaveInterval ?? Constants.Notebook.autoSaveIntervalMs)
);
createConfigCollection({
key: "monaco"
});
defineConfigOption({
label: "Show Line numbers",
key: "monaco.lineNumbers",
values: [
{ label: "Yes", value: true },
{ label: "No", value: false }
],
defaultValue: true
});
} }
/** /**

View File

@@ -98,7 +98,7 @@ export class NotebookComponentBootstrapper {
actions.fetchContentFulfilled({ actions.fetchContentFulfilled({
filepath: undefined, filepath: undefined,
model: NotebookComponentBootstrapper.wrapModelIntoContent(name, undefined, content), model: NotebookComponentBootstrapper.wrapModelIntoContent(name, undefined, content),
kernelRef: undefined, // must be undefined or it will be auto-started by the epic kernelRef: undefined,
contentRef: this.contentRef contentRef: this.contentRef
}) })
); );
@@ -196,7 +196,6 @@ export class NotebookComponentBootstrapper {
this.getStore().dispatch( this.getStore().dispatch(
actions.createCellBelow({ actions.createCellBelow({
cellType: "code", cellType: "code",
source: "",
contentRef: this.contentRef contentRef: this.contentRef
}) })
); );

View File

@@ -1,6 +1,6 @@
import { StringUtils } from "../../../../../Utils/StringUtils"; import { StringUtils } from "../../../../../Utils/StringUtils";
import { actions, AppState, ContentRef, selectors } from "@nteract/core"; import { actions, AppState, ContentRef, selectors } from "@nteract/core";
import { MonacoEditorProps } from "@nteract/monaco-editor"; import { IMonacoProps as MonacoEditorProps } from "@nteract/monaco-editor";
import * as React from "react"; import * as React from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { Dispatch } from "redux"; import { Dispatch } from "redux";
@@ -21,7 +21,7 @@ interface MappedStateProps {
mimetype: string; mimetype: string;
text: string; text: string;
contentRef: ContentRef; contentRef: ContentRef;
theme: string; // "light" | "dark"; theme?: "light" | "dark";
} }
interface MappedDispatchProps { interface MappedDispatchProps {
@@ -65,8 +65,10 @@ export class TextFile extends React.PureComponent<TextFileProps, TextFileState>
return ( return (
<EditorContainer className="nteract-editor" style={{ position: "static" }}> <EditorContainer className="nteract-editor" style={{ position: "static" }}>
<Editor <Editor
id={"no-cell-id-for-single-editor"}
contentRef={this.props.contentRef}
theme={this.props.theme === "dark" ? "vs-dark" : "vs"} theme={this.props.theme === "dark" ? "vs-dark" : "vs"}
mode={this.props.mimetype} language={"plaintext"}
editorFocused editorFocused
value={this.props.text} value={this.props.text}
onChange={this.handleChange.bind(this)} onChange={this.handleChange.bind(this)}
@@ -97,8 +99,7 @@ function makeMapStateToTextFileProps(
return { return {
contentRef, contentRef,
mimetype: content.mimetype != null ? content.mimetype : "text/plain", mimetype: content.mimetype != null ? content.mimetype : "text/plain",
text, text
theme: selectors.currentTheme(state)
}; };
}; };
return mapStateToTextFileProps; return mapStateToTextFileProps;

View File

@@ -153,7 +153,7 @@ const mapDispatchToProps = (dispatch: Dispatch, ownProps: ContentsProps): object
}, },
COPY_CELL: () => dispatch(actions.copyCell({ contentRef })), COPY_CELL: () => dispatch(actions.copyCell({ contentRef })),
CREATE_CELL_ABOVE: () => dispatch(actions.createCellAbove({ cellType: "code", contentRef })), CREATE_CELL_ABOVE: () => dispatch(actions.createCellAbove({ cellType: "code", contentRef })),
CREATE_CELL_BELOW: () => dispatch(actions.createCellBelow({ cellType: "code", source: "", contentRef })), CREATE_CELL_BELOW: () => dispatch(actions.createCellBelow({ cellType: "code", contentRef })),
CUT_CELL: () => dispatch(actions.cutCell({ contentRef })), CUT_CELL: () => dispatch(actions.cutCell({ contentRef })),
DELETE_CELL: () => dispatch(actions.deleteCell({ contentRef })), DELETE_CELL: () => dispatch(actions.deleteCell({ contentRef })),
EXECUTE_ALL_CELLS: () => dispatch(actions.executeAllCells({ contentRef })), EXECUTE_ALL_CELLS: () => dispatch(actions.executeAllCells({ contentRef })),

View File

@@ -1,13 +1,13 @@
import * as Immutable from "immutable"; import * as Immutable from "immutable";
import { ActionsObservable, StateObservable } from "redux-observable"; import { StateObservable } from "redux-observable";
import { Subject, empty } from "rxjs"; import { Subject, of } from "rxjs";
import { toArray } from "rxjs/operators"; import { toArray } from "rxjs/operators";
import { makeNotebookRecord } from "@nteract/commutable"; import { makeNotebookRecord } from "@nteract/commutable";
import { actions, state } from "@nteract/core"; import { actions, state, epics } from "@nteract/core";
import * as sinon from "sinon"; import * as sinon from "sinon";
import { CdbAppState, makeCdbRecord } from "./types"; import { CdbAppState, makeCdbRecord } from "./types";
import { launchWebSocketKernelEpic, autoStartKernelEpic } from "./epics"; import { launchWebSocketKernelEpic } from "./epics";
import { NotebookUtil } from "../NotebookUtil"; import { NotebookUtil } from "../NotebookUtil";
import { sessions } from "rx-jupyter"; import { sessions } from "rx-jupyter";
@@ -124,7 +124,7 @@ describe("launchWebSocketKernelEpic", () => {
const kernelSpecName = "kernelspecname"; const kernelSpecName = "kernelspecname";
const sessionId = "sessionId"; const sessionId = "sessionId";
const action$ = ActionsObservable.of( const action$ = of(
actions.launchKernelByName({ actions.launchKernelByName({
contentRef, contentRef,
kernelRef, kernelRef,
@@ -192,7 +192,7 @@ describe("launchWebSocketKernelEpic", () => {
const kernelSpecName = "kernelspecname"; const kernelSpecName = "kernelspecname";
const sessionId = "sessionId"; const sessionId = "sessionId";
const action$ = ActionsObservable.of( const action$ = of(
actions.launchKernelByName({ actions.launchKernelByName({
contentRef, contentRef,
kernelRef, kernelRef,
@@ -247,7 +247,7 @@ describe("launchWebSocketKernelEpic", () => {
const kernelSpecName = "kernelspecname"; const kernelSpecName = "kernelspecname";
const sessionId = "sessionId"; const sessionId = "sessionId";
const action$ = ActionsObservable.of( const action$ = of(
actions.launchKernelByName({ actions.launchKernelByName({
contentRef, contentRef,
kernelRef, kernelRef,
@@ -298,7 +298,7 @@ describe("launchWebSocketKernelEpic", () => {
const state$ = new StateObservable(new Subject<CdbAppState>(), initialState); const state$ = new StateObservable(new Subject<CdbAppState>(), initialState);
const cwd = "/"; const cwd = "/";
const action$ = ActionsObservable.of( const action$ = of(
actions.launchKernelByName({ actions.launchKernelByName({
contentRef, contentRef,
kernelRef, kernelRef,
@@ -398,7 +398,7 @@ describe("launchWebSocketKernelEpic", () => {
it("launches supported kernel in kernelspecs", async () => { it("launches supported kernel in kernelspecs", async () => {
const state$ = new StateObservable(new Subject<CdbAppState>(), initialState); const state$ = new StateObservable(new Subject<CdbAppState>(), initialState);
const action$ = ActionsObservable.of( const action$ = of(
actions.launchKernelByName({ actions.launchKernelByName({
contentRef, contentRef,
kernelRef, kernelRef,
@@ -421,7 +421,7 @@ describe("launchWebSocketKernelEpic", () => {
it("launches undefined kernel uses default kernel from kernelspecs", async () => { it("launches undefined kernel uses default kernel from kernelspecs", async () => {
const state$ = new StateObservable(new Subject<CdbAppState>(), initialState); const state$ = new StateObservable(new Subject<CdbAppState>(), initialState);
const action$ = ActionsObservable.of( const action$ = of(
actions.launchKernelByName({ actions.launchKernelByName({
contentRef, contentRef,
kernelRef, kernelRef,
@@ -445,7 +445,7 @@ describe("launchWebSocketKernelEpic", () => {
it("launches unsupported kernel uses default kernel from kernelspecs", async () => { it("launches unsupported kernel uses default kernel from kernelspecs", async () => {
const state$ = new StateObservable(new Subject<CdbAppState>(), initialState); const state$ = new StateObservable(new Subject<CdbAppState>(), initialState);
const action$ = ActionsObservable.of( const action$ = of(
actions.launchKernelByName({ actions.launchKernelByName({
contentRef, contentRef,
kernelRef, kernelRef,
@@ -469,7 +469,7 @@ describe("launchWebSocketKernelEpic", () => {
it("launches unsupported kernel uses kernelspecs with similar name", async () => { it("launches unsupported kernel uses kernelspecs with similar name", async () => {
const state$ = new StateObservable(new Subject<CdbAppState>(), initialState); const state$ = new StateObservable(new Subject<CdbAppState>(), initialState);
const action$ = ActionsObservable.of( const action$ = of(
actions.launchKernelByName({ actions.launchKernelByName({
contentRef, contentRef,
kernelRef, kernelRef,
@@ -491,55 +491,3 @@ describe("launchWebSocketKernelEpic", () => {
}); });
}); });
}); });
describe("autoStartKernelEpic", () => {
const contentRef = "fakeContentRef";
const kernelRef = "fake";
it("automatically starts kernel when content fetch is successful if kernelRef is defined", async () => {
const state$ = new StateObservable(new Subject<CdbAppState>(), initialState);
const action$ = ActionsObservable.of(
actions.fetchContentFulfilled({
contentRef,
kernelRef,
filepath: "filepath",
model: {}
})
);
const responseActions = await autoStartKernelEpic(action$, state$)
.pipe(toArray())
.toPromise();
expect(responseActions).toMatchObject([
{
type: actions.RESTART_KERNEL,
payload: {
contentRef,
kernelRef,
outputHandling: "None"
}
}
]);
});
it("Don't start kernel when content fetch is successful if kernelRef is not defined", async () => {
const state$ = new StateObservable(new Subject<CdbAppState>(), initialState);
const action$ = ActionsObservable.of(
actions.fetchContentFulfilled({
contentRef,
kernelRef: undefined,
filepath: "filepath",
model: {}
})
);
const responseActions = await autoStartKernelEpic(action$, state$)
.pipe(toArray())
.toPromise();
expect(responseActions).toMatchObject([]);
});
});

View File

@@ -1,6 +1,6 @@
import { EMPTY, merge, of, timer, concat, Subject, Subscriber, Observable, Observer } from "rxjs"; import { EMPTY, merge, of, timer, concat, Subject, Subscriber, Observable, Observer } from "rxjs";
import { webSocket } from "rxjs/webSocket"; import { webSocket } from "rxjs/webSocket";
import { ActionsObservable, StateObservable } from "redux-observable"; import { StateObservable } from "redux-observable";
import { ofType } from "redux-observable"; import { ofType } from "redux-observable";
import { import {
mergeMap, mergeMap,
@@ -32,24 +32,27 @@ import {
import { message, JupyterMessage, Channels, createMessage, childOf, ofMessageType } from "@nteract/messaging"; import { message, JupyterMessage, Channels, createMessage, childOf, ofMessageType } from "@nteract/messaging";
import { sessions, kernels } from "rx-jupyter"; import { sessions, kernels } from "rx-jupyter";
import { RecordOf } from "immutable"; import { RecordOf } from "immutable";
import { AnyAction } from "redux";
import * as Constants from "../../../Common/Constants"; import * as Constants from "../../../Common/Constants";
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils"; import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
import { ConsoleDataType } from "../../Menus/NotificationConsole/NotificationConsoleComponent"; import { ConsoleDataType } from "../../Menus/NotificationConsole/NotificationConsoleComponent";
import * as CdbActions from "./actions"; import * as CdbActions from "./actions";
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor"; import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
import { Action as TelemetryAction } from "../../../Shared/Telemetry/TelemetryConstants"; import { Action as TelemetryAction, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
import { CdbAppState } from "./types"; import { CdbAppState } from "./types";
import { decryptJWTToken } from "../../../Utils/AuthorizationUtils"; import { decryptJWTToken } from "../../../Utils/AuthorizationUtils";
import * as TextFile from "./contents/file/text-file"; import * as TextFile from "./contents/file/text-file";
import { NotebookUtil } from "../NotebookUtil"; import { NotebookUtil } from "../NotebookUtil";
import { FileSystemUtil } from "../FileSystemUtil"; import { FileSystemUtil } from "../FileSystemUtil";
import * as cdbActions from "../NotebookComponent/actions";
import { Areas } from "../../../Common/Constants";
interface NotebookServiceConfig extends JupyterServerConfig { interface NotebookServiceConfig extends JupyterServerConfig {
userPuid?: string; userPuid?: string;
} }
const logToTelemetry = (state: CdbAppState, title: string, error?: string) => { const logFailureToTelemetry = (state: CdbAppState, title: string, error?: string) => {
TelemetryProcessor.traceFailure(TelemetryAction.NotebookErrorNotification, { TelemetryProcessor.traceFailure(TelemetryAction.NotebookErrorNotification, {
databaseAccountName: state.cdb.databaseAccountName, databaseAccountName: state.cdb.databaseAccountName,
defaultExperience: state.cdb.defaultExperience, defaultExperience: state.cdb.defaultExperience,
@@ -65,7 +68,7 @@ const logToTelemetry = (state: CdbAppState, title: string, error?: string) => {
* @param state$ * @param state$
*/ */
const addInitialCodeCellEpic = ( const addInitialCodeCellEpic = (
action$: ActionsObservable<actions.FetchContentFulfilled>, action$: Observable<actions.FetchContentFulfilled>,
state$: StateObservable<AppState> state$: StateObservable<AppState>
): Observable<{} | actions.CreateCellBelow> => { ): Observable<{} | actions.CreateCellBelow> => {
return action$.pipe( return action$.pipe(
@@ -95,39 +98,6 @@ const addInitialCodeCellEpic = (
); );
}; };
/**
* Automatically start kernel if kernelRef is present.
* The kernel is normally lazy-started when a cell is being executed, but a running kernel is
* required for code completion to work.
* For notebook viewer, there is no kernel
* @param action$
* @param state$
*/
export const autoStartKernelEpic = (
action$: ActionsObservable<actions.FetchContentFulfilled>,
state$: StateObservable<AppState>
): Observable<{} | actions.CreateCellBelow> => {
return action$.pipe(
ofType(actions.FETCH_CONTENT_FULFILLED),
mergeMap(action => {
const state = state$.value;
const { contentRef, kernelRef } = action.payload;
if (!kernelRef) {
return EMPTY;
}
return of(
actions.restartKernel({
contentRef,
kernelRef,
outputHandling: "None"
})
);
})
);
};
/** /**
* Updated kernels.formWebSocketURL so we pass the userId as a query param * Updated kernels.formWebSocketURL so we pass the userId as a query param
*/ */
@@ -157,7 +127,7 @@ const formWebSocketURL = (serverConfig: NotebookServiceConfig, kernelId: string,
* Override from kernel-lifecycle to improve code mirror language intellisense * Override from kernel-lifecycle to improve code mirror language intellisense
* @param action$ * @param action$
*/ */
export const acquireKernelInfoEpic = (action$: ActionsObservable<actions.NewKernelAction>) => { export const acquireKernelInfoEpic = (action$: Observable<actions.NewKernelAction>) => {
return action$.pipe( return action$.pipe(
ofType(actions.LAUNCH_KERNEL_SUCCESSFUL), ofType(actions.LAUNCH_KERNEL_SUCCESSFUL),
switchMap((action: actions.NewKernelAction) => { switchMap((action: actions.NewKernelAction) => {
@@ -310,7 +280,7 @@ const connect = (serverConfig: NotebookServiceConfig, kernelID: string, sessionI
* @param state$ * @param state$
*/ */
export const launchWebSocketKernelEpic = ( export const launchWebSocketKernelEpic = (
action$: ActionsObservable<actions.LaunchKernelByNameAction>, action$: Observable<actions.LaunchKernelByNameAction>,
state$: StateObservable<CdbAppState> state$: StateObservable<CdbAppState>
) => { ) => {
return action$.pipe( return action$.pipe(
@@ -344,7 +314,7 @@ export const launchWebSocketKernelEpic = (
kernelSpecToLaunch = currentKernelspecs.defaultKernelName; kernelSpecToLaunch = currentKernelspecs.defaultKernelName;
const msg = `No kernelspec name specified to launch, using default kernel: ${kernelSpecToLaunch}`; const msg = `No kernelspec name specified to launch, using default kernel: ${kernelSpecToLaunch}`;
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Info, msg); NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Info, msg);
logToTelemetry(state$.value, "Launching alternate kernel", msg); logFailureToTelemetry(state$.value, "Launching alternate kernel", msg);
} else { } else {
return of( return of(
actions.launchKernelFailed({ actions.launchKernelFailed({
@@ -370,7 +340,7 @@ export const launchWebSocketKernelEpic = (
msg += ` Using default kernel: ${kernelSpecToLaunch}`; msg += ` Using default kernel: ${kernelSpecToLaunch}`;
} }
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Info, msg); NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Info, msg);
logToTelemetry(state$.value, "Launching alternate kernel", msg); logFailureToTelemetry(state$.value, "Launching alternate kernel", msg);
} }
const sessionPayload = { const sessionPayload = {
@@ -422,7 +392,7 @@ export const launchWebSocketKernelEpic = (
* TODO: Remove this epic once the /restart endpoint is implemented. * TODO: Remove this epic once the /restart endpoint is implemented.
*/ */
export const restartWebSocketKernelEpic = ( export const restartWebSocketKernelEpic = (
action$: ActionsObservable<actions.RestartKernel | actions.NewKernelAction>, action$: Observable<actions.RestartKernel | actions.NewKernelAction>,
state$: StateObservable<AppState> state$: StateObservable<AppState>
) => ) =>
action$.pipe( action$.pipe(
@@ -532,7 +502,7 @@ export const restartWebSocketKernelEpic = (
* @param state$ * @param state$
*/ */
const changeWebSocketKernelEpic = ( const changeWebSocketKernelEpic = (
action$: ActionsObservable<actions.ChangeKernelByName>, action$: Observable<actions.ChangeKernelByName>,
state$: StateObservable<AppState> state$: StateObservable<AppState>
) => { ) => {
return action$.pipe( return action$.pipe(
@@ -614,7 +584,7 @@ const changeWebSocketKernelEpic = (
* @param state$ * @param state$
*/ */
const focusInitialCodeCellEpic = ( const focusInitialCodeCellEpic = (
action$: ActionsObservable<actions.CreateCellAppend>, action$: Observable<actions.CreateCellAppend>,
state$: StateObservable<AppState> state$: StateObservable<AppState>
): Observable<{} | actions.FocusCell> => { ): Observable<{} | actions.FocusCell> => {
return action$.pipe( return action$.pipe(
@@ -652,10 +622,7 @@ const focusInitialCodeCellEpic = (
* @param action$ * @param action$
* @param state$ * @param state$
*/ */
const notificationsToUserEpic = ( const notificationsToUserEpic = (action$: Observable<any>, state$: StateObservable<CdbAppState>): Observable<{}> => {
action$: ActionsObservable<any>,
state$: StateObservable<CdbAppState>
): Observable<{}> => {
return action$.pipe( return action$.pipe(
ofType( ofType(
actions.RESTART_KERNEL_SUCCESSFUL, actions.RESTART_KERNEL_SUCCESSFUL,
@@ -670,7 +637,7 @@ const notificationsToUserEpic = (
const title = "Kernel restart"; const title = "Kernel restart";
const msg = "Kernel successfully restarted"; const msg = "Kernel successfully restarted";
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Info, msg); NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Info, msg);
logToTelemetry(state$.value, title, msg); logFailureToTelemetry(state$.value, title, msg);
break; break;
} }
case actions.RESTART_KERNEL_FAILED: case actions.RESTART_KERNEL_FAILED:
@@ -681,7 +648,7 @@ const notificationsToUserEpic = (
const title = "Save failure"; const title = "Save failure";
const msg = `Failed to save notebook: ${(action as actions.SaveFailed).payload.error}`; const msg = `Failed to save notebook: ${(action as actions.SaveFailed).payload.error}`;
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, msg); NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, msg);
logToTelemetry(state$.value, title, msg); logFailureToTelemetry(state$.value, title, msg);
break; break;
} }
case actions.FETCH_CONTENT_FAILED: { case actions.FETCH_CONTENT_FAILED: {
@@ -690,7 +657,7 @@ const notificationsToUserEpic = (
const title = "Fetching content failure"; const title = "Fetching content failure";
const msg = `Failed to fetch notebook content: ${filepath}, error: ${typedAction.payload.error}`; const msg = `Failed to fetch notebook content: ${filepath}, error: ${typedAction.payload.error}`;
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, msg); NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, msg);
logToTelemetry(state$.value, title, msg); logFailureToTelemetry(state$.value, title, msg);
break; break;
} }
} }
@@ -705,7 +672,7 @@ const notificationsToUserEpic = (
* @param state$ * @param state$
*/ */
const handleKernelConnectionLostEpic = ( const handleKernelConnectionLostEpic = (
action$: ActionsObservable<actions.UpdateDisplayFailed>, action$: Observable<actions.UpdateDisplayFailed>,
state$: StateObservable<CdbAppState> state$: StateObservable<CdbAppState>
): Observable<CdbActions.UpdateKernelRestartDelayAction | actions.RestartKernel | {}> => { ): Observable<CdbActions.UpdateKernelRestartDelayAction | actions.RestartKernel | {}> => {
return action$.pipe( return action$.pipe(
@@ -715,7 +682,7 @@ const handleKernelConnectionLostEpic = (
const msg = "Notebook was disconnected from kernel"; const msg = "Notebook was disconnected from kernel";
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, msg); NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, msg);
logToTelemetry(state, "Error", "Kernel connection error"); logFailureToTelemetry(state, "Error", "Kernel connection error");
const host = selectors.currentHost(state); const host = selectors.currentHost(state);
const serverConfig: NotebookServiceConfig = selectors.serverConfig(host as RecordOf<JupyterHostRecordProps>); const serverConfig: NotebookServiceConfig = selectors.serverConfig(host as RecordOf<JupyterHostRecordProps>);
@@ -728,7 +695,7 @@ const handleKernelConnectionLostEpic = (
const msg = const msg =
"Restarted kernel too many times. Please reload the page to enable Data Explorer to restart the kernel automatically."; "Restarted kernel too many times. Please reload the page to enable Data Explorer to restart the kernel automatically.";
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, msg); NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, msg);
logToTelemetry(state, "Kernel restart error", msg); logFailureToTelemetry(state, "Kernel restart error", msg);
const explorer = window.dataExplorer; const explorer = window.dataExplorer;
if (explorer) { if (explorer) {
@@ -766,7 +733,7 @@ const handleKernelConnectionLostEpic = (
* @param state$ * @param state$
*/ */
export const cleanKernelOnConnectionLostEpic = ( export const cleanKernelOnConnectionLostEpic = (
action$: ActionsObservable<actions.UpdateDisplayFailed>, action$: Observable<actions.UpdateDisplayFailed>,
state$: StateObservable<AppState> state$: StateObservable<AppState>
): Observable<actions.KillKernelSuccessful> => { ): Observable<actions.KillKernelSuccessful> => {
return action$.pipe( return action$.pipe(
@@ -789,7 +756,7 @@ export const cleanKernelOnConnectionLostEpic = (
* @param state$ * @param state$
*/ */
const executeFocusedCellAndFocusNextEpic = ( const executeFocusedCellAndFocusNextEpic = (
action$: ActionsObservable<CdbActions.ExecuteFocusedCellAndFocusNextAction>, action$: Observable<CdbActions.ExecuteFocusedCellAndFocusNextAction>,
state$: StateObservable<AppState> state$: StateObservable<AppState>
): Observable<{} | actions.FocusNextCellEditor> => { ): Observable<{} | actions.FocusNextCellEditor> => {
return action$.pipe( return action$.pipe(
@@ -829,7 +796,7 @@ function getUserPuid(): string {
* @param state$ * @param state$
*/ */
const closeUnsupportedMimetypesEpic = ( const closeUnsupportedMimetypesEpic = (
action$: ActionsObservable<actions.FetchContentFulfilled>, action$: Observable<actions.FetchContentFulfilled>,
state$: StateObservable<AppState> state$: StateObservable<AppState>
): Observable<{}> => { ): Observable<{}> => {
return action$.pipe( return action$.pipe(
@@ -858,7 +825,7 @@ const closeUnsupportedMimetypesEpic = (
* @param state$ * @param state$
*/ */
const closeContentFailedToFetchEpic = ( const closeContentFailedToFetchEpic = (
action$: ActionsObservable<actions.FetchContentFailed>, action$: Observable<actions.FetchContentFailed>,
state$: StateObservable<AppState> state$: StateObservable<AppState>
): Observable<{}> => { ): Observable<{}> => {
return action$.pipe( return action$.pipe(
@@ -880,9 +847,107 @@ const closeContentFailedToFetchEpic = (
); );
}; };
const traceNotebookTelemetryEpic = (
action$: Observable<cdbActions.TraceNotebookTelemetryAction>,
state$: StateObservable<CdbAppState>
): Observable<{}> => {
return action$.pipe(
ofType(cdbActions.TRACE_NOTEBOOK_TELEMETRY),
mergeMap((action: cdbActions.TraceNotebookTelemetryAction) => {
const state = state$.value;
TelemetryProcessor.trace(action.payload.action, action.payload.actionModifier, {
...action.payload.data,
databaseAccountName: state.cdb.databaseAccountName,
defaultExperience: state.cdb.defaultExperience,
dataExplorerArea: Areas.Notebook
});
return EMPTY;
})
);
};
/**
* Log notebook information to telemetry
* # raw cells, # markdown cells, # code cells, total
* @param action$
* @param state$
*/
const traceNotebookInfoEpic = (
action$: Observable<actions.FetchContentFulfilled>,
state$: StateObservable<AppState>
): Observable<{} | cdbActions.TraceNotebookTelemetryAction> => {
return action$.pipe(
ofType(actions.FETCH_CONTENT_FULFILLED),
mergeMap((action: { payload: any }) => {
const state = state$.value;
const contentRef = action.payload.contentRef;
const model = selectors.model(state, { contentRef });
// If it's not a notebook, we shouldn't be here
if (!model || model.type !== "notebook") {
return EMPTY;
}
const dataToLog = {
nbCodeCells: 0,
nbRawCells: 0,
nbMarkdownCells: 0,
nbCells: 0
};
for (let [id, cell] of selectors.notebook.cellMap(model)) {
switch (cell.cell_type) {
case "code":
dataToLog.nbCodeCells++;
break;
case "markdown":
dataToLog.nbMarkdownCells++;
break;
case "raw":
dataToLog.nbRawCells++;
break;
}
dataToLog.nbCells++;
}
return of(
cdbActions.traceNotebookTelemetry({
action: TelemetryAction.NotebooksFetched,
actionModifier: ActionModifiers.Mark,
data: dataToLog
})
);
})
);
};
/**
* Log Kernel spec to start
* @param action$
* @param state$
*/
const traceNotebookKernelEpic = (
action$: Observable<AnyAction>,
state$: StateObservable<AppState>
): Observable<cdbActions.TraceNotebookTelemetryAction> => {
return action$.pipe(
ofType(actions.LAUNCH_KERNEL_SUCCESSFUL),
mergeMap((action: { payload: any; type: string }) => {
return of(
cdbActions.traceNotebookTelemetry({
action: TelemetryAction.NotebooksKernelSpecName,
actionModifier: ActionModifiers.Mark,
data: {
kernelSpecName: action.payload.kernel.name
}
})
);
})
);
};
export const allEpics = [ export const allEpics = [
addInitialCodeCellEpic, addInitialCodeCellEpic,
autoStartKernelEpic,
focusInitialCodeCellEpic, focusInitialCodeCellEpic,
notificationsToUserEpic, notificationsToUserEpic,
launchWebSocketKernelEpic, launchWebSocketKernelEpic,
@@ -893,5 +958,8 @@ export const allEpics = [
executeFocusedCellAndFocusNextEpic, executeFocusedCellAndFocusNextEpic,
closeUnsupportedMimetypesEpic, closeUnsupportedMimetypesEpic,
closeContentFailedToFetchEpic, closeContentFailedToFetchEpic,
restartWebSocketKernelEpic restartWebSocketKernelEpic,
traceNotebookTelemetryEpic,
traceNotebookInfoEpic,
traceNotebookKernelEpic
]; ];

View File

@@ -1,7 +1,5 @@
import { actions, CoreRecord, reducers as nteractReducers } from "@nteract/core"; import { actions, CoreRecord, reducers as nteractReducers } from "@nteract/core";
import { Action } from "redux"; import { Action } from "redux";
import { Areas } from "../../../Common/Constants";
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
import * as cdbActions from "./actions"; import * as cdbActions from "./actions";
import { CdbRecord } from "./types"; import { CdbRecord } from "./types";
@@ -72,17 +70,6 @@ export const cdbReducer = (state: CdbRecord, action: Action) => {
return state.set("hoveredCellId", typedAction.payload.cellId); return state.set("hoveredCellId", typedAction.payload.cellId);
} }
case cdbActions.TRACE_NOTEBOOK_TELEMETRY: {
const typedAction = action as cdbActions.TraceNotebookTelemetryAction;
TelemetryProcessor.trace(typedAction.payload.action, typedAction.payload.actionModifier, {
...typedAction.payload.data,
databaseAccountName: state.databaseAccountName,
defaultExperience: state.defaultExperience,
dataExplorerArea: Areas.Notebook
});
return state;
}
case cdbActions.UPDATE_NOTEBOOK_PARENT_DOM_ELTS: { case cdbActions.UPDATE_NOTEBOOK_PARENT_DOM_ELTS: {
const typedAction = action as cdbActions.UpdateNotebookParentDomEltAction; const typedAction = action as cdbActions.UpdateNotebookParentDomEltAction;
var parentEltsMap = state.get("currentNotebookParentElements"); var parentEltsMap = state.get("currentNotebookParentElements");

View File

@@ -0,0 +1,13 @@
import { getCoreEpics } from "./store";
import { epics } from "@nteract/core";
describe("configure redux store", () => {
it("configures store with correct epic if based on autoStartKernelOnNotebookOpen", () => {
// For now, assume launchKernelWhenNotebookSetEpic is the last epic
let filteredEpics = getCoreEpics(true);
expect(filteredEpics.pop()).toEqual(epics.launchKernelWhenNotebookSetEpic);
filteredEpics = getCoreEpics(false);
expect(filteredEpics.pop()).not.toEqual(epics.launchKernelWhenNotebookSetEpic);
});
});

View File

@@ -1,36 +1,23 @@
import { AppState, epics as coreEpics, reducers, IContentProvider } from "@nteract/core"; import { AppState, epics as coreEpics, reducers, IContentProvider } from "@nteract/core";
import { import { compose, Store, AnyAction, Middleware, Dispatch, MiddlewareAPI } from "redux";
applyMiddleware, import { Epic } from "redux-observable";
combineReducers,
compose,
createStore,
Store,
AnyAction,
Middleware,
Dispatch,
MiddlewareAPI
} from "redux";
import { combineEpics, createEpicMiddleware, Epic, ActionsObservable } from "redux-observable";
import { allEpics } from "./epics"; import { allEpics } from "./epics";
import { coreReducer, cdbReducer } from "./reducers"; import { coreReducer, cdbReducer } from "./reducers";
import { catchError } from "rxjs/operators"; import { catchError } from "rxjs/operators";
import { Observable } from "rxjs";
import { configuration } from "@nteract/mythic-configuration";
import { makeConfigureStore } from "@nteract/myths";
import { CdbAppState } from "./types";
const composeEnhancers = (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; const composeEnhancers = (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
export default function configureStore( export default function configureStore(
initialState: Partial<AppState>, initialState: Partial<CdbAppState>,
contentProvider: IContentProvider, contentProvider: IContentProvider,
onTraceFailure: (title: string, message: string) => void, onTraceFailure: (title: string, message: string) => void,
customMiddlewares?: Middleware<{}, any, Dispatch<AnyAction>>[] customMiddlewares?: Middleware<{}, any, Dispatch<AnyAction>>[],
): Store<AppState, AnyAction> { autoStartKernelOnNotebookOpen?: boolean
const rootReducer = combineReducers({ ): Store<CdbAppState, AnyAction> {
app: reducers.app,
comms: reducers.comms,
config: reducers.config,
core: coreReducer,
cdb: cdbReducer
});
/** /**
* Catches errors in reducers * Catches errors in reducers
*/ */
@@ -46,7 +33,7 @@ export default function configureStore(
}; };
const protect = (epic: Epic) => { const protect = (epic: Epic) => {
return (action$: ActionsObservable<any>, state$: any, dependencies: any) => return (action$: Observable<any>, state$: any, dependencies: any) =>
epic(action$, state$, dependencies).pipe( epic(action$, state$, dependencies).pipe(
catchError((error, caught) => { catchError((error, caught) => {
traceFailure("Epic failure", error); traceFailure("Epic failure", error);
@@ -64,11 +51,33 @@ export default function configureStore(
} }
}; };
const combineAndProtectEpics = (epics: Epic[]): Epic => { const protectEpics = (epics: Epic[]): Epic[] => {
const protectedEpics = epics.map(epic => protect(epic)); return epics.map(epic => protect(epic));
return combineEpics<Epic>(...protectedEpics);
}; };
const filteredCoreEpics = getCoreEpics(autoStartKernelOnNotebookOpen);
const mythConfigureStore = makeConfigureStore<CdbAppState>()({
packages: [configuration],
reducers: {
app: reducers.app,
core: coreReducer as any,
cdb: cdbReducer
},
epics: protectEpics([...filteredCoreEpics, ...allEpics]),
epicDependencies: { contentProvider },
epicMiddleware: customMiddlewares.concat(catchErrorMiddleware),
enhancer: composeEnhancers
});
const store = mythConfigureStore(initialState as any);
// TODO Fix typing issue here: createStore() output type doesn't quite match AppState
// return store as Store<AppState, AnyAction>;
return store as any;
}
export const getCoreEpics = (autoStartKernelOnNotebookOpen: boolean): Epic[] => {
// This list needs to be consistent and in sync with core.allEpics until we figure // This list needs to be consistent and in sync with core.allEpics until we figure
// out how to safely filter out the ones we are overriding here. // out how to safely filter out the ones we are overriding here.
const filteredCoreEpics = [ const filteredCoreEpics = [
@@ -93,20 +102,10 @@ export default function configureStore(
coreEpics.publishToBookstoreAfterSave, coreEpics.publishToBookstoreAfterSave,
coreEpics.sendInputReplyEpic coreEpics.sendInputReplyEpic
]; ];
const rootEpic = combineAndProtectEpics([...filteredCoreEpics, ...allEpics]);
const epicMiddleware = createEpicMiddleware({ dependencies: { contentProvider } });
let middlewares: Middleware[] = [epicMiddleware];
// TODO: tamitta: errorMiddleware was removed, do we need a substitute?
if (customMiddlewares) { if (autoStartKernelOnNotebookOpen) {
middlewares = middlewares.concat(customMiddlewares); filteredCoreEpics.push(coreEpics.launchKernelWhenNotebookSetEpic);
} }
middlewares.push(catchErrorMiddleware);
const store = createStore(rootReducer, initialState, composeEnhancers(applyMiddleware(...middlewares))); return filteredCoreEpics;
};
epicMiddleware.run(rootEpic);
// TODO Fix typing issue here: createStore() output type doesn't quite match AppState
return store as Store<AppState, AnyAction>;
}

View File

@@ -166,7 +166,7 @@ export default class NotebookManager {
private promptForCommitMsg = (title: string, primaryButtonLabel: string) => { private promptForCommitMsg = (title: string, primaryButtonLabel: string) => {
return new Promise<string>((resolve, reject) => { return new Promise<string>((resolve, reject) => {
let commitMsg = "Committed from Azure Cosmos DB Notebooks"; let commitMsg = "Committed from Azure Cosmos DB Notebooks";
this.params.container.showOkCancelTextFieldModalDialog( this.params.container.showOkCancelModalDialog(
title || "Commit", title || "Commit",
undefined, undefined,
primaryButtonLabel || "Commit", primaryButtonLabel || "Commit",
@@ -181,6 +181,7 @@ export default class NotebookManager {
}, },
"Cancel", "Cancel",
() => reject(new Error("Commit dialog canceled")), () => reject(new Error("Commit dialog canceled")),
undefined,
{ {
label: "Commit message", label: "Commit message",
autoAdjustHeight: true, autoAdjustHeight: true,

View File

@@ -10,7 +10,8 @@ import { connect } from "react-redux";
import { Dispatch } from "redux"; import { Dispatch } from "redux";
import { actions, ContentRef } from "@nteract/core"; import { actions, ContentRef } from "@nteract/core";
import loadTransform from "../NotebookComponent/loadTransform"; import loadTransform from "../NotebookComponent/loadTransform";
import CodeMirrorEditor from "@nteract/stateful-components/lib/inputs/connected-editors/codemirror"; import MonacoEditor from "@nteract/stateful-components/lib/inputs/connected-editors/monacoEditor";
import { PassedEditorProps } from "@nteract/stateful-components/lib/inputs/editor";
import "./NotebookReadOnlyRenderer.less"; import "./NotebookReadOnlyRenderer.less";
export interface NotebookRendererProps { export interface NotebookRendererProps {
@@ -19,19 +20,6 @@ export interface NotebookRendererProps {
hidePrompts?: boolean; hidePrompts?: boolean;
} }
interface PassedEditorProps {
id: string;
contentRef: ContentRef;
editorFocused: boolean;
value: string;
channels: any;
kernelStatus: string;
theme: string;
onChange: (text: string) => void;
onFocusChange: (focused: boolean) => void;
className: string;
}
/** /**
* This is the class that uses nteract to render a read-only notebook. * This is the class that uses nteract to render a read-only notebook.
*/ */
@@ -73,8 +61,8 @@ class NotebookReadOnlyRenderer extends React.Component<NotebookRendererProps> {
{{ {{
prompt: (props: { id: string; contentRef: string }) => this.renderPrompt(props.id, props.contentRef), prompt: (props: { id: string; contentRef: string }) => this.renderPrompt(props.id, props.contentRef),
editor: { editor: {
codemirror: (props: PassedEditorProps) => monaco: (props: PassedEditorProps) =>
this.props.hideInputs ? <></> : <CodeMirrorEditor {...props} readOnly={"nocursor"} /> this.props.hideInputs ? <></> : <MonacoEditor readOnly={true} {...props} editorType={"monaco"} />
} }
}} }}
</CodeCell> </CodeCell>
@@ -90,8 +78,8 @@ class NotebookReadOnlyRenderer extends React.Component<NotebookRendererProps> {
<RawCell id={id} contentRef={contentRef} cell_type="raw"> <RawCell id={id} contentRef={contentRef} cell_type="raw">
{{ {{
editor: { editor: {
codemirror: (props: PassedEditorProps) => monaco: (props: PassedEditorProps) =>
this.props.hideInputs ? <></> : <CodeMirrorEditor {...props} readOnly={"nocursor"} /> this.props.hideInputs ? <></> : <MonacoEditor {...props} readOnly={true} editorType={"monaco"} />
} }
}} }}
</RawCell> </RawCell>

View File

@@ -3,7 +3,8 @@ import "./base.css";
import "./default.css"; import "./default.css";
import { RawCell, Cells, CodeCell, MarkdownCell } from "@nteract/stateful-components"; import { RawCell, Cells, CodeCell, MarkdownCell } from "@nteract/stateful-components";
import CodeMirrorEditor from "@nteract/stateful-components/lib/inputs/connected-editors/codemirror"; import MonacoEditor from "@nteract/stateful-components/lib/inputs/connected-editors/monacoEditor";
import { PassedEditorProps } from "@nteract/stateful-components/lib/inputs/editor";
import Prompt from "./Prompt"; import Prompt from "./Prompt";
import { promptContent } from "./PromptContent"; import { promptContent } from "./PromptContent";
@@ -42,19 +43,6 @@ interface NotebookRendererDispatchProps {
type NotebookRendererProps = NotebookRendererBaseProps & NotebookRendererDispatchProps; type NotebookRendererProps = NotebookRendererBaseProps & NotebookRendererDispatchProps;
interface PassedEditorProps {
id: string;
contentRef: ContentRef;
editorFocused: boolean;
value: string;
channels: any;
kernelStatus: string;
theme: string;
onChange: (text: string) => void;
onFocusChange: (focused: boolean) => void;
className: string;
}
const decorate = (id: string, contentRef: ContentRef, cell_type: CellType, children: React.ReactNode) => { const decorate = (id: string, contentRef: ContentRef, cell_type: CellType, children: React.ReactNode) => {
const Cell = () => ( const Cell = () => (
<DraggableCell id={id} contentRef={contentRef}> <DraggableCell id={id} contentRef={contentRef}>
@@ -115,9 +103,7 @@ class BaseNotebookRenderer extends React.Component<NotebookRendererProps> {
<CodeCell id={id} contentRef={contentRef} cell_type="code"> <CodeCell id={id} contentRef={contentRef} cell_type="code">
{{ {{
editor: { editor: {
codemirror: (props: PassedEditorProps) => ( monaco: (props: PassedEditorProps) => <MonacoEditor {...props} editorType={"monaco"} />
<CodeMirrorEditor {...props} lineNumbers={true} />
)
}, },
prompt: ({ id, contentRef }: { id: CellId; contentRef: ContentRef }) => ( prompt: ({ id, contentRef }: { id: CellId; contentRef: ContentRef }) => (
<Prompt id={id} contentRef={contentRef} isHovered={false}> <Prompt id={id} contentRef={contentRef} isHovered={false}>
@@ -135,6 +121,9 @@ class BaseNotebookRenderer extends React.Component<NotebookRendererProps> {
"markdown", "markdown",
<MarkdownCell id={id} contentRef={contentRef} cell_type="markdown"> <MarkdownCell id={id} contentRef={contentRef} cell_type="markdown">
{{ {{
editor: {
monaco: (props: PassedEditorProps) => <MonacoEditor {...props} editorType={"monaco"} />
},
toolbar: () => <CellToolbar id={id} contentRef={contentRef} /> toolbar: () => <CellToolbar id={id} contentRef={contentRef} />
}} }}
</MarkdownCell> </MarkdownCell>
@@ -147,6 +136,9 @@ class BaseNotebookRenderer extends React.Component<NotebookRendererProps> {
"raw", "raw",
<RawCell id={id} contentRef={contentRef} cell_type="raw"> <RawCell id={id} contentRef={contentRef} cell_type="raw">
{{ {{
editor: {
monaco: (props: PassedEditorProps) => <MonacoEditor {...props} editorType={"monaco"} />
},
toolbar: () => <CellToolbar id={id} contentRef={contentRef} /> toolbar: () => <CellToolbar id={id} contentRef={contentRef} />
}} }}
</RawCell> </RawCell>

View File

@@ -14,6 +14,8 @@ import { CellToolbarContext } from "@nteract/stateful-components";
import { CellType, CellId } from "@nteract/commutable"; import { CellType, CellId } from "@nteract/commutable";
import * as selectors from "@nteract/selectors"; import * as selectors from "@nteract/selectors";
import { RecordOf } from "immutable"; import { RecordOf } from "immutable";
import * as cdbActions from "../NotebookComponent/actions";
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
export interface ComponentProps { export interface ComponentProps {
contentRef: ContentRef; contentRef: ContentRef;
@@ -29,6 +31,7 @@ interface DispatchProps {
moveCell: (destinationId: CellId, above: boolean) => void; moveCell: (destinationId: CellId, above: boolean) => void;
clearOutputs: () => void; clearOutputs: () => void;
deleteCell: () => void; deleteCell: () => void;
traceNotebookTelemetry: (action: Action, actionModifier?: string, data?: any) => void;
} }
interface StateProps { interface StateProps {
@@ -48,12 +51,18 @@ class BaseToolbar extends React.PureComponent<ComponentProps & DispatchProps & S
{ {
key: "Run", key: "Run",
text: "Run", text: "Run",
onClick: this.props.executeCell onClick: () => {
this.props.executeCell();
this.props.traceNotebookTelemetry(Action.NotebooksExecuteCellFromMenu, ActionModifiers.Mark);
}
}, },
{ {
key: "Clear Outputs", key: "Clear Outputs",
text: "Clear Outputs", text: "Clear Outputs",
onClick: this.props.clearOutputs onClick: () => {
this.props.clearOutputs();
this.props.traceNotebookTelemetry(Action.NotebooksClearOutputsFromMenu, ActionModifiers.Mark);
}
}, },
{ {
key: "Divider", key: "Divider",
@@ -64,31 +73,43 @@ class BaseToolbar extends React.PureComponent<ComponentProps & DispatchProps & S
items = items.concat([ items = items.concat([
{ {
key: "Divider", key: "Divider2",
itemType: ContextualMenuItemType.Divider itemType: ContextualMenuItemType.Divider
}, },
{ {
key: "Insert Code Cell Above", key: "Insert Code Cell Above",
text: "Insert Code Cell Above", text: "Insert Code Cell Above",
onClick: this.props.insertCodeCellAbove onClick: () => {
this.props.insertCodeCellAbove();
this.props.traceNotebookTelemetry(Action.NotebooksInsertCodeCellAboveFromMenu, ActionModifiers.Mark);
}
}, },
{ {
key: "Insert Code Cell Below", key: "Insert Code Cell Below",
text: "Insert Code Cell Below", text: "Insert Code Cell Below",
onClick: this.props.insertCodeCellBelow onClick: () => {
this.props.insertCodeCellBelow();
this.props.traceNotebookTelemetry(Action.NotebooksInsertCodeCellBelowFromMenu, ActionModifiers.Mark);
}
}, },
{ {
key: "Insert Text Cell Above", key: "Insert Text Cell Above",
text: "Insert Text Cell Above", text: "Insert Text Cell Above",
onClick: this.props.insertTextCellAbove onClick: () => {
this.props.insertTextCellAbove();
this.props.traceNotebookTelemetry(Action.NotebooksInsertTextCellAboveFromMenu, ActionModifiers.Mark);
}
}, },
{ {
key: "Insert Text Cell Below", key: "Insert Text Cell Below",
text: "Insert Text Cell Below", text: "Insert Text Cell Below",
onClick: this.props.insertTextCellBelow onClick: () => {
this.props.insertTextCellBelow();
this.props.traceNotebookTelemetry(Action.NotebooksInsertTextCellBelowFromMenu, ActionModifiers.Mark);
}
}, },
{ {
key: "Divider", key: "Divider3",
itemType: ContextualMenuItemType.Divider itemType: ContextualMenuItemType.Divider
} }
]); ]);
@@ -98,7 +119,10 @@ class BaseToolbar extends React.PureComponent<ComponentProps & DispatchProps & S
moveItems.push({ moveItems.push({
key: "Move Cell Up", key: "Move Cell Up",
text: "Move Cell Up", text: "Move Cell Up",
onClick: () => this.props.moveCell(this.props.cellIdAbove, true) onClick: () => {
this.props.moveCell(this.props.cellIdAbove, true);
this.props.traceNotebookTelemetry(Action.NotebooksMoveCellUpFromMenu, ActionModifiers.Mark);
}
}); });
} }
@@ -106,13 +130,16 @@ class BaseToolbar extends React.PureComponent<ComponentProps & DispatchProps & S
moveItems.push({ moveItems.push({
key: "Move Cell Down", key: "Move Cell Down",
text: "Move Cell Down", text: "Move Cell Down",
onClick: () => this.props.moveCell(this.props.cellIdBelow, false) onClick: () => {
this.props.moveCell(this.props.cellIdBelow, false);
this.props.traceNotebookTelemetry(Action.NotebooksMoveCellDownFromMenu, ActionModifiers.Mark);
}
}); });
} }
if (moveItems.length > 0) { if (moveItems.length > 0) {
moveItems.push({ moveItems.push({
key: "Divider", key: "Divider4",
itemType: ContextualMenuItemType.Divider itemType: ContextualMenuItemType.Divider
}); });
items = items.concat(moveItems); items = items.concat(moveItems);
@@ -121,7 +148,10 @@ class BaseToolbar extends React.PureComponent<ComponentProps & DispatchProps & S
items.push({ items.push({
key: "Delete Cell", key: "Delete Cell",
text: "Delete Cell", text: "Delete Cell",
onClick: this.props.deleteCell onClick: () => {
this.props.deleteCell();
this.props.traceNotebookTelemetry(Action.DeleteCellFromMenu, ActionModifiers.Mark);
}
}); });
const menuItemLabel = "More"; const menuItemLabel = "More";
@@ -150,13 +180,15 @@ const mapDispatchToProps = (
): DispatchProps => ({ ): DispatchProps => ({
executeCell: () => dispatch(actions.executeCell({ id, contentRef })), executeCell: () => dispatch(actions.executeCell({ id, contentRef })),
insertCodeCellAbove: () => dispatch(actions.createCellAbove({ id, contentRef, cellType: "code" })), insertCodeCellAbove: () => dispatch(actions.createCellAbove({ id, contentRef, cellType: "code" })),
insertCodeCellBelow: () => dispatch(actions.createCellBelow({ id, contentRef, cellType: "code", source: "" })), insertCodeCellBelow: () => dispatch(actions.createCellBelow({ id, contentRef, cellType: "code" })),
insertTextCellAbove: () => dispatch(actions.createCellAbove({ id, contentRef, cellType: "markdown" })), insertTextCellAbove: () => dispatch(actions.createCellAbove({ id, contentRef, cellType: "markdown" })),
insertTextCellBelow: () => dispatch(actions.createCellBelow({ id, contentRef, cellType: "markdown", source: "" })), insertTextCellBelow: () => dispatch(actions.createCellBelow({ id, contentRef, cellType: "markdown" })),
moveCell: (destinationId: CellId, above: boolean) => moveCell: (destinationId: CellId, above: boolean) =>
dispatch(actions.moveCell({ id, contentRef, destinationId, above })), dispatch(actions.moveCell({ id, contentRef, destinationId, above })),
clearOutputs: () => dispatch(actions.clearOutputs({ id, contentRef })), clearOutputs: () => dispatch(actions.clearOutputs({ id, contentRef })),
deleteCell: () => dispatch(actions.deleteCell({ id, contentRef })) deleteCell: () => dispatch(actions.deleteCell({ id, contentRef })),
traceNotebookTelemetry: (action: Action, actionModifier?: string, data?: any) =>
dispatch(cdbActions.traceNotebookTelemetry({ action, actionModifier, data }))
}); });
const makeMapStateToProps = (state: AppState, ownProps: ComponentProps): ((state: AppState) => StateProps) => { const makeMapStateToProps = (state: AppState, ownProps: ComponentProps): ((state: AppState) => StateProps) => {

View File

@@ -49,7 +49,7 @@
.nteract-cell-input .nteract-cell-source { .nteract-cell-input .nteract-cell-source {
flex: 1 1 auto; flex: 1 1 auto;
overflow: auto; overflow: visible;
} }
/** Adaptation for the R kernel's inline lists **/ /** Adaptation for the R kernel's inline lists **/

View File

@@ -0,0 +1,48 @@
jest.mock("./NotebookComponent/store");
jest.mock("@nteract/core");
import { NotebookClientV2 } from "./NotebookClientV2";
import configureStore from "./NotebookComponent/store";
import { defineConfigOption } from "@nteract/mythic-configuration";
describe("auto start kernel", () => {
it("configure autoStartKernelOnNotebookOpen properly depending whether notebook is/is not read-only", async () => {
(configureStore as jest.Mock).mockReturnValue({
dispatch: () => {
/* noop */
}
});
defineConfigOption({
label: "editorType",
key: "editorType",
defaultValue: "foo"
});
defineConfigOption({
label: "autoSaveInterval",
key: "autoSaveInterval",
defaultValue: 1234
});
[true, false].forEach(isReadOnly => {
new NotebookClientV2({
connectionInfo: {
authToken: "autToken",
notebookServerEndpoint: "notebookServerEndpoint"
},
databaseAccountName: undefined,
defaultExperience: undefined,
isReadOnly,
contentProvider: undefined
});
expect(configureStore).toHaveBeenCalledWith(
expect.anything(), // initial state
undefined, // content provider
expect.anything(), // onTraceFailure
expect.anything(), // customMiddlewares
!isReadOnly
);
});
});
});

View File

@@ -40,7 +40,7 @@ describe("Add Collection Pane", () => {
}; };
beforeEach(() => { beforeEach(() => {
explorer = new Explorer({ notificationsClient: null, isEmulator: false }); explorer = new Explorer();
explorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true); explorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
}); });

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