Compare commits

...

10 Commits

Author SHA1 Message Date
dependabot[bot]
61771403c7 Bump @babel/traverse from 7.12.12 to 7.23.2
Bumps [@babel/traverse](https://github.com/babel/babel/tree/HEAD/packages/babel-traverse) from 7.12.12 to 7.23.2.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.23.2/packages/babel-traverse)

---
updated-dependencies:
- dependency-name: "@babel/traverse"
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-16 22:42:19 +00:00
jawelton74
14d7677056 Add feature for disabling connection string login and enable this for aad redirect. (#1660)
* Add redirect for /aad to /?feature.enableAadDataPlane=true

* Add feature to hide the connection string login link. Enable this new
feature for the aad redirect.
2023-10-16 13:18:40 -07:00
sunghyunkang1111
d376a7463c P1, P2 bug fixes for private preview (#1657)
* P1 bug fix for private preview

* Add updated snapshot files

* Fix failing unit test

* Fix unit tests and update snapshot
2023-10-12 21:54:01 -05:00
Laurent Nguyen
9669301d14 Remove obsolete unit test (#1655) 2023-10-12 08:00:08 +02:00
Laurent Nguyen
dcd8d1637b Implement retrieval of authorization token for Fabric via iframe rpc (#1647)
* For Fabric, send message to get Authorization token from iframe parent

* tokenProvider: set date header and return token

* Expect account endpoint on initialize message from Fabric

* Fix format

---------

Co-authored-by: artrejo <artrejo@microsoft.com>
2023-10-10 12:25:58 -07:00
Laurent Nguyen
f36fccd3ef Auto-select first item in DocumentsTab. Fix selection highlighting (#1645)
* Auto-select first document in documentsTab

* Fix style row selection by making selector more specific
2023-10-10 07:19:35 +02:00
jawelton74
8ff9a84004 Add redirect for /aad to /?feature.enableAadDataPlane=true (#1649) 2023-10-09 11:31:36 -07:00
sunghyunkang1111
e42e24b175 Redesign copilot (#1650)
* Add copilot toggle button

* take out toggle button when closing tab

* Update snapshots

* Fix the prettier issue

* fix prettier
2023-10-09 12:42:25 -05:00
Laurent Nguyen
44d6f29edd Revert "For Fabric, send message to get Authorization token from iframe parent"
This reverts commit 9db06af552.
2023-10-06 14:35:30 +00:00
Laurent Nguyen
9db06af552 For Fabric, send message to get Authorization token from iframe parent 2023-10-06 14:33:46 +00:00
25 changed files with 2325 additions and 2130 deletions

View File

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.3626 11.9771C13.1573 11.9837 13.019 12.0288 12.9215 12.0836C12.817 12.1424 12.7301 12.229 12.6508 12.3542C12.4802 12.6239 12.3837 12.9957 12.252 13.5033L12.2347 13.5699C12.1079 14.0581 11.9424 14.6764 11.5775 15.1594C11.1788 15.687 10.5903 16 9.74835 16H4.63681C3.55423 16 2.75741 15.2435 2.18373 14.3271C1.60629 13.4047 1.17786 12.2045 0.845481 11.0542L0.845003 11.0525C0.464365 9.73512 -0.017355 8.06787 0.000479698 6.72244C0.00951195 6.04103 0.145994 5.34953 0.569528 4.825C1.00712 4.28305 1.66518 4.02294 2.50365 4.02294H2.63739C2.84267 4.01631 2.98102 3.97123 3.07848 3.9164C3.18305 3.85757 3.26995 3.77102 3.34916 3.64584C3.51985 3.37613 3.61631 3.00432 3.74801 2.49671L3.76529 2.43012C3.89213 1.94191 4.05757 1.32365 4.42251 0.840633C4.82119 0.312966 5.40972 0 6.25165 0H11.3632C12.4458 0 13.2426 0.756543 13.8163 1.67295C14.3937 2.59534 14.8221 3.79546 15.1545 4.94582L15.155 4.94748C15.5356 6.26488 16.0174 7.93213 15.9995 9.27756C15.9905 9.95897 15.854 10.6505 15.4305 11.175C14.9929 11.717 14.3348 11.9771 13.4963 11.9771H13.3626ZM14.1354 5.28381C13.8114 4.16251 13.4194 3.09096 12.9303 2.3096C12.4374 1.52225 11.9215 1.14296 11.3632 1.14296H7.95613C8.10274 1.42082 8.22537 1.731 8.33548 2.05199C8.48008 2.47355 8.61427 2.94842 8.75233 3.43699L8.78302 3.54557C9.32758 5.47082 10.008 7.94794 10.4498 9.5646C10.6507 10.2998 11.2693 10.8098 11.9798 10.8333H13.4156C13.4253 10.8333 13.4351 10.8335 13.4447 10.8341H13.4963C14.1298 10.8341 14.4484 10.6443 14.6237 10.4272C14.813 10.1928 14.9255 9.8162 14.9328 9.26133C14.9477 8.13479 14.5323 6.65749 14.1354 5.28381ZM3.06972 13.6904C3.56261 14.4777 4.0785 14.857 4.63681 14.857H8.04387C7.89726 14.5792 7.77463 14.269 7.66452 13.948C7.51992 13.5265 7.38573 13.0516 7.24767 12.563L7.21698 12.4544C6.67242 10.5292 5.99197 8.05206 5.55019 6.4354C5.34929 5.70024 4.73069 5.19015 4.02018 5.16674H2.58445C2.57465 5.16674 2.56492 5.16646 2.55526 5.1659H2.50365C1.87025 5.1659 1.5516 5.35572 1.37634 5.57277C1.18703 5.80723 1.07454 6.1838 1.06719 6.73867C1.05225 7.86521 1.46769 9.34251 1.86459 10.7162C2.18857 11.8375 2.58057 12.909 3.06972 13.6904ZM9.10298 10.8333H9.87304C9.67506 10.5554 9.5217 10.236 9.42598 9.88575C9.233 9.17956 8.99466 8.30985 8.74215 7.39421L8.47331 6.42622C8.26579 5.67902 7.62465 5.16674 6.89702 5.16674H6.12695C6.32494 5.44463 6.4783 5.76395 6.57402 6.11425C6.767 6.82044 7.00534 7.69015 7.25785 8.60579L7.52669 9.57378C7.73421 10.321 8.37535 10.8333 9.10298 10.8333ZM6.89702 4.02378C7.23212 4.02378 7.55609 4.08968 7.85646 4.21152C7.82459 4.0984 7.7931 3.98682 7.76205 3.87706L7.73417 3.77844C7.59304 3.27914 7.46754 2.83514 7.33414 2.44626C7.19129 2.02979 7.05211 1.71602 6.90214 1.4942C6.77014 1.29897 6.56087 1.14296 6.25165 1.14296C5.69009 1.14296 5.42306 1.33297 5.2517 1.55976C5.04661 1.83121 4.92755 2.21889 4.79304 2.73662C4.78284 2.77588 4.77249 2.81622 4.76191 2.85744C4.66966 3.21703 4.55997 3.64464 4.37733 4.02378H6.89702ZM9.10298 11.9762C8.76788 11.9762 8.44391 11.9103 8.14354 11.7885C8.1754 11.9016 8.2069 12.0132 8.23795 12.1229L8.26583 12.2216C8.40696 12.7208 8.53246 13.1649 8.66585 13.5537C8.80871 13.9702 8.94789 14.284 9.09786 14.5058C9.22986 14.701 9.43913 14.857 9.74835 14.857C10.3099 14.857 10.5769 14.667 10.7483 14.4402C10.9534 14.1688 11.0725 13.7811 11.207 13.2634C11.2172 13.2241 11.2275 13.1838 11.2381 13.1426C11.3303 12.783 11.44 12.3554 11.6227 11.9762H9.10298Z" fill="#0078D4"/>
</svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@@ -1179,16 +1179,16 @@ menuQuickStart {
}
}
.gridRowSelected {
#tbodycontent tr.gridRowSelected {
.active();
}
.gridRowSelected:hover {
#tbodycontent tr.gridRowSelected:hover {
cursor: default;
.hover();
}
.gridRowHighlighted {
#tbodycontent tr.gridRowHighlighted {
border-style: dotted;
border-width: 2px;
}
@@ -2576,9 +2576,10 @@ a:link {
.querydropdown.placeholderVisible {
font-style: italic;
}
.querydropdown.placeholderVisible::placeholder { /* Chrome, Firefox, Opera, Safari 10.1+ */
.querydropdown.placeholderVisible::placeholder {
/* Chrome, Firefox, Opera, Safari 10.1+ */
color: #767474;
opacity: 1;
opacity: 1;
}
.querydropdown:hover {
@@ -2648,7 +2649,7 @@ a:link {
.nav-tabs > li.active > .tabNavContentContainer > .tab_Content > .tabNavText {
font-weight: bolder;
border-bottom: 2px solid rgba(0,120,212,1);
border-bottom: 2px solid rgba(0, 120, 212, 1);
}
.nav-tabs > li.active:focus > .tabNavContentContainer {
@@ -3096,4 +3097,3 @@ a:link {
background: white;
height: 100%;
}

256
package-lock.json generated
View File

@@ -533,6 +533,11 @@
"regexpu-core": "^4.7.1"
}
},
"@babel/helper-environment-visitor": {
"version": "7.22.20",
"resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz",
"integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA=="
},
"@babel/helper-explode-assignable-expression": {
"version": "7.13.0",
"resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.13.0.tgz",
@@ -620,24 +625,7 @@
"@babel/parser": {
"version": "7.13.13",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.13.tgz",
"integrity": "sha512-OhsyMrqygfk5v8HmWwOzlYjJrtLaFhF34MrfG/Z73DgYCI6ojNUTUp2TYbtnjo8PegeJp12eamsNettCQjKjVw==",
"dev": true
},
"@babel/traverse": {
"version": "7.13.13",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.13.13.tgz",
"integrity": "sha512-CblEcwmXKR6eP43oQGG++0QMTtCjAsa3frUuzHoiIJWpaIIi8dwMyEFUJoXRLxagGqCK+jALRwIO+o3R9p/uUg==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.12.13",
"@babel/generator": "^7.13.9",
"@babel/helper-function-name": "^7.12.13",
"@babel/helper-split-export-declaration": "^7.12.13",
"@babel/parser": "^7.13.13",
"@babel/types": "^7.13.13",
"debug": "^4.1.0",
"globals": "^11.1.0"
}
"integrity": "sha512-OhsyMrqygfk5v8HmWwOzlYjJrtLaFhF34MrfG/Z73DgYCI6ojNUTUp2TYbtnjo8PegeJp12eamsNettCQjKjVw=="
},
"@babel/types": {
"version": "7.13.14",
@@ -718,28 +706,6 @@
"@babel/types": "^7.12.13"
}
},
"@babel/traverse": {
"version": "7.13.13",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.13.13.tgz",
"integrity": "sha512-CblEcwmXKR6eP43oQGG++0QMTtCjAsa3frUuzHoiIJWpaIIi8dwMyEFUJoXRLxagGqCK+jALRwIO+o3R9p/uUg==",
"requires": {
"@babel/code-frame": "^7.12.13",
"@babel/generator": "^7.13.9",
"@babel/helper-function-name": "^7.12.13",
"@babel/helper-split-export-declaration": "^7.12.13",
"@babel/parser": "^7.13.13",
"@babel/types": "^7.13.13",
"debug": "^4.1.0",
"globals": "^11.1.0"
},
"dependencies": {
"@babel/parser": {
"version": "7.13.13",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.13.tgz",
"integrity": "sha512-OhsyMrqygfk5v8HmWwOzlYjJrtLaFhF34MrfG/Z73DgYCI6ojNUTUp2TYbtnjo8PegeJp12eamsNettCQjKjVw=="
}
}
},
"@babel/types": {
"version": "7.13.14",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.14.tgz",
@@ -817,21 +783,6 @@
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.13.tgz",
"integrity": "sha512-OhsyMrqygfk5v8HmWwOzlYjJrtLaFhF34MrfG/Z73DgYCI6ojNUTUp2TYbtnjo8PegeJp12eamsNettCQjKjVw=="
},
"@babel/traverse": {
"version": "7.13.13",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.13.13.tgz",
"integrity": "sha512-CblEcwmXKR6eP43oQGG++0QMTtCjAsa3frUuzHoiIJWpaIIi8dwMyEFUJoXRLxagGqCK+jALRwIO+o3R9p/uUg==",
"requires": {
"@babel/code-frame": "^7.12.13",
"@babel/generator": "^7.13.9",
"@babel/helper-function-name": "^7.12.13",
"@babel/helper-split-export-declaration": "^7.12.13",
"@babel/parser": "^7.13.13",
"@babel/types": "^7.13.13",
"debug": "^4.1.0",
"globals": "^11.1.0"
}
},
"@babel/types": {
"version": "7.13.14",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.14.tgz",
@@ -893,6 +844,11 @@
}
}
},
"@babel/helper-string-parser": {
"version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz",
"integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw=="
},
"@babel/helper-validator-identifier": {
"version": "7.12.11",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz",
@@ -927,30 +883,6 @@
"@babel/types": "^7.12.13"
}
},
"@babel/traverse": {
"version": "7.13.13",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.13.13.tgz",
"integrity": "sha512-CblEcwmXKR6eP43oQGG++0QMTtCjAsa3frUuzHoiIJWpaIIi8dwMyEFUJoXRLxagGqCK+jALRwIO+o3R9p/uUg==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.12.13",
"@babel/generator": "^7.13.9",
"@babel/helper-function-name": "^7.12.13",
"@babel/helper-split-export-declaration": "^7.12.13",
"@babel/parser": "^7.13.13",
"@babel/types": "^7.13.13",
"debug": "^4.1.0",
"globals": "^11.1.0"
},
"dependencies": {
"@babel/parser": {
"version": "7.13.13",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.13.tgz",
"integrity": "sha512-OhsyMrqygfk5v8HmWwOzlYjJrtLaFhF34MrfG/Z73DgYCI6ojNUTUp2TYbtnjo8PegeJp12eamsNettCQjKjVw==",
"dev": true
}
}
},
"@babel/types": {
"version": "7.13.14",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.14.tgz",
@@ -984,28 +916,6 @@
"@babel/types": "^7.12.13"
}
},
"@babel/traverse": {
"version": "7.13.13",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.13.13.tgz",
"integrity": "sha512-CblEcwmXKR6eP43oQGG++0QMTtCjAsa3frUuzHoiIJWpaIIi8dwMyEFUJoXRLxagGqCK+jALRwIO+o3R9p/uUg==",
"requires": {
"@babel/code-frame": "^7.12.13",
"@babel/generator": "^7.13.9",
"@babel/helper-function-name": "^7.12.13",
"@babel/helper-split-export-declaration": "^7.12.13",
"@babel/parser": "^7.13.13",
"@babel/types": "^7.13.13",
"debug": "^4.1.0",
"globals": "^11.1.0"
},
"dependencies": {
"@babel/parser": {
"version": "7.13.13",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.13.tgz",
"integrity": "sha512-OhsyMrqygfk5v8HmWwOzlYjJrtLaFhF34MrfG/Z73DgYCI6ojNUTUp2TYbtnjo8PegeJp12eamsNettCQjKjVw=="
}
}
},
"@babel/types": {
"version": "7.13.14",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.14.tgz",
@@ -2031,19 +1941,107 @@
}
},
"@babel/traverse": {
"version": "7.12.12",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.12.tgz",
"integrity": "sha512-s88i0X0lPy45RrLM8b9mz8RPH5FqO9G9p7ti59cToE44xFm1Q+Pjh5Gq4SXBbtb88X7Uy7pexeqRIQDDMNkL0w==",
"version": "7.23.2",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz",
"integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==",
"requires": {
"@babel/code-frame": "^7.12.11",
"@babel/generator": "^7.12.11",
"@babel/helper-function-name": "^7.12.11",
"@babel/helper-split-export-declaration": "^7.12.11",
"@babel/parser": "^7.12.11",
"@babel/types": "^7.12.12",
"@babel/code-frame": "^7.22.13",
"@babel/generator": "^7.23.0",
"@babel/helper-environment-visitor": "^7.22.20",
"@babel/helper-function-name": "^7.23.0",
"@babel/helper-hoist-variables": "^7.22.5",
"@babel/helper-split-export-declaration": "^7.22.6",
"@babel/parser": "^7.23.0",
"@babel/types": "^7.23.0",
"debug": "^4.1.0",
"globals": "^11.1.0",
"lodash": "^4.17.19"
"globals": "^11.1.0"
},
"dependencies": {
"@babel/code-frame": {
"version": "7.22.13",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz",
"integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==",
"requires": {
"@babel/highlight": "^7.22.13",
"chalk": "^2.4.2"
}
},
"@babel/generator": {
"version": "7.23.0",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz",
"integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==",
"requires": {
"@babel/types": "^7.23.0",
"@jridgewell/gen-mapping": "^0.3.2",
"@jridgewell/trace-mapping": "^0.3.17",
"jsesc": "^2.5.1"
}
},
"@babel/helper-function-name": {
"version": "7.23.0",
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz",
"integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==",
"requires": {
"@babel/template": "^7.22.15",
"@babel/types": "^7.23.0"
}
},
"@babel/helper-hoist-variables": {
"version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz",
"integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==",
"requires": {
"@babel/types": "^7.22.5"
}
},
"@babel/helper-split-export-declaration": {
"version": "7.22.6",
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz",
"integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==",
"requires": {
"@babel/types": "^7.22.5"
}
},
"@babel/helper-validator-identifier": {
"version": "7.22.20",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz",
"integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A=="
},
"@babel/highlight": {
"version": "7.22.20",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz",
"integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==",
"requires": {
"@babel/helper-validator-identifier": "^7.22.20",
"chalk": "^2.4.2",
"js-tokens": "^4.0.0"
}
},
"@babel/parser": {
"version": "7.23.0",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz",
"integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw=="
},
"@babel/template": {
"version": "7.22.15",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz",
"integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==",
"requires": {
"@babel/code-frame": "^7.22.13",
"@babel/parser": "^7.22.15",
"@babel/types": "^7.22.15"
}
},
"@babel/types": {
"version": "7.23.0",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz",
"integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==",
"requires": {
"@babel/helper-string-parser": "^7.22.5",
"@babel/helper-validator-identifier": "^7.22.20",
"to-fast-properties": "^2.0.0"
}
}
}
},
"@babel/types": {
@@ -4008,6 +4006,40 @@
"@types/yargs": "^13.0.0"
}
},
"@jridgewell/gen-mapping": {
"version": "0.3.3",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz",
"integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==",
"requires": {
"@jridgewell/set-array": "^1.0.1",
"@jridgewell/sourcemap-codec": "^1.4.10",
"@jridgewell/trace-mapping": "^0.3.9"
}
},
"@jridgewell/resolve-uri": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz",
"integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA=="
},
"@jridgewell/set-array": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
"integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw=="
},
"@jridgewell/sourcemap-codec": {
"version": "1.4.15",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
"integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg=="
},
"@jridgewell/trace-mapping": {
"version": "0.3.19",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz",
"integrity": "sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==",
"requires": {
"@jridgewell/resolve-uri": "^3.1.0",
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
"@jupyterlab/apputils": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@jupyterlab/apputils/-/apputils-3.0.2.tgz",

View File

@@ -1,13 +1,14 @@
import * as Cosmos from "@azure/cosmos";
import { configContext, Platform } from "../ConfigContext";
import { sendCachedDataMessage } from "Common/MessageHandler";
import { AuthorizationToken, MessageTypes } from "Contracts/MessageTypes";
import { AuthType } from "../AuthType";
import { PriorityLevel } from "../Common/Constants";
import { Platform, configContext } from "../ConfigContext";
import { userContext } from "../UserContext";
import { logConsoleError } from "../Utils/NotificationConsoleUtils";
import * as PriorityBasedExecutionUtils from "../Utils/PriorityBasedExecutionUtils";
import { EmulatorMasterKey, HttpHeaders } from "./Constants";
import { getErrorMessage } from "./ErrorHandlingUtils";
import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility";
import { PriorityLevel } from "../Common/Constants";
import * as PriorityBasedExecutionUtils from "../Utils/PriorityBasedExecutionUtils";
import { AuthType } from "../AuthType";
const _global = typeof self === "undefined" ? window : self;
@@ -26,6 +27,15 @@ export const tokenProvider = async (requestInfo: Cosmos.RequestInfo) => {
return decodeURIComponent(headers.authorization);
}
if (configContext.platform === Platform.Fabric) {
const authorizationToken = await sendCachedDataMessage<AuthorizationToken>(MessageTypes.GetAuthorizationToken, [
requestInfo,
]);
console.log("Response from Fabric: ", authorizationToken);
headers[HttpHeaders.msDate] = authorizationToken.XDate;
return authorizationToken.PrimaryReadWriteToken;
}
if (userContext.masterKey) {
// TODO This SDK method mutates the headers object. Find a better one or fix the SDK.
await Cosmos.setAuthorizationTokenHeaderUsingMasterKey(verb, resourceId, resourceType, headers, EmulatorMasterKey);
@@ -56,7 +66,11 @@ export const endpoint = () => {
return userContext.endpoint || userContext?.databaseAccount?.properties?.documentEndpoint;
};
export async function getTokenFromAuthService(verb: string, resourceType: string, resourceId?: string): Promise<any> {
export async function getTokenFromAuthService(
verb: string,
resourceType: string,
resourceId?: string,
): Promise<AuthorizationToken> {
try {
const host = configContext.BACKEND_ENDPOINT;
const response = await _global.fetch(host + "/api/guest/runtimeproxy/authorizationTokens", {

View File

@@ -22,7 +22,7 @@ export function handleCachedDataMessage(message: any): void {
if (messageContent.error != null) {
cachedDataPromise.deferred.reject(messageContent.error);
} else {
cachedDataPromise.deferred.resolve(JSON.parse(messageContent.data));
cachedDataPromise.deferred.resolve(messageContent.data);
}
runGarbageCollector();
}

View File

@@ -1,46 +1,6 @@
import { MessageTypes } from "Contracts/MessageTypes";
import * as ActionContracts from "./ActionContracts";
import * as Diagnostics from "./Diagnostics";
import * as Versions from "./Versions";
/**
* Messaging types used with Data Explorer <-> Portal communication
* and Hosted <-> Explorer communication
*/
export enum MessageTypes {
TelemetryInfo,
LogInfo,
RefreshResources,
AllDatabases,
CollectionsForDatabase,
RefreshOffers,
AllOffers,
UpdateLocationHash,
SingleOffer,
RefreshOffer,
UpdateAccountName,
ForbiddenError,
AadSignIn,
GetAccessAadRequest,
GetAccessAadResponse,
UpdateAccountSwitch,
UpdateDirectoryControl,
SwitchAccount,
SendNotification,
ClearNotification,
ExplorerClickEvent,
LoadingStatus,
GetArcadiaToken,
CreateWorkspace,
CreateSparkPool,
RefreshDatabaseAccount,
CloseTab,
OpenQuickstartBlade,
OpenPostgreSQLPasswordReset,
OpenPostgresNetworkingBlade,
OpenCosmosDBNetworkingBlade,
DisplayNPSSurvey,
OpenVCoreMongoNetworkingBlade,
OpenVCoreMongoConnectionStringsBlade,
}
export { ActionContracts, Diagnostics, Versions };
export { ActionContracts, Diagnostics, MessageTypes, Versions };

View File

@@ -1,3 +1,5 @@
import { AuthorizationToken, MessageTypes } from "./MessageTypes";
export type FabricMessage =
| {
type: "newContainer";
@@ -5,21 +7,52 @@ export type FabricMessage =
}
| {
type: "initialize";
connectionString: string | undefined;
message: {
endpoint: string | undefined;
error: string | undefined;
};
}
| {
type: "openTab";
databaseName: string;
collectionName: string | undefined;
}
| {
type: "authorizationToken";
message: {
id: string;
error: string | undefined;
data: AuthorizationToken | undefined;
};
};
export type DataExploreMessage =
| "ready"
| {
type: number;
type: MessageTypes.TelemetryInfo;
data: {
action: "LoadDatabases";
actionModifier: "success" | "start";
defaultExperience: "SQL";
};
}
| {
type: MessageTypes.GetAuthorizationToken;
id: string;
params: GetCosmosTokenMessageOptions[];
};
export type GetCosmosTokenMessageOptions = {
verb: "connect" | "delete" | "get" | "head" | "options" | "patch" | "post" | "put" | "trace";
resourceType: "" | "dbs" | "colls" | "docs" | "sprocs" | "pkranges";
resourceId: string;
};
export type CosmosDBTokenResponse = {
token: string;
date: string;
};
export type CosmosDBConnectionInfoResponse = {
endpoint: string;
};

View File

@@ -0,0 +1,48 @@
/**
* Messaging types used with Data Explorer <-> Portal communication,
* Hosted <-> Explorer communication and Data Explorer -> Fabric communication.
*/
export enum MessageTypes {
TelemetryInfo,
LogInfo,
RefreshResources,
AllDatabases,
CollectionsForDatabase,
RefreshOffers,
AllOffers,
UpdateLocationHash,
SingleOffer,
RefreshOffer,
UpdateAccountName,
ForbiddenError,
AadSignIn,
GetAccessAadRequest,
GetAccessAadResponse,
UpdateAccountSwitch,
UpdateDirectoryControl,
SwitchAccount,
SendNotification,
ClearNotification,
ExplorerClickEvent,
LoadingStatus,
GetArcadiaToken,
CreateWorkspace,
CreateSparkPool,
RefreshDatabaseAccount,
CloseTab,
OpenQuickstartBlade,
OpenPostgreSQLPasswordReset,
OpenPostgresNetworkingBlade,
OpenCosmosDBNetworkingBlade,
DisplayNPSSurvey,
OpenVCoreMongoNetworkingBlade,
OpenVCoreMongoConnectionStringsBlade,
// Data Explorer -> Fabric communication
GetAuthorizationToken,
}
export interface AuthorizationToken {
XDate: string;
PrimaryReadWriteToken: string;
}

View File

@@ -123,19 +123,6 @@ describe("ContainerSampleGenerator", () => {
await generator.createSampleContainerAsync();
});
it("should not create any sample for Mongo API account", async () => {
const experience = "Sample generation not supported for this API Mongo";
updateUserContext({
databaseAccount: {
properties: {
capabilities: [{ name: "EnableMongo" }],
},
} as DatabaseAccount,
});
expect(ContainerSampleGenerator.createSampleGeneratorAsync(explorerStub)).rejects.toMatch(experience);
});
it("should not create any sample for Table API account", async () => {
const experience = "Sample generation not supported for this API Tables";
updateUserContext({

View File

@@ -102,7 +102,7 @@ describe("Query Copilot Feedback Modal snapshot test", () => {
expect(wrapper).toMatchSnapshot();
});
it("should submit submission", () => {
it("should not submit submission if required description field is null", () => {
const explorer = new Explorer();
const wrapper = shallow(<QueryCopilotFeedbackModal explorer={explorer} />);
@@ -110,12 +110,24 @@ describe("Query Copilot Feedback Modal snapshot test", () => {
submitButton.simulate("click");
wrapper.setProps({});
expect(SubmitFeedback).toHaveBeenCalledTimes(0);
});
it("should submit submission", () => {
useQueryCopilot.getState().openFeedbackModal("test query", false, "test prompt");
const explorer = new Explorer();
const wrapper = shallow(<QueryCopilotFeedbackModal explorer={explorer} />);
const submitButton = wrapper.find("form");
submitButton.simulate("submit");
wrapper.setProps({});
expect(SubmitFeedback).toHaveBeenCalledTimes(1);
expect(SubmitFeedback).toHaveBeenCalledWith({
params: {
likeQuery: false,
generatedQuery: "",
userPrompt: "",
generatedQuery: "test query",
userPrompt: "test prompt",
description: "",
contact: getUserEmail(),
},

View File

@@ -25,93 +25,94 @@ export const QueryCopilotFeedbackModal = ({ explorer }: { explorer: Explorer }):
closeFeedbackModal,
setHideFeedbackModalForLikedQueries,
} = useQueryCopilot();
const [isContactAllowed, setIsContactAllowed] = React.useState<boolean>(true);
const [isContactAllowed, setIsContactAllowed] = React.useState<boolean>(false);
const [description, setDescription] = React.useState<string>("");
const [doNotShowAgainChecked, setDoNotShowAgainChecked] = React.useState<boolean>(false);
const [contact, setContact] = React.useState<string>(getUserEmail());
const handleSubmit = () => {
closeFeedbackModal();
setHideFeedbackModalForLikedQueries(doNotShowAgainChecked);
SubmitFeedback({
params: { generatedQuery, likeQuery, description, userPrompt, contact },
explorer: explorer,
});
};
return (
<Modal isOpen={showFeedbackModal}>
<Stack style={{ padding: 24 }}>
<Stack horizontal horizontalAlign="space-between">
<Text style={{ fontSize: 20, fontWeight: 600, marginBottom: 20 }}>Send feedback to Microsoft</Text>
<IconButton iconProps={{ iconName: "Cancel" }} onClick={() => closeFeedbackModal()} />
</Stack>
<Text style={{ fontSize: 14, marginBottom: 14 }}>Your feedback will help improve the experience.</Text>
<TextField
styles={{ root: { marginBottom: 14 } }}
label="Description"
required
placeholder="Provide more details"
value={description}
onChange={(_, newValue) => setDescription(newValue)}
multiline
rows={3}
/>
<TextField
styles={{ root: { marginBottom: 14 } }}
label="Query generated"
defaultValue={generatedQuery}
readOnly
/>
<ChoiceGroup
styles={{
root: {
marginBottom: 14,
},
flexContainer: {
selectors: {
".ms-ChoiceField-field::before": { marginTop: 4 },
".ms-ChoiceField-field::after": { marginTop: 4 },
".ms-ChoiceFieldLabel": { paddingLeft: 6 },
},
},
}}
label="May we contact you about your feedback?"
options={[
{ key: "yes", text: "Yes, you may contact me." },
{ key: "no", text: "No, do not contact me." },
]}
selectedKey={isContactAllowed ? "yes" : "no"}
onChange={(_, option) => {
setIsContactAllowed(option.key === "yes");
setContact(option.key === "yes" ? getUserEmail() : "");
}}
></ChoiceGroup>
<Text style={{ fontSize: 12, marginBottom: 14 }}>
By pressing submit, your feedback will be used to improve Microsoft products and services. Please see the{" "}
{
<Link href="https://privacy.microsoft.com/privacystatement" target="_blank">
Privacy statement
</Link>
}{" "}
for more information.
</Text>
{likeQuery && (
<Checkbox
styles={{ label: { paddingLeft: 0 }, root: { marginBottom: 14 } }}
label="Don't show me this next time"
checked={doNotShowAgainChecked}
onChange={(_, checked) => setDoNotShowAgainChecked(checked)}
<form onSubmit={handleSubmit}>
<Stack style={{ padding: 24 }}>
<Stack horizontal horizontalAlign="space-between">
<Text style={{ fontSize: 20, fontWeight: 600, marginBottom: 20 }}>Send feedback to Microsoft</Text>
<IconButton iconProps={{ iconName: "Cancel" }} onClick={() => closeFeedbackModal()} />
</Stack>
<Text style={{ fontSize: 14, marginBottom: 14 }}>Your feedback will help improve the experience.</Text>
<TextField
styles={{ root: { marginBottom: 14 } }}
label="Description"
required
placeholder="Provide more details"
value={description}
onChange={(_, newValue) => setDescription(newValue)}
multiline
rows={3}
/>
)}
<Stack horizontal horizontalAlign="end">
<PrimaryButton
styles={{ root: { marginRight: 8 } }}
onClick={() => {
closeFeedbackModal();
setHideFeedbackModalForLikedQueries(doNotShowAgainChecked);
SubmitFeedback({
params: { generatedQuery, likeQuery, description, userPrompt, contact },
explorer: explorer,
});
<TextField
styles={{ root: { marginBottom: 14 } }}
label="Query generated"
defaultValue={generatedQuery}
readOnly
/>
<ChoiceGroup
styles={{
root: {
marginBottom: 14,
},
flexContainer: {
selectors: {
".ms-ChoiceField-field::before": { marginTop: 4 },
".ms-ChoiceField-field::after": { marginTop: 4 },
".ms-ChoiceFieldLabel": { paddingLeft: 6 },
},
},
}}
>
Submit
</PrimaryButton>
<DefaultButton onClick={() => closeFeedbackModal()}>Cancel</DefaultButton>
label="May we contact you about your feedback?"
options={[
{ key: "yes", text: "Yes, you may contact me." },
{ key: "no", text: "No, do not contact me." },
]}
selectedKey={isContactAllowed ? "yes" : "no"}
onChange={(_, option) => {
setIsContactAllowed(option.key === "yes");
setContact(option.key === "yes" ? getUserEmail() : "");
}}
></ChoiceGroup>
<Text style={{ fontSize: 12, marginBottom: 14 }}>
By pressing submit, your feedback will be used to improve Microsoft products and services. Please see the{" "}
{
<Link href="https://privacy.microsoft.com/privacystatement" target="_blank">
Privacy statement
</Link>
}{" "}
for more information.
</Text>
{likeQuery && (
<Checkbox
styles={{ label: { paddingLeft: 0 }, root: { marginBottom: 14 } }}
label="Don't show me this next time"
checked={doNotShowAgainChecked}
onChange={(_, checked) => setDoNotShowAgainChecked(checked)}
/>
)}
<Stack horizontal horizontalAlign="end">
<PrimaryButton styles={{ root: { marginRight: 8 } }} type="submit">
Submit
</PrimaryButton>
<DefaultButton onClick={() => closeFeedbackModal()}>Cancel</DefaultButton>
</Stack>
</Stack>
</Stack>
</form>
</Modal>
);
};

View File

@@ -52,7 +52,7 @@ export const WelcomeModal = ({ visible }: { visible: boolean }): JSX.Element =>
</Stack>
<Stack horizontalAlign="center">
<Stack.Item align="center" style={{ textAlign: "center" }}>
<Text className="title bold">Welcome to Query Copilot for Azure Cosmos DB (Private Preview)</Text>
<Text className="title bold">Welcome to Copilot in Azure Cosmos DB (Private Preview)</Text>
</Stack.Item>
<Stack.Item align="center" className="text">
<Stack horizontal>
@@ -61,7 +61,7 @@ export const WelcomeModal = ({ visible }: { visible: boolean }): JSX.Element =>
</StackItem>
<StackItem align="start">
<Text className="bold">
Let Query Copilot do the work for you
Let Copilot do the work for you
<br />
</Text>
</StackItem>

View File

@@ -76,7 +76,7 @@ exports[`Query Copilot Welcome Modal snapshot test should render when isOpen is
<Text
className="title bold"
>
Welcome to Query Copilot for Azure Cosmos DB (Private Preview)
Welcome to Copilot in Azure Cosmos DB (Private Preview)
</Text>
</StackItem>
<StackItem
@@ -100,7 +100,7 @@ exports[`Query Copilot Welcome Modal snapshot test should render when isOpen is
<Text
className="bold"
>
Let Query Copilot do the work for you
Let Copilot do the work for you
<br />
</Text>
</StackItem>

View File

@@ -0,0 +1,567 @@
import {
Callout,
CommandBarButton,
DefaultButton,
DirectionalHint,
IButtonStyles,
IconButton,
Image,
Link,
MessageBar,
MessageBarType,
Separator,
Spinner,
Stack,
TeachingBubble,
Text,
TextField,
} from "@fluentui/react";
import { useBoolean } from "@fluentui/react-hooks";
import {
ContainerStatusType,
PoolIdType,
QueryCopilotSampleContainerSchema,
ShortenedQueryCopilotSampleContainerSchema,
} from "Common/Constants";
import { handleError } from "Common/ErrorHandlingUtils";
import { createUri } from "Common/UrlUtility";
import { WelcomeModal } from "Explorer/QueryCopilot/Modal/WelcomeModal";
import { CopyPopup } from "Explorer/QueryCopilot/Popup/CopyPopup";
import { DeletePopup } from "Explorer/QueryCopilot/Popup/DeletePopup";
import { SubmitFeedback } from "Explorer/QueryCopilot/Shared/QueryCopilotClient";
import { GenerateSQLQueryResponse, QueryCopilotProps } from "Explorer/QueryCopilot/Shared/QueryCopilotInterfaces";
import { SamplePrompts, SamplePromptsProps } from "Explorer/QueryCopilot/Shared/SamplePrompts/SamplePrompts";
import { userContext } from "UserContext";
import { useQueryCopilot } from "hooks/useQueryCopilot";
import React, { useRef, useState } from "react";
import HintIcon from "../../../images/Hint.svg";
import CopilotIcon from "../../../images/QueryCopilotNewLogo.svg";
import RecentIcon from "../../../images/Recent.svg";
import errorIcon from "../../../images/close-black.svg";
import { useTabs } from "../../hooks/useTabs";
type QueryCopilotPromptProps = QueryCopilotProps & {
toggleCopilot: (toggle: boolean) => void;
};
interface SuggestedPrompt {
id: number;
text: string;
}
const promptStyles: IButtonStyles = {
root: { border: 0, selectors: { ":hover": { outline: "1px dashed #605e5c" } } },
label: { fontWeight: 400, textAlign: "left", paddingLeft: 8 },
};
export const QueryCopilotPromptbar: React.FC<QueryCopilotPromptProps> = ({
explorer,
toggleCopilot,
}: QueryCopilotPromptProps): JSX.Element => {
const [copilotTeachingBubbleVisible, { toggle: toggleCopilotTeachingBubbleVisible }] = useBoolean(false);
const inputEdited = useRef(false);
const {
hideFeedbackModalForLikedQueries,
userPrompt,
setUserPrompt,
generatedQuery,
setGeneratedQuery,
query,
setQuery,
isGeneratingQuery,
setIsGeneratingQuery,
likeQuery,
setLikeQuery,
dislikeQuery,
setDislikeQuery,
showCallout,
setShowCallout,
isSamplePromptsOpen,
setIsSamplePromptsOpen,
showSamplePrompts,
setShowSamplePrompts,
showDeletePopup,
setShowDeletePopup,
showFeedbackBar,
setShowFeedbackBar,
showCopyPopup,
setshowCopyPopup,
showErrorMessageBar,
showInvalidQueryMessageBar,
setShowInvalidQueryMessageBar,
setShowErrorMessageBar,
setGeneratedQueryComments,
setQueryResults,
setErrorMessage,
} = useQueryCopilot();
const sampleProps: SamplePromptsProps = {
isSamplePromptsOpen: isSamplePromptsOpen,
setIsSamplePromptsOpen: setIsSamplePromptsOpen,
setTextBox: setUserPrompt,
};
const copyGeneratedCode = () => {
if (!query) {
return;
}
const queryElement = document.createElement("textarea");
queryElement.value = query;
document.body.appendChild(queryElement);
queryElement.select();
document.execCommand("copy");
document.body.removeChild(queryElement);
setshowCopyPopup(true);
setTimeout(() => {
setshowCopyPopup(false);
}, 6000);
};
const cachedHistoriesString = localStorage.getItem(`${userContext.databaseAccount?.id}-queryCopilotHistories`);
const cachedHistories = cachedHistoriesString?.split("|");
const [histories, setHistories] = useState<string[]>(cachedHistories || []);
const suggestedPrompts: SuggestedPrompt[] = [
{ id: 1, text: 'Show all products that have the word "ultra" in the name or description' },
{ id: 2, text: "What are all of the possible categories for the products, and their counts?" },
{ id: 3, text: 'Show me all products that have been reviewed by someone with a username that contains "bob"' },
];
const [filteredHistories, setFilteredHistories] = useState<string[]>(histories);
const [filteredSuggestedPrompts, setFilteredSuggestedPrompts] = useState<SuggestedPrompt[]>(suggestedPrompts);
const handleUserPromptChange = (event: React.ChangeEvent<HTMLInputElement>) => {
inputEdited.current = true;
const { value } = event.target;
setUserPrompt(value);
// Filter history prompts
const filteredHistory = histories.filter((history) => history.toLowerCase().includes(value.toLowerCase()));
setFilteredHistories(filteredHistory);
// Filter suggested prompts
const filteredSuggested = suggestedPrompts.filter((prompt) =>
prompt.text.toLowerCase().includes(value.toLowerCase()),
);
setFilteredSuggestedPrompts(filteredSuggested);
};
const updateHistories = (): void => {
const formattedUserPrompt = userPrompt.replace(/\s+/g, " ").trim();
const existingHistories = histories.map((history) => history.replace(/\s+/g, " ").trim());
const updatedHistories = existingHistories.filter(
(history) => history.toLowerCase() !== formattedUserPrompt.toLowerCase(),
);
const newHistories = [formattedUserPrompt, ...updatedHistories.slice(0, 2)];
setHistories(newHistories);
localStorage.setItem(`${userContext.databaseAccount.id}-queryCopilotHistories`, newHistories.join("|"));
};
const resetMessageStates = (): void => {
setShowErrorMessageBar(false);
setShowInvalidQueryMessageBar(false);
setShowFeedbackBar(false);
};
const resetQueryResults = (): void => {
setQueryResults(null);
setErrorMessage("");
};
const generateSQLQuery = async (): Promise<void> => {
try {
resetMessageStates();
setIsGeneratingQuery(true);
setShowDeletePopup(false);
useTabs.getState().setIsTabExecuting(true);
useTabs.getState().setIsQueryErrorThrown(false);
if (
useQueryCopilot.getState().containerStatus.status !== ContainerStatusType.Active &&
!userContext.features.disableCopilotPhoenixGateaway
) {
await explorer.allocateContainer(PoolIdType.QueryCopilot);
}
const payload = {
containerSchema: userContext.features.enableCopilotFullSchema
? QueryCopilotSampleContainerSchema
: ShortenedQueryCopilotSampleContainerSchema,
userPrompt: userPrompt,
};
useQueryCopilot.getState().refreshCorrelationId();
const serverInfo = useQueryCopilot.getState().notebookServerInfo;
const queryUri = userContext.features.disableCopilotPhoenixGateaway
? createUri("https://copilotorchestrater.azurewebsites.net/", "generateSQLQuery")
: createUri(serverInfo.notebookServerEndpoint, "generateSQLQuery");
const response = await fetch(queryUri, {
method: "POST",
headers: {
"content-type": "application/json",
"x-ms-correlationid": useQueryCopilot.getState().correlationId,
},
body: JSON.stringify(payload),
});
const generateSQLQueryResponse: GenerateSQLQueryResponse = await response?.json();
if (response.ok) {
if (generateSQLQueryResponse?.sql !== "N/A") {
let query = `-- **Prompt:** ${userPrompt}\r\n`;
if (generateSQLQueryResponse.explanation) {
query += `-- **Explanation of query:** ${generateSQLQueryResponse.explanation}\r\n`;
}
query += generateSQLQueryResponse.sql;
setQuery(query);
setGeneratedQuery(generateSQLQueryResponse.sql);
setGeneratedQueryComments(generateSQLQueryResponse.explanation);
setShowFeedbackBar(true);
resetQueryResults();
} else {
setShowInvalidQueryMessageBar(true);
}
} else {
handleError(JSON.stringify(generateSQLQueryResponse), "copilotInternalServerError");
useTabs.getState().setIsQueryErrorThrown(true);
setShowErrorMessageBar(true);
}
} catch (error) {
handleError(error, "executeNaturalLanguageQuery");
useTabs.getState().setIsQueryErrorThrown(true);
setShowErrorMessageBar(true);
throw error;
} finally {
setIsGeneratingQuery(false);
useTabs.getState().setIsTabExecuting(false);
}
};
const showTeachingBubble = (): void => {
if (!inputEdited.current) {
setTimeout(() => {
if (!inputEdited.current && !isWelcomModalVisible()) {
toggleCopilotTeachingBubbleVisible();
inputEdited.current = true;
}
}, 30000);
}
};
const isWelcomModalVisible = (): boolean => {
return localStorage.getItem("hideWelcomeModal") !== "true";
};
const clearFeedback = () => {
resetButtonState();
resetQueryResults();
};
const resetButtonState = () => {
setDislikeQuery(false);
setLikeQuery(false);
setShowCallout(false);
};
const startGenerateQueryProcess = () => {
updateHistories();
generateSQLQuery();
resetButtonState();
};
React.useEffect(() => {
showTeachingBubble();
useTabs.getState().setIsQueryErrorThrown(false);
}, []);
return (
<Stack className="copilot-prompt-pane" styles={{ root: { backgroundColor: "#FAFAFA", padding: "16px 24px 0px" } }}>
<Stack horizontal>
<Image src={CopilotIcon} style={{ width: 24, height: 24 }} />
<Text style={{ marginLeft: 8, fontWeight: 600, fontSize: 16 }}>Copilot</Text>
<IconButton
iconProps={{ imageProps: { src: errorIcon } }}
onClick={() => {
toggleCopilot(false);
clearFeedback();
resetMessageStates();
}}
styles={{
root: {
marginLeft: "auto !important",
},
}}
ariaLabel="Close"
/>
</Stack>
<Stack horizontal verticalAlign="center">
<TextField
id="naturalLanguageInput"
value={userPrompt}
onChange={handleUserPromptChange}
onClick={() => {
inputEdited.current = true;
setShowSamplePrompts(true);
}}
onKeyDown={(e) => {
if (e.key === "Enter" && userPrompt) {
inputEdited.current = true;
startGenerateQueryProcess();
}
}}
style={{ lineHeight: 30 }}
styles={{ root: { width: "95%" }, fieldGroup: { borderRadius: 6 } }}
disabled={isGeneratingQuery}
autoComplete="off"
/>
{copilotTeachingBubbleVisible && (
<TeachingBubble
calloutProps={{ directionalHint: DirectionalHint.bottomCenter }}
target="#naturalLanguageInput"
hasCloseButton={true}
closeButtonAriaLabel="Close"
onDismiss={toggleCopilotTeachingBubbleVisible}
hasSmallHeadline={true}
headline="Write a prompt"
>
Write a prompt here and Copilot will generate the query for you. You can also choose from our{" "}
<Link
onClick={() => {
setShowSamplePrompts(true);
toggleCopilotTeachingBubbleVisible();
}}
style={{ color: "white", fontWeight: 600 }}
>
sample prompts
</Link>{" "}
or write your own query
</TeachingBubble>
)}
<IconButton
iconProps={{ iconName: "Send" }}
disabled={isGeneratingQuery || !userPrompt.trim()}
style={{ marginLeft: 8 }}
onClick={() => startGenerateQueryProcess()}
/>
{isGeneratingQuery && <Spinner style={{ marginLeft: 8 }} />}
{showSamplePrompts && (
<Callout
styles={{ root: { minWidth: 400 } }}
target="#naturalLanguageInput"
isBeakVisible={false}
onDismiss={() => setShowSamplePrompts(false)}
directionalHintFixed={true}
directionalHint={DirectionalHint.bottomLeftEdge}
alignTargetEdge={true}
gapSpace={4}
>
<Stack>
{filteredHistories?.length > 0 && (
<Stack>
<Text
style={{
width: "100%",
fontSize: 14,
fontWeight: 600,
color: "#0078D4",
marginLeft: 16,
padding: "4px 0",
}}
>
Recent
</Text>
{filteredHistories.map((history, i) => (
<DefaultButton
key={i}
onClick={() => {
setUserPrompt(history);
setShowSamplePrompts(false);
inputEdited.current = true;
}}
onRenderIcon={() => <Image src={RecentIcon} />}
styles={promptStyles}
>
{history}
</DefaultButton>
))}
</Stack>
)}
{filteredSuggestedPrompts?.length > 0 && (
<Stack>
<Text
style={{
width: "100%",
fontSize: 14,
fontWeight: 600,
color: "#0078D4",
marginLeft: 16,
padding: "4px 0",
}}
>
Suggested Prompts
</Text>
{filteredSuggestedPrompts.map((prompt) => (
<DefaultButton
key={prompt.id}
onClick={() => {
setUserPrompt(prompt.text);
setShowSamplePrompts(false);
inputEdited.current = true;
}}
onRenderIcon={() => <Image src={HintIcon} />}
styles={promptStyles}
>
{prompt.text}
</DefaultButton>
))}
</Stack>
)}
{(filteredHistories?.length > 0 || filteredSuggestedPrompts?.length > 0) && (
<Stack>
<Separator
styles={{
root: {
selectors: { "::before": { background: "#E1DFDD" } },
padding: 0,
},
}}
/>
<Text
style={{
width: "100%",
fontSize: 14,
marginLeft: 16,
padding: "4px 0",
}}
>
Learn about{" "}
<Link target="_blank" href="https://aka.ms/cdb-copilot-writing">
writing effective prompts
</Link>
</Text>
</Stack>
)}
</Stack>
</Callout>
)}
</Stack>
<Stack style={{ margin: "8px 0" }}>
<Text style={{ fontSize: 12 }}>
AI-generated content can have mistakes. Make sure it&apos;s accurate and appropriate before using it.{" "}
<Link href="https://aka.ms/cdb-copilot-preview-terms" target="_blank">
Read preview terms
</Link>
{showErrorMessageBar && (
<MessageBar messageBarType={MessageBarType.error}>
We ran into an error and were not able to execute query.
</MessageBar>
)}
{showInvalidQueryMessageBar && (
<MessageBar
messageBarType={MessageBarType.info}
styles={{ root: { backgroundColor: "#F0F6FF" }, icon: { color: "#015CDA" } }}
>
We were unable to generate a query based upon the prompt provided. Please modify the prompt and try again.
For examples of how to write a good prompt, please read
<Link href="https://aka.ms/cdb-copilot-writing" target="_blank">
this article.
</Link>{" "}
Our content guidelines can be found
<Link href="https://aka.ms/cdb-query-copilot" target="_blank">
here.
</Link>
</MessageBar>
)}
</Text>
</Stack>
{showFeedbackBar && (
<Stack style={{ backgroundColor: "#FFF8F0", padding: "2px 8px" }} horizontal verticalAlign="center">
<Text style={{ fontWeight: 600, fontSize: 12 }}>Provide feedback on the query generated</Text>
{showCallout && !hideFeedbackModalForLikedQueries && (
<Callout
style={{ padding: 8 }}
target="#likeBtn"
onDismiss={() => {
setShowCallout(false);
SubmitFeedback({
params: {
generatedQuery: generatedQuery,
likeQuery: likeQuery,
description: "",
userPrompt: userPrompt,
},
explorer: explorer,
});
}}
directionalHint={DirectionalHint.topCenter}
>
<Text>
Thank you. Need to give{" "}
<Link
onClick={() => {
setShowCallout(false);
useQueryCopilot.getState().openFeedbackModal(generatedQuery, true, userPrompt);
}}
>
more feedback?
</Link>
</Text>
</Callout>
)}
<IconButton
id="likeBtn"
style={{ marginLeft: 20 }}
iconProps={{ iconName: likeQuery === true ? "LikeSolid" : "Like" }}
onClick={() => {
setShowCallout(!likeQuery);
setLikeQuery(!likeQuery);
if (dislikeQuery) {
setDislikeQuery(!dislikeQuery);
}
}}
/>
<IconButton
style={{ margin: "0 10px" }}
iconProps={{ iconName: dislikeQuery === true ? "DislikeSolid" : "Dislike" }}
onClick={() => {
if (!dislikeQuery) {
useQueryCopilot.getState().openFeedbackModal(generatedQuery, false, userPrompt);
setLikeQuery(false);
}
setDislikeQuery(!dislikeQuery);
setShowCallout(false);
}}
/>
<Separator vertical style={{ color: "#EDEBE9" }} />
<CommandBarButton
onClick={copyGeneratedCode}
iconProps={{ iconName: "Copy" }}
style={{ margin: "0 10px", backgroundColor: "#FFF8F0", transition: "background-color 0.3s ease" }}
>
Copy query
</CommandBarButton>
<CommandBarButton
onClick={() => {
setShowDeletePopup(true);
}}
iconProps={{ iconName: "Delete" }}
style={{ margin: "0 10px", backgroundColor: "#FFF8F0", transition: "background-color 0.3s ease" }}
>
Delete query
</CommandBarButton>
</Stack>
)}
<WelcomeModal visible={isWelcomModalVisible()} />
{isSamplePromptsOpen && <SamplePrompts sampleProps={sampleProps} />}
{query !== "" && query.trim().length !== 0 && (
<DeletePopup
showDeletePopup={showDeletePopup}
setShowDeletePopup={setShowDeletePopup}
setQuery={setQuery}
clearFeedback={clearFeedback}
showFeedbackBar={setShowFeedbackBar}
/>
)}
<CopyPopup showCopyPopup={showCopyPopup} setShowCopyPopup={setshowCopyPopup} />
</Stack>
);
};

View File

@@ -1,242 +1,33 @@
/* eslint-disable no-console */
import {
Callout,
CommandBarButton,
DefaultButton,
DirectionalHint,
IButtonStyles,
IconButton,
Image,
Link,
MessageBar,
MessageBarType,
Separator,
Spinner,
Stack,
TeachingBubble,
Text,
TextField,
} from "@fluentui/react";
import { useBoolean } from "@fluentui/react-hooks";
import {
ContainerStatusType,
PoolIdType,
QueryCopilotSampleContainerSchema,
ShortenedQueryCopilotSampleContainerSchema,
} from "Common/Constants";
import { handleError } from "Common/ErrorHandlingUtils";
import { createUri } from "Common/UrlUtility";
import { Stack } from "@fluentui/react";
import { CommandButtonComponentProps } from "Explorer/Controls/CommandButton/CommandButtonComponent";
import { EditorReact } from "Explorer/Controls/Editor/EditorReact";
import { useCommandBar } from "Explorer/Menus/CommandBar/CommandBarComponentAdapter";
import { SaveQueryPane } from "Explorer/Panes/SaveQueryPane/SaveQueryPane";
import { WelcomeModal } from "Explorer/QueryCopilot/Modal/WelcomeModal";
import { CopyPopup } from "Explorer/QueryCopilot/Popup/CopyPopup";
import { DeletePopup } from "Explorer/QueryCopilot/Popup/DeletePopup";
import { OnExecuteQueryClick, SubmitFeedback } from "Explorer/QueryCopilot/Shared/QueryCopilotClient";
import { GenerateSQLQueryResponse, QueryCopilotProps } from "Explorer/QueryCopilot/Shared/QueryCopilotInterfaces";
import { QueryCopilotPromptbar } from "Explorer/QueryCopilot/QueryCopilotPromptbar";
import { OnExecuteQueryClick } from "Explorer/QueryCopilot/Shared/QueryCopilotClient";
import { QueryCopilotProps } from "Explorer/QueryCopilot/Shared/QueryCopilotInterfaces";
import { QueryCopilotResults } from "Explorer/QueryCopilot/Shared/QueryCopilotResults";
import { SamplePrompts, SamplePromptsProps } from "Explorer/QueryCopilot/Shared/SamplePrompts/SamplePrompts";
import { userContext } from "UserContext";
import { useQueryCopilot } from "hooks/useQueryCopilot";
import { useSidePanel } from "hooks/useSidePanel";
import React, { useRef, useState } from "react";
import React, { useState } from "react";
import SplitterLayout from "react-splitter-layout";
import QueryCommandIcon from "../../../images/CopilotCommand.svg";
import ExecuteQueryIcon from "../../../images/ExecuteQuery.svg";
import HintIcon from "../../../images/Hint.svg";
import CopilotIcon from "../../../images/QueryCopilotNewLogo.svg";
import RecentIcon from "../../../images/Recent.svg";
import SaveQueryIcon from "../../../images/save-cosmos.svg";
import { useTabs } from "../../hooks/useTabs";
interface SuggestedPrompt {
id: number;
text: string;
}
const promptStyles: IButtonStyles = {
root: { border: 0, selectors: { ":hover": { outline: "1px dashed #605e5c" } } },
label: { fontWeight: 400, textAlign: "left", paddingLeft: 8 },
};
import * as StringUtility from "../../Shared/StringUtility";
export const QueryCopilotTab: React.FC<QueryCopilotProps> = ({ explorer }: QueryCopilotProps): JSX.Element => {
const [copilotTeachingBubbleVisible, { toggle: toggleCopilotTeachingBubbleVisible }] = useBoolean(false);
const inputEdited = useRef(false);
const {
hideFeedbackModalForLikedQueries,
userPrompt,
setUserPrompt,
generatedQuery,
setGeneratedQuery,
query,
setQuery,
selectedQuery,
setSelectedQuery,
isGeneratingQuery,
setIsGeneratingQuery,
likeQuery,
setLikeQuery,
dislikeQuery,
setDislikeQuery,
showCallout,
setShowCallout,
showSamplePrompts,
setShowSamplePrompts,
isSamplePromptsOpen,
setIsSamplePromptsOpen,
showDeletePopup,
setShowDeletePopup,
showFeedbackBar,
setShowFeedbackBar,
showCopyPopup,
setshowCopyPopup,
showErrorMessageBar,
showInvalidQueryMessageBar,
setShowInvalidQueryMessageBar,
setShowErrorMessageBar,
setGeneratedQueryComments,
setQueryResults,
setErrorMessage,
} = useQueryCopilot();
const { query, setQuery, selectedQuery, setSelectedQuery, isGeneratingQuery } = useQueryCopilot();
const sampleProps: SamplePromptsProps = {
isSamplePromptsOpen: isSamplePromptsOpen,
setIsSamplePromptsOpen: setIsSamplePromptsOpen,
setTextBox: setUserPrompt,
};
const copyGeneratedCode = () => {
if (!query) {
return;
}
const queryElement = document.createElement("textarea");
queryElement.value = query;
document.body.appendChild(queryElement);
queryElement.select();
document.execCommand("copy");
document.body.removeChild(queryElement);
setshowCopyPopup(true);
setTimeout(() => {
setshowCopyPopup(false);
}, 6000);
};
const cachedHistoriesString = localStorage.getItem(`${userContext.databaseAccount?.id}-queryCopilotHistories`);
const cachedHistories = cachedHistoriesString?.split("|");
const [histories, setHistories] = useState<string[]>(cachedHistories || []);
const suggestedPrompts: SuggestedPrompt[] = [
{ id: 1, text: 'Show all products that have the word "ultra" in the name or description' },
{ id: 2, text: "What are all of the possible categories for the products, and their counts?" },
{ id: 3, text: 'Show me all products that have been reviewed by someone with a username that contains "bob"' },
];
const [filteredHistories, setFilteredHistories] = useState<string[]>(histories);
const [filteredSuggestedPrompts, setFilteredSuggestedPrompts] = useState<SuggestedPrompt[]>(suggestedPrompts);
const handleUserPromptChange = (event: React.ChangeEvent<HTMLInputElement>) => {
inputEdited.current = true;
const { value } = event.target;
setUserPrompt(value);
// Filter history prompts
const filteredHistory = histories.filter((history) => history.toLowerCase().includes(value.toLowerCase()));
setFilteredHistories(filteredHistory);
// Filter suggested prompts
const filteredSuggested = suggestedPrompts.filter((prompt) =>
prompt.text.toLowerCase().includes(value.toLowerCase()),
);
setFilteredSuggestedPrompts(filteredSuggested);
};
const updateHistories = (): void => {
const formattedUserPrompt = userPrompt.replace(/\s+/g, " ").trim();
const existingHistories = histories.map((history) => history.replace(/\s+/g, " ").trim());
const updatedHistories = existingHistories.filter(
(history) => history.toLowerCase() !== formattedUserPrompt.toLowerCase(),
);
const newHistories = [formattedUserPrompt, ...updatedHistories.slice(0, 2)];
setHistories(newHistories);
localStorage.setItem(`${userContext.databaseAccount.id}-queryCopilotHistories`, newHistories.join("|"));
};
const resetMessageStates = (): void => {
setShowErrorMessageBar(false);
setShowInvalidQueryMessageBar(false);
setShowFeedbackBar(false);
};
const resetQueryResults = (): void => {
setQueryResults(null);
setErrorMessage("");
};
const generateSQLQuery = async (): Promise<void> => {
try {
resetMessageStates();
setIsGeneratingQuery(true);
setShowDeletePopup(false);
useTabs.getState().setIsTabExecuting(true);
useTabs.getState().setIsQueryErrorThrown(false);
if (
useQueryCopilot.getState().containerStatus.status !== ContainerStatusType.Active &&
!userContext.features.disableCopilotPhoenixGateaway
) {
await explorer.allocateContainer(PoolIdType.QueryCopilot);
}
const payload = {
containerSchema: userContext.features.enableCopilotFullSchema
? QueryCopilotSampleContainerSchema
: ShortenedQueryCopilotSampleContainerSchema,
userPrompt: userPrompt,
};
useQueryCopilot.getState().refreshCorrelationId();
const serverInfo = useQueryCopilot.getState().notebookServerInfo;
const queryUri = userContext.features.disableCopilotPhoenixGateaway
? createUri("https://copilotorchestrater.azurewebsites.net/", "generateSQLQuery")
: createUri(serverInfo.notebookServerEndpoint, "generateSQLQuery");
const response = await fetch(queryUri, {
method: "POST",
headers: {
"content-type": "application/json",
"x-ms-correlationid": useQueryCopilot.getState().correlationId,
},
body: JSON.stringify(payload),
});
const generateSQLQueryResponse: GenerateSQLQueryResponse = await response?.json();
if (response.ok) {
if (generateSQLQueryResponse?.sql !== "N/A") {
let query = `-- **Prompt:** ${userPrompt}\r\n`;
if (generateSQLQueryResponse.explanation) {
query += `-- **Explanation of query:** ${generateSQLQueryResponse.explanation}\r\n`;
}
query += generateSQLQueryResponse.sql;
setQuery(query);
setGeneratedQuery(generateSQLQueryResponse.sql);
setGeneratedQueryComments(generateSQLQueryResponse.explanation);
setShowFeedbackBar(true);
resetQueryResults();
} else {
setShowInvalidQueryMessageBar(true);
}
} else {
handleError(JSON.stringify(generateSQLQueryResponse), "copilotInternalServerError");
useTabs.getState().setIsQueryErrorThrown(true);
setShowErrorMessageBar(true);
}
} catch (error) {
handleError(error, "executeNaturalLanguageQuery");
useTabs.getState().setIsQueryErrorThrown(true);
setShowErrorMessageBar(true);
throw error;
} finally {
setIsGeneratingQuery(false);
useTabs.getState().setIsTabExecuting(false);
}
};
const cachedCopilotToggleStatus: string = localStorage.getItem(
`${userContext.databaseAccount?.id}-queryCopilotToggleStatus`,
);
const copilotInitialActive: boolean = cachedCopilotToggleStatus
? StringUtility.toBoolean(cachedCopilotToggleStatus)
: true;
const [copilotActive, setCopilotActive] = useState<boolean>(copilotInitialActive);
const getCommandbarButtons = (): CommandButtonComponentProps[] => {
const executeQueryBtnLabel = selectedQuery ? "Execute Selection" : "Execute Query";
@@ -261,324 +52,47 @@ export const QueryCopilotTab: React.FC<QueryCopilotProps> = ({ explorer }: Query
disabled: true,
};
// Sample Prompts temporary disabled due current design
// const samplePromptsBtn = {
// iconSrc: SamplePromptsIcon,
// iconAlt: "Sample Prompts",
// onCommandClick: () => setIsSamplePromptsOpen(true),
// commandButtonLabel: "Sample Prompts",
// ariaLabel: "Sample Prompts",
// hasPopup: false,
// };
const toggleCopilotButton = {
iconSrc: QueryCommandIcon,
iconAlt: "Copilot",
onCommandClick: () => {
toggleCopilot(true);
},
commandButtonLabel: "Copilot",
ariaLabel: "Copilot",
hasPopup: false,
disabled: copilotActive,
};
return [executeQueryBtn, saveQueryBtn];
};
const showTeachingBubble = (): void => {
if (!inputEdited.current) {
setTimeout(() => {
if (!inputEdited.current) {
toggleCopilotTeachingBubbleVisible();
inputEdited.current = true;
}
}, 30000);
}
};
const clearFeedback = () => {
resetButtonState();
resetQueryResults();
};
const resetButtonState = () => {
setDislikeQuery(false);
setLikeQuery(false);
setShowCallout(false);
};
const startGenerateQueryProcess = () => {
updateHistories();
generateSQLQuery();
resetButtonState();
return [executeQueryBtn, saveQueryBtn, toggleCopilotButton];
};
React.useEffect(() => {
useCommandBar.getState().setContextButtons(getCommandbarButtons());
}, [query, selectedQuery]);
}, [query, selectedQuery, copilotActive]);
React.useEffect(() => {
showTeachingBubble();
useTabs.getState().setIsQueryErrorThrown(false);
return () => {
const commandbarButtons = getCommandbarButtons();
commandbarButtons.pop();
commandbarButtons.map((props: CommandButtonComponentProps) => (props.disabled = true));
useCommandBar.getState().setContextButtons(commandbarButtons);
};
}, []);
const toggleCopilot = (toggle: boolean) => {
setCopilotActive(toggle);
localStorage.setItem(`${userContext.databaseAccount?.id}-queryCopilotToggleStatus`, toggle.toString());
};
return (
<Stack className="tab-pane" style={{ padding: 24, width: "100%" }}>
<Stack className="tab-pane" style={{ width: "100%" }}>
<div style={isGeneratingQuery ? { height: "100%" } : { overflowY: "auto", height: "100%" }}>
<Stack horizontal verticalAlign="center">
<Image src={CopilotIcon} style={{ width: 24, height: 24 }} />
<Text style={{ marginLeft: 8, fontWeight: 600, fontSize: 16 }}>Copilot</Text>
</Stack>
<Stack horizontal verticalAlign="center" style={{ marginTop: 16, width: "100%", position: "relative" }}>
<TextField
id="naturalLanguageInput"
value={userPrompt}
onChange={handleUserPromptChange}
onClick={() => {
inputEdited.current = true;
setShowSamplePrompts(true);
}}
onKeyDown={(e) => {
if (e.key === "Enter") {
inputEdited.current = true;
startGenerateQueryProcess();
}
}}
style={{ lineHeight: 30 }}
styles={{ root: { width: "95%" } }}
disabled={isGeneratingQuery}
autoComplete="off"
/>
{copilotTeachingBubbleVisible && (
<TeachingBubble
calloutProps={{ directionalHint: DirectionalHint.bottomCenter }}
target="#naturalLanguageInput"
hasCloseButton={true}
closeButtonAriaLabel="Close"
onDismiss={toggleCopilotTeachingBubbleVisible}
hasSmallHeadline={true}
headline="Write a prompt"
>
Write a prompt here and Copilot will generate the query for you. You can also choose from our{" "}
<Link
onClick={() => {
setShowSamplePrompts(true);
toggleCopilotTeachingBubbleVisible();
}}
style={{ color: "white", fontWeight: 600 }}
>
sample prompts
</Link>{" "}
or write your own query
</TeachingBubble>
)}
<IconButton
iconProps={{ iconName: "Send" }}
disabled={isGeneratingQuery || !userPrompt.trim()}
style={{ marginLeft: 8 }}
onClick={() => startGenerateQueryProcess()}
/>
{isGeneratingQuery && <Spinner style={{ marginLeft: 8 }} />}
{showSamplePrompts && (
<Callout
styles={{ root: { minWidth: 400 } }}
target="#naturalLanguageInput"
isBeakVisible={false}
onDismiss={() => setShowSamplePrompts(false)}
directionalHintFixed={true}
directionalHint={DirectionalHint.bottomLeftEdge}
alignTargetEdge={true}
gapSpace={4}
>
<Stack>
{filteredHistories?.length > 0 && (
<Stack>
<Text
style={{
width: "100%",
fontSize: 14,
fontWeight: 600,
color: "#0078D4",
marginLeft: 16,
padding: "4px 0",
}}
>
Recent
</Text>
{filteredHistories.map((history, i) => (
<DefaultButton
key={i}
onClick={() => {
setUserPrompt(history);
setShowSamplePrompts(false);
inputEdited.current = true;
}}
onRenderIcon={() => <Image src={RecentIcon} />}
styles={promptStyles}
>
{history}
</DefaultButton>
))}
</Stack>
)}
{filteredSuggestedPrompts?.length > 0 && (
<Stack>
<Text
style={{
width: "100%",
fontSize: 14,
fontWeight: 600,
color: "#0078D4",
marginLeft: 16,
padding: "4px 0",
}}
>
Suggested Prompts
</Text>
{filteredSuggestedPrompts.map((prompt) => (
<DefaultButton
key={prompt.id}
onClick={() => {
setUserPrompt(prompt.text);
setShowSamplePrompts(false);
inputEdited.current = true;
}}
onRenderIcon={() => <Image src={HintIcon} />}
styles={promptStyles}
>
{prompt.text}
</DefaultButton>
))}
</Stack>
)}
{(filteredHistories?.length > 0 || filteredSuggestedPrompts?.length > 0) && (
<Stack>
<Separator
styles={{
root: {
selectors: { "::before": { background: "#E1DFDD" } },
padding: 0,
},
}}
/>
<Text
style={{
width: "100%",
fontSize: 14,
marginLeft: 16,
padding: "4px 0",
}}
>
Learn about{" "}
<Link target="_blank" href="https://aka.ms/cdb-copilot-writing">
writing effective prompts
</Link>
</Text>
</Stack>
)}
</Stack>
</Callout>
)}
</Stack>
<Stack style={{ marginTop: 8, marginBottom: 24 }}>
<Text style={{ fontSize: 12 }}>
AI-generated content can have mistakes. Make sure it&apos;s accurate and appropriate before using it.{" "}
<Link href="https://aka.ms/cdb-copilot-preview-terms" target="_blank">
Read preview terms
</Link>
{showErrorMessageBar && (
<MessageBar messageBarType={MessageBarType.error}>
We ran into an error and were not able to execute query.
</MessageBar>
)}
{showInvalidQueryMessageBar && (
<MessageBar
messageBarType={MessageBarType.info}
styles={{ root: { backgroundColor: "#F0F6FF" }, icon: { color: "#015CDA" } }}
>
We were unable to generate a query based upon the prompt provided. Please modify the prompt and try
again. For examples of how to write a good prompt, please read
<Link href="https://aka.ms/cdb-copilot-writing" target="_blank">
this article.
</Link>{" "}
Our content guidelines can be found
<Link href="https://aka.ms/cdb-query-copilot" target="_blank">
here.
</Link>
</MessageBar>
)}
</Text>
</Stack>
{showFeedbackBar && (
<Stack style={{ backgroundColor: "#FFF8F0", padding: "2px 8px" }} horizontal verticalAlign="center">
<Text style={{ fontWeight: 600, fontSize: 12 }}>Provide feedback on the query generated</Text>
{showCallout && !hideFeedbackModalForLikedQueries && (
<Callout
style={{ padding: 8 }}
target="#likeBtn"
onDismiss={() => {
setShowCallout(false);
SubmitFeedback({
params: {
generatedQuery: generatedQuery,
likeQuery: likeQuery,
description: "",
userPrompt: userPrompt,
},
explorer: explorer,
});
}}
directionalHint={DirectionalHint.topCenter}
>
<Text>
Thank you. Need to give{" "}
<Link
onClick={() => {
setShowCallout(false);
useQueryCopilot.getState().openFeedbackModal(generatedQuery, true, userPrompt);
}}
>
more feedback?
</Link>
</Text>
</Callout>
)}
<IconButton
id="likeBtn"
style={{ marginLeft: 20 }}
iconProps={{ iconName: likeQuery === true ? "LikeSolid" : "Like" }}
onClick={() => {
setShowCallout(!likeQuery);
setLikeQuery(!likeQuery);
if (dislikeQuery) {
setDislikeQuery(!dislikeQuery);
}
}}
/>
<IconButton
style={{ margin: "0 10px" }}
iconProps={{ iconName: dislikeQuery === true ? "DislikeSolid" : "Dislike" }}
onClick={() => {
if (!dislikeQuery) {
useQueryCopilot.getState().openFeedbackModal(generatedQuery, false, userPrompt);
setLikeQuery(false);
}
setDislikeQuery(!dislikeQuery);
setShowCallout(false);
}}
/>
<Separator vertical style={{ color: "#EDEBE9" }} />
<CommandBarButton
onClick={copyGeneratedCode}
iconProps={{ iconName: "Copy" }}
style={{ margin: "0 10px", backgroundColor: "#FFF8F0", transition: "background-color 0.3s ease" }}
>
Copy code
</CommandBarButton>
<CommandBarButton
onClick={() => {
setShowDeletePopup(true);
}}
iconProps={{ iconName: "Delete" }}
style={{ margin: "0 10px", backgroundColor: "#FFF8F0", transition: "background-color 0.3s ease" }}
>
Delete code
</CommandBarButton>
</Stack>
{copilotActive && (
<QueryCopilotPromptbar explorer={explorer} toggleCopilot={toggleCopilot}></QueryCopilotPromptbar>
)}
<Stack className="tabPaneContentContainer">
<SplitterLayout vertical={true} primaryIndex={0} primaryMinSize={100} secondaryMinSize={200}>
<SplitterLayout percentage={true} vertical={true} primaryIndex={0} primaryMinSize={30} secondaryMinSize={70}>
<EditorReact
language={"sql"}
content={query}
@@ -592,18 +106,6 @@ export const QueryCopilotTab: React.FC<QueryCopilotProps> = ({ explorer }: Query
<QueryCopilotResults />
</SplitterLayout>
</Stack>
<WelcomeModal visible={localStorage.getItem("hideWelcomeModal") !== "true"} />
{isSamplePromptsOpen && <SamplePrompts sampleProps={sampleProps} />}
{query !== "" && query.trim().length !== 0 && (
<DeletePopup
showDeletePopup={showDeletePopup}
setShowDeletePopup={setShowDeletePopup}
setQuery={setQuery}
clearFeedback={clearFeedback}
showFeedbackBar={setShowFeedbackBar}
/>
)}
<CopyPopup showCopyPopup={showCopyPopup} setShowCopyPopup={setshowCopyPopup} />
</div>
</Stack>
);

View File

@@ -21,6 +21,7 @@ import { userContext } from "UserContext";
import { queryPagesUntilContentPresent } from "Utils/QueryUtils";
import { useQueryCopilot } from "hooks/useQueryCopilot";
import { useTabs } from "hooks/useTabs";
import * as StringUtility from "../../../Shared/StringUtility";
export const SendQueryRequest = async ({
userPrompt,
@@ -184,15 +185,20 @@ export const QueryDocumentsPerPage = async (
correlationId: useQueryCopilot.getState().correlationId,
});
} catch (error) {
const isCopilotActive = StringUtility.toBoolean(
localStorage.getItem(`${userContext.databaseAccount?.id}-queryCopilotToggleStatus`),
);
const errorMessage = getErrorMessage(error);
traceFailure(Action.ExecuteQueryGeneratedFromQueryCopilot, {
correlationId: useQueryCopilot.getState().correlationId,
errorMessage: errorMessage,
});
useQueryCopilot.getState().setErrorMessage(errorMessage);
handleError(errorMessage, "executeQueryCopilotTab");
useTabs.getState().setIsQueryErrorThrown(true);
useQueryCopilot.getState().setShowErrorMessageBar(true);
if (isCopilotActive) {
useQueryCopilot.getState().setErrorMessage(errorMessage);
useQueryCopilot.getState().setShowErrorMessageBar(true);
}
} finally {
useQueryCopilot.getState().setIsExecuting(false);
useTabs.getState().setIsTabExecuting(false);

View File

@@ -5,7 +5,6 @@ exports[`Query copilot tab snapshot test should render with initial input 1`] =
className="tab-pane"
style={
Object {
"padding": 24,
"width": "100%",
}
}
@@ -18,103 +17,37 @@ exports[`Query copilot tab snapshot test should render with initial input 1`] =
}
}
>
<Stack
horizontal={true}
verticalAlign="center"
>
<Image
src={Object {}}
style={
Object {
"height": 24,
"width": 24,
}
}
/>
<Text
style={
Object {
"fontSize": 16,
"fontWeight": 600,
"marginLeft": 8,
}
}
>
Copilot
</Text>
</Stack>
<Stack
horizontal={true}
style={
Object {
"marginTop": 16,
"position": "relative",
"width": "100%",
}
}
verticalAlign="center"
>
<StyledTextFieldBase
autoComplete="off"
disabled={false}
id="naturalLanguageInput"
onChange={[Function]}
onClick={[Function]}
onKeyDown={[Function]}
style={
Object {
"lineHeight": 30,
}
}
styles={
Object {
"root": Object {
"width": "95%",
<QueryCopilotPromptbar
explorer={
Explorer {
"_isInitializingNotebooks": false,
"_resetNotebookWorkspace": [Function],
"isFixedCollectionWithSharedThroughputSupported": [Function],
"isTabsContentExpanded": [Function],
"onRefreshDatabasesKeyPress": [Function],
"onRefreshResourcesClick": [Function],
"phoenixClient": PhoenixClient {
"armResourceId": undefined,
"retryOptions": Object {
"maxTimeout": 5000,
"minTimeout": 5000,
"retries": 3,
},
}
}
value=""
/>
<CustomizedIconButton
disabled={true}
iconProps={
Object {
"iconName": "Send",
}
}
onClick={[Function]}
style={
Object {
"marginLeft": 8,
}
}
/>
</Stack>
<Stack
style={
Object {
"marginBottom": 24,
"marginTop": 8,
},
"provideFeedbackEmail": [Function],
"queriesClient": QueriesClient {
"container": [Circular],
},
"refreshNotebookList": [Function],
"resourceTree": ResourceTreeAdapter {
"container": [Circular],
"copyNotebook": [Function],
"parameters": [Function],
},
}
}
>
<Text
style={
Object {
"fontSize": 12,
}
}
>
AI-generated content can have mistakes. Make sure it's accurate and appropriate before using it.
<StyledLinkBase
href="https://aka.ms/cdb-copilot-preview-terms"
target="_blank"
>
Read preview terms
</StyledLinkBase>
</Text>
</Stack>
toggleCopilot={[Function]}
/>
<Stack
className="tabPaneContentContainer"
>
@@ -123,10 +56,10 @@ exports[`Query copilot tab snapshot test should render with initial input 1`] =
onDragEnd={null}
onDragStart={null}
onSecondaryPaneSizeChange={null}
percentage={false}
percentage={true}
primaryIndex={0}
primaryMinSize={100}
secondaryMinSize={200}
primaryMinSize={30}
secondaryMinSize={70}
vertical={true}
>
<EditorReact
@@ -142,20 +75,6 @@ exports[`Query copilot tab snapshot test should render with initial input 1`] =
<QueryCopilotResults />
</t>
</Stack>
<WelcomeModal
visible={true}
/>
<DeletePopup
clearFeedback={[Function]}
setQuery={[Function]}
setShowDeletePopup={[Function]}
showDeletePopup={false}
showFeedbackBar={[Function]}
/>
<CopyPopup
setShowCopyPopup={[Function]}
showCopyPopup={false}
/>
</div>
</Stack>
`;

View File

@@ -346,6 +346,22 @@ export default class DocumentsTab extends TabsBase {
return true;
}
/**
* Query first page of documents
* Select and query first document and display content
*/
private async autoPopulateContent() {
// reset iterator
this._documentsIterator = this.createIterator();
// load documents
await this.loadNextPage();
// Select first document and load content
if (this.documentIds().length > 0) {
this.documentIds()[0].click();
}
}
public onShowFilterClick(): Q.Promise<any> {
this.isFilterCreated(true);
this.isFilterExpanded(true);
@@ -380,10 +396,7 @@ export default class DocumentsTab extends TabsBase {
this.documentIds([]);
try {
// reset iterator
this._documentsIterator = this.createIterator();
// load documents
await this.loadNextPage();
await this.autoPopulateContent();
// collapse filter
this.appliedFilter(this.filterContent());
this.isFilterExpanded(false);
@@ -406,6 +419,11 @@ export default class DocumentsTab extends TabsBase {
this.queryAbortController.abort();
}
/**
* TODO Doesn't seem to be used: remove?
* @param clickedDocumentId
* @returns
*/
public onDocumentIdClick(clickedDocumentId: DocumentId): Q.Promise<any> {
if (this.editorState() !== ViewModels.DocumentExplorerState.noDocumentSelected) {
return Q();
@@ -624,8 +642,7 @@ export default class DocumentsTab extends TabsBase {
if (!this._documentsIterator) {
try {
this._documentsIterator = this.createIterator();
await this.loadNextPage();
await this.autoPopulateContent();
} catch (error) {
if (this.onLoadStartKey != null && this.onLoadStartKey != undefined) {
TelemetryProcessor.traceFailure(

View File

@@ -1,6 +1,8 @@
jest.mock("../../../hooks/useDirectories");
import "@testing-library/jest-dom";
import { fireEvent, render, screen } from "@testing-library/react";
import { extractFeatures } from "Platform/Hosted/extractFeatures";
import { updateUserContext, userContext } from "UserContext";
import React from "react";
import { ConnectExplorer } from "./ConnectExplorer";
@@ -16,3 +18,24 @@ it("shows the connect form", () => {
fireEvent.click(screen.getByText("Connect to your account with connection string"));
expect(screen.queryByPlaceholderText("Please enter a connection string")).toBeDefined();
});
it("hides the connection string link when feature.disableConnectionStringLogin is true", () => {
const connectionString = "fakeConnectionString";
const login = jest.fn();
const setConnectionString = jest.fn();
const setEncryptedToken = jest.fn();
const setAuthType = jest.fn();
const oldFeatures = userContext.features;
const params = new URLSearchParams({
"feature.disableConnectionStringLogin": "true",
});
const testFeatures = extractFeatures(params);
updateUserContext({ features: testFeatures });
render(<ConnectExplorer {...{ login, setEncryptedToken, setAuthType, connectionString, setConnectionString }} />);
expect(screen.queryByPlaceholderText("Connect to your account with connection string")).toBeNull();
updateUserContext({ features: oldFeatures });
});

View File

@@ -1,4 +1,5 @@
import { useBoolean } from "@fluentui/react-hooks";
import { userContext } from "UserContext";
import * as React from "react";
import ConnectImage from "../../../../images/HdeConnectCosmosDB.svg";
import ErrorImage from "../../../../images/error.svg";
@@ -37,6 +38,7 @@ export const ConnectExplorer: React.FunctionComponent<Props> = ({
setConnectionString,
}: Props) => {
const [isFormVisible, { setTrue: showForm }] = useBoolean(false);
const enableConnectionStringLogin = !userContext.features.disableConnectionStringLogin;
return (
<div id="connectExplorer" className="connectExplorerContainer" style={{ display: "flex" }}>
@@ -46,7 +48,7 @@ export const ConnectExplorer: React.FunctionComponent<Props> = ({
<img src={ConnectImage} alt="Azure Cosmos DB" />
</p>
<p className="welcomeText">Welcome to Azure Cosmos DB</p>
{isFormVisible ? (
{isFormVisible && enableConnectionStringLogin ? (
<form
id="connectWithConnectionString"
onSubmit={async (event) => {
@@ -89,9 +91,11 @@ export const ConnectExplorer: React.FunctionComponent<Props> = ({
) : (
<div id="connectWithAad">
<input className="filterbtnstyle" type="button" value="Sign In" onClick={login} />
<p className="switchConnectTypeText" onClick={showForm}>
Connect to your account with connection string
</p>
{enableConnectionStringLogin && (
<p className="switchConnectTypeText" onClick={showForm}>
Connect to your account with connection string
</p>
)}
</div>
)}
</div>

View File

@@ -41,6 +41,7 @@ export type Features = {
readonly enableCopilotFullSchema: boolean;
readonly copilotChatFixedMonacoEditorHeight: boolean;
readonly enablePriorityBasedExecution: boolean;
readonly disableConnectionStringLogin: boolean;
// can be set via both flight and feature flag
autoscaleDefault: boolean;
@@ -114,6 +115,7 @@ export function extractFeatures(given = new URLSearchParams(window.location.sear
enableCopilotFullSchema: "true" === get("enablecopilotfullschema", "true"),
copilotChatFixedMonacoEditorHeight: "true" === get("copilotchatfixedmonacoeditorheight"),
enablePriorityBasedExecution: "true" === get("enableprioritybasedexecution"),
disableConnectionStringLogin: "true" === get("disableconnectionstringlogin"),
};
}

View File

@@ -2,15 +2,13 @@ import { createUri } from "Common/UrlUtility";
import { FabricMessage } from "Contracts/FabricContract";
import Explorer from "Explorer/Explorer";
import { useSelectedNode } from "Explorer/useSelectedNode";
import { fetchEncryptedToken } from "Platform/Hosted/Components/ConnectExplorer";
import { getNetworkSettingsWarningMessage } from "Utils/NetworkUtility";
import { fetchAccessData } from "hooks/usePortalAccessToken";
import { ReactTabKind, useTabs } from "hooks/useTabs";
import { useEffect, useState } from "react";
import { AuthType } from "../AuthType";
import { AccountKind, Flights } from "../Common/Constants";
import { normalizeArmEndpoint } from "../Common/EnvironmentUtility";
import { sendMessage, sendReadyMessage } from "../Common/MessageHandler";
import { handleCachedDataMessage, sendMessage, sendReadyMessage } from "../Common/MessageHandler";
import { Platform, configContext, updateConfigContext } from "../ConfigContext";
import { ActionType, DataExplorerAction, TabKind } from "../Contracts/ActionContracts";
import { MessageTypes } from "../Contracts/ExplorerContracts";
@@ -107,23 +105,7 @@ async function configureFabric(): Promise<Explorer> {
switch (data.type) {
case "initialize": {
// TODO For now, retrieve info from session storage. Replace with info injected into Data Explorer
const connectionString = data.connectionString ?? sessionStorage.getItem("connectionString");
if (!connectionString) {
console.error("No connection string found in session storage");
return undefined;
}
const encryptedToken = await fetchEncryptedToken(connectionString);
// TODO Duplicated from useTokenMetadata
const encryptedTokenMetadata = await fetchAccessData(encryptedToken);
const hostedConfig: EncryptedToken = {
authType: AuthType.EncryptedToken,
encryptedToken,
encryptedTokenMetadata,
};
explorer = await configureWithEncryptedToken(hostedConfig);
explorer = await configureWithFabric(data.message.endpoint);
resolve(explorer);
break;
}
@@ -166,6 +148,10 @@ async function configureFabric(): Promise<Explorer> {
break;
}
case "authorizationToken": {
handleCachedDataMessage(data);
break;
}
default:
console.error(`Unknown Fabric message type: ${JSON.stringify(data)}`);
break;
@@ -315,6 +301,25 @@ function configureHostedWithResourceToken(config: ResourceToken): Explorer {
return explorer;
}
function configureWithFabric(documentEndpoint: string): Explorer {
updateUserContext({
authType: AuthType.ConnectionString,
databaseAccount: {
id: "",
location: "",
type: "",
name: "Mounted",
kind: AccountKind.Default,
properties: {
documentEndpoint,
},
},
});
const explorer = new Explorer();
setTimeout(() => explorer.refreshAllDatabases(), 0);
return explorer;
}
function configureWithEncryptedToken(config: EncryptedToken): Explorer {
const apiExperience = DefaultExperienceUtility.getDefaultExperienceFromApiKind(config.encryptedTokenMetadata.apiKind);
updateUserContext({

View File

@@ -128,7 +128,7 @@ export const useQueryCopilot: QueryCopilotStore = create((set) => ({
openFeedbackModal: (generatedQuery: string, likeQuery: boolean, userPrompt: string) =>
set({ generatedQuery, likeQuery, userPrompt, showFeedbackModal: true }),
closeFeedbackModal: () => set({ generatedQuery: "", likeQuery: false, userPrompt: "", showFeedbackModal: false }),
closeFeedbackModal: () => set({ showFeedbackModal: false }),
setHideFeedbackModalForLikedQueries: (hideFeedbackModalForLikedQueries: boolean) =>
set({ hideFeedbackModalForLikedQueries }),
refreshCorrelationId: () => set({ correlationId: guid() }),

View File

@@ -6,6 +6,15 @@
<mimeMap fileExtension="woff" mimeType="application/font-woff" />
</staticContent>
<rewrite>
<rules>
<rule name="AAD-Redirect" stopProcessing="true">
<match url="^aad" ignoreCase="true"/>
<conditions>
<add input="{HTTP_HOST}" pattern="^cosmos.azure.com" />
</conditions>
<action type="Redirect" url="/?feature.enableAadDataPlane=true&amp;feature.disableConnectionStringLogin=true" redirectType="Permanent" />
</rule>
</rules>
<outboundRules>
<rule name="Strict-Transport-Security" enabled="true">
<match serverVariable="RESPONSE_Strict_Transport_Security" pattern=".*" />