Compare commits
30 Commits
update_cop
...
users/sind
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b432bd38eb | ||
|
|
ecb39dd99f | ||
|
|
51782f1157 | ||
|
|
425e375d50 | ||
|
|
55846b98bd | ||
|
|
3d02f07262 | ||
|
|
449118a1bf | ||
|
|
8405dbe8da | ||
|
|
19100ec437 | ||
|
|
ebd40cb9b0 | ||
|
|
986dbe7d54 | ||
|
|
143f7d8f2c | ||
|
|
b646f9f4cb | ||
|
|
0f52db73e7 | ||
|
|
9c1b9e6ff6 | ||
|
|
19041ffedd | ||
|
|
ceb66ed5b8 | ||
|
|
96b88ac344 | ||
|
|
bcedf0a29f | ||
|
|
67133017ce | ||
|
|
b5d7ab0a30 | ||
|
|
6dba4937ce | ||
|
|
1a3ca94efb | ||
|
|
9f7783f3f9 | ||
|
|
daa26d289b | ||
|
|
879cb08949 | ||
|
|
92f43c28a7 | ||
|
|
5f0c7bcea2 | ||
|
|
fa55d528ad | ||
|
|
c873fed7aa |
@@ -144,4 +144,5 @@ src/Explorer/Notebook/NotebookRenderer/decorators/kbd-shortcuts/index.tsx
|
||||
src/Explorer/Notebook/temp/inputs/connected-editors/codemirror.tsx
|
||||
src/Explorer/Tree/ResourceTreeAdapter.tsx
|
||||
__mocks__/monaco-editor.ts
|
||||
src/Explorer/Tree/ResourceTree.tsx
|
||||
src/Explorer/Tree/ResourceTree.tsx
|
||||
src/Utils/PriorityBasedExecutionUtils.ts
|
||||
@@ -1,3 +1,3 @@
|
||||
<svg width="10" height="14" viewBox="0 0 10 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg width="12" height="14" viewBox="0 0 12 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10 2.5C10 3.88071 7.76142 5 5 5C2.23858 5 0 3.88071 0 2.5C0 1.11929 2.23858 0 5 0C7.76142 0 10 1.11929 10 2.5ZM0 11.5V4.487C1.057 5.413 2.864 6 5 6C7.136 6 8.943 5.413 10 4.487V11.5C10 12.925 7.851 14 5 14C2.149 14 0 12.925 0 11.5Z" fill="#0078D4"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 363 B After Width: | Height: | Size: 363 B |
@@ -1,3 +1,3 @@
|
||||
<svg width="13" height="17" viewBox="0 0 13 17" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg width="12" height="14" viewBox="0 0 12 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4.21207 0C3.73772 0 3.32085 0.314446 3.19054 0.770537L0.940937 8.64415C0.747029 9.32283 1.25663 9.99841 1.96246 9.99841H3.22974L2.06002 14.6773C1.79611 15.7329 3.10089 16.4551 3.85526 15.6726L12.5318 6.81506L12.5354 6.81137C13.1762 6.1436 12.7152 5 11.7688 5H9.20509L10.4665 1.40582L10.469 1.39836C10.6983 0.710426 10.1863 0 9.46114 0H4.21207Z" fill="#0078D4"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 475 B After Width: | Height: | Size: 475 B |
34
images/CopilotSidebarLogo.svg
Normal file
@@ -0,0 +1,34 @@
|
||||
<svg width="20" height="22" viewBox="0 0 20 22" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.79997 1.36139L9.59997 1.54445V6.72957L6.71997 7.39864L5.75998 8.71588V10.7866C5.75998 12.178 6.51265 13.4605 7.72739 14.139L10.4421 15.6554L5.75997 18.1318L3.81352 18.4092L1.96738 17.378C0.75264 16.6995 -3.05176e-05 15.417 -3.05176e-05 14.0256V7.97332C-3.05176e-05 6.58155 0.753043 5.29871 1.9683 4.62034L7.3904 1.59367L7.38722 1.59591L7.79997 1.36139Z" fill="url(#paint0_radial_1157_162756)"/>
|
||||
<path d="M7.79997 1.36139L9.59997 1.54445V6.72957L6.71997 7.39864L5.75998 8.71588V10.7866C5.75998 12.178 6.51265 13.4605 7.72739 14.139L10.4421 15.6554L5.75997 18.1318L3.81352 18.4092L1.96738 17.378C0.75264 16.6995 -3.05176e-05 15.417 -3.05176e-05 14.0256V7.97332C-3.05176e-05 6.58155 0.753043 5.29871 1.9683 4.62034L7.3904 1.59367L7.38722 1.59591L7.79997 1.36139Z" fill="url(#paint1_linear_1157_162756)"/>
|
||||
<path d="M13.4397 8.11841L18.2397 10.9984L19.1997 11.9584V14.025C19.1997 15.4164 18.447 16.6989 17.2323 17.3774L11.4723 20.5948C10.3085 21.2448 8.89088 21.2448 7.72711 20.5948L1.96711 17.3774C1.85515 17.3149 1.74713 17.2472 1.64325 17.1748L1.96708 17.3557C3.13085 18.0057 4.54849 18.0057 5.71226 17.3557L11.4723 14.1383C12.687 13.4598 13.4397 12.1773 13.4397 10.7859V8.11841Z" fill="url(#paint2_radial_1157_162756)"/>
|
||||
<path d="M13.4397 8.11841L18.2397 10.9984L19.1997 11.9584V14.025C19.1997 15.4164 18.447 16.6989 17.2323 17.3774L11.4723 20.5948C10.3085 21.2448 8.89088 21.2448 7.72711 20.5948L1.96711 17.3774C1.85515 17.3149 1.74713 17.2472 1.64325 17.1748L1.96708 17.3557C3.13085 18.0057 4.54849 18.0057 5.71226 17.3557L11.4723 14.1383C12.687 13.4598 13.4397 12.1773 13.4397 10.7859V8.11841Z" fill="url(#paint3_linear_1157_162756)"/>
|
||||
<path d="M17.2316 4.62014L11.4716 1.40484C10.3083 0.755475 8.89151 0.755475 7.72821 1.40484L7.38921 1.59407C6.37448 2.30824 5.75989 3.47665 5.75989 4.73397V8.74548L7.72821 7.64674C8.89151 6.99738 10.3083 6.99738 11.4716 7.64674L17.2316 10.862C18.4192 11.525 19.1654 12.7652 19.1987 14.1202C19.1995 14.0886 19.1999 14.057 19.1999 14.0254V7.97311C19.1999 6.58134 18.4468 5.29851 17.2316 4.62014Z" fill="url(#paint4_radial_1157_162756)"/>
|
||||
<path d="M17.2316 4.62014L11.4716 1.40484C10.3083 0.755475 8.89151 0.755475 7.72821 1.40484L7.38921 1.59407C6.37448 2.30824 5.75989 3.47665 5.75989 4.73397V8.74548L7.72821 7.64674C8.89151 6.99738 10.3083 6.99738 11.4716 7.64674L17.2316 10.862C18.4192 11.525 19.1654 12.7652 19.1987 14.1202C19.1995 14.0886 19.1999 14.057 19.1999 14.0254V7.97311C19.1999 6.58134 18.4468 5.29851 17.2316 4.62014Z" fill="url(#paint5_linear_1157_162756)"/>
|
||||
<defs>
|
||||
<radialGradient id="paint0_radial_1157_162756" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(8.1032 6.84805) rotate(112.544) scale(14.0711 11.584)">
|
||||
<stop offset="0.206732" stop-color="#4995D0"/>
|
||||
<stop offset="0.875628" stop-color="#0078D4"/>
|
||||
</radialGradient>
|
||||
<linearGradient id="paint1_linear_1157_162756" x1="6.63744" y1="16.8883" x2="5.87214" y2="15.464" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.9999" stop-color="#0078D4"/>
|
||||
<stop offset="1" stop-color="#0078D4" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<radialGradient id="paint2_radial_1157_162756" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(4.13968 15.6306) rotate(-3.9995) scale(14.7718 9.27561)">
|
||||
<stop offset="0.140029" stop-color="#80C8FF"/>
|
||||
<stop offset="0.952721" stop-color="#0078D4"/>
|
||||
</radialGradient>
|
||||
<linearGradient id="paint3_linear_1157_162756" x1="16.1362" y1="10.2483" x2="15.3298" y2="11.6531" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.9999" stop-color="#3F8AC3"/>
|
||||
<stop offset="1" stop-color="#8C66BA" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<radialGradient id="paint4_radial_1157_162756" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(15.9793 10.7505) rotate(-138.33) scale(13.6809 7.80282)">
|
||||
<stop stop-color="#7BC6FF"/>
|
||||
<stop offset="0.839255" stop-color="#0078D4"/>
|
||||
</radialGradient>
|
||||
<linearGradient id="paint5_linear_1157_162756" x1="5.75989" y1="6.14584" x2="7.32799" y2="6.14584" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.9999" stop-color="#0078D4"/>
|
||||
<stop offset="1" stop-color="#436DCD" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.2 KiB |
29
images/CopilotSidebarWelcomeIllustration.svg
Normal file
@@ -0,0 +1,29 @@
|
||||
<svg width="121" height="94" viewBox="0 0 121 94" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path opacity="0.3" d="M3.25029 80.7474H97.1699C97.8805 80.7474 98.5648 80.471 99.078 79.9578C99.5913 79.4446 99.8676 78.7603 99.8676 78.0497V13.8575C99.8676 12.3704 98.3148 11.0018 96.2224 11.0018L4.53992 12.3968C4.18461 12.3968 3.84247 12.4626 3.51348 12.5942C3.18449 12.7389 2.89498 12.9231 2.64495 13.1863C2.39492 13.4364 2.19752 13.7259 2.05277 14.0549C1.90801 14.3838 1.85538 14.7392 1.85538 15.0813L0.552582 78.076C0.552582 78.7866 0.82893 79.4709 1.34215 79.971C1.85537 80.471 2.53967 80.7474 3.25029 80.7474Z" fill="#1F1D20"/>
|
||||
<path d="M96.9332 9.80411H2.67139C1.19752 9.80411 0 11.0016 0 12.4755V76.7993C0 78.2732 1.19752 79.4707 2.67139 79.4707H96.9463C98.4202 79.4707 99.6177 78.2732 99.6177 76.7993V12.4755C99.6045 11.0016 98.407 9.80411 96.9332 9.80411Z" fill="url(#paint0_linear_1157_163007)"/>
|
||||
<path d="M95.6963 13.7121H50.1379V74.904H95.6963V13.7121Z" fill="white"/>
|
||||
<path d="M50.7829 13.7121H5.22461V74.904H50.7829V13.7121Z" fill="#CDCDD0"/>
|
||||
<path d="M14.3309 0V60.6918C14.3309 60.6918 26.4903 59.4811 36.3468 64.4291C46.2165 69.3771 50.7829 74.8514 50.7829 74.8514V14.1465C50.7829 14.1465 45.0979 6.90876 36.3468 4.23737C29.2275 2.05289 14.3309 0 14.3309 0Z" fill="#EAEAEA"/>
|
||||
<path d="M59.1387 42.9135L56.2699 41.6633L53.388 42.9135V9.80411H59.1387V42.9135Z" fill="url(#paint1_linear_1157_163007)"/>
|
||||
<path opacity="0.15" d="M86.7212 25.6883H66.7713C65.9028 25.6883 65.2053 26.3989 65.2053 27.2543C65.2053 28.1228 65.9159 28.8202 66.7713 28.8202H86.7212C87.5897 28.8202 88.2872 28.1096 88.2872 27.2543C88.3003 26.3857 87.5897 25.6883 86.7212 25.6883Z" fill="#1F1D20"/>
|
||||
<path opacity="0.15" d="M86.7212 32.5045H66.7713C65.9028 32.5045 65.2053 33.2151 65.2053 34.0705C65.2053 34.939 65.9159 35.6365 66.7713 35.6365H86.7212C87.5897 35.6365 88.2872 34.9258 88.2872 34.0705C88.3003 33.2019 87.5897 32.5045 86.7212 32.5045Z" fill="#1F1D20"/>
|
||||
<path opacity="0.15" fill-rule="evenodd" clip-rule="evenodd" d="M66.7715 39.3336H86.695C87.5636 39.3336 88.261 40.0442 88.261 40.8995C88.261 41.7681 87.5504 42.4655 86.695 42.4655H66.7715C65.903 42.4655 65.2055 41.7549 65.2055 40.8995C65.1923 40.0442 65.8898 39.3336 66.7715 39.3336Z" fill="#1F1D20"/>
|
||||
<path d="M86.7212 25.1614H66.7713C65.9028 25.1614 65.2053 25.872 65.2053 26.7273C65.2053 27.5959 65.9159 28.2933 66.7713 28.2933H86.7212C87.5897 28.2933 88.2872 27.5827 88.2872 26.7273C88.3003 25.8588 87.5897 25.1614 86.7212 25.1614Z" fill="#50E6FF"/>
|
||||
<path d="M86.7212 31.9917H66.7713C65.9028 31.9917 65.2053 32.7023 65.2053 33.5577C65.2053 34.4262 65.9159 35.1237 66.7713 35.1237H86.7212C87.5897 35.1237 88.2872 34.4131 88.2872 33.5577C88.3003 32.6892 87.5897 31.9917 86.7212 31.9917Z" fill="#32B0E7"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M66.7715 38.808H86.695C87.5636 38.808 88.261 39.5186 88.261 40.3739C88.261 41.2425 87.5504 41.9399 86.695 41.9399H66.7715C65.903 41.9531 65.2055 41.2293 65.2055 40.3739C65.1923 39.5186 65.8898 38.808 66.7715 38.808Z" fill="#185A97"/>
|
||||
<path opacity="0.2" d="M120.295 60.2875H80.476V85.9653C80.476 87.0514 81.3515 87.9269 82.4375 87.9269H86.6156V94L92.733 87.9269H118.333C119.419 87.9269 120.295 87.0514 120.295 85.9653V60.2875Z" fill="#1F1D21"/>
|
||||
<path d="M118.965 58.6804H81.8058C81.0744 58.6804 80.476 59.2788 80.476 60.0103V84.9898C80.476 85.7213 81.0744 86.3197 81.8058 86.3197H86.6156V92.3928L92.733 86.3197H118.965C119.696 86.3197 120.295 85.7213 120.295 84.9898V60.0103C120.295 59.2788 119.696 58.6804 118.965 58.6804Z" fill="#49C8EF"/>
|
||||
<path d="M106.564 67.8017H94.1846C93.697 67.8017 93.298 68.2006 93.298 68.6883C93.298 69.1759 93.697 69.5748 94.1846 69.5748H106.564C107.051 69.5748 107.45 69.1759 107.45 68.6883C107.45 68.2006 107.051 67.8017 106.564 67.8017Z" fill="#C3F1FF"/>
|
||||
<path d="M106.564 71.6913H94.1846C93.697 71.6913 93.298 72.0903 93.298 72.5779C93.298 73.0655 93.697 73.4645 94.1846 73.4645H106.564C107.051 73.4645 107.45 73.0655 107.45 72.5779C107.45 72.1014 107.051 71.6913 106.564 71.6913Z" fill="#C3F1FF"/>
|
||||
<path d="M106.564 75.6026H94.1846C93.697 75.6026 93.298 76.0016 93.298 76.4892C93.298 76.9768 93.697 77.3758 94.1846 77.3758H106.564C107.051 77.3758 107.45 76.9768 107.45 76.4892C107.45 75.9905 107.051 75.6026 106.564 75.6026Z" fill="#C3F1FF"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_1157_163007" x1="49.8133" y1="14.2868" x2="49.8133" y2="85.0398" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#007ED8"/>
|
||||
<stop offset="0.7065" stop-color="#002D4C"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_1157_163007" x1="36.5785" y1="-9.69372" x2="104.813" y2="115.29" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#007ED8"/>
|
||||
<stop offset="0.7065" stop-color="#002D4C"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.6 KiB |
3
images/CopilotTabIcon.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="20" height="22" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12.5849 1.28273C11.0125 0.262814 8.98746 0.262813 7.41509 1.28273L2.16509 4.68813C0.8149 5.56393 0 7.06384 0 8.6732V13.3268C0 14.9361 0.814898 16.4361 2.16509 17.3119L7.41509 20.7173C8.98746 21.7372 11.0125 21.7372 12.5849 20.7173L17.8349 17.3119C19.1851 16.4361 20 14.9361 20 13.3268V8.6732C20 7.06384 19.1851 5.56393 17.8349 4.68813L12.5849 1.28273ZM9.50097 2.05627C10.2759 1.93601 11.0848 2.09764 11.7686 2.54117L17.0186 5.94657C17.9424 6.54581 18.5 7.57205 18.5 8.6732V11.3955C18.287 11.1926 18.0465 11.0117 17.7802 10.8585L11.8647 7.4571C10.7104 6.79343 9.29086 6.79152 8.13486 7.45209L7.5 7.81487V4.41942C7.5 3.54181 8.01028 2.74428 8.80712 2.37651L9.50097 2.05627ZM17.8644 15.2257L17.7932 15.3503C17.5776 15.6214 17.3172 15.8597 17.0186 16.0534L11.7686 19.4588C10.6928 20.1567 9.30721 20.1567 8.23138 19.4588L5.86807 17.9259C6.11557 17.8361 6.35595 17.7193 6.58487 17.5754L12.2457 14.0172C13.3374 13.3309 14 12.1318 14 10.8423V10.4152L17.0324 12.1589C18.1077 12.7771 18.4798 14.1489 17.8644 15.2257ZM12.5 9.55272V10.8423C12.5 11.616 12.1025 12.3354 11.4474 12.7472L10.0303 13.6379L8.57078 12.7398C7.90535 12.3303 7.5 11.6049 7.5 10.8235V9.54249L8.87907 8.75445C9.57267 8.35811 10.4244 8.35926 11.1169 8.75746L12.5 9.55272ZM8.61445 14.5279L5.78662 16.3054C5.02045 16.787 4.04031 16.7627 3.29894 16.2437L2.5521 15.721C1.88767 15.1111 1.5 14.245 1.5 13.3268V8.6732C1.5 7.57205 2.05756 6.5458 2.98138 5.94657L6.0268 3.97116C6.00907 4.11873 6 4.26836 6 4.41942V10.8235C6 12.1258 6.67558 13.3348 7.78463 14.0173L8.61445 14.5279Z" fill="#0078D4"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
3
images/StopGenerating.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="14" height="13" viewBox="0 0 14 13" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3 0C1.61929 0 0.5 1.11929 0.5 2.5V9.5C0.5 10.8807 1.61929 12 3 12H4.75716C4.50353 11.6929 4.28261 11.3578 4.09971 11H3C2.17157 11 1.5 10.3284 1.5 9.5V2.5C1.5 1.67157 2.17157 1 3 1H10C10.8284 1 11.5 1.67157 11.5 2.5V3.59971C11.8578 3.78261 12.1929 4.00353 12.5 4.25716V2.5C12.5 1.11929 11.3807 0 10 0H3ZM9 13C11.4853 13 13.5 10.9853 13.5 8.5C13.5 6.01472 11.4853 4 9 4C6.51472 4 4.5 6.01472 4.5 8.5C4.5 10.9853 6.51472 13 9 13ZM10.8536 6.64645C11.0488 6.84171 11.0488 7.15829 10.8536 7.35355L9.70711 8.5L10.8536 9.64645C11.0488 9.84171 11.0488 10.1583 10.8536 10.3536C10.6583 10.5488 10.3417 10.5488 10.1464 10.3536L9 9.20711L7.85355 10.3536C7.65829 10.5488 7.34171 10.5488 7.14645 10.3536C6.95118 10.1583 6.95118 9.84171 7.14645 9.64645L8.29289 8.5L7.14645 7.35355C6.95118 7.15829 6.95118 6.84171 7.14645 6.64645C7.34171 6.45118 7.65829 6.45118 7.85355 6.64645L9 7.79289L10.1464 6.64645C10.3417 6.45118 10.6583 6.45118 10.8536 6.64645Z" fill="#0078D4"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
@@ -108,7 +108,7 @@ table.storage thead td {
|
||||
background-color: @BaseLight;
|
||||
/*[{datatable-header-background}]*/
|
||||
text-align: left;
|
||||
color: #808080;
|
||||
color: #333333;
|
||||
/*[{datatable-header-text}]*/
|
||||
outline: none;
|
||||
}
|
||||
|
||||
@@ -358,6 +358,7 @@ export enum ContainerStatusType {
|
||||
|
||||
export enum PoolIdType {
|
||||
DefaultPoolId = "default",
|
||||
QueryCopilot = "query-copilot",
|
||||
}
|
||||
|
||||
export const EmulatorMasterKey =
|
||||
@@ -429,6 +430,12 @@ export class JunoEndpoints {
|
||||
public static readonly Stage = "https://tools-staging.cosmos.azure.com";
|
||||
}
|
||||
|
||||
export class PriorityLevel {
|
||||
public static readonly High = "high";
|
||||
public static readonly Low = "low";
|
||||
public static readonly Default = "low";
|
||||
}
|
||||
|
||||
export const QueryCopilotSampleDatabaseId = "CopilotSampleDb";
|
||||
export const QueryCopilotSampleContainerId = "SampleContainer";
|
||||
|
||||
@@ -599,3 +606,69 @@ export const QueryCopilotSampleContainerSchema = {
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const ShortenedQueryCopilotSampleContainerSchema = {
|
||||
containerSchema: {
|
||||
product: {
|
||||
sampleData: {
|
||||
categoryName: "Components, Saddles",
|
||||
|
||||
name: "LL Road Seat/Saddle",
|
||||
|
||||
price: 27.12,
|
||||
|
||||
tags: [
|
||||
{
|
||||
id: "0573D684-9140-4DEE-89AF-4E4A90E65666",
|
||||
|
||||
name: "Tag-113",
|
||||
},
|
||||
|
||||
{
|
||||
id: "6C2F05C8-1E61-4912-BE1A-C67A378429BB",
|
||||
|
||||
name: "Tag-5",
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
schema: {
|
||||
properties: {
|
||||
categoryName: {
|
||||
type: "string",
|
||||
},
|
||||
|
||||
name: {
|
||||
type: "string",
|
||||
},
|
||||
|
||||
price: {
|
||||
type: "number",
|
||||
},
|
||||
|
||||
tags: {
|
||||
items: {
|
||||
properties: {
|
||||
id: {
|
||||
type: "string",
|
||||
},
|
||||
|
||||
name: {
|
||||
type: "string",
|
||||
},
|
||||
},
|
||||
|
||||
type: "object",
|
||||
},
|
||||
|
||||
type: "array",
|
||||
},
|
||||
},
|
||||
|
||||
type: "object",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
userPrompt: "find all products",
|
||||
};
|
||||
|
||||
@@ -4,6 +4,9 @@ import { userContext } from "../UserContext";
|
||||
import { logConsoleError } from "../Utils/NotificationConsoleUtils";
|
||||
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";
|
||||
|
||||
const _global = typeof self === "undefined" ? window : self;
|
||||
|
||||
@@ -105,6 +108,13 @@ export function client(): Cosmos.CosmosClient {
|
||||
if (configContext.PROXY_PATH !== undefined) {
|
||||
(options as any).plugins = [{ on: "request", plugin: requestPlugin }];
|
||||
}
|
||||
|
||||
if (userContext.features.enablePriorityBasedThrottling && userContext.apiType === "SQL") {
|
||||
const plugins = (options as any).plugins || [];
|
||||
plugins.push({ on: "request", plugin: PriorityBasedExecutionUtils.requestPlugin });
|
||||
(options as any).plugins = plugins;
|
||||
}
|
||||
|
||||
_client = new Cosmos.CosmosClient(options);
|
||||
return _client;
|
||||
}
|
||||
|
||||
@@ -39,7 +39,6 @@ export const EntityValue: FunctionComponent<TableEntityProps> = ({
|
||||
/>
|
||||
<TextField
|
||||
label={entityValueLabel && entityValueLabel}
|
||||
id="entityTimeId"
|
||||
autoFocus
|
||||
type="time"
|
||||
value={entityTimeValue}
|
||||
@@ -54,7 +53,6 @@ export const EntityValue: FunctionComponent<TableEntityProps> = ({
|
||||
<TextField
|
||||
label={entityValueLabel && entityValueLabel}
|
||||
className="addEntityTextField"
|
||||
id="entityValueId"
|
||||
disabled={isEntityValueDisable}
|
||||
type={entityValueType}
|
||||
placeholder={entityValuePlaceholder}
|
||||
|
||||
@@ -106,7 +106,6 @@ export const TableEntity: FunctionComponent<TableEntityProps> = ({
|
||||
<Stack horizontal tokens={sectionStackTokens}>
|
||||
<TextField
|
||||
label={entityPropertyLabel && entityPropertyLabel}
|
||||
id="entityPropertyId"
|
||||
autoFocus
|
||||
disabled={isPropertyTypeDisable}
|
||||
placeholder={entityPropertyPlaceHolder}
|
||||
@@ -120,7 +119,6 @@ export const TableEntity: FunctionComponent<TableEntityProps> = ({
|
||||
onChange={onEntityTypeChange}
|
||||
options={options}
|
||||
disabled={isPropertyTypeDisable}
|
||||
id="entityTypeId"
|
||||
styles={dropdownStyles}
|
||||
/>
|
||||
<EntityValue
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import {
|
||||
allowedAadEndpoints,
|
||||
allowedArcadiaEndpoints,
|
||||
allowedArmEndpoints,
|
||||
allowedBackendEndpoints,
|
||||
allowedEmulatorEndpoints,
|
||||
allowedGraphEndpoints,
|
||||
allowedHostedExplorerEndpoints,
|
||||
allowedJunoOrigins,
|
||||
allowedMongoBackendEndpoints,
|
||||
allowedMsalRedirectEndpoints,
|
||||
defaultAllowedArmEndpoints,
|
||||
defaultAllowedBackendEndpoints,
|
||||
validateEndpoint,
|
||||
} from "Utils/EndpointValidation";
|
||||
|
||||
@@ -20,6 +20,8 @@ export enum Platform {
|
||||
|
||||
export interface ConfigContext {
|
||||
platform: Platform;
|
||||
allowedArmEndpoints: ReadonlyArray<string>;
|
||||
allowedBackendEndpoints: ReadonlyArray<string>;
|
||||
allowedParentFrameOrigins: ReadonlyArray<string>;
|
||||
gitSha?: string;
|
||||
proxyPath?: string;
|
||||
@@ -49,6 +51,8 @@ export interface ConfigContext {
|
||||
// Default configuration
|
||||
let configContext: Readonly<ConfigContext> = {
|
||||
platform: Platform.Portal,
|
||||
allowedArmEndpoints: defaultAllowedArmEndpoints,
|
||||
allowedBackendEndpoints: defaultAllowedBackendEndpoints,
|
||||
allowedParentFrameOrigins: [
|
||||
`^https:\\/\\/cosmos\\.azure\\.(com|cn|us)$`,
|
||||
`^https:\\/\\/[\\.\\w]*portal\\.azure\\.(com|cn|us)$`,
|
||||
@@ -77,7 +81,7 @@ let configContext: Readonly<ConfigContext> = {
|
||||
|
||||
export function resetConfigContext(): void {
|
||||
if (process.env.NODE_ENV !== "test") {
|
||||
throw new Error("resetConfigContext can only becalled in a test environment");
|
||||
throw new Error("resetConfigContext can only be called in a test environment");
|
||||
}
|
||||
configContext = {} as ConfigContext;
|
||||
}
|
||||
@@ -87,7 +91,7 @@ export function updateConfigContext(newContext: Partial<ConfigContext>): void {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!validateEndpoint(newContext.ARM_ENDPOINT, allowedArmEndpoints)) {
|
||||
if (!validateEndpoint(newContext.ARM_ENDPOINT, configContext.allowedArmEndpoints || defaultAllowedArmEndpoints)) {
|
||||
delete newContext.ARM_ENDPOINT;
|
||||
}
|
||||
|
||||
@@ -107,7 +111,12 @@ export function updateConfigContext(newContext: Partial<ConfigContext>): void {
|
||||
delete newContext.ARCADIA_ENDPOINT;
|
||||
}
|
||||
|
||||
if (!validateEndpoint(newContext.BACKEND_ENDPOINT, allowedBackendEndpoints)) {
|
||||
if (
|
||||
!validateEndpoint(
|
||||
newContext.BACKEND_ENDPOINT,
|
||||
configContext.allowedBackendEndpoints || defaultAllowedBackendEndpoints
|
||||
)
|
||||
) {
|
||||
delete newContext.BACKEND_ENDPOINT;
|
||||
}
|
||||
|
||||
@@ -130,7 +139,7 @@ export function updateConfigContext(newContext: Partial<ConfigContext>): void {
|
||||
Object.assign(configContext, newContext);
|
||||
}
|
||||
|
||||
// Injected for local develpment. These will be removed in the production bundle by webpack
|
||||
// Injected for local development. These will be removed in the production bundle by webpack
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
const port: string = process.env.PORT || "1234";
|
||||
updateConfigContext({
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useDatabases } from "Explorer/useDatabases";
|
||||
import { Action } from "Shared/Telemetry/TelemetryConstants";
|
||||
import { traceOpen } from "Shared/Telemetry/TelemetryProcessor";
|
||||
import { ReactTabKind, useTabs } from "hooks/useTabs";
|
||||
@@ -146,14 +147,24 @@ export const createCollectionContextMenuButton = (
|
||||
export const createSampleCollectionContextMenuButton = (): TreeNodeMenuItem[] => {
|
||||
const items: TreeNodeMenuItem[] = [];
|
||||
if (userContext.apiType === "SQL") {
|
||||
items.push({
|
||||
iconSrc: AddSqlQueryIcon,
|
||||
onClick: () => {
|
||||
useTabs.getState().openAndActivateReactTab(ReactTabKind.QueryCopilot);
|
||||
traceOpen(Action.OpenQueryCopilotFromNewQuery, { apiType: userContext.apiType });
|
||||
},
|
||||
label: "New SQL Query",
|
||||
});
|
||||
const copilotVersion = userContext.features.copilotVersion;
|
||||
if (copilotVersion === "v1.0") {
|
||||
items.push({
|
||||
iconSrc: AddSqlQueryIcon,
|
||||
onClick: () => {
|
||||
useTabs.getState().openAndActivateReactTab(ReactTabKind.QueryCopilot);
|
||||
traceOpen(Action.OpenQueryCopilotFromNewQuery, { apiType: userContext.apiType });
|
||||
},
|
||||
label: "New SQL Query",
|
||||
});
|
||||
} else if (copilotVersion === "v2.0") {
|
||||
const sampleCollection = useDatabases.getState().sampleDataResourceTokenCollection;
|
||||
items.push({
|
||||
iconSrc: AddSqlQueryIcon,
|
||||
onClick: () => sampleCollection && sampleCollection.onNewQueryClick(sampleCollection, undefined),
|
||||
label: "New SQL Query",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return items;
|
||||
|
||||
@@ -49,7 +49,6 @@ export class CollapsibleSectionComponent extends React.Component<CollapsibleSect
|
||||
onClick={this.toggleCollapsed}
|
||||
onKeyPress={this.onKeyPress}
|
||||
tabIndex={0}
|
||||
aria-name="Advanced"
|
||||
role="button"
|
||||
aria-expanded={this.state.isExpanded}
|
||||
>
|
||||
|
||||
@@ -4,7 +4,6 @@ exports[`CollapsibleSectionComponent renders 1`] = `
|
||||
<Fragment>
|
||||
<Stack
|
||||
aria-expanded={true}
|
||||
aria-name="Advanced"
|
||||
className="collapsibleSection"
|
||||
horizontal={true}
|
||||
onClick={[Function]}
|
||||
|
||||
@@ -249,6 +249,7 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
|
||||
onChange={(event, newInput?: string) => onThroughputValueChange(newInput)}
|
||||
step={AutoPilotUtils.autoPilotIncrementStep}
|
||||
min={AutoPilotUtils.autoPilotThroughput1K}
|
||||
max={isSharded ? Number.MAX_SAFE_INTEGER.toString() : "10000"}
|
||||
value={throughput.toString()}
|
||||
ariaLabel={`${isDatabase ? "Database" : getCollectionName()} max RU/s`}
|
||||
required={true}
|
||||
|
||||
@@ -1644,6 +1644,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
||||
errorMessage=""
|
||||
id="autoscaleRUValueField"
|
||||
key=".0:$.2"
|
||||
max="9007199254740991"
|
||||
min={1000}
|
||||
onChange={[Function]}
|
||||
required={true}
|
||||
@@ -1667,6 +1668,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
||||
deferredValidationTime={200}
|
||||
errorMessage=""
|
||||
id="autoscaleRUValueField"
|
||||
max="9007199254740991"
|
||||
min={1000}
|
||||
onChange={[Function]}
|
||||
required={true}
|
||||
@@ -1964,6 +1966,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
||||
aria-label="Container max RU/s"
|
||||
className="ms-TextField-field field-64"
|
||||
id="autoscaleRUValueField"
|
||||
max="9007199254740991"
|
||||
min={1000}
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { Link } from "@fluentui/react/lib/Link";
|
||||
import { isPublicInternetAccessAllowed } from "Common/DatabaseAccountUtility";
|
||||
import { sendMessage } from "Common/MessageHandler";
|
||||
import { Platform } from "ConfigContext";
|
||||
import { MessageTypes } from "Contracts/ExplorerContracts";
|
||||
import { IGalleryItem } from "Juno/JunoClient";
|
||||
import { allowedNotebookServerUrls, validateEndpoint } from "Utils/EndpointValidation";
|
||||
import * as ko from "knockout";
|
||||
@@ -23,7 +26,7 @@ import { PhoenixClient } from "../Phoenix/PhoenixClient";
|
||||
import * as ExplorerSettings from "../Shared/ExplorerSettings";
|
||||
import { Action, ActionModifiers } from "../Shared/Telemetry/TelemetryConstants";
|
||||
import * as TelemetryProcessor from "../Shared/Telemetry/TelemetryProcessor";
|
||||
import { userContext } from "../UserContext";
|
||||
import { isAccountNewerThanThresholdInMs, userContext } from "../UserContext";
|
||||
import { getCollectionName, getUploadName } from "../Utils/APITypeUtils";
|
||||
import { stringToBlob } from "../Utils/BlobUtils";
|
||||
import { isCapabilityEnabled } from "../Utils/CapabilityUtils";
|
||||
@@ -258,6 +261,45 @@ export default class Explorer {
|
||||
// TODO: return result
|
||||
}
|
||||
|
||||
private getRandomInt(max: number) {
|
||||
return Math.floor(Math.random() * max);
|
||||
}
|
||||
|
||||
public openNPSSurveyDialog(): void {
|
||||
if (!Platform.Portal) {
|
||||
return;
|
||||
}
|
||||
|
||||
const NINETY_DAYS_IN_MS = 7776000000;
|
||||
const ONE_DAY_IN_MS = 86400000;
|
||||
const isAccountNewerThanNinetyDays = isAccountNewerThanThresholdInMs(
|
||||
userContext.databaseAccount?.systemData?.createdAt || "",
|
||||
NINETY_DAYS_IN_MS
|
||||
);
|
||||
|
||||
// Try Cosmos DB subscription - survey shown to random 25% of users at day 1 in Data Explorer.
|
||||
if (userContext.isTryCosmosDBSubscription) {
|
||||
if (
|
||||
isAccountNewerThanThresholdInMs(userContext.databaseAccount?.systemData?.createdAt || "", ONE_DAY_IN_MS) &&
|
||||
this.getRandomInt(100) < 25
|
||||
) {
|
||||
sendMessage({ type: MessageTypes.DisplayNPSSurvey });
|
||||
}
|
||||
} else {
|
||||
// An existing account is lesser than 90 days old. For existing account show to random 10 % of users in Data Explorer.
|
||||
if (isAccountNewerThanNinetyDays) {
|
||||
if (this.getRandomInt(100) < 10) {
|
||||
sendMessage({ type: MessageTypes.DisplayNPSSurvey });
|
||||
}
|
||||
} else {
|
||||
// An existing account is greater than 90 days. For existing account show to random 25 % of users in Data Explorer.
|
||||
if (this.getRandomInt(100) < 25) {
|
||||
sendMessage({ type: MessageTypes.DisplayNPSSurvey });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async refreshDatabaseForResourceToken(): Promise<void> {
|
||||
const databaseId = userContext.parsedResourceToken?.databaseId;
|
||||
const collectionId = userContext.parsedResourceToken?.collectionId;
|
||||
@@ -343,7 +385,7 @@ export default class Explorer {
|
||||
this._isInitializingNotebooks = false;
|
||||
}
|
||||
|
||||
public async allocateContainer(): Promise<void> {
|
||||
public async allocateContainer(poolId: PoolIdType): Promise<void> {
|
||||
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
|
||||
const isAllocating = useNotebook.getState().isAllocating;
|
||||
if (
|
||||
@@ -353,7 +395,7 @@ export default class Explorer {
|
||||
) {
|
||||
const provisionData: IProvisionData = {
|
||||
cosmosEndpoint: userContext?.databaseAccount?.properties?.documentEndpoint,
|
||||
poolId: PoolIdType.DefaultPoolId,
|
||||
poolId: poolId === PoolIdType.DefaultPoolId ? undefined : poolId,
|
||||
};
|
||||
const connectionStatus: ContainerConnectionInfo = {
|
||||
status: ConnectionStatusType.Connecting,
|
||||
@@ -608,6 +650,8 @@ export default class Explorer {
|
||||
private _initSettings() {
|
||||
if (!ExplorerSettings.hasSettingsDefined()) {
|
||||
ExplorerSettings.createDefaultSettings();
|
||||
} else {
|
||||
ExplorerSettings.ensurePriorityLevel();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -711,7 +755,7 @@ export default class Explorer {
|
||||
throw new Error(`Invalid notebookContentItem: ${notebookContentItem}`);
|
||||
}
|
||||
if (notebookContentItem.type === NotebookContentItemType.Notebook && useNotebook.getState().isPhoenixNotebooks) {
|
||||
await this.allocateContainer();
|
||||
await this.allocateContainer(PoolIdType.DefaultPoolId);
|
||||
}
|
||||
|
||||
const notebookTabs = useTabs
|
||||
@@ -936,7 +980,7 @@ export default class Explorer {
|
||||
}
|
||||
if (useNotebook.getState().isPhoenixNotebooks) {
|
||||
if (isGithubTree) {
|
||||
await this.allocateContainer();
|
||||
await this.allocateContainer(PoolIdType.DefaultPoolId);
|
||||
parent = parent || this.resourceTree.myNotebooksContentRoot;
|
||||
this.createNewNoteBook(parent, isGithubTree);
|
||||
} else {
|
||||
@@ -945,7 +989,7 @@ export default class Explorer {
|
||||
undefined,
|
||||
"Create",
|
||||
async () => {
|
||||
await this.allocateContainer();
|
||||
await this.allocateContainer(PoolIdType.DefaultPoolId);
|
||||
parent = parent || this.resourceTree.myNotebooksContentRoot;
|
||||
this.createNewNoteBook(parent, isGithubTree);
|
||||
},
|
||||
@@ -1024,7 +1068,7 @@ export default class Explorer {
|
||||
|
||||
public async openNotebookTerminal(kind: ViewModels.TerminalKind): Promise<void> {
|
||||
if (useNotebook.getState().isPhoenixFeatures) {
|
||||
await this.allocateContainer();
|
||||
await this.allocateContainer(PoolIdType.DefaultPoolId);
|
||||
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
|
||||
if (notebookServerInfo && notebookServerInfo.notebookServerEndpoint !== undefined) {
|
||||
this.connectToNotebookTerminal(kind);
|
||||
@@ -1168,7 +1212,7 @@ export default class Explorer {
|
||||
await useNotebook.getState().getPhoenixStatus();
|
||||
}
|
||||
if (useNotebook.getState().isPhoenixNotebooks) {
|
||||
await this.allocateContainer();
|
||||
await this.allocateContainer(PoolIdType.DefaultPoolId);
|
||||
}
|
||||
|
||||
// We still use github urls like https://github.com/Azure-Samples/cosmos-notebooks/blob/master/CSharp_quickstarts/GettingStarted_CSharp.ipynb
|
||||
@@ -1205,7 +1249,7 @@ export default class Explorer {
|
||||
undefined,
|
||||
"Upload",
|
||||
async () => {
|
||||
await this.allocateContainer();
|
||||
await this.allocateContainer(PoolIdType.DefaultPoolId);
|
||||
parent = parent || this.resourceTree.myNotebooksContentRoot;
|
||||
this.uploadFilePanel(parent);
|
||||
},
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
*/
|
||||
import { CommandBar as FluentCommandBar, ICommandBarItemProps } from "@fluentui/react";
|
||||
import { useNotebook } from "Explorer/Notebook/useNotebook";
|
||||
import * as React from "react";
|
||||
import { userContext } from "UserContext";
|
||||
import * as React from "react";
|
||||
import create, { UseStore } from "zustand";
|
||||
import { ConnectionStatusType, StyleConstants } from "../../../Common/Constants";
|
||||
import { ConnectionStatusType, PoolIdType, StyleConstants } from "../../../Common/Constants";
|
||||
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
||||
import Explorer from "../../Explorer";
|
||||
import { useSelectedNode } from "../../useSelectedNode";
|
||||
@@ -76,7 +76,9 @@ export const CommandBar: React.FC<Props> = ({ container }: Props) => {
|
||||
(useNotebook.getState().isPhoenixNotebooks || useNotebook.getState().isPhoenixFeatures) &&
|
||||
connectionInfo?.status !== ConnectionStatusType.Connect
|
||||
) {
|
||||
uiFabricControlButtons.unshift(CommandBarUtil.createConnectionStatus(container, "connectionStatus"));
|
||||
uiFabricControlButtons.unshift(
|
||||
CommandBarUtil.createConnectionStatus(container, PoolIdType.DefaultPoolId, "connectionStatus")
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
import * as React from "react";
|
||||
import _ from "underscore";
|
||||
import ChevronDownIcon from "../../../../images/Chevron_down.svg";
|
||||
import { StyleConstants } from "../../../Common/Constants";
|
||||
import { PoolIdType, StyleConstants } from "../../../Common/Constants";
|
||||
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
||||
@@ -204,9 +204,9 @@ export const createMemoryTracker = (key: string): ICommandBarItemProps => {
|
||||
};
|
||||
};
|
||||
|
||||
export const createConnectionStatus = (container: Explorer, key: string): ICommandBarItemProps => {
|
||||
export const createConnectionStatus = (container: Explorer, poolId: PoolIdType, key: string): ICommandBarItemProps => {
|
||||
return {
|
||||
key,
|
||||
onRender: () => <ConnectionStatus container={container} />,
|
||||
onRender: () => <ConnectionStatus container={container} poolId={poolId} />,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -14,14 +14,15 @@ import { useId } from "@fluentui/react-hooks";
|
||||
import { ActionButton, DefaultButton } from "@fluentui/react/lib/Button";
|
||||
import * as React from "react";
|
||||
import "../../../../less/hostedexplorer.less";
|
||||
import { ConnectionStatusType, ContainerStatusType, Notebook } from "../../../Common/Constants";
|
||||
import { ConnectionStatusType, ContainerStatusType, Notebook, PoolIdType } from "../../../Common/Constants";
|
||||
import Explorer from "../../Explorer";
|
||||
import { useNotebook } from "../../Notebook/useNotebook";
|
||||
import "../CommandBar/ConnectionStatusComponent.less";
|
||||
interface Props {
|
||||
container: Explorer;
|
||||
poolId: PoolIdType;
|
||||
}
|
||||
export const ConnectionStatus: React.FC<Props> = ({ container }: Props): JSX.Element => {
|
||||
export const ConnectionStatus: React.FC<Props> = ({ container, poolId }: Props): JSX.Element => {
|
||||
const connectionInfo = useNotebook((state) => state.connectionInfo);
|
||||
const [second, setSecond] = React.useState("00");
|
||||
const [minute, setMinute] = React.useState("00");
|
||||
@@ -93,7 +94,7 @@ export const ConnectionStatus: React.FC<Props> = ({ container }: Props): JSX.Ele
|
||||
(connectionInfo.status === ConnectionStatusType.Connect || connectionInfo.status === ConnectionStatusType.Reconnect)
|
||||
) {
|
||||
return (
|
||||
<ActionButton className="commandReactBtn" onClick={() => container.allocateContainer()}>
|
||||
<ActionButton className="commandReactBtn" onClick={() => container.allocateContainer(poolId)}>
|
||||
<TooltipHost content={toolTipContent}>
|
||||
<Stack className="connectionStatusContainer" horizontal>
|
||||
<Icon iconName="ConnectVirtualMachine" className="connectIcon" />
|
||||
@@ -133,7 +134,9 @@ export const ConnectionStatus: React.FC<Props> = ({ container }: Props): JSX.Ele
|
||||
id={buttonId}
|
||||
className={connectionInfo.status === ConnectionStatusType.Failed ? "commandReactBtn" : "connectedReactBtn"}
|
||||
onClick={(e: React.MouseEvent<HTMLSpanElement>) =>
|
||||
connectionInfo.status === ConnectionStatusType.Failed ? container.allocateContainer() : e.preventDefault()
|
||||
connectionInfo.status === ConnectionStatusType.Failed
|
||||
? container.allocateContainer(poolId)
|
||||
: e.preventDefault()
|
||||
}
|
||||
>
|
||||
<Stack className="connectionStatusContainer" horizontal>
|
||||
|
||||
@@ -122,7 +122,9 @@ export class NotificationConsoleComponent extends React.Component<
|
||||
{userContext.features.pr && <PrPreview pr={userContext.features.pr} />}
|
||||
<span className="consoleSplitter" />
|
||||
<span className="headerStatus">
|
||||
<span className="headerStatusEllipsis">{this.state.headerStatus}</span>
|
||||
<span className="headerStatusEllipsis" aria-live="assertive" aria-atomic="true">
|
||||
{this.state.headerStatus}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
|
||||
@@ -64,6 +64,8 @@ exports[`NotificationConsoleComponent renders the console 1`] = `
|
||||
className="headerStatus"
|
||||
>
|
||||
<span
|
||||
aria-atomic="true"
|
||||
aria-live="assertive"
|
||||
className="headerStatusEllipsis"
|
||||
/>
|
||||
</span>
|
||||
@@ -225,6 +227,8 @@ exports[`NotificationConsoleComponent renders the console 2`] = `
|
||||
className="headerStatus"
|
||||
>
|
||||
<span
|
||||
aria-atomic="true"
|
||||
aria-live="assertive"
|
||||
className="headerStatusEllipsis"
|
||||
>
|
||||
message
|
||||
|
||||
@@ -1425,6 +1425,8 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||
this.setState({ isExecuting: false });
|
||||
TelemetryProcessor.traceSuccess(Action.CreateCollection, telemetryData, startKey);
|
||||
useSidePanel.getState().closeSidePanel();
|
||||
// open NPS Survey Dialog once the collection is created
|
||||
this.props.explorer.openNPSSurveyDialog();
|
||||
} catch (error) {
|
||||
const errorMessage: string = getErrorMessage(error);
|
||||
this.setState({ isExecuting: false, errorMessage, showErrorDetails: true });
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { IDropdownOption } from "@fluentui/react";
|
||||
import React, { FormEvent, FunctionComponent, useEffect, useState } from "react";
|
||||
import { HttpStatusCodes } from "../../../Common/Constants";
|
||||
import { HttpStatusCodes, PoolIdType } from "../../../Common/Constants";
|
||||
import { getErrorMessage, handleError } from "../../../Common/ErrorHandlingUtils";
|
||||
import { GitHubOAuthService } from "../../../GitHub/GitHubOAuthService";
|
||||
import { useSidePanel } from "../../../hooks/useSidePanel";
|
||||
import { IPinnedRepo, JunoClient } from "../../../Juno/JunoClient";
|
||||
import * as GitHubUtils from "../../../Utils/GitHubUtils";
|
||||
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
|
||||
import { useSidePanel } from "../../../hooks/useSidePanel";
|
||||
import Explorer from "../../Explorer";
|
||||
import { NotebookContentItem, NotebookContentItemType } from "../../Notebook/NotebookContentItem";
|
||||
import { useNotebook } from "../../Notebook/useNotebook";
|
||||
@@ -109,7 +109,7 @@ export const CopyNotebookPane: FunctionComponent<CopyNotebookPanelProps> = ({
|
||||
};
|
||||
isGithubTree = false;
|
||||
if (useNotebook.getState().isPhoenixNotebooks) {
|
||||
await container.allocateContainer();
|
||||
await container.allocateContainer(PoolIdType.DefaultPoolId);
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
@@ -41,12 +41,18 @@ export const SettingsPane: FunctionComponent = () => {
|
||||
? LocalStorageUtility.getEntryNumber(StorageKey.MaxDegreeOfParellism)
|
||||
: Constants.Queries.DefaultMaxDegreeOfParallelism
|
||||
);
|
||||
const [priorityLevel, setPriorityLevel] = useState<string>(
|
||||
LocalStorageUtility.hasItem(StorageKey.PriorityLevel)
|
||||
? LocalStorageUtility.getEntryString(StorageKey.PriorityLevel)
|
||||
: Constants.PriorityLevel.Default
|
||||
);
|
||||
const explorerVersion = configContext.gitSha;
|
||||
const shouldShowQueryPageOptions = userContext.apiType === "SQL";
|
||||
const shouldShowGraphAutoVizOption = userContext.apiType === "Gremlin";
|
||||
const shouldShowCrossPartitionOption = userContext.apiType !== "Gremlin";
|
||||
const shouldShowParallelismOption = userContext.apiType !== "Gremlin";
|
||||
|
||||
const shouldShowPriorityLevelOption =
|
||||
userContext.features.enablePriorityBasedThrottling && userContext.apiType === "SQL";
|
||||
const handlerOnSubmit = (e: MouseEvent<HTMLButtonElement>) => {
|
||||
setIsExecuting(true);
|
||||
|
||||
@@ -58,6 +64,7 @@ export const SettingsPane: FunctionComponent = () => {
|
||||
LocalStorageUtility.setEntryString(StorageKey.ContainerPaginationEnabled, containerPaginationEnabled.toString());
|
||||
LocalStorageUtility.setEntryString(StorageKey.IsCrossPartitionQueryEnabled, crossPartitionQueryEnabled.toString());
|
||||
LocalStorageUtility.setEntryNumber(StorageKey.MaxDegreeOfParellism, maxDegreeOfParallelism);
|
||||
LocalStorageUtility.setEntryString(StorageKey.PriorityLevel, priorityLevel.toString());
|
||||
|
||||
if (shouldShowGraphAutoVizOption) {
|
||||
LocalStorageUtility.setEntryBoolean(
|
||||
@@ -76,6 +83,7 @@ export const SettingsPane: FunctionComponent = () => {
|
||||
StorageKey.MaxDegreeOfParellism
|
||||
)}`
|
||||
);
|
||||
logConsoleInfo(`Updated priority level setting to ${LocalStorageUtility.getEntryString(StorageKey.PriorityLevel)}`);
|
||||
|
||||
if (shouldShowGraphAutoVizOption) {
|
||||
logConsoleInfo(
|
||||
@@ -116,6 +124,18 @@ export const SettingsPane: FunctionComponent = () => {
|
||||
{ key: "true", text: "JSON" },
|
||||
];
|
||||
|
||||
const priorityLevelOptionList: IChoiceGroupOption[] = [
|
||||
{ key: Constants.PriorityLevel.Low, text: "Low" },
|
||||
{ key: Constants.PriorityLevel.High, text: "High" },
|
||||
];
|
||||
|
||||
const handleOnPriorityLevelOptionChange = (
|
||||
ev: React.FormEvent<HTMLInputElement>,
|
||||
option: IChoiceGroupOption
|
||||
): void => {
|
||||
setPriorityLevel(option.key);
|
||||
};
|
||||
|
||||
const handleOnPageOptionChange = (ev: React.FormEvent<HTMLInputElement>, option: IChoiceGroupOption): void => {
|
||||
setPageOption(option.key);
|
||||
};
|
||||
@@ -260,6 +280,29 @@ export const SettingsPane: FunctionComponent = () => {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{shouldShowPriorityLevelOption && (
|
||||
<div className="settingsSection">
|
||||
<div className="settingsSectionPart">
|
||||
<fieldset>
|
||||
<legend id="priorityLevel" className="settingsSectionLabel legendLabel">
|
||||
Priority Level
|
||||
</legend>
|
||||
<InfoTooltip>
|
||||
Sets the priority level for data-plane requests from Data Explorer when using Priority-Based
|
||||
Execution. If "None" is selected, Data Explorer will not specify priority level, and the
|
||||
server-side default priority level will be used.
|
||||
</InfoTooltip>
|
||||
<ChoiceGroup
|
||||
ariaLabelledBy="priorityLevel"
|
||||
selectedKey={priorityLevel}
|
||||
options={priorityLevelOptionList}
|
||||
styles={choiceButtonStyles}
|
||||
onChange={handleOnPriorityLevelOptionChange}
|
||||
/>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{shouldShowGraphAutoVizOption && (
|
||||
<div className="settingsSection">
|
||||
<div className="settingsSectionPart">
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Checkbox, ChoiceGroup, DefaultButton, IconButton, PrimaryButton, TextField } from "@fluentui/react";
|
||||
import Explorer from "Explorer/Explorer";
|
||||
import { QueryCopilotFeedbackModal } from "Explorer/QueryCopilot/Modal/QueryCopilotFeedbackModal";
|
||||
import { submitFeedback } from "Explorer/QueryCopilot/QueryCopilotUtilities";
|
||||
import { SubmitFeedback } from "Explorer/QueryCopilot/Shared/QueryCopilotClient";
|
||||
import { getUserEmail } from "Utils/UserUtils";
|
||||
import { shallow } from "enzyme";
|
||||
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
||||
@@ -9,8 +10,8 @@ import React from "react";
|
||||
jest.mock("Utils/UserUtils");
|
||||
(getUserEmail as jest.Mock).mockResolvedValue("test@email.com");
|
||||
|
||||
jest.mock("Explorer/QueryCopilot/QueryCopilotUtilities");
|
||||
submitFeedback as jest.Mock;
|
||||
jest.mock("Explorer/QueryCopilot/Shared/QueryCopilotClient");
|
||||
SubmitFeedback as jest.Mock;
|
||||
|
||||
describe("Query Copilot Feedback Modal snapshot test", () => {
|
||||
beforeEach(() => {
|
||||
@@ -19,14 +20,14 @@ describe("Query Copilot Feedback Modal snapshot test", () => {
|
||||
it("shoud render and match snapshot", () => {
|
||||
useQueryCopilot.getState().openFeedbackModal("test query", false, "test prompt");
|
||||
|
||||
const wrapper = shallow(<QueryCopilotFeedbackModal />);
|
||||
const wrapper = shallow(<QueryCopilotFeedbackModal explorer={new Explorer()} />);
|
||||
|
||||
expect(wrapper.props().isOpen).toBeTruthy();
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should close on cancel click", () => {
|
||||
const wrapper = shallow(<QueryCopilotFeedbackModal />);
|
||||
const wrapper = shallow(<QueryCopilotFeedbackModal explorer={new Explorer()} />);
|
||||
|
||||
const cancelButton = wrapper.find(IconButton);
|
||||
cancelButton.simulate("click");
|
||||
@@ -37,7 +38,7 @@ describe("Query Copilot Feedback Modal snapshot test", () => {
|
||||
});
|
||||
|
||||
it("should get user unput", () => {
|
||||
const wrapper = shallow(<QueryCopilotFeedbackModal />);
|
||||
const wrapper = shallow(<QueryCopilotFeedbackModal explorer={new Explorer()} />);
|
||||
const testUserInput = "test user input";
|
||||
|
||||
const userInput = wrapper.find(TextField).first();
|
||||
@@ -48,7 +49,7 @@ describe("Query Copilot Feedback Modal snapshot test", () => {
|
||||
});
|
||||
|
||||
it("should record user contact choice no", () => {
|
||||
const wrapper = shallow(<QueryCopilotFeedbackModal />);
|
||||
const wrapper = shallow(<QueryCopilotFeedbackModal explorer={new Explorer()} />);
|
||||
const contactAllowed = wrapper.find(ChoiceGroup);
|
||||
|
||||
contactAllowed.simulate("change", {}, { key: "no" });
|
||||
@@ -59,7 +60,7 @@ describe("Query Copilot Feedback Modal snapshot test", () => {
|
||||
});
|
||||
|
||||
it("should record user contact choice yes", () => {
|
||||
const wrapper = shallow(<QueryCopilotFeedbackModal />);
|
||||
const wrapper = shallow(<QueryCopilotFeedbackModal explorer={new Explorer()} />);
|
||||
const contactAllowed = wrapper.find(ChoiceGroup);
|
||||
|
||||
contactAllowed.simulate("change", {}, { key: "yes" });
|
||||
@@ -70,7 +71,7 @@ describe("Query Copilot Feedback Modal snapshot test", () => {
|
||||
});
|
||||
|
||||
it("should not render dont show again button", () => {
|
||||
const wrapper = shallow(<QueryCopilotFeedbackModal />);
|
||||
const wrapper = shallow(<QueryCopilotFeedbackModal explorer={new Explorer()} />);
|
||||
|
||||
const dontShowAgain = wrapper.find(Checkbox);
|
||||
|
||||
@@ -80,7 +81,7 @@ describe("Query Copilot Feedback Modal snapshot test", () => {
|
||||
|
||||
it("should render dont show again button and check it ", () => {
|
||||
useQueryCopilot.getState().openFeedbackModal("test query", true, "test prompt");
|
||||
const wrapper = shallow(<QueryCopilotFeedbackModal />);
|
||||
const wrapper = shallow(<QueryCopilotFeedbackModal explorer={new Explorer()} />);
|
||||
|
||||
const dontShowAgain = wrapper.find(Checkbox);
|
||||
dontShowAgain.simulate("change", {}, true);
|
||||
@@ -91,7 +92,7 @@ describe("Query Copilot Feedback Modal snapshot test", () => {
|
||||
});
|
||||
|
||||
it("should cancel submission", () => {
|
||||
const wrapper = shallow(<QueryCopilotFeedbackModal />);
|
||||
const wrapper = shallow(<QueryCopilotFeedbackModal explorer={new Explorer()} />);
|
||||
|
||||
const cancelButton = wrapper.find(DefaultButton);
|
||||
cancelButton.simulate("click");
|
||||
@@ -102,19 +103,23 @@ describe("Query Copilot Feedback Modal snapshot test", () => {
|
||||
});
|
||||
|
||||
it("should submit submission", () => {
|
||||
const wrapper = shallow(<QueryCopilotFeedbackModal />);
|
||||
const explorer = new Explorer();
|
||||
const wrapper = shallow(<QueryCopilotFeedbackModal explorer={explorer} />);
|
||||
|
||||
const submitButton = wrapper.find(PrimaryButton);
|
||||
submitButton.simulate("click");
|
||||
wrapper.setProps({});
|
||||
|
||||
expect(submitFeedback).toHaveBeenCalledTimes(1);
|
||||
expect(submitFeedback).toHaveBeenCalledWith({
|
||||
likeQuery: false,
|
||||
generatedQuery: "",
|
||||
userPrompt: "",
|
||||
description: "",
|
||||
contact: getUserEmail(),
|
||||
expect(SubmitFeedback).toHaveBeenCalledTimes(1);
|
||||
expect(SubmitFeedback).toHaveBeenCalledWith({
|
||||
params: {
|
||||
likeQuery: false,
|
||||
generatedQuery: "",
|
||||
userPrompt: "",
|
||||
description: "",
|
||||
contact: getUserEmail(),
|
||||
},
|
||||
explorer: explorer,
|
||||
});
|
||||
expect(wrapper.props().isOpen).toBeFalsy();
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
|
||||
@@ -10,12 +10,13 @@ import {
|
||||
Text,
|
||||
TextField,
|
||||
} from "@fluentui/react";
|
||||
import { submitFeedback } from "Explorer/QueryCopilot/QueryCopilotUtilities";
|
||||
import Explorer from "Explorer/Explorer";
|
||||
import { SubmitFeedback } from "Explorer/QueryCopilot/Shared/QueryCopilotClient";
|
||||
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
||||
import React from "react";
|
||||
import { getUserEmail } from "../../../Utils/UserUtils";
|
||||
|
||||
export const QueryCopilotFeedbackModal: React.FC = (): JSX.Element => {
|
||||
export const QueryCopilotFeedbackModal = ({ explorer }: { explorer: Explorer }): JSX.Element => {
|
||||
const {
|
||||
generatedQuery,
|
||||
userPrompt,
|
||||
@@ -78,11 +79,13 @@ export const QueryCopilotFeedbackModal: React.FC = (): JSX.Element => {
|
||||
}}
|
||||
></ChoiceGroup>
|
||||
<Text style={{ fontSize: 12, marginBottom: 14 }}>
|
||||
By pressing submit, your feedback will be used to improve Microsoft products and services. IT admins for your
|
||||
organization will be able to view and manage your feedback data.{" "}
|
||||
<Link href="https://privacy.microsoft.com/privacystatement" target="_blank">
|
||||
Privacy statement
|
||||
</Link>
|
||||
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
|
||||
@@ -98,7 +101,10 @@ export const QueryCopilotFeedbackModal: React.FC = (): JSX.Element => {
|
||||
onClick={() => {
|
||||
closeFeedbackModal();
|
||||
setHideFeedbackModalForLikedQueries(doNotShowAgainChecked);
|
||||
submitFeedback({ generatedQuery, likeQuery, description, userPrompt, contact });
|
||||
SubmitFeedback({
|
||||
params: { generatedQuery, likeQuery, description, userPrompt, contact },
|
||||
explorer: explorer,
|
||||
});
|
||||
}}
|
||||
>
|
||||
Submit
|
||||
|
||||
@@ -42,8 +42,8 @@ export const WelcomeModal = ({ visible }: { visible: boolean }): JSX.Element =>
|
||||
</Stack>
|
||||
</Stack>
|
||||
<Stack horizontalAlign="center">
|
||||
<Stack.Item align="center">
|
||||
<Text className="title bold">Welcome to Copilot in CosmosDB</Text>
|
||||
<Stack.Item align="center" style={{ textAlign: "center" }}>
|
||||
<Text className="title bold">Welcome to Query Copilot for Azure Cosmos DB (Private Preview)</Text>
|
||||
</Stack.Item>
|
||||
<Stack.Item align="center" className="text">
|
||||
<Stack horizontal>
|
||||
@@ -52,7 +52,7 @@ export const WelcomeModal = ({ visible }: { visible: boolean }): JSX.Element =>
|
||||
</StackItem>
|
||||
<StackItem align="start">
|
||||
<Text className="bold">
|
||||
Let copilot do the work for you
|
||||
Let Query Copilot do the work for you
|
||||
<br />
|
||||
</Text>
|
||||
</StackItem>
|
||||
@@ -88,13 +88,14 @@ export const WelcomeModal = ({ visible }: { visible: boolean }): JSX.Element =>
|
||||
</StackItem>
|
||||
<StackItem align="start">
|
||||
<Text className="bold">
|
||||
Copilot currently works only a sample database
|
||||
Query Copilot works on a sample database.
|
||||
<br />
|
||||
</Text>
|
||||
</StackItem>
|
||||
</Stack>
|
||||
<Text>
|
||||
Copilot is setup on a sample database we have configured for you at no cost
|
||||
While in Private Preview, Query Copilot is setup to work on sample database we have configured for you
|
||||
at no cost.
|
||||
<br />
|
||||
<Link href="http://aka.ms/cdb-copilot-learn-more">Learn more</Link>
|
||||
</Text>
|
||||
|
||||
@@ -118,7 +118,7 @@ exports[`Query Copilot Feedback Modal snapshot test shoud render and match snaps
|
||||
}
|
||||
}
|
||||
>
|
||||
By pressing submit, your feedback will be used to improve Microsoft products and services. IT admins for your organization will be able to view and manage your feedback data.
|
||||
By pressing submit, your feedback will be used to improve Microsoft products and services. Please see the
|
||||
|
||||
<StyledLinkBase
|
||||
href="https://privacy.microsoft.com/privacystatement"
|
||||
@@ -126,6 +126,8 @@ exports[`Query Copilot Feedback Modal snapshot test shoud render and match snaps
|
||||
>
|
||||
Privacy statement
|
||||
</StyledLinkBase>
|
||||
|
||||
for more information.
|
||||
</Text>
|
||||
<Stack
|
||||
horizontal={true}
|
||||
@@ -271,7 +273,7 @@ exports[`Query Copilot Feedback Modal snapshot test should cancel submission 1`]
|
||||
}
|
||||
}
|
||||
>
|
||||
By pressing submit, your feedback will be used to improve Microsoft products and services. IT admins for your organization will be able to view and manage your feedback data.
|
||||
By pressing submit, your feedback will be used to improve Microsoft products and services. Please see the
|
||||
|
||||
<StyledLinkBase
|
||||
href="https://privacy.microsoft.com/privacystatement"
|
||||
@@ -279,6 +281,8 @@ exports[`Query Copilot Feedback Modal snapshot test should cancel submission 1`]
|
||||
>
|
||||
Privacy statement
|
||||
</StyledLinkBase>
|
||||
|
||||
for more information.
|
||||
</Text>
|
||||
<Stack
|
||||
horizontal={true}
|
||||
@@ -424,7 +428,7 @@ exports[`Query Copilot Feedback Modal snapshot test should close on cancel click
|
||||
}
|
||||
}
|
||||
>
|
||||
By pressing submit, your feedback will be used to improve Microsoft products and services. IT admins for your organization will be able to view and manage your feedback data.
|
||||
By pressing submit, your feedback will be used to improve Microsoft products and services. Please see the
|
||||
|
||||
<StyledLinkBase
|
||||
href="https://privacy.microsoft.com/privacystatement"
|
||||
@@ -432,6 +436,8 @@ exports[`Query Copilot Feedback Modal snapshot test should close on cancel click
|
||||
>
|
||||
Privacy statement
|
||||
</StyledLinkBase>
|
||||
|
||||
for more information.
|
||||
</Text>
|
||||
<Stack
|
||||
horizontal={true}
|
||||
@@ -577,7 +583,7 @@ exports[`Query Copilot Feedback Modal snapshot test should get user unput 1`] =
|
||||
}
|
||||
}
|
||||
>
|
||||
By pressing submit, your feedback will be used to improve Microsoft products and services. IT admins for your organization will be able to view and manage your feedback data.
|
||||
By pressing submit, your feedback will be used to improve Microsoft products and services. Please see the
|
||||
|
||||
<StyledLinkBase
|
||||
href="https://privacy.microsoft.com/privacystatement"
|
||||
@@ -585,6 +591,8 @@ exports[`Query Copilot Feedback Modal snapshot test should get user unput 1`] =
|
||||
>
|
||||
Privacy statement
|
||||
</StyledLinkBase>
|
||||
|
||||
for more information.
|
||||
</Text>
|
||||
<Stack
|
||||
horizontal={true}
|
||||
@@ -730,7 +738,7 @@ exports[`Query Copilot Feedback Modal snapshot test should not render dont show
|
||||
}
|
||||
}
|
||||
>
|
||||
By pressing submit, your feedback will be used to improve Microsoft products and services. IT admins for your organization will be able to view and manage your feedback data.
|
||||
By pressing submit, your feedback will be used to improve Microsoft products and services. Please see the
|
||||
|
||||
<StyledLinkBase
|
||||
href="https://privacy.microsoft.com/privacystatement"
|
||||
@@ -738,6 +746,8 @@ exports[`Query Copilot Feedback Modal snapshot test should not render dont show
|
||||
>
|
||||
Privacy statement
|
||||
</StyledLinkBase>
|
||||
|
||||
for more information.
|
||||
</Text>
|
||||
<Stack
|
||||
horizontal={true}
|
||||
@@ -883,7 +893,7 @@ exports[`Query Copilot Feedback Modal snapshot test should record user contact c
|
||||
}
|
||||
}
|
||||
>
|
||||
By pressing submit, your feedback will be used to improve Microsoft products and services. IT admins for your organization will be able to view and manage your feedback data.
|
||||
By pressing submit, your feedback will be used to improve Microsoft products and services. Please see the
|
||||
|
||||
<StyledLinkBase
|
||||
href="https://privacy.microsoft.com/privacystatement"
|
||||
@@ -891,6 +901,8 @@ exports[`Query Copilot Feedback Modal snapshot test should record user contact c
|
||||
>
|
||||
Privacy statement
|
||||
</StyledLinkBase>
|
||||
|
||||
for more information.
|
||||
</Text>
|
||||
<Stack
|
||||
horizontal={true}
|
||||
@@ -1036,7 +1048,7 @@ exports[`Query Copilot Feedback Modal snapshot test should record user contact c
|
||||
}
|
||||
}
|
||||
>
|
||||
By pressing submit, your feedback will be used to improve Microsoft products and services. IT admins for your organization will be able to view and manage your feedback data.
|
||||
By pressing submit, your feedback will be used to improve Microsoft products and services. Please see the
|
||||
|
||||
<StyledLinkBase
|
||||
href="https://privacy.microsoft.com/privacystatement"
|
||||
@@ -1044,6 +1056,8 @@ exports[`Query Copilot Feedback Modal snapshot test should record user contact c
|
||||
>
|
||||
Privacy statement
|
||||
</StyledLinkBase>
|
||||
|
||||
for more information.
|
||||
</Text>
|
||||
<Stack
|
||||
horizontal={true}
|
||||
@@ -1189,7 +1203,7 @@ exports[`Query Copilot Feedback Modal snapshot test should render dont show agai
|
||||
}
|
||||
}
|
||||
>
|
||||
By pressing submit, your feedback will be used to improve Microsoft products and services. IT admins for your organization will be able to view and manage your feedback data.
|
||||
By pressing submit, your feedback will be used to improve Microsoft products and services. Please see the
|
||||
|
||||
<StyledLinkBase
|
||||
href="https://privacy.microsoft.com/privacystatement"
|
||||
@@ -1197,6 +1211,8 @@ exports[`Query Copilot Feedback Modal snapshot test should render dont show agai
|
||||
>
|
||||
Privacy statement
|
||||
</StyledLinkBase>
|
||||
|
||||
for more information.
|
||||
</Text>
|
||||
<StyledCheckboxBase
|
||||
checked={true}
|
||||
@@ -1357,7 +1373,7 @@ exports[`Query Copilot Feedback Modal snapshot test should submit submission 1`]
|
||||
}
|
||||
}
|
||||
>
|
||||
By pressing submit, your feedback will be used to improve Microsoft products and services. IT admins for your organization will be able to view and manage your feedback data.
|
||||
By pressing submit, your feedback will be used to improve Microsoft products and services. Please see the
|
||||
|
||||
<StyledLinkBase
|
||||
href="https://privacy.microsoft.com/privacystatement"
|
||||
@@ -1365,6 +1381,8 @@ exports[`Query Copilot Feedback Modal snapshot test should submit submission 1`]
|
||||
>
|
||||
Privacy statement
|
||||
</StyledLinkBase>
|
||||
|
||||
for more information.
|
||||
</Text>
|
||||
<Stack
|
||||
horizontal={true}
|
||||
|
||||
@@ -60,11 +60,16 @@ exports[`Query Copilot Welcome Modal snapshot test should render when isOpen is
|
||||
>
|
||||
<StackItem
|
||||
align="center"
|
||||
style={
|
||||
Object {
|
||||
"textAlign": "center",
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text
|
||||
className="title bold"
|
||||
>
|
||||
Welcome to Copilot in CosmosDB
|
||||
Welcome to Query Copilot for Azure Cosmos DB (Private Preview)
|
||||
</Text>
|
||||
</StackItem>
|
||||
<StackItem
|
||||
@@ -88,7 +93,7 @@ exports[`Query Copilot Welcome Modal snapshot test should render when isOpen is
|
||||
<Text
|
||||
className="bold"
|
||||
>
|
||||
Let copilot do the work for you
|
||||
Let Query Copilot do the work for you
|
||||
<br />
|
||||
</Text>
|
||||
</StackItem>
|
||||
@@ -160,13 +165,13 @@ exports[`Query Copilot Welcome Modal snapshot test should render when isOpen is
|
||||
<Text
|
||||
className="bold"
|
||||
>
|
||||
Copilot currently works only a sample database
|
||||
Query Copilot works on a sample database.
|
||||
<br />
|
||||
</Text>
|
||||
</StackItem>
|
||||
</Stack>
|
||||
<Text>
|
||||
Copilot is setup on a sample database we have configured for you at no cost
|
||||
While in Private Preview, Query Copilot is setup to work on sample database we have configured for you at no cost.
|
||||
<br />
|
||||
<StyledLinkBase
|
||||
href="http://aka.ms/cdb-copilot-learn-more"
|
||||
|
||||
@@ -5,9 +5,7 @@ import { QueryCopilotTab } from "./QueryCopilotTab";
|
||||
|
||||
describe("Query copilot tab snapshot test", () => {
|
||||
it("should render with initial input", () => {
|
||||
const wrapper = shallow(
|
||||
<QueryCopilotTab initialInput="Write a query to return all records in this table" explorer={new Explorer()} />
|
||||
);
|
||||
const wrapper = shallow(<QueryCopilotTab explorer={new Explorer()} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -17,22 +17,30 @@ import {
|
||||
TextField,
|
||||
} from "@fluentui/react";
|
||||
import { useBoolean } from "@fluentui/react-hooks";
|
||||
import { QueryCopilotSampleContainerId, QueryCopilotSampleContainerSchema } from "Common/Constants";
|
||||
import {
|
||||
PoolIdType,
|
||||
QueryCopilotSampleContainerId,
|
||||
QueryCopilotSampleContainerSchema,
|
||||
ShortenedQueryCopilotSampleContainerSchema,
|
||||
} from "Common/Constants";
|
||||
import { getErrorMessage, handleError } from "Common/ErrorHandlingUtils";
|
||||
import { shouldEnableCrossPartitionKey } from "Common/HeadersUtility";
|
||||
import { MinimalQueryIterator } from "Common/IteratorUtilities";
|
||||
import { createUri } from "Common/UrlUtility";
|
||||
import { queryDocumentsPage } from "Common/dataAccess/queryDocumentsPage";
|
||||
import { QueryResults } from "Contracts/ViewModels";
|
||||
import { CommandButtonComponentProps } from "Explorer/Controls/CommandButton/CommandButtonComponent";
|
||||
import { EditorReact } from "Explorer/Controls/Editor/EditorReact";
|
||||
import Explorer from "Explorer/Explorer";
|
||||
import { useCommandBar } from "Explorer/Menus/CommandBar/CommandBarComponentAdapter";
|
||||
import { useNotebook } from "Explorer/Notebook/useNotebook";
|
||||
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 { querySampleDocuments, submitFeedback } from "Explorer/QueryCopilot/QueryCopilotUtilities";
|
||||
import { SamplePrompts, SamplePromptsProps } from "Explorer/QueryCopilot/SamplePrompts/SamplePrompts";
|
||||
import { querySampleDocuments } from "Explorer/QueryCopilot/QueryCopilotUtilities";
|
||||
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 { QueryResultSection } from "Explorer/Tabs/QueryTab/QueryResultSection";
|
||||
import { Action } from "Shared/Telemetry/TelemetryConstants";
|
||||
import { traceFailure, traceStart, traceSuccess } from "Shared/Telemetry/TelemetryProcessor";
|
||||
@@ -55,50 +63,57 @@ interface SuggestedPrompt {
|
||||
text: string;
|
||||
}
|
||||
|
||||
interface QueryCopilotTabProps {
|
||||
initialInput: string;
|
||||
explorer: Explorer;
|
||||
}
|
||||
|
||||
interface GenerateSQLQueryResponse {
|
||||
apiVersion: string;
|
||||
sql: string;
|
||||
explanation: string;
|
||||
generateStart: string;
|
||||
generateEnd: string;
|
||||
}
|
||||
|
||||
const promptStyles: IButtonStyles = {
|
||||
root: { border: 0, selectors: { ":hover": { outline: "1px dashed #605e5c" } } },
|
||||
label: { fontWeight: 400, textAlign: "left", paddingLeft: 8 },
|
||||
};
|
||||
|
||||
export const QueryCopilotTab: React.FC<QueryCopilotTabProps> = ({
|
||||
initialInput,
|
||||
explorer,
|
||||
}: QueryCopilotTabProps): JSX.Element => {
|
||||
const hideFeedbackModalForLikedQueries = useQueryCopilot((state) => state.hideFeedbackModalForLikedQueries);
|
||||
const [userPrompt, setUserPrompt] = useState<string>(initialInput || "");
|
||||
const [generatedQuery, setGeneratedQuery] = useState<string>("");
|
||||
const [generatedQueryComments, setGeneratedQueryComments] = useState<string>("");
|
||||
const [query, setQuery] = useState<string>("");
|
||||
const [selectedQuery, setSelectedQuery] = useState<string>("");
|
||||
const [isGeneratingQuery, setIsGeneratingQuery] = useState<boolean>(false);
|
||||
const [isExecuting, setIsExecuting] = useState<boolean>(false);
|
||||
const [likeQuery, setLikeQuery] = useState<boolean>();
|
||||
const [dislikeQuery, setDislikeQuery] = useState<boolean>();
|
||||
const [showCallout, setShowCallout] = useState<boolean>(false);
|
||||
const [showSamplePrompts, setShowSamplePrompts] = useState<boolean>(false);
|
||||
const [queryIterator, setQueryIterator] = useState<MinimalQueryIterator>();
|
||||
const [queryResults, setQueryResults] = useState<QueryResults>();
|
||||
const [errorMessage, setErrorMessage] = useState<string>("");
|
||||
export const QueryCopilotTab: React.FC<QueryCopilotProps> = ({ explorer }: QueryCopilotProps): JSX.Element => {
|
||||
const [copilotTeachingBubbleVisible, { toggle: toggleCopilotTeachingBubbleVisible }] = useBoolean(false);
|
||||
const inputEdited = useRef(false);
|
||||
const [isSamplePromptsOpen, setIsSamplePromptsOpen] = useState<boolean>(false);
|
||||
const [showDeletePopup, setShowDeletePopup] = useState<boolean>(false);
|
||||
const [showFeedbackBar, setShowFeedbackBar] = useState<boolean>(false);
|
||||
const [showCopyPopup, setshowCopyPopup] = useState<boolean>(false);
|
||||
const [showErrorMessageBar, setShowErrorMessageBar] = useState<boolean>(false);
|
||||
const {
|
||||
hideFeedbackModalForLikedQueries,
|
||||
userPrompt,
|
||||
setUserPrompt,
|
||||
generatedQuery,
|
||||
setGeneratedQuery,
|
||||
query,
|
||||
setQuery,
|
||||
selectedQuery,
|
||||
setSelectedQuery,
|
||||
isGeneratingQuery,
|
||||
setIsGeneratingQuery,
|
||||
isExecuting,
|
||||
setIsExecuting,
|
||||
likeQuery,
|
||||
setLikeQuery,
|
||||
dislikeQuery,
|
||||
setDislikeQuery,
|
||||
showCallout,
|
||||
setShowCallout,
|
||||
showSamplePrompts,
|
||||
setShowSamplePrompts,
|
||||
queryIterator,
|
||||
setQueryIterator,
|
||||
queryResults,
|
||||
setQueryResults,
|
||||
errorMessage,
|
||||
setErrorMessage,
|
||||
isSamplePromptsOpen,
|
||||
setIsSamplePromptsOpen,
|
||||
showDeletePopup,
|
||||
setShowDeletePopup,
|
||||
showFeedbackBar,
|
||||
setShowFeedbackBar,
|
||||
showCopyPopup,
|
||||
setshowCopyPopup,
|
||||
showErrorMessageBar,
|
||||
setShowErrorMessageBar,
|
||||
generatedQueryComments,
|
||||
setGeneratedQueryComments,
|
||||
shouldAllocateContainer,
|
||||
setShouldAllocateContainer,
|
||||
} = useQueryCopilot();
|
||||
|
||||
const sampleProps: SamplePromptsProps = {
|
||||
isSamplePromptsOpen: isSamplePromptsOpen,
|
||||
@@ -165,16 +180,27 @@ export const QueryCopilotTab: React.FC<QueryCopilotTabProps> = ({
|
||||
|
||||
const generateSQLQuery = async (): Promise<void> => {
|
||||
try {
|
||||
if (shouldAllocateContainer && userContext.features.enableCopilotPhoenixGateaway) {
|
||||
await explorer.allocateContainer(PoolIdType.QueryCopilot);
|
||||
setShouldAllocateContainer(false);
|
||||
}
|
||||
|
||||
setIsGeneratingQuery(true);
|
||||
useTabs.getState().setIsTabExecuting(true);
|
||||
useTabs.getState().setIsQueryErrorThrown(false);
|
||||
const payload = {
|
||||
containerSchema: QueryCopilotSampleContainerSchema,
|
||||
containerSchema: userContext.features.enableCopilotFullSchema
|
||||
? QueryCopilotSampleContainerSchema
|
||||
: ShortenedQueryCopilotSampleContainerSchema,
|
||||
userPrompt: userPrompt,
|
||||
};
|
||||
setShowDeletePopup(false);
|
||||
useQueryCopilot.getState().refreshCorrelationId();
|
||||
const response = await fetch("https://copilotorchestrater.azurewebsites.net/generateSQLQuery", {
|
||||
const serverInfo = useNotebook.getState().notebookServerInfo;
|
||||
const queryUri = userContext.features.enableCopilotPhoenixGateaway
|
||||
? createUri(serverInfo.notebookServerEndpoint, "generateSQLQuery")
|
||||
: createUri("https://copilotorchestrater.azurewebsites.net/", "generateSQLQuery");
|
||||
const response = await fetch(queryUri, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
@@ -184,6 +210,9 @@ export const QueryCopilotTab: React.FC<QueryCopilotTabProps> = ({
|
||||
});
|
||||
|
||||
const generateSQLQueryResponse: GenerateSQLQueryResponse = await response?.json();
|
||||
if (response.status === 404) {
|
||||
setShouldAllocateContainer(true);
|
||||
}
|
||||
if (response.ok) {
|
||||
if (generateSQLQueryResponse?.sql) {
|
||||
let query = `-- **Prompt:** ${userPrompt}\r\n`;
|
||||
@@ -301,9 +330,10 @@ export const QueryCopilotTab: React.FC<QueryCopilotTabProps> = ({
|
||||
return [executeQueryBtn, saveQueryBtn];
|
||||
};
|
||||
const showTeachingBubble = (): void => {
|
||||
if (!inputEdited.current) {
|
||||
const shouldShowTeachingBubble = !inputEdited.current && userPrompt.trim() === "";
|
||||
if (shouldShowTeachingBubble) {
|
||||
setTimeout(() => {
|
||||
if (!inputEdited.current) {
|
||||
if (shouldShowTeachingBubble) {
|
||||
toggleCopilotTeachingBubbleVisible();
|
||||
inputEdited.current = true;
|
||||
}
|
||||
@@ -328,16 +358,13 @@ export const QueryCopilotTab: React.FC<QueryCopilotTabProps> = ({
|
||||
}, [query, selectedQuery]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (initialInput) {
|
||||
generateSQLQuery();
|
||||
}
|
||||
showTeachingBubble();
|
||||
useTabs.getState().setIsQueryErrorThrown(false);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Stack className="tab-pane" style={{ padding: 24, width: "100%" }}>
|
||||
<div style={{ overflowY: "auto", height: "100%" }}>
|
||||
<div style={isGeneratingQuery ? { height: "100%" } : { overflowY: "auto", height: "100%" }}>
|
||||
<Stack horizontal verticalAlign="center">
|
||||
<Image src={CopilotIcon} />
|
||||
<Text style={{ marginLeft: 8, fontWeight: 600, fontSize: 16 }}>Copilot</Text>
|
||||
@@ -517,7 +544,15 @@ export const QueryCopilotTab: React.FC<QueryCopilotTabProps> = ({
|
||||
target="#likeBtn"
|
||||
onDismiss={() => {
|
||||
setShowCallout(false);
|
||||
submitFeedback({ generatedQuery, likeQuery, description: "", userPrompt: userPrompt });
|
||||
SubmitFeedback({
|
||||
params: {
|
||||
generatedQuery: generatedQuery,
|
||||
likeQuery: likeQuery,
|
||||
description: "",
|
||||
userPrompt: userPrompt,
|
||||
},
|
||||
explorer: explorer,
|
||||
});
|
||||
}}
|
||||
directionalHint={DirectionalHint.topCenter}
|
||||
>
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { FeedOptions } from "@azure/cosmos";
|
||||
import { QueryCopilotSampleContainerSchema } from "Common/Constants";
|
||||
import { handleError } from "Common/ErrorHandlingUtils";
|
||||
import { sampleDataClient } from "Common/SampleDataClient";
|
||||
import * as commonUtils from "Common/dataAccess/queryDocuments";
|
||||
import DocumentId from "Explorer/Tree/DocumentId";
|
||||
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
||||
import { querySampleDocuments, readSampleDocument, submitFeedback } from "./QueryCopilotUtilities";
|
||||
import { querySampleDocuments, readSampleDocument } from "./QueryCopilotUtilities";
|
||||
|
||||
jest.mock("Explorer/Tree/DocumentId", () => {
|
||||
return jest.fn().mockImplementation(() => {
|
||||
return {
|
||||
@@ -16,23 +15,21 @@ jest.mock("Explorer/Tree/DocumentId", () => {
|
||||
});
|
||||
|
||||
jest.mock("Utils/NotificationConsoleUtils", () => ({
|
||||
logConsoleProgress: jest.fn(),
|
||||
logConsoleProgress: jest.fn().mockReturnValue((): void => undefined),
|
||||
logConsoleError: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock("@azure/cosmos", () => ({
|
||||
FeedOptions: jest.fn(),
|
||||
QueryIterator: jest.fn(),
|
||||
Constants: {
|
||||
HttpHeaders: {},
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock("Common/ErrorHandlingUtils", () => ({
|
||||
handleError: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock("Utils/NotificationConsoleUtils", () => ({
|
||||
logConsoleProgress: jest.fn().mockReturnValue((): void => undefined),
|
||||
}));
|
||||
|
||||
jest.mock("Common/dataAccess/queryDocuments", () => ({
|
||||
getCommonQueryOptions: jest.fn((options) => options),
|
||||
}));
|
||||
@@ -41,95 +38,27 @@ jest.mock("Common/SampleDataClient");
|
||||
|
||||
jest.mock("node-fetch");
|
||||
|
||||
jest.mock("Explorer/Explorer", () => {
|
||||
class MockExplorer {
|
||||
allocateContainer = jest.fn().mockResolvedValueOnce({});
|
||||
}
|
||||
return MockExplorer;
|
||||
});
|
||||
|
||||
jest.mock("hooks/useQueryCopilot", () => {
|
||||
const mockQueryCopilotStore = {
|
||||
shouldAllocateContainer: true,
|
||||
setShouldAllocateContainer: jest.fn(),
|
||||
correlationId: "mocked-correlation-id",
|
||||
};
|
||||
|
||||
return {
|
||||
useQueryCopilot: jest.fn(() => mockQueryCopilotStore),
|
||||
};
|
||||
});
|
||||
|
||||
describe("QueryCopilotUtilities", () => {
|
||||
beforeEach(() => jest.clearAllMocks());
|
||||
describe("submitFeedback", () => {
|
||||
const payload = {
|
||||
like: "like",
|
||||
generatedSql: "GeneratedQuery",
|
||||
userPrompt: "UserPrompt",
|
||||
description: "Description",
|
||||
contact: "Contact",
|
||||
containerSchema: QueryCopilotSampleContainerSchema,
|
||||
};
|
||||
|
||||
it("should call fetch with the payload with like", async () => {
|
||||
const mockFetch = jest.fn().mockResolvedValueOnce({});
|
||||
|
||||
globalThis.fetch = mockFetch;
|
||||
useQueryCopilot.getState().refreshCorrelationId();
|
||||
|
||||
await submitFeedback({
|
||||
likeQuery: true,
|
||||
generatedQuery: "GeneratedQuery",
|
||||
userPrompt: "UserPrompt",
|
||||
description: "Description",
|
||||
contact: "Contact",
|
||||
});
|
||||
|
||||
expect(mockFetch).toHaveBeenCalledWith(
|
||||
"https://copilotorchestrater.azurewebsites.net/feedback",
|
||||
expect.objectContaining({
|
||||
method: "POST",
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
"x-ms-correlationid": useQueryCopilot.getState().correlationId,
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
const actualBody = JSON.parse(mockFetch.mock.calls[0][1].body);
|
||||
expect(actualBody).toEqual(payload);
|
||||
});
|
||||
|
||||
it("should call fetch with the payload with unlike and empty parameters", async () => {
|
||||
payload.like = "dislike";
|
||||
payload.description = "";
|
||||
payload.contact = "";
|
||||
const mockFetch = jest.fn().mockResolvedValueOnce({});
|
||||
|
||||
globalThis.fetch = mockFetch;
|
||||
useQueryCopilot.getState().refreshCorrelationId();
|
||||
|
||||
await submitFeedback({
|
||||
likeQuery: false,
|
||||
generatedQuery: "GeneratedQuery",
|
||||
userPrompt: "UserPrompt",
|
||||
description: undefined,
|
||||
contact: undefined,
|
||||
});
|
||||
|
||||
expect(mockFetch).toHaveBeenCalledWith(
|
||||
"https://copilotorchestrater.azurewebsites.net/feedback",
|
||||
expect.objectContaining({
|
||||
method: "POST",
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
"x-ms-correlationid": useQueryCopilot.getState().correlationId,
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
const actualBody = JSON.parse(mockFetch.mock.calls[0][1].body);
|
||||
expect(actualBody).toEqual(payload);
|
||||
});
|
||||
|
||||
it("should handle errors and call handleError", async () => {
|
||||
globalThis.fetch = jest.fn().mockRejectedValueOnce(new Error("Mock error"));
|
||||
|
||||
await submitFeedback({
|
||||
likeQuery: true,
|
||||
generatedQuery: "GeneratedQuery",
|
||||
userPrompt: "UserPrompt",
|
||||
description: "Description",
|
||||
contact: "Contact",
|
||||
}).catch((error) => {
|
||||
expect(error.message).toEqual("Mock error");
|
||||
});
|
||||
|
||||
expect(handleError).toHaveBeenCalledWith(new Error("Mock error"), expect.any(String));
|
||||
});
|
||||
});
|
||||
|
||||
describe("querySampleDocuments", () => {
|
||||
(sampleDataClient as jest.Mock).mockReturnValue({
|
||||
|
||||
@@ -1,49 +1,11 @@
|
||||
import { FeedOptions, Item, ItemDefinition, QueryIterator, Resource } from "@azure/cosmos";
|
||||
import {
|
||||
QueryCopilotSampleContainerId,
|
||||
QueryCopilotSampleContainerSchema,
|
||||
QueryCopilotSampleDatabaseId,
|
||||
} from "Common/Constants";
|
||||
import { QueryCopilotSampleContainerId, QueryCopilotSampleDatabaseId } from "Common/Constants";
|
||||
import { handleError } from "Common/ErrorHandlingUtils";
|
||||
import { sampleDataClient } from "Common/SampleDataClient";
|
||||
import { getPartitionKeyValue } from "Common/dataAccess/getPartitionKeyValue";
|
||||
import { getCommonQueryOptions } from "Common/dataAccess/queryDocuments";
|
||||
import DocumentId from "Explorer/Tree/DocumentId";
|
||||
import { logConsoleProgress } from "Utils/NotificationConsoleUtils";
|
||||
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
||||
|
||||
interface FeedbackParams {
|
||||
likeQuery: boolean;
|
||||
generatedQuery: string;
|
||||
userPrompt: string;
|
||||
description?: string;
|
||||
contact?: string;
|
||||
}
|
||||
|
||||
export const submitFeedback = async (params: FeedbackParams): Promise<void> => {
|
||||
try {
|
||||
const { likeQuery, generatedQuery, userPrompt, description, contact } = params;
|
||||
const payload = {
|
||||
containerSchema: QueryCopilotSampleContainerSchema,
|
||||
like: likeQuery ? "like" : "dislike",
|
||||
generatedSql: generatedQuery,
|
||||
userPrompt,
|
||||
description: description || "",
|
||||
contact: contact || "",
|
||||
};
|
||||
|
||||
await fetch("https://copilotorchestrater.azurewebsites.net/feedback", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
"x-ms-correlationid": useQueryCopilot.getState().correlationId,
|
||||
},
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
} catch (error) {
|
||||
handleError(error, "copilotSubmitFeedback");
|
||||
}
|
||||
};
|
||||
|
||||
export const querySampleDocuments = (query: string, options: FeedOptions): QueryIterator<ItemDefinition & Resource> => {
|
||||
options = getCommonQueryOptions(options);
|
||||
|
||||
151
src/Explorer/QueryCopilot/Shared/QueryCopilotClient.test.ts
Normal file
@@ -0,0 +1,151 @@
|
||||
import { QueryCopilotSampleContainerSchema, ShortenedQueryCopilotSampleContainerSchema } from "Common/Constants";
|
||||
import { handleError } from "Common/ErrorHandlingUtils";
|
||||
import { createUri } from "Common/UrlUtility";
|
||||
import Explorer from "Explorer/Explorer";
|
||||
import { useNotebook } from "Explorer/Notebook/useNotebook";
|
||||
import { SubmitFeedback } from "Explorer/QueryCopilot/Shared/QueryCopilotClient";
|
||||
import { userContext } from "UserContext";
|
||||
|
||||
jest.mock("@azure/cosmos", () => ({
|
||||
Constants: {
|
||||
HttpHeaders: {},
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock("Common/ErrorHandlingUtils", () => ({
|
||||
handleError: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock("Common/SampleDataClient");
|
||||
|
||||
jest.mock("node-fetch");
|
||||
|
||||
jest.mock("Explorer/Explorer", () => {
|
||||
class MockExplorer {
|
||||
allocateContainer = jest.fn().mockResolvedValueOnce({});
|
||||
}
|
||||
return MockExplorer;
|
||||
});
|
||||
|
||||
jest.mock("hooks/useQueryCopilot", () => {
|
||||
const mockQueryCopilotStore = {
|
||||
shouldAllocateContainer: true,
|
||||
setShouldAllocateContainer: jest.fn(),
|
||||
correlationId: "mocked-correlation-id",
|
||||
};
|
||||
|
||||
return {
|
||||
useQueryCopilot: jest.fn(() => mockQueryCopilotStore),
|
||||
};
|
||||
});
|
||||
|
||||
describe("Query Copilot Client", () => {
|
||||
beforeEach(() => jest.clearAllMocks());
|
||||
|
||||
describe("submitFeedback", () => {
|
||||
const payload = {
|
||||
like: "like",
|
||||
generatedSql: "GeneratedQuery",
|
||||
userPrompt: "UserPrompt",
|
||||
description: "Description",
|
||||
contact: "Contact",
|
||||
containerSchema: userContext.features.enableCopilotFullSchema
|
||||
? QueryCopilotSampleContainerSchema
|
||||
: ShortenedQueryCopilotSampleContainerSchema,
|
||||
};
|
||||
|
||||
const mockStore = useNotebook.getState();
|
||||
beforeEach(() => {
|
||||
mockStore.notebookServerInfo = {
|
||||
notebookServerEndpoint: "mocked-endpoint",
|
||||
authToken: "mocked-token",
|
||||
forwardingId: "mocked-forwarding-id",
|
||||
};
|
||||
});
|
||||
|
||||
const feedbackUri = userContext.features.enableCopilotPhoenixGateaway
|
||||
? createUri(useNotebook.getState().notebookServerInfo.notebookServerEndpoint, "feedback")
|
||||
: createUri("https://copilotorchestrater.azurewebsites.net/", "feedback");
|
||||
|
||||
it("should call fetch with the payload with like", async () => {
|
||||
const mockFetch = jest.fn().mockResolvedValueOnce({});
|
||||
|
||||
globalThis.fetch = mockFetch;
|
||||
await SubmitFeedback({
|
||||
params: {
|
||||
likeQuery: true,
|
||||
generatedQuery: "GeneratedQuery",
|
||||
userPrompt: "UserPrompt",
|
||||
description: "Description",
|
||||
contact: "Contact",
|
||||
},
|
||||
explorer: new Explorer(),
|
||||
});
|
||||
|
||||
expect(mockFetch).toHaveBeenCalledWith(
|
||||
feedbackUri,
|
||||
expect.objectContaining({
|
||||
headers: expect.objectContaining({
|
||||
"x-ms-correlationid": "mocked-correlation-id",
|
||||
}),
|
||||
})
|
||||
);
|
||||
|
||||
const actualBody = JSON.parse(mockFetch.mock.calls[0][1].body);
|
||||
expect(actualBody).toEqual(payload);
|
||||
});
|
||||
|
||||
it("should call fetch with the payload with unlike and empty parameters", async () => {
|
||||
payload.like = "dislike";
|
||||
payload.description = "";
|
||||
payload.contact = "";
|
||||
const mockFetch = jest.fn().mockResolvedValueOnce({});
|
||||
|
||||
globalThis.fetch = mockFetch;
|
||||
|
||||
await SubmitFeedback({
|
||||
params: {
|
||||
likeQuery: false,
|
||||
generatedQuery: "GeneratedQuery",
|
||||
userPrompt: "UserPrompt",
|
||||
description: undefined,
|
||||
contact: undefined,
|
||||
},
|
||||
explorer: new Explorer(),
|
||||
});
|
||||
|
||||
expect(mockFetch).toHaveBeenCalledWith(
|
||||
feedbackUri,
|
||||
expect.objectContaining({
|
||||
method: "POST",
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
"x-ms-correlationid": "mocked-correlation-id",
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
const actualBody = JSON.parse(mockFetch.mock.calls[0][1].body);
|
||||
expect(actualBody).toEqual(payload);
|
||||
});
|
||||
|
||||
it("should handle errors and call handleError", async () => {
|
||||
globalThis.fetch = jest.fn().mockRejectedValueOnce(new Error("Mock error"));
|
||||
|
||||
await SubmitFeedback({
|
||||
params: {
|
||||
likeQuery: true,
|
||||
generatedQuery: "GeneratedQuery",
|
||||
userPrompt: "UserPrompt",
|
||||
description: "Description",
|
||||
contact: "Contact",
|
||||
},
|
||||
explorer: new Explorer(),
|
||||
}).catch((error) => {
|
||||
expect(error.message).toEqual("Mock error");
|
||||
});
|
||||
|
||||
expect(handleError).toHaveBeenCalledWith(new Error("Mock error"), expect.any(String));
|
||||
});
|
||||
});
|
||||
});
|
||||
132
src/Explorer/QueryCopilot/Shared/QueryCopilotClient.ts
Normal file
@@ -0,0 +1,132 @@
|
||||
import {
|
||||
PoolIdType,
|
||||
QueryCopilotSampleContainerSchema,
|
||||
ShortenedQueryCopilotSampleContainerSchema,
|
||||
} from "Common/Constants";
|
||||
import { handleError } from "Common/ErrorHandlingUtils";
|
||||
import { createUri } from "Common/UrlUtility";
|
||||
import Explorer from "Explorer/Explorer";
|
||||
import { useNotebook } from "Explorer/Notebook/useNotebook";
|
||||
import { FeedbackParams, GenerateSQLQueryResponse } from "Explorer/QueryCopilot/Shared/QueryCopilotInterfaces";
|
||||
import { userContext } from "UserContext";
|
||||
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
||||
import { useTabs } from "hooks/useTabs";
|
||||
|
||||
export const SendQueryRequest = async ({
|
||||
userPrompt,
|
||||
explorer,
|
||||
}: {
|
||||
userPrompt: string;
|
||||
explorer: Explorer;
|
||||
}): Promise<void> => {
|
||||
if (userPrompt.trim() !== "") {
|
||||
useQueryCopilot.getState().setIsGeneratingQuery(true);
|
||||
useTabs.getState().setIsTabExecuting(true);
|
||||
useTabs.getState().setIsQueryErrorThrown(false);
|
||||
useQueryCopilot
|
||||
.getState()
|
||||
.setChatMessages([...useQueryCopilot.getState().chatMessages, { source: 0, message: userPrompt }]);
|
||||
try {
|
||||
if (useQueryCopilot.getState().shouldAllocateContainer && userContext.features.enableCopilotPhoenixGateaway) {
|
||||
await explorer.allocateContainer(PoolIdType.DefaultPoolId);
|
||||
useQueryCopilot.getState().setShouldAllocateContainer(false);
|
||||
}
|
||||
|
||||
useQueryCopilot.getState().refreshCorrelationId();
|
||||
const serverInfo = useNotebook.getState().notebookServerInfo;
|
||||
|
||||
const queryUri = userContext.features.enableCopilotPhoenixGateaway
|
||||
? createUri(serverInfo.notebookServerEndpoint, "generateSQLQuery")
|
||||
: createUri("https://copilotorchestrater.azurewebsites.net/", "generateSQLQuery");
|
||||
|
||||
const payload = {
|
||||
containerSchema: userContext.features.enableCopilotFullSchema
|
||||
? QueryCopilotSampleContainerSchema
|
||||
: ShortenedQueryCopilotSampleContainerSchema,
|
||||
userPrompt: userPrompt,
|
||||
};
|
||||
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.status === 404) {
|
||||
useQueryCopilot.getState().setShouldAllocateContainer(true);
|
||||
}
|
||||
if (response.ok) {
|
||||
if (generateSQLQueryResponse?.sql) {
|
||||
let query = `Here is a query which will help you with provided prompt.\r\n **Prompt:** ${userPrompt}`;
|
||||
query += `\r\n${generateSQLQueryResponse.sql}`;
|
||||
useQueryCopilot
|
||||
.getState()
|
||||
.setChatMessages([
|
||||
...useQueryCopilot.getState().chatMessages,
|
||||
{ source: 1, message: query, explanation: generateSQLQueryResponse.explanation },
|
||||
]);
|
||||
useQueryCopilot.getState().setGeneratedQuery(generateSQLQueryResponse.sql);
|
||||
useQueryCopilot.getState().setGeneratedQueryComments(generateSQLQueryResponse.explanation);
|
||||
}
|
||||
} else {
|
||||
handleError(JSON.stringify(generateSQLQueryResponse), "copilotInternalServerError");
|
||||
useTabs.getState().setIsQueryErrorThrown(true);
|
||||
}
|
||||
} catch (error) {
|
||||
handleError(error, "executeNaturalLanguageQuery");
|
||||
useTabs.getState().setIsQueryErrorThrown(true);
|
||||
throw error;
|
||||
} finally {
|
||||
useQueryCopilot.getState().setUserPrompt("");
|
||||
useQueryCopilot.getState().setIsGeneratingQuery(false);
|
||||
useTabs.getState().setIsTabExecuting(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const SubmitFeedback = async ({
|
||||
params,
|
||||
explorer,
|
||||
}: {
|
||||
params: FeedbackParams;
|
||||
explorer: Explorer;
|
||||
}): Promise<void> => {
|
||||
try {
|
||||
const { likeQuery, generatedQuery, userPrompt, description, contact } = params;
|
||||
const { correlationId, shouldAllocateContainer, setShouldAllocateContainer } = useQueryCopilot();
|
||||
const payload = {
|
||||
containerSchema: userContext.features.enableCopilotFullSchema
|
||||
? QueryCopilotSampleContainerSchema
|
||||
: ShortenedQueryCopilotSampleContainerSchema,
|
||||
like: likeQuery ? "like" : "dislike",
|
||||
generatedSql: generatedQuery,
|
||||
userPrompt,
|
||||
description: description || "",
|
||||
contact: contact || "",
|
||||
};
|
||||
if (shouldAllocateContainer && userContext.features.enableCopilotPhoenixGateaway) {
|
||||
await explorer.allocateContainer(PoolIdType.DefaultPoolId);
|
||||
setShouldAllocateContainer(false);
|
||||
}
|
||||
const serverInfo = useNotebook.getState().notebookServerInfo;
|
||||
const feedbackUri = userContext.features.enableCopilotPhoenixGateaway
|
||||
? createUri(serverInfo.notebookServerEndpoint, "feedback")
|
||||
: createUri("https://copilotorchestrater.azurewebsites.net/", "feedback");
|
||||
const response = await fetch(feedbackUri, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
"x-ms-correlationid": correlationId,
|
||||
},
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
if (response.status === 404) {
|
||||
setShouldAllocateContainer(true);
|
||||
}
|
||||
} catch (error) {
|
||||
handleError(error, "copilotSubmitFeedback");
|
||||
}
|
||||
};
|
||||
32
src/Explorer/QueryCopilot/Shared/QueryCopilotInterfaces.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import Explorer from "Explorer/Explorer";
|
||||
|
||||
export interface GenerateSQLQueryResponse {
|
||||
apiVersion: string;
|
||||
sql: string;
|
||||
explanation: string;
|
||||
generateStart: string;
|
||||
generateEnd: string;
|
||||
}
|
||||
|
||||
enum MessageSource {
|
||||
User,
|
||||
AI,
|
||||
}
|
||||
|
||||
export interface CopilotMessage {
|
||||
source: MessageSource;
|
||||
message: string;
|
||||
explanation?: string;
|
||||
}
|
||||
|
||||
export interface FeedbackParams {
|
||||
likeQuery: boolean;
|
||||
generatedQuery: string;
|
||||
userPrompt: string;
|
||||
description?: string;
|
||||
contact?: string;
|
||||
}
|
||||
|
||||
export interface QueryCopilotProps {
|
||||
explorer: Explorer;
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
import { DefaultButton, FontIcon, IconButton, Image, Modal, Stack, Text } from "@fluentui/react";
|
||||
import React, { Dispatch, SetStateAction } from "react";
|
||||
import ComplexPrompts from "../../../../images/ComplexPrompts.svg";
|
||||
import IntermediatePrompts from "../../../../images/IntermediatePrompts.svg";
|
||||
import SimplePrompts from "../../../../images/SimplePrompts.svg";
|
||||
import ComplexPrompts from "../../../../../images/ComplexPrompts.svg";
|
||||
import IntermediatePrompts from "../../../../../images/IntermediatePrompts.svg";
|
||||
import SimplePrompts from "../../../../../images/SimplePrompts.svg";
|
||||
|
||||
export interface SamplePromptsProps {
|
||||
isSamplePromptsOpen: boolean;
|
||||
@@ -0,0 +1,8 @@
|
||||
@keyframes loadingAnimation {
|
||||
0% {
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
100% {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import { DefaultButton } from "@fluentui/react";
|
||||
import { shallow } from "enzyme";
|
||||
import React from "react";
|
||||
import { RetrievingBubble } from "./RetrievingBubble";
|
||||
|
||||
const mockUseQueryCopilot = {
|
||||
isGeneratingQuery: false,
|
||||
setIsGeneratingQuery: jest.fn(),
|
||||
isGeneratingExplanation: false,
|
||||
setIsGeneratingExplanation: jest.fn(),
|
||||
shouldIncludeInMessages: true,
|
||||
setShouldIncludeInMessages: jest.fn(),
|
||||
};
|
||||
|
||||
jest.mock("hooks/useQueryCopilot", () => ({
|
||||
useQueryCopilot: jest.fn(() => mockUseQueryCopilot),
|
||||
}));
|
||||
|
||||
describe("RetrievingBubble", () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockUseQueryCopilot.isGeneratingQuery = false;
|
||||
mockUseQueryCopilot.isGeneratingExplanation = false;
|
||||
mockUseQueryCopilot.setIsGeneratingQuery.mockClear();
|
||||
mockUseQueryCopilot.setIsGeneratingExplanation.mockClear();
|
||||
mockUseQueryCopilot.setShouldIncludeInMessages.mockClear();
|
||||
});
|
||||
|
||||
it("should render properly when isGeneratingQuery is true", () => {
|
||||
mockUseQueryCopilot.isGeneratingQuery = true;
|
||||
const wrapper = shallow(<RetrievingBubble />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should render properly when isGeneratingExplanation is true", () => {
|
||||
mockUseQueryCopilot.isGeneratingExplanation = true;
|
||||
const wrapper = shallow(<RetrievingBubble />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("when isGeneratingQuery is true clicking stop generating button invokes the correct callbacks", () => {
|
||||
mockUseQueryCopilot.isGeneratingQuery = true;
|
||||
|
||||
const wrapper = shallow(<RetrievingBubble />);
|
||||
wrapper.find(DefaultButton).at(0).simulate("click");
|
||||
|
||||
expect(mockUseQueryCopilot.setIsGeneratingQuery).toHaveBeenCalledWith(false);
|
||||
expect(mockUseQueryCopilot.setIsGeneratingExplanation).toHaveBeenCalledTimes(0);
|
||||
expect(mockUseQueryCopilot.setShouldIncludeInMessages).toHaveBeenCalledWith(false);
|
||||
});
|
||||
|
||||
it("when isGeneratingExplanation is true clicking stop generating button invokes the correct callbacks", () => {
|
||||
mockUseQueryCopilot.isGeneratingExplanation = true;
|
||||
|
||||
const wrapper = shallow(<RetrievingBubble />);
|
||||
wrapper.find(DefaultButton).at(0).simulate("click");
|
||||
|
||||
expect(mockUseQueryCopilot.setIsGeneratingQuery).toHaveBeenCalledTimes(0);
|
||||
expect(mockUseQueryCopilot.setIsGeneratingExplanation).toHaveBeenCalledWith(false);
|
||||
expect(mockUseQueryCopilot.setShouldIncludeInMessages).toHaveBeenCalledWith(false);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,98 @@
|
||||
import { DefaultButton, Image, Stack, Text } from "@fluentui/react";
|
||||
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
||||
import React from "react";
|
||||
import StopGeneratingIcon from "../../../../../../images/StopGenerating.svg";
|
||||
import "./RetrievingBubble.css";
|
||||
|
||||
export const RetrievingBubble = (): JSX.Element => {
|
||||
const {
|
||||
isGeneratingQuery,
|
||||
setIsGeneratingQuery,
|
||||
isGeneratingExplanation,
|
||||
setIsGeneratingExplanation,
|
||||
shouldIncludeInMessages,
|
||||
setShouldIncludeInMessages,
|
||||
} = useQueryCopilot();
|
||||
|
||||
const stopGenerating = () => {
|
||||
if (isGeneratingQuery) {
|
||||
setIsGeneratingQuery(false);
|
||||
}
|
||||
if (isGeneratingExplanation) {
|
||||
setIsGeneratingExplanation(false);
|
||||
}
|
||||
if (shouldIncludeInMessages) {
|
||||
setShouldIncludeInMessages(false);
|
||||
}
|
||||
};
|
||||
|
||||
const bubbleContent = (bubbleType: string) => {
|
||||
return (
|
||||
<Stack
|
||||
horizontalAlign="end"
|
||||
verticalAlign="end"
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
padding: "10px",
|
||||
margin: "10px",
|
||||
backgroundColor: "#FAFAFA",
|
||||
borderRadius: "8px",
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "46px",
|
||||
backgroundColor: "white",
|
||||
padding: "12px 16px 16px 16px",
|
||||
gap: "12px",
|
||||
borderRadius: "8px",
|
||||
fontWeight: "bold",
|
||||
}}
|
||||
>
|
||||
Retriveing {bubbleType}
|
||||
</Text>
|
||||
<div
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "4px",
|
||||
backgroundColor: "#E6E6E6",
|
||||
borderRadius: "4px",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
width: "50%",
|
||||
height: "100%",
|
||||
backgroundColor: "#0078D4",
|
||||
animation: "loadingAnimation 2s linear infinite",
|
||||
}}
|
||||
></div>
|
||||
</div>
|
||||
<Stack
|
||||
horizontalAlign="center"
|
||||
verticalAlign="center"
|
||||
style={{ marginTop: "8px", gap: "8px", alignItems: "center" }}
|
||||
>
|
||||
<DefaultButton
|
||||
onClick={stopGenerating}
|
||||
styles={{ root: { border: "none", background: "none", padding: 0, color: "#424242" } }}
|
||||
style={{ color: "#424242" }}
|
||||
onRenderIcon={() => <Image src={StopGeneratingIcon} />}
|
||||
>
|
||||
Stop generating
|
||||
</DefaultButton>
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{isGeneratingQuery && bubbleContent("queries")}
|
||||
{isGeneratingExplanation && bubbleContent("explanation")}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,183 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`RetrievingBubble should render properly when isGeneratingExplanation is true 1`] = `
|
||||
<Fragment>
|
||||
<Stack
|
||||
horizontalAlign="end"
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"backgroundColor": "#FAFAFA",
|
||||
"borderRadius": "8px",
|
||||
"display": "flex",
|
||||
"margin": "10px",
|
||||
"padding": "10px",
|
||||
}
|
||||
}
|
||||
verticalAlign="end"
|
||||
>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "white",
|
||||
"borderRadius": "8px",
|
||||
"fontWeight": "bold",
|
||||
"gap": "12px",
|
||||
"height": "46px",
|
||||
"padding": "12px 16px 16px 16px",
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
Retriveing
|
||||
explanation
|
||||
</Text>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#E6E6E6",
|
||||
"borderRadius": "4px",
|
||||
"height": "4px",
|
||||
"overflow": "hidden",
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"animation": "loadingAnimation 2s linear infinite",
|
||||
"backgroundColor": "#0078D4",
|
||||
"height": "100%",
|
||||
"width": "50%",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<Stack
|
||||
horizontalAlign="center"
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"gap": "8px",
|
||||
"marginTop": "8px",
|
||||
}
|
||||
}
|
||||
verticalAlign="center"
|
||||
>
|
||||
<CustomizedDefaultButton
|
||||
onClick={[Function]}
|
||||
onRenderIcon={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"color": "#424242",
|
||||
}
|
||||
}
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
"background": "none",
|
||||
"border": "none",
|
||||
"color": "#424242",
|
||||
"padding": 0,
|
||||
},
|
||||
}
|
||||
}
|
||||
>
|
||||
Stop generating
|
||||
</CustomizedDefaultButton>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Fragment>
|
||||
`;
|
||||
|
||||
exports[`RetrievingBubble should render properly when isGeneratingQuery is true 1`] = `
|
||||
<Fragment>
|
||||
<Stack
|
||||
horizontalAlign="end"
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"backgroundColor": "#FAFAFA",
|
||||
"borderRadius": "8px",
|
||||
"display": "flex",
|
||||
"margin": "10px",
|
||||
"padding": "10px",
|
||||
}
|
||||
}
|
||||
verticalAlign="end"
|
||||
>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "white",
|
||||
"borderRadius": "8px",
|
||||
"fontWeight": "bold",
|
||||
"gap": "12px",
|
||||
"height": "46px",
|
||||
"padding": "12px 16px 16px 16px",
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
Retriveing
|
||||
queries
|
||||
</Text>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#E6E6E6",
|
||||
"borderRadius": "4px",
|
||||
"height": "4px",
|
||||
"overflow": "hidden",
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"animation": "loadingAnimation 2s linear infinite",
|
||||
"backgroundColor": "#0078D4",
|
||||
"height": "100%",
|
||||
"width": "50%",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<Stack
|
||||
horizontalAlign="center"
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"gap": "8px",
|
||||
"marginTop": "8px",
|
||||
}
|
||||
}
|
||||
verticalAlign="center"
|
||||
>
|
||||
<CustomizedDefaultButton
|
||||
onClick={[Function]}
|
||||
onRenderIcon={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"color": "#424242",
|
||||
}
|
||||
}
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
"background": "none",
|
||||
"border": "none",
|
||||
"color": "#424242",
|
||||
"padding": 0,
|
||||
},
|
||||
}
|
||||
}
|
||||
>
|
||||
Stop generating
|
||||
</CustomizedDefaultButton>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Fragment>
|
||||
`;
|
||||
@@ -0,0 +1,26 @@
|
||||
import { Text } from "@fluentui/react";
|
||||
import { shallow } from "enzyme";
|
||||
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
||||
import React from "react";
|
||||
import { SampleBubble } from "./SampleBubble";
|
||||
|
||||
describe("Sample Bubble snapshot test", () => {
|
||||
it("should render", () => {
|
||||
const wrapper = shallow(<SampleBubble />);
|
||||
|
||||
const sampleInputs = wrapper.find(Text);
|
||||
|
||||
expect(sampleInputs.length).toEqual(2);
|
||||
expect(useQueryCopilot.getState().userPrompt).toEqual("");
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
it("should render and be clicked", () => {
|
||||
const wrapper = shallow(<SampleBubble />);
|
||||
|
||||
const firstSampleInput = wrapper.find(Text).first();
|
||||
firstSampleInput.simulate("click", {}, "");
|
||||
|
||||
expect(useQueryCopilot.getState().userPrompt).toEqual(expect.any(String));
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
42
src/Explorer/QueryCopilot/V2/Bubbles/Sample/SampleBubble.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
import { Stack, Text } from "@fluentui/react";
|
||||
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
||||
import React from "react";
|
||||
|
||||
export const SampleBubble: React.FC = (): JSX.Element => {
|
||||
const { setUserPrompt } = useQueryCopilot();
|
||||
|
||||
const sampleChatMessages: string[] = [
|
||||
"Write a query to return last 10 records in the database",
|
||||
'Write a query to return all records in this table created in the last thirty days which also have the record owner as "Contoso"',
|
||||
];
|
||||
|
||||
return (
|
||||
<Stack
|
||||
horizontalAlign="end"
|
||||
verticalAlign="end"
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
padding: "10px",
|
||||
margin: "10px",
|
||||
}}
|
||||
>
|
||||
{sampleChatMessages.map((text, index) => (
|
||||
<Text
|
||||
key={index}
|
||||
onClick={() => setUserPrompt(text)}
|
||||
style={{
|
||||
cursor: "pointer",
|
||||
border: "1.5px solid #B0BEFF",
|
||||
width: "100%",
|
||||
padding: "2px",
|
||||
borderRadius: "4px",
|
||||
marginBottom: "5px",
|
||||
}}
|
||||
>
|
||||
{text}
|
||||
</Text>
|
||||
))}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,97 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Sample Bubble snapshot test should render 1`] = `
|
||||
<Stack
|
||||
horizontalAlign="end"
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"display": "flex",
|
||||
"margin": "10px",
|
||||
"padding": "10px",
|
||||
}
|
||||
}
|
||||
verticalAlign="end"
|
||||
>
|
||||
<Text
|
||||
key="0"
|
||||
onClick={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"border": "1.5px solid #B0BEFF",
|
||||
"borderRadius": "4px",
|
||||
"cursor": "pointer",
|
||||
"marginBottom": "5px",
|
||||
"padding": "2px",
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
Write a query to return last 10 records in the database
|
||||
</Text>
|
||||
<Text
|
||||
key="1"
|
||||
onClick={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"border": "1.5px solid #B0BEFF",
|
||||
"borderRadius": "4px",
|
||||
"cursor": "pointer",
|
||||
"marginBottom": "5px",
|
||||
"padding": "2px",
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
Write a query to return all records in this table created in the last thirty days which also have the record owner as "Contoso"
|
||||
</Text>
|
||||
</Stack>
|
||||
`;
|
||||
|
||||
exports[`Sample Bubble snapshot test should render and be clicked 1`] = `
|
||||
<Stack
|
||||
horizontalAlign="end"
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"display": "flex",
|
||||
"margin": "10px",
|
||||
"padding": "10px",
|
||||
}
|
||||
}
|
||||
verticalAlign="end"
|
||||
>
|
||||
<Text
|
||||
key="0"
|
||||
onClick={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"border": "1.5px solid #B0BEFF",
|
||||
"borderRadius": "4px",
|
||||
"cursor": "pointer",
|
||||
"marginBottom": "5px",
|
||||
"padding": "2px",
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
Write a query to return last 10 records in the database
|
||||
</Text>
|
||||
<Text
|
||||
key="1"
|
||||
onClick={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"border": "1.5px solid #B0BEFF",
|
||||
"borderRadius": "4px",
|
||||
"cursor": "pointer",
|
||||
"marginBottom": "5px",
|
||||
"padding": "2px",
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
Write a query to return all records in this table created in the last thirty days which also have the record owner as "Contoso"
|
||||
</Text>
|
||||
</Stack>
|
||||
`;
|
||||
@@ -0,0 +1,13 @@
|
||||
import { shallow } from "enzyme";
|
||||
import React from "react";
|
||||
import { WelcomeBubble } from "./WelcomeBubble";
|
||||
|
||||
const mockedDate = new Date(2023, 7, 15);
|
||||
jest.spyOn(global, "Date").mockImplementation(() => (mockedDate as unknown) as string);
|
||||
|
||||
describe("Welcome Bubble snapshot test", () => {
|
||||
it("should render ", () => {
|
||||
const wrapper = shallow(<WelcomeBubble />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,52 @@
|
||||
import { Stack, Text } from "@fluentui/react";
|
||||
import React from "react";
|
||||
|
||||
export const WelcomeBubble: React.FC = (): JSX.Element => {
|
||||
return (
|
||||
<Stack>
|
||||
<Stack horizontalAlign="center" style={{ color: "#707070" }} tokens={{ padding: 8, childrenGap: 8 }}>
|
||||
{new Date().toLocaleDateString("en-US", {
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
})}{" "}
|
||||
{new Date().toLocaleTimeString("en-US", {
|
||||
hour: "numeric",
|
||||
minute: "numeric",
|
||||
hour12: true,
|
||||
})}
|
||||
</Stack>
|
||||
<Stack
|
||||
tokens={{ padding: 16, childrenGap: 12 }}
|
||||
style={{
|
||||
backgroundColor: "white",
|
||||
margin: "5px 10px",
|
||||
borderRadius: "8px",
|
||||
}}
|
||||
>
|
||||
<Text variant="medium">
|
||||
Hello, I am Cosmos Db's copilot assistant. I can help you do the following things:
|
||||
</Text>
|
||||
<Stack tokens={{ childrenGap: 8 }}>
|
||||
<Stack horizontal style={{ marginLeft: "15px" }} tokens={{ childrenGap: 8 }}>
|
||||
<Text style={{ fontSize: "16px", lineHeight: "16px", verticalAlign: "middle" }}>•</Text>
|
||||
<Text style={{ verticalAlign: "middle" }}>Generate queries based upon prompt you suggest</Text>
|
||||
</Stack>
|
||||
<Stack horizontal style={{ marginLeft: "15px" }} tokens={{ childrenGap: 8 }}>
|
||||
<Text style={{ fontSize: "16px", lineHeight: "16px", verticalAlign: "middle" }}>•</Text>
|
||||
<Text style={{ verticalAlign: "middle" }}>
|
||||
Explain and provide alternate queries for a query suggested by you
|
||||
</Text>
|
||||
</Stack>
|
||||
<Stack horizontal style={{ marginLeft: "15px" }} tokens={{ childrenGap: 8 }}>
|
||||
<Text style={{ fontSize: "16px", lineHeight: "16px", verticalAlign: "middle" }}>•</Text>
|
||||
<Text style={{ verticalAlign: "middle" }}>Help answer questions about Cosmos DB</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
<Text variant="medium">
|
||||
To get started, ask me a question or use one of the sample prompts to generate a query. AI-generated content
|
||||
may be incorrect.
|
||||
</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,160 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Welcome Bubble snapshot test should render 1`] = `
|
||||
<Stack>
|
||||
<Stack
|
||||
horizontalAlign="center"
|
||||
style={
|
||||
Object {
|
||||
"color": "#707070",
|
||||
}
|
||||
}
|
||||
tokens={
|
||||
Object {
|
||||
"childrenGap": 8,
|
||||
"padding": 8,
|
||||
}
|
||||
}
|
||||
>
|
||||
August 15
|
||||
|
||||
12:00 AM
|
||||
</Stack>
|
||||
<Stack
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "white",
|
||||
"borderRadius": "8px",
|
||||
"margin": "5px 10px",
|
||||
}
|
||||
}
|
||||
tokens={
|
||||
Object {
|
||||
"childrenGap": 12,
|
||||
"padding": 16,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text
|
||||
variant="medium"
|
||||
>
|
||||
Hello, I am Cosmos Db's copilot assistant. I can help you do the following things:
|
||||
</Text>
|
||||
<Stack
|
||||
tokens={
|
||||
Object {
|
||||
"childrenGap": 8,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Stack
|
||||
horizontal={true}
|
||||
style={
|
||||
Object {
|
||||
"marginLeft": "15px",
|
||||
}
|
||||
}
|
||||
tokens={
|
||||
Object {
|
||||
"childrenGap": 8,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"fontSize": "16px",
|
||||
"lineHeight": "16px",
|
||||
"verticalAlign": "middle",
|
||||
}
|
||||
}
|
||||
>
|
||||
•
|
||||
</Text>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"verticalAlign": "middle",
|
||||
}
|
||||
}
|
||||
>
|
||||
Generate queries based upon prompt you suggest
|
||||
</Text>
|
||||
</Stack>
|
||||
<Stack
|
||||
horizontal={true}
|
||||
style={
|
||||
Object {
|
||||
"marginLeft": "15px",
|
||||
}
|
||||
}
|
||||
tokens={
|
||||
Object {
|
||||
"childrenGap": 8,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"fontSize": "16px",
|
||||
"lineHeight": "16px",
|
||||
"verticalAlign": "middle",
|
||||
}
|
||||
}
|
||||
>
|
||||
•
|
||||
</Text>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"verticalAlign": "middle",
|
||||
}
|
||||
}
|
||||
>
|
||||
Explain and provide alternate queries for a query suggested by you
|
||||
</Text>
|
||||
</Stack>
|
||||
<Stack
|
||||
horizontal={true}
|
||||
style={
|
||||
Object {
|
||||
"marginLeft": "15px",
|
||||
}
|
||||
}
|
||||
tokens={
|
||||
Object {
|
||||
"childrenGap": 8,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"fontSize": "16px",
|
||||
"lineHeight": "16px",
|
||||
"verticalAlign": "middle",
|
||||
}
|
||||
}
|
||||
>
|
||||
•
|
||||
</Text>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"verticalAlign": "middle",
|
||||
}
|
||||
}
|
||||
>
|
||||
Help answer questions about Cosmos DB
|
||||
</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
<Text
|
||||
variant="medium"
|
||||
>
|
||||
To get started, ask me a question or use one of the sample prompts to generate a query. AI-generated content may be incorrect.
|
||||
</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
`;
|
||||
112
src/Explorer/QueryCopilot/V2/Footer/Footer.test.tsx
Normal file
@@ -0,0 +1,112 @@
|
||||
import { IconButton, Image, TextField } from "@fluentui/react";
|
||||
import Explorer from "Explorer/Explorer";
|
||||
import { SendQueryRequest } from "Explorer/QueryCopilot/Shared/QueryCopilotClient";
|
||||
import { shallow } from "enzyme";
|
||||
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
||||
import React from "react";
|
||||
import { act } from "react-dom/test-utils";
|
||||
import { Footer } from "./Footer";
|
||||
|
||||
jest.mock("@azure/cosmos", () => ({
|
||||
Constants: {
|
||||
HttpHeaders: {},
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock("Common/SampleDataClient");
|
||||
|
||||
jest.mock("Explorer/Explorer");
|
||||
|
||||
jest.mock("node-fetch");
|
||||
|
||||
jest.mock("Common/ErrorHandlingUtils", () => ({
|
||||
handleError: jest.fn(),
|
||||
getErrorMessage: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock("Explorer/QueryCopilot/Shared/QueryCopilotClient");
|
||||
|
||||
describe("Footer snapshot test", () => {
|
||||
const testMessage = "test message";
|
||||
|
||||
const initialStoreState = useQueryCopilot.getState();
|
||||
beforeEach(() => {
|
||||
useQueryCopilot.setState(initialStoreState, true);
|
||||
});
|
||||
|
||||
it("should open sample prompts on button click", () => {
|
||||
const wrapper = shallow(<Footer explorer={new Explorer()} />);
|
||||
|
||||
const samplePromptsImage = wrapper.find(Image).first();
|
||||
samplePromptsImage.simulate("click", {});
|
||||
|
||||
expect(useQueryCopilot.getState().isSamplePromptsOpen).toBeTruthy();
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should update user input", () => {
|
||||
const wrapper = shallow(<Footer explorer={new Explorer()} />);
|
||||
const newInput = "some new input";
|
||||
|
||||
const textInput = wrapper.find(TextField).first();
|
||||
textInput.simulate("change", {}, newInput);
|
||||
|
||||
expect(useQueryCopilot.getState().userPrompt).toEqual(newInput);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should pass text with enter key", async () => {
|
||||
useQueryCopilot.getState().setUserPrompt(testMessage);
|
||||
const wrapper = shallow(<Footer explorer={new Explorer()} />);
|
||||
|
||||
const textInput = wrapper.find(TextField).first();
|
||||
await act(async () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
textInput.simulate("keydown", { key: "Enter", shiftKey: false, preventDefault: () => {} });
|
||||
});
|
||||
|
||||
await Promise.resolve();
|
||||
|
||||
expect(SendQueryRequest).toHaveBeenCalledWith({ userPrompt: testMessage, explorer: expect.any(Explorer) });
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should not pass text with non enter key", () => {
|
||||
const wrapper = shallow(<Footer explorer={new Explorer()} />);
|
||||
|
||||
const textInput = wrapper.find(TextField).first();
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
textInput.simulate("keydown", { key: "K", shiftKey: false, preventDefault: () => {} });
|
||||
|
||||
expect(useQueryCopilot.getState().chatMessages).toEqual([]);
|
||||
expect(useQueryCopilot.getState().userPrompt).toEqual("");
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should not pass if no text", () => {
|
||||
const wrapper = shallow(<Footer explorer={new Explorer()} />);
|
||||
|
||||
const textInput = wrapper.find(TextField).first();
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
textInput.simulate("keydown", { key: "Enter", shiftKey: false, preventDefault: () => {} });
|
||||
|
||||
expect(useQueryCopilot.getState().chatMessages).toEqual([]);
|
||||
expect(useQueryCopilot.getState().userPrompt).toEqual("");
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should pass text with icon button", async () => {
|
||||
useQueryCopilot.getState().setUserPrompt(testMessage);
|
||||
const wrapper = shallow(<Footer explorer={new Explorer()} />);
|
||||
|
||||
const iconButton = wrapper.find(IconButton).first();
|
||||
await act(async () => {
|
||||
iconButton.simulate("click", {});
|
||||
});
|
||||
|
||||
await Promise.resolve();
|
||||
|
||||
expect(SendQueryRequest).toHaveBeenCalledWith({ userPrompt: testMessage, explorer: expect.any(Explorer) });
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
82
src/Explorer/QueryCopilot/V2/Footer/Footer.tsx
Normal file
@@ -0,0 +1,82 @@
|
||||
import { IButtonStyles, IconButton, Image, Stack, TextField } from "@fluentui/react";
|
||||
import { SendQueryRequest } from "Explorer/QueryCopilot/Shared/QueryCopilotClient";
|
||||
import { QueryCopilotProps } from "Explorer/QueryCopilot/Shared/QueryCopilotInterfaces";
|
||||
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
||||
import React from "react";
|
||||
import HintIcon from "../../../../../images/Hint.svg";
|
||||
import { SamplePrompts, SamplePromptsProps } from "../../Shared/SamplePrompts/SamplePrompts";
|
||||
|
||||
export const Footer: React.FC<QueryCopilotProps> = ({ explorer }: QueryCopilotProps): JSX.Element => {
|
||||
const {
|
||||
userPrompt,
|
||||
setUserPrompt,
|
||||
isSamplePromptsOpen,
|
||||
setIsSamplePromptsOpen,
|
||||
isGeneratingQuery,
|
||||
} = useQueryCopilot();
|
||||
|
||||
const promptStyles: IButtonStyles = {
|
||||
root: { border: "5px", selectors: { ":hover": { outline: "1px dashed #605e5c" } } },
|
||||
label: { fontWeight: 400, textAlign: "left", paddingLeft: 8 },
|
||||
};
|
||||
|
||||
const sampleProps: SamplePromptsProps = {
|
||||
isSamplePromptsOpen: isSamplePromptsOpen,
|
||||
setIsSamplePromptsOpen: setIsSamplePromptsOpen,
|
||||
setTextBox: setUserPrompt,
|
||||
};
|
||||
|
||||
const handleEnterKeyPress = (event: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (event.key === "Enter" && !event.shiftKey) {
|
||||
event.preventDefault();
|
||||
startSentMessageProcess();
|
||||
}
|
||||
};
|
||||
|
||||
const startSentMessageProcess = async () => {
|
||||
await SendQueryRequest({ userPrompt, explorer });
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack
|
||||
horizontal
|
||||
horizontalAlign="end"
|
||||
verticalAlign="end"
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
borderRadius: "20px",
|
||||
background: "white",
|
||||
padding: "5px",
|
||||
margin: "5px",
|
||||
}}
|
||||
>
|
||||
<Stack>
|
||||
<Image src={HintIcon} styles={promptStyles} onClick={() => setIsSamplePromptsOpen(true)} />
|
||||
<SamplePrompts sampleProps={sampleProps} />
|
||||
</Stack>
|
||||
<TextField
|
||||
placeholder="Write your own prompt or ask a question"
|
||||
value={userPrompt}
|
||||
onChange={(_, newValue) => setUserPrompt(newValue)}
|
||||
onKeyDown={handleEnterKeyPress}
|
||||
multiline
|
||||
resizable={false}
|
||||
disabled={isGeneratingQuery}
|
||||
styles={{
|
||||
root: {
|
||||
width: "100%",
|
||||
height: "80px",
|
||||
borderRadius: "20px",
|
||||
padding: "8px",
|
||||
border: "none",
|
||||
outline: "none",
|
||||
marginLeft: "10px",
|
||||
},
|
||||
fieldGroup: { border: "none" },
|
||||
}}
|
||||
/>
|
||||
<IconButton disabled={isGeneratingQuery} iconProps={{ iconName: "Send" }} onClick={startSentMessageProcess} />
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,511 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Footer snapshot test should not pass if no text 1`] = `
|
||||
<Stack
|
||||
horizontal={true}
|
||||
horizontalAlign="end"
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"background": "white",
|
||||
"borderRadius": "20px",
|
||||
"display": "flex",
|
||||
"margin": "5px",
|
||||
"padding": "5px",
|
||||
}
|
||||
}
|
||||
verticalAlign="end"
|
||||
>
|
||||
<Stack>
|
||||
<Image
|
||||
onClick={[Function]}
|
||||
src=""
|
||||
styles={
|
||||
Object {
|
||||
"label": Object {
|
||||
"fontWeight": 400,
|
||||
"paddingLeft": 8,
|
||||
"textAlign": "left",
|
||||
},
|
||||
"root": Object {
|
||||
"border": "5px",
|
||||
"selectors": Object {
|
||||
":hover": Object {
|
||||
"outline": "1px dashed #605e5c",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
<SamplePrompts
|
||||
sampleProps={
|
||||
Object {
|
||||
"isSamplePromptsOpen": false,
|
||||
"setIsSamplePromptsOpen": [Function],
|
||||
"setTextBox": [Function],
|
||||
}
|
||||
}
|
||||
/>
|
||||
</Stack>
|
||||
<StyledTextFieldBase
|
||||
disabled={false}
|
||||
multiline={true}
|
||||
onChange={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
placeholder="Write your own prompt or ask a question"
|
||||
resizable={false}
|
||||
styles={
|
||||
Object {
|
||||
"fieldGroup": Object {
|
||||
"border": "none",
|
||||
},
|
||||
"root": Object {
|
||||
"border": "none",
|
||||
"borderRadius": "20px",
|
||||
"height": "80px",
|
||||
"marginLeft": "10px",
|
||||
"outline": "none",
|
||||
"padding": "8px",
|
||||
"width": "100%",
|
||||
},
|
||||
}
|
||||
}
|
||||
value=""
|
||||
/>
|
||||
<CustomizedIconButton
|
||||
disabled={false}
|
||||
iconProps={
|
||||
Object {
|
||||
"iconName": "Send",
|
||||
}
|
||||
}
|
||||
onClick={[Function]}
|
||||
/>
|
||||
</Stack>
|
||||
`;
|
||||
|
||||
exports[`Footer snapshot test should not pass text with non enter key 1`] = `
|
||||
<Stack
|
||||
horizontal={true}
|
||||
horizontalAlign="end"
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"background": "white",
|
||||
"borderRadius": "20px",
|
||||
"display": "flex",
|
||||
"margin": "5px",
|
||||
"padding": "5px",
|
||||
}
|
||||
}
|
||||
verticalAlign="end"
|
||||
>
|
||||
<Stack>
|
||||
<Image
|
||||
onClick={[Function]}
|
||||
src=""
|
||||
styles={
|
||||
Object {
|
||||
"label": Object {
|
||||
"fontWeight": 400,
|
||||
"paddingLeft": 8,
|
||||
"textAlign": "left",
|
||||
},
|
||||
"root": Object {
|
||||
"border": "5px",
|
||||
"selectors": Object {
|
||||
":hover": Object {
|
||||
"outline": "1px dashed #605e5c",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
<SamplePrompts
|
||||
sampleProps={
|
||||
Object {
|
||||
"isSamplePromptsOpen": false,
|
||||
"setIsSamplePromptsOpen": [Function],
|
||||
"setTextBox": [Function],
|
||||
}
|
||||
}
|
||||
/>
|
||||
</Stack>
|
||||
<StyledTextFieldBase
|
||||
disabled={false}
|
||||
multiline={true}
|
||||
onChange={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
placeholder="Write your own prompt or ask a question"
|
||||
resizable={false}
|
||||
styles={
|
||||
Object {
|
||||
"fieldGroup": Object {
|
||||
"border": "none",
|
||||
},
|
||||
"root": Object {
|
||||
"border": "none",
|
||||
"borderRadius": "20px",
|
||||
"height": "80px",
|
||||
"marginLeft": "10px",
|
||||
"outline": "none",
|
||||
"padding": "8px",
|
||||
"width": "100%",
|
||||
},
|
||||
}
|
||||
}
|
||||
value=""
|
||||
/>
|
||||
<CustomizedIconButton
|
||||
disabled={false}
|
||||
iconProps={
|
||||
Object {
|
||||
"iconName": "Send",
|
||||
}
|
||||
}
|
||||
onClick={[Function]}
|
||||
/>
|
||||
</Stack>
|
||||
`;
|
||||
|
||||
exports[`Footer snapshot test should open sample prompts on button click 1`] = `
|
||||
<Stack
|
||||
horizontal={true}
|
||||
horizontalAlign="end"
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"background": "white",
|
||||
"borderRadius": "20px",
|
||||
"display": "flex",
|
||||
"margin": "5px",
|
||||
"padding": "5px",
|
||||
}
|
||||
}
|
||||
verticalAlign="end"
|
||||
>
|
||||
<Stack>
|
||||
<Image
|
||||
onClick={[Function]}
|
||||
src=""
|
||||
styles={
|
||||
Object {
|
||||
"label": Object {
|
||||
"fontWeight": 400,
|
||||
"paddingLeft": 8,
|
||||
"textAlign": "left",
|
||||
},
|
||||
"root": Object {
|
||||
"border": "5px",
|
||||
"selectors": Object {
|
||||
":hover": Object {
|
||||
"outline": "1px dashed #605e5c",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
<SamplePrompts
|
||||
sampleProps={
|
||||
Object {
|
||||
"isSamplePromptsOpen": false,
|
||||
"setIsSamplePromptsOpen": [Function],
|
||||
"setTextBox": [Function],
|
||||
}
|
||||
}
|
||||
/>
|
||||
</Stack>
|
||||
<StyledTextFieldBase
|
||||
disabled={false}
|
||||
multiline={true}
|
||||
onChange={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
placeholder="Write your own prompt or ask a question"
|
||||
resizable={false}
|
||||
styles={
|
||||
Object {
|
||||
"fieldGroup": Object {
|
||||
"border": "none",
|
||||
},
|
||||
"root": Object {
|
||||
"border": "none",
|
||||
"borderRadius": "20px",
|
||||
"height": "80px",
|
||||
"marginLeft": "10px",
|
||||
"outline": "none",
|
||||
"padding": "8px",
|
||||
"width": "100%",
|
||||
},
|
||||
}
|
||||
}
|
||||
value=""
|
||||
/>
|
||||
<CustomizedIconButton
|
||||
disabled={false}
|
||||
iconProps={
|
||||
Object {
|
||||
"iconName": "Send",
|
||||
}
|
||||
}
|
||||
onClick={[Function]}
|
||||
/>
|
||||
</Stack>
|
||||
`;
|
||||
|
||||
exports[`Footer snapshot test should pass text with enter key 1`] = `
|
||||
<Stack
|
||||
horizontal={true}
|
||||
horizontalAlign="end"
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"background": "white",
|
||||
"borderRadius": "20px",
|
||||
"display": "flex",
|
||||
"margin": "5px",
|
||||
"padding": "5px",
|
||||
}
|
||||
}
|
||||
verticalAlign="end"
|
||||
>
|
||||
<Stack>
|
||||
<Image
|
||||
onClick={[Function]}
|
||||
src=""
|
||||
styles={
|
||||
Object {
|
||||
"label": Object {
|
||||
"fontWeight": 400,
|
||||
"paddingLeft": 8,
|
||||
"textAlign": "left",
|
||||
},
|
||||
"root": Object {
|
||||
"border": "5px",
|
||||
"selectors": Object {
|
||||
":hover": Object {
|
||||
"outline": "1px dashed #605e5c",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
<SamplePrompts
|
||||
sampleProps={
|
||||
Object {
|
||||
"isSamplePromptsOpen": false,
|
||||
"setIsSamplePromptsOpen": [Function],
|
||||
"setTextBox": [Function],
|
||||
}
|
||||
}
|
||||
/>
|
||||
</Stack>
|
||||
<StyledTextFieldBase
|
||||
disabled={false}
|
||||
multiline={true}
|
||||
onChange={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
placeholder="Write your own prompt or ask a question"
|
||||
resizable={false}
|
||||
styles={
|
||||
Object {
|
||||
"fieldGroup": Object {
|
||||
"border": "none",
|
||||
},
|
||||
"root": Object {
|
||||
"border": "none",
|
||||
"borderRadius": "20px",
|
||||
"height": "80px",
|
||||
"marginLeft": "10px",
|
||||
"outline": "none",
|
||||
"padding": "8px",
|
||||
"width": "100%",
|
||||
},
|
||||
}
|
||||
}
|
||||
value="test message"
|
||||
/>
|
||||
<CustomizedIconButton
|
||||
disabled={false}
|
||||
iconProps={
|
||||
Object {
|
||||
"iconName": "Send",
|
||||
}
|
||||
}
|
||||
onClick={[Function]}
|
||||
/>
|
||||
</Stack>
|
||||
`;
|
||||
|
||||
exports[`Footer snapshot test should pass text with icon button 1`] = `
|
||||
<Stack
|
||||
horizontal={true}
|
||||
horizontalAlign="end"
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"background": "white",
|
||||
"borderRadius": "20px",
|
||||
"display": "flex",
|
||||
"margin": "5px",
|
||||
"padding": "5px",
|
||||
}
|
||||
}
|
||||
verticalAlign="end"
|
||||
>
|
||||
<Stack>
|
||||
<Image
|
||||
onClick={[Function]}
|
||||
src=""
|
||||
styles={
|
||||
Object {
|
||||
"label": Object {
|
||||
"fontWeight": 400,
|
||||
"paddingLeft": 8,
|
||||
"textAlign": "left",
|
||||
},
|
||||
"root": Object {
|
||||
"border": "5px",
|
||||
"selectors": Object {
|
||||
":hover": Object {
|
||||
"outline": "1px dashed #605e5c",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
<SamplePrompts
|
||||
sampleProps={
|
||||
Object {
|
||||
"isSamplePromptsOpen": false,
|
||||
"setIsSamplePromptsOpen": [Function],
|
||||
"setTextBox": [Function],
|
||||
}
|
||||
}
|
||||
/>
|
||||
</Stack>
|
||||
<StyledTextFieldBase
|
||||
disabled={false}
|
||||
multiline={true}
|
||||
onChange={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
placeholder="Write your own prompt or ask a question"
|
||||
resizable={false}
|
||||
styles={
|
||||
Object {
|
||||
"fieldGroup": Object {
|
||||
"border": "none",
|
||||
},
|
||||
"root": Object {
|
||||
"border": "none",
|
||||
"borderRadius": "20px",
|
||||
"height": "80px",
|
||||
"marginLeft": "10px",
|
||||
"outline": "none",
|
||||
"padding": "8px",
|
||||
"width": "100%",
|
||||
},
|
||||
}
|
||||
}
|
||||
value="test message"
|
||||
/>
|
||||
<CustomizedIconButton
|
||||
disabled={false}
|
||||
iconProps={
|
||||
Object {
|
||||
"iconName": "Send",
|
||||
}
|
||||
}
|
||||
onClick={[Function]}
|
||||
/>
|
||||
</Stack>
|
||||
`;
|
||||
|
||||
exports[`Footer snapshot test should update user input 1`] = `
|
||||
<Stack
|
||||
horizontal={true}
|
||||
horizontalAlign="end"
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"background": "white",
|
||||
"borderRadius": "20px",
|
||||
"display": "flex",
|
||||
"margin": "5px",
|
||||
"padding": "5px",
|
||||
}
|
||||
}
|
||||
verticalAlign="end"
|
||||
>
|
||||
<Stack>
|
||||
<Image
|
||||
onClick={[Function]}
|
||||
src=""
|
||||
styles={
|
||||
Object {
|
||||
"label": Object {
|
||||
"fontWeight": 400,
|
||||
"paddingLeft": 8,
|
||||
"textAlign": "left",
|
||||
},
|
||||
"root": Object {
|
||||
"border": "5px",
|
||||
"selectors": Object {
|
||||
":hover": Object {
|
||||
"outline": "1px dashed #605e5c",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
<SamplePrompts
|
||||
sampleProps={
|
||||
Object {
|
||||
"isSamplePromptsOpen": false,
|
||||
"setIsSamplePromptsOpen": [Function],
|
||||
"setTextBox": [Function],
|
||||
}
|
||||
}
|
||||
/>
|
||||
</Stack>
|
||||
<StyledTextFieldBase
|
||||
disabled={false}
|
||||
multiline={true}
|
||||
onChange={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
placeholder="Write your own prompt or ask a question"
|
||||
resizable={false}
|
||||
styles={
|
||||
Object {
|
||||
"fieldGroup": Object {
|
||||
"border": "none",
|
||||
},
|
||||
"root": Object {
|
||||
"border": "none",
|
||||
"borderRadius": "20px",
|
||||
"height": "80px",
|
||||
"marginLeft": "10px",
|
||||
"outline": "none",
|
||||
"padding": "8px",
|
||||
"width": "100%",
|
||||
},
|
||||
}
|
||||
}
|
||||
value=""
|
||||
/>
|
||||
<CustomizedIconButton
|
||||
disabled={false}
|
||||
iconProps={
|
||||
Object {
|
||||
"iconName": "Send",
|
||||
}
|
||||
}
|
||||
onClick={[Function]}
|
||||
/>
|
||||
</Stack>
|
||||
`;
|
||||
17
src/Explorer/QueryCopilot/V2/Header/Header.test.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import { IconButton } from "@fluentui/react";
|
||||
import { shallow } from "enzyme";
|
||||
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
||||
import React from "react";
|
||||
import { Header } from "./Header";
|
||||
|
||||
describe("Header snapshot test", () => {
|
||||
it("should close on button click ", () => {
|
||||
const wrapper = shallow(<Header />);
|
||||
|
||||
const iconButton = wrapper.find(IconButton).first();
|
||||
iconButton.simulate("click", {});
|
||||
|
||||
expect(useQueryCopilot.getState().showCopilotSidebar).toBeFalsy();
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
39
src/Explorer/QueryCopilot/V2/Header/Header.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
import { IconButton, Image, Stack, Text } from "@fluentui/react";
|
||||
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
||||
import React from "react";
|
||||
import CopilotIcon from "../../../../../images/CopilotSidebarLogo.svg";
|
||||
|
||||
export const Header: React.FC = (): JSX.Element => {
|
||||
const { setShowCopilotSidebar } = useQueryCopilot();
|
||||
|
||||
return (
|
||||
<Stack
|
||||
style={{ margin: "15px 0px 0px 0px", padding: "5px", display: "flex", justifyContent: "space-between" }}
|
||||
horizontal
|
||||
verticalAlign="center"
|
||||
>
|
||||
<Stack horizontal verticalAlign="center">
|
||||
<Image src={CopilotIcon} />
|
||||
<Text style={{ marginLeft: "5px", fontWeight: "bold" }}>Copilot</Text>
|
||||
<Text
|
||||
style={{
|
||||
background: "#f0f0f0",
|
||||
fontSize: "10px",
|
||||
padding: "2px 4px",
|
||||
marginLeft: "5px",
|
||||
borderRadius: "8px",
|
||||
}}
|
||||
>
|
||||
Preview
|
||||
</Text>
|
||||
</Stack>
|
||||
<IconButton
|
||||
onClick={() => setShowCopilotSidebar(false)}
|
||||
iconProps={{ iconName: "Cancel" }}
|
||||
title="Exit"
|
||||
ariaLabel="Exit"
|
||||
style={{ color: "#424242", verticalAlign: "middle" }}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,64 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Header snapshot test should close on button click 1`] = `
|
||||
<Stack
|
||||
horizontal={true}
|
||||
style={
|
||||
Object {
|
||||
"display": "flex",
|
||||
"justifyContent": "space-between",
|
||||
"margin": "15px 0px 0px 0px",
|
||||
"padding": "5px",
|
||||
}
|
||||
}
|
||||
verticalAlign="center"
|
||||
>
|
||||
<Stack
|
||||
horizontal={true}
|
||||
verticalAlign="center"
|
||||
>
|
||||
<Image
|
||||
src=""
|
||||
/>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"fontWeight": "bold",
|
||||
"marginLeft": "5px",
|
||||
}
|
||||
}
|
||||
>
|
||||
Copilot
|
||||
</Text>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"background": "#f0f0f0",
|
||||
"borderRadius": "8px",
|
||||
"fontSize": "10px",
|
||||
"marginLeft": "5px",
|
||||
"padding": "2px 4px",
|
||||
}
|
||||
}
|
||||
>
|
||||
Preview
|
||||
</Text>
|
||||
</Stack>
|
||||
<CustomizedIconButton
|
||||
ariaLabel="Exit"
|
||||
iconProps={
|
||||
Object {
|
||||
"iconName": "Cancel",
|
||||
}
|
||||
}
|
||||
onClick={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"color": "#424242",
|
||||
"verticalAlign": "middle",
|
||||
}
|
||||
}
|
||||
title="Exit"
|
||||
/>
|
||||
</Stack>
|
||||
`;
|
||||
@@ -0,0 +1,32 @@
|
||||
import { PrimaryButton } from "@fluentui/react";
|
||||
import { shallow } from "enzyme";
|
||||
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
||||
import { withHooks } from "jest-react-hooks-shallow";
|
||||
import React from "react";
|
||||
import { WelcomeSidebarModal } from "./WelcomeSidebarModal";
|
||||
|
||||
describe("Welcome Sidebar Modal snapshot test", () => {
|
||||
it("should close on button click ", () => {
|
||||
withHooks(() => {
|
||||
const wrapper = shallow(<WelcomeSidebarModal />);
|
||||
const spy = jest.spyOn(localStorage, "setItem");
|
||||
spy.mockClear();
|
||||
|
||||
const button = wrapper.find(PrimaryButton).first();
|
||||
button.simulate("click", {});
|
||||
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
expect(spy).toHaveBeenLastCalledWith("showWelcomeSidebar", "false");
|
||||
expect(useQueryCopilot.getState().showWelcomeSidebar).toBeFalsy();
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
it("should not reneder with local storage key", () => {
|
||||
withHooks(() => {
|
||||
window.localStorage.setItem("showWelcomeSidebar", "false");
|
||||
const wrapper = shallow(<WelcomeSidebarModal />);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
||||
130
src/Explorer/QueryCopilot/V2/Modal/WelcomeSidebarModal.tsx
Normal file
@@ -0,0 +1,130 @@
|
||||
import { Image, Link, PrimaryButton, Stack, Text } from "@fluentui/react";
|
||||
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
||||
import React from "react";
|
||||
import Database from "../../../../../images/CopilotDatabase.svg";
|
||||
import Flash from "../../../../../images/CopilotFlash.svg";
|
||||
import CopilotSidebarWelcomeIllustration from "../../../../../images/CopilotSidebarWelcomeIllustration.svg";
|
||||
import Thumb from "../../../../../images/CopilotThumb.svg";
|
||||
|
||||
export const WelcomeSidebarModal: React.FC = (): JSX.Element => {
|
||||
const { showWelcomeSidebar, setShowWelcomeSidebar } = useQueryCopilot();
|
||||
|
||||
const hideModal = (): void => {
|
||||
setShowWelcomeSidebar(false);
|
||||
window.localStorage.setItem("showWelcomeSidebar", "false");
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
const showWelcomeSidebar = window.localStorage.getItem("showWelcomeSidebar");
|
||||
setShowWelcomeSidebar(showWelcomeSidebar && showWelcomeSidebar === "false" ? false : true);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
showWelcomeSidebar && (
|
||||
<Stack
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
overflow: "auto",
|
||||
backgroundColor: "#FAFAFA",
|
||||
flex: "1 0 100%",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
margin: "20px 10px",
|
||||
padding: "20px",
|
||||
maxHeight: "100%",
|
||||
boxSizing: "border-box",
|
||||
borderRadius: "20px",
|
||||
backgroundColor: "white",
|
||||
}}
|
||||
>
|
||||
<Stack horizontalAlign="center" verticalAlign="center">
|
||||
<Image src={CopilotSidebarWelcomeIllustration} />
|
||||
</Stack>
|
||||
|
||||
<Stack>
|
||||
<Stack.Item align="center" style={{ marginBottom: "10px" }}>
|
||||
<Text className="title bold">Welcome to Copilot in CosmosDB</Text>
|
||||
</Stack.Item>
|
||||
<Stack.Item style={{ marginBottom: "15px" }}>
|
||||
<Stack>
|
||||
<Stack horizontal>
|
||||
<Stack.Item align="start">
|
||||
<Image src={Flash} />
|
||||
</Stack.Item>
|
||||
<Stack.Item align="center" style={{ marginLeft: "10px" }}>
|
||||
<Text style={{ fontWeight: 600 }}>
|
||||
Let copilot do the work for you
|
||||
<br />
|
||||
</Text>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
<Stack.Item style={{ textAlign: "start", marginLeft: "25px" }}>
|
||||
<Text>
|
||||
Ask Copilot to generate a query by describing the query in your words.
|
||||
<br />
|
||||
<Link href="http://aka.ms/cdb-copilot-learn-more">Learn more</Link>
|
||||
</Text>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
<Stack.Item style={{ marginBottom: "15px" }}>
|
||||
<Stack>
|
||||
<Stack horizontal>
|
||||
<Stack.Item align="start">
|
||||
<Image src={Thumb} />
|
||||
</Stack.Item>
|
||||
<Stack.Item align="center" style={{ marginLeft: "10px" }}>
|
||||
<Text style={{ fontWeight: 600 }}>
|
||||
Use your judgement
|
||||
<br />
|
||||
</Text>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
<Stack.Item style={{ textAlign: "start", marginLeft: "25px" }}>
|
||||
<Text>
|
||||
AI-generated content can have mistakes. Make sure it’s accurate and appropriate before using it.
|
||||
<br />
|
||||
<Link href="http://aka.ms/cdb-copilot-preview-terms">Read preview terms</Link>
|
||||
</Text>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
<Stack.Item style={{ marginBottom: "15px" }}>
|
||||
<Stack>
|
||||
<Stack horizontal>
|
||||
<Stack.Item align="start">
|
||||
<Image src={Database} />
|
||||
</Stack.Item>
|
||||
<Stack.Item align="center" style={{ marginLeft: "10px" }}>
|
||||
<Text style={{ fontWeight: 600 }}>
|
||||
Copilot currently works only a sample database
|
||||
<br />
|
||||
</Text>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
<Stack.Item style={{ textAlign: "start", marginLeft: "25px" }}>
|
||||
<Text>
|
||||
Copilot is setup on a sample database we have configured for you at no cost
|
||||
<br />
|
||||
<Link href="http://aka.ms/cdb-copilot-learn-more">Learn more</Link>
|
||||
</Text>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
|
||||
<Stack>
|
||||
<Stack.Item align="center">
|
||||
<PrimaryButton style={{ width: "224px", height: "32px" }} onClick={hideModal}>
|
||||
Get Started
|
||||
</PrimaryButton>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</div>
|
||||
</Stack>
|
||||
)
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,5 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Welcome Sidebar Modal snapshot test should close on button click 1`] = `""`;
|
||||
|
||||
exports[`Welcome Sidebar Modal snapshot test should not reneder with local storage key 1`] = `""`;
|
||||
@@ -0,0 +1,55 @@
|
||||
import { Stack } from "@fluentui/react";
|
||||
import Explorer from "Explorer/Explorer";
|
||||
import { SampleBubble } from "Explorer/QueryCopilot/V2/Bubbles/Sample/SampleBubble";
|
||||
import { shallow } from "enzyme";
|
||||
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
||||
import { withHooks } from "jest-react-hooks-shallow";
|
||||
import React from "react";
|
||||
import { QueryCopilotSidebar } from "./QueryCopilotSidebar";
|
||||
|
||||
describe("Query Copilot Sidebar snapshot test", () => {
|
||||
const initialState = useQueryCopilot.getState();
|
||||
|
||||
beforeEach(() => {
|
||||
useQueryCopilot.setState(initialState, true);
|
||||
});
|
||||
|
||||
it("should render and set copilot used flag ", () => {
|
||||
withHooks(() => {
|
||||
useQueryCopilot.getState().setShowCopilotSidebar(true);
|
||||
const wrapper = shallow(<QueryCopilotSidebar explorer={new Explorer()} />);
|
||||
|
||||
expect(useQueryCopilot.getState().wasCopilotUsed).toBeTruthy();
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
it("should render and not set copilot used flag ", () => {
|
||||
withHooks(() => {
|
||||
const wrapper = shallow(<QueryCopilotSidebar explorer={new Explorer()} />);
|
||||
|
||||
expect(useQueryCopilot.getState().wasCopilotUsed).toBeFalsy();
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
it("should render with chat messages", () => {
|
||||
const message = "some test message";
|
||||
useQueryCopilot.getState().setChatMessages([{ source: 0, message: message }]);
|
||||
const wrapper = shallow(<QueryCopilotSidebar explorer={new Explorer()} />);
|
||||
|
||||
const messageContainer = wrapper.find(Stack).findWhere((x) => x.text() === message);
|
||||
|
||||
expect(messageContainer).toBeDefined();
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should render samples without messages", () => {
|
||||
const wrapper = shallow(<QueryCopilotSidebar explorer={new Explorer()} />);
|
||||
|
||||
const sampleBubble = wrapper.find(SampleBubble);
|
||||
|
||||
expect(sampleBubble).toBeDefined();
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
84
src/Explorer/QueryCopilot/V2/Sidebar/QueryCopilotSidebar.tsx
Normal file
@@ -0,0 +1,84 @@
|
||||
import { Stack } from "@fluentui/react";
|
||||
import { QueryCopilotProps } from "Explorer/QueryCopilot/Shared/QueryCopilotInterfaces";
|
||||
import { RetrievingBubble } from "Explorer/QueryCopilot/V2/Bubbles/Retriveing/RetrievingBubble";
|
||||
import { SampleBubble } from "Explorer/QueryCopilot/V2/Bubbles/Sample/SampleBubble";
|
||||
import { WelcomeBubble } from "Explorer/QueryCopilot/V2/Bubbles/Welcome/WelcomeBubble";
|
||||
import { Footer } from "Explorer/QueryCopilot/V2/Footer/Footer";
|
||||
import { Header } from "Explorer/QueryCopilot/V2/Header/Header";
|
||||
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
||||
import React from "react";
|
||||
import { WelcomeSidebarModal } from "../Modal/WelcomeSidebarModal";
|
||||
|
||||
export const QueryCopilotSidebar: React.FC<QueryCopilotProps> = ({ explorer }: QueryCopilotProps): JSX.Element => {
|
||||
const {
|
||||
setWasCopilotUsed,
|
||||
showCopilotSidebar,
|
||||
chatMessages,
|
||||
showWelcomeSidebar,
|
||||
isGeneratingQuery,
|
||||
} = useQueryCopilot();
|
||||
|
||||
React.useEffect(() => {
|
||||
if (showCopilotSidebar) {
|
||||
setWasCopilotUsed(true);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Stack style={{ width: "100%", height: "100%", backgroundColor: "#FAFAFA" }}>
|
||||
<Header />
|
||||
{showWelcomeSidebar ? (
|
||||
<WelcomeSidebarModal />
|
||||
) : (
|
||||
<>
|
||||
<Stack
|
||||
style={{
|
||||
flexGrow: 1,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
overflowY: "auto",
|
||||
}}
|
||||
>
|
||||
<WelcomeBubble />
|
||||
{chatMessages.map((message, index) =>
|
||||
message.source === 0 ? (
|
||||
<Stack
|
||||
key={index}
|
||||
horizontalAlign="center"
|
||||
tokens={{ padding: 8, childrenGap: 8 }}
|
||||
style={{
|
||||
backgroundColor: "#E0E7FF",
|
||||
borderRadius: "8px",
|
||||
margin: "5px 10px",
|
||||
textAlign: "start",
|
||||
}}
|
||||
>
|
||||
{message.message}
|
||||
</Stack>
|
||||
) : (
|
||||
<Stack
|
||||
key={index}
|
||||
horizontalAlign="center"
|
||||
tokens={{ padding: 8, childrenGap: 8 }}
|
||||
style={{
|
||||
backgroundColor: "white",
|
||||
borderRadius: "8px",
|
||||
margin: "5px 10px",
|
||||
textAlign: "start",
|
||||
}}
|
||||
>
|
||||
{message.message}
|
||||
</Stack>
|
||||
)
|
||||
)}
|
||||
|
||||
<RetrievingBubble />
|
||||
|
||||
{chatMessages.length === 0 && !isGeneratingQuery && <SampleBubble />}
|
||||
</Stack>
|
||||
<Footer explorer={explorer} />
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,61 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Query Copilot Sidebar snapshot test should render and not set copilot used flag 1`] = `
|
||||
<Stack
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#FAFAFA",
|
||||
"height": "100%",
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<Header />
|
||||
<WelcomeSidebarModal />
|
||||
</Stack>
|
||||
`;
|
||||
|
||||
exports[`Query Copilot Sidebar snapshot test should render and set copilot used flag 1`] = `
|
||||
<Stack
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#FAFAFA",
|
||||
"height": "100%",
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<Header />
|
||||
<WelcomeSidebarModal />
|
||||
</Stack>
|
||||
`;
|
||||
|
||||
exports[`Query Copilot Sidebar snapshot test should render samples without messages 1`] = `
|
||||
<Stack
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#FAFAFA",
|
||||
"height": "100%",
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<Header />
|
||||
<WelcomeSidebarModal />
|
||||
</Stack>
|
||||
`;
|
||||
|
||||
exports[`Query Copilot Sidebar snapshot test should render with chat messages 1`] = `
|
||||
<Stack
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#FAFAFA",
|
||||
"height": "100%",
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<Header />
|
||||
<WelcomeSidebarModal />
|
||||
</Stack>
|
||||
`;
|
||||
@@ -67,10 +67,10 @@ exports[`Query copilot tab snapshot test should render with initial input 1`] =
|
||||
},
|
||||
}
|
||||
}
|
||||
value="Write a query to return all records in this table"
|
||||
value=""
|
||||
/>
|
||||
<CustomizedIconButton
|
||||
disabled={false}
|
||||
disabled={true}
|
||||
iconProps={
|
||||
Object {
|
||||
"iconName": "Send",
|
||||
|
||||
@@ -148,7 +148,13 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
||||
"Copilot is your AI buddy that helps you write Azure Cosmos DB queries like a pro. Try it using our sample data set now!"
|
||||
}
|
||||
onClick={() => {
|
||||
useTabs.getState().openAndActivateReactTab(ReactTabKind.QueryCopilot);
|
||||
const copilotVersion = userContext.features.copilotVersion;
|
||||
if (copilotVersion === "v1.0") {
|
||||
useTabs.getState().openAndActivateReactTab(ReactTabKind.QueryCopilot);
|
||||
} else if (copilotVersion === "v2.0") {
|
||||
const sampleCollection = useDatabases.getState().sampleDataResourceTokenCollection;
|
||||
sampleCollection.onNewQueryClick(sampleCollection, undefined);
|
||||
}
|
||||
traceOpen(Action.OpenQueryCopilotFromSplashScreen, { apiType: userContext.apiType });
|
||||
}}
|
||||
/>
|
||||
|
||||
34
src/Explorer/Tabs/QueryTab/QueryTabComponent.test.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import { fireEvent, render } from "@testing-library/react";
|
||||
import QueryTabComponent, { IQueryTabComponentProps } from "Explorer/Tabs/QueryTab/QueryTabComponent";
|
||||
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
||||
import React from "react";
|
||||
|
||||
jest.mock("Explorer/Controls/Editor/EditorReact");
|
||||
|
||||
describe("QueryTabComponent", () => {
|
||||
const mockStore = useQueryCopilot.getState();
|
||||
beforeEach(() => {
|
||||
mockStore.showCopilotSidebar = false;
|
||||
mockStore.setShowCopilotSidebar = jest.fn();
|
||||
});
|
||||
beforeEach(() => jest.clearAllMocks());
|
||||
|
||||
it("should launch Copilot when ALT+C is pressed", () => {
|
||||
const propsMock: Readonly<IQueryTabComponentProps> = ({
|
||||
collection: { databaseId: "CopilotSampleDb" },
|
||||
onTabAccessor: () => jest.fn(),
|
||||
isExecutionError: false,
|
||||
tabId: "mockTabId",
|
||||
tabsBaseInstance: {
|
||||
updateNavbarWithTabsButtons: () => jest.fn(),
|
||||
},
|
||||
} as unknown) as IQueryTabComponentProps;
|
||||
|
||||
const { container } = render(<QueryTabComponent {...propsMock} />);
|
||||
|
||||
const launchCopilotButton = container.querySelector(".queryEditorWatermarkText");
|
||||
fireEvent.keyDown(launchCopilotButton, { key: "c", altKey: true });
|
||||
|
||||
expect(mockStore.setShowCopilotSidebar).toHaveBeenCalledWith(true);
|
||||
});
|
||||
});
|
||||
@@ -1,11 +1,14 @@
|
||||
import { FeedOptions } from "@azure/cosmos";
|
||||
import { QueryCopilotSidebar } from "Explorer/QueryCopilot/V2/Sidebar/QueryCopilotSidebar";
|
||||
import { QueryResultSection } from "Explorer/Tabs/QueryTab/QueryResultSection";
|
||||
import { QueryCopilotState, useQueryCopilot } from "hooks/useQueryCopilot";
|
||||
import React, { Fragment } from "react";
|
||||
import SplitterLayout from "react-splitter-layout";
|
||||
import "react-splitter-layout/lib/index.css";
|
||||
import LaunchCopilot from "../../../../images/CopilotTabIcon.svg";
|
||||
import ExecuteQueryIcon from "../../../../images/ExecuteQuery.svg";
|
||||
import SaveQueryIcon from "../../../../images/save-cosmos.svg";
|
||||
import { NormalizedEventKey } from "../../../Common/Constants";
|
||||
import { NormalizedEventKey, QueryCopilotSampleDatabaseId } from "../../../Common/Constants";
|
||||
import { getErrorMessage } from "../../../Common/ErrorHandlingUtils";
|
||||
import * as HeadersUtility from "../../../Common/HeadersUtility";
|
||||
import { MinimalQueryIterator } from "../../../Common/IteratorUtilities";
|
||||
@@ -72,15 +75,18 @@ interface IQueryTabStates {
|
||||
error: string;
|
||||
isExecutionError: boolean;
|
||||
isExecuting: boolean;
|
||||
showCopilotSidebar: boolean;
|
||||
}
|
||||
|
||||
export default class QueryTabComponent extends React.Component<IQueryTabComponentProps, IQueryTabStates> {
|
||||
public queryEditorId: string;
|
||||
public executeQueryButton: Button;
|
||||
public saveQueryButton: Button;
|
||||
public launchCopilotButton: Button;
|
||||
public splitterId: string;
|
||||
public isPreferredApiMongoDB: boolean;
|
||||
public isCloseClicked: boolean;
|
||||
public isCopilotTabActive: boolean;
|
||||
private _iterator: MinimalQueryIterator;
|
||||
|
||||
constructor(props: IQueryTabComponentProps) {
|
||||
@@ -94,11 +100,13 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
|
||||
error: "",
|
||||
isExecutionError: this.props.isExecutionError,
|
||||
isExecuting: false,
|
||||
showCopilotSidebar: useQueryCopilot.getState().showCopilotSidebar,
|
||||
};
|
||||
this.isCloseClicked = false;
|
||||
this.splitterId = this.props.tabId + "_splitter";
|
||||
this.queryEditorId = `queryeditor${this.props.tabId}`;
|
||||
this.isPreferredApiMongoDB = this.props.isPreferredApiMongoDB;
|
||||
this.isCopilotTabActive = QueryCopilotSampleDatabaseId === this.props.collection.databaseId;
|
||||
|
||||
this.executeQueryButton = {
|
||||
enabled: !!this.state.sqlQueryEditorContent && this.state.sqlQueryEditorContent.length > 0,
|
||||
@@ -111,6 +119,11 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
|
||||
visible: isSaveQueryBtnEnabled,
|
||||
};
|
||||
|
||||
this.launchCopilotButton = {
|
||||
enabled: userContext.apiType === "SQL" && true,
|
||||
visible: userContext.apiType === "SQL" && true,
|
||||
};
|
||||
|
||||
this.props.tabsBaseInstance.updateNavbarWithTabsButtons();
|
||||
props.onTabAccessor({
|
||||
onTabClickEvent: this.onTabClick.bind(this),
|
||||
@@ -121,6 +134,9 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
|
||||
|
||||
public onCloseClick(isClicked: boolean): void {
|
||||
this.isCloseClicked = isClicked;
|
||||
if (useQueryCopilot.getState().wasCopilotUsed && this.isCopilotTabActive) {
|
||||
useQueryCopilot.getState().resetQueryCopilotStates();
|
||||
}
|
||||
}
|
||||
|
||||
public getCurrentEditorQuery(): string {
|
||||
@@ -146,6 +162,10 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
|
||||
useSidePanel.getState().openSidePanel("Save Query", <SaveQueryPane explorer={this.props.collection.container} />);
|
||||
};
|
||||
|
||||
public launchQueryCopilotChat = (): void => {
|
||||
useQueryCopilot.getState().setShowCopilotSidebar(!useQueryCopilot.getState().showCopilotSidebar);
|
||||
};
|
||||
|
||||
public onSavedQueriesClick = (): void => {
|
||||
useSidePanel
|
||||
.getState()
|
||||
@@ -164,6 +184,12 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
|
||||
});
|
||||
}
|
||||
|
||||
public handleCopilotKeyDown = (event: KeyboardEvent): void => {
|
||||
if (this.isCopilotTabActive && event.altKey && event.key === "c") {
|
||||
this.launchQueryCopilotChat();
|
||||
}
|
||||
};
|
||||
|
||||
public onToggleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>): boolean => {
|
||||
if (event.key === NormalizedEventKey.LeftArrow) {
|
||||
this.toggleResult();
|
||||
@@ -269,6 +295,39 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
|
||||
});
|
||||
}
|
||||
|
||||
if (this.launchCopilotButton.visible && this.isCopilotTabActive) {
|
||||
const mainButtonLabel = "Launch Copilot";
|
||||
const chatPaneLabel = "Open Copilot in chat pane (ALT+C)";
|
||||
const copilotSettingLabel = "Copilot settings";
|
||||
|
||||
const openCopilotChatButton: CommandButtonComponentProps = {
|
||||
iconAlt: chatPaneLabel,
|
||||
onCommandClick: this.launchQueryCopilotChat,
|
||||
commandButtonLabel: chatPaneLabel,
|
||||
ariaLabel: chatPaneLabel,
|
||||
hasPopup: false,
|
||||
};
|
||||
|
||||
const copilotSettingsButton: CommandButtonComponentProps = {
|
||||
iconAlt: copilotSettingLabel,
|
||||
onCommandClick: () => undefined,
|
||||
commandButtonLabel: copilotSettingLabel,
|
||||
ariaLabel: copilotSettingLabel,
|
||||
hasPopup: false,
|
||||
};
|
||||
|
||||
const launchCopilotButton = {
|
||||
iconSrc: LaunchCopilot,
|
||||
iconAlt: mainButtonLabel,
|
||||
onCommandClick: this.launchQueryCopilotChat,
|
||||
commandButtonLabel: mainButtonLabel,
|
||||
ariaLabel: mainButtonLabel,
|
||||
hasPopup: false,
|
||||
children: [openCopilotChatButton, copilotSettingsButton],
|
||||
};
|
||||
buttons.push(launchCopilotButton);
|
||||
}
|
||||
|
||||
return buttons;
|
||||
}
|
||||
|
||||
@@ -306,11 +365,25 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
|
||||
useCommandBar.getState().setContextButtons(this.getTabsButtons());
|
||||
}
|
||||
|
||||
private unsubscribeCopilotSidebar: () => void;
|
||||
|
||||
componentDidMount(): void {
|
||||
this.unsubscribeCopilotSidebar = useQueryCopilot.subscribe((state: QueryCopilotState) => {
|
||||
if (this.state.showCopilotSidebar !== state.showCopilotSidebar) {
|
||||
this.setState({ showCopilotSidebar: state.showCopilotSidebar });
|
||||
}
|
||||
});
|
||||
|
||||
useCommandBar.getState().setContextButtons(this.getTabsButtons());
|
||||
document.addEventListener("keydown", this.handleCopilotKeyDown);
|
||||
}
|
||||
|
||||
render(): JSX.Element {
|
||||
componentWillUnmount(): void {
|
||||
this.unsubscribeCopilotSidebar();
|
||||
document.removeEventListener("keydown", this.handleCopilotKeyDown);
|
||||
}
|
||||
|
||||
private getEditorAndQueryResult(): JSX.Element {
|
||||
return (
|
||||
<Fragment>
|
||||
<div className="tab-pane" id={this.props.tabId} role="tabpanel">
|
||||
@@ -343,4 +416,20 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
render(): JSX.Element {
|
||||
const shouldScaleElements = this.state.showCopilotSidebar && this.isCopilotTabActive;
|
||||
return (
|
||||
<div style={{ display: "flex", flexDirection: "row", height: "100%" }}>
|
||||
<div style={{ width: shouldScaleElements ? "70%" : "100%", height: "100%" }}>
|
||||
{this.getEditorAndQueryResult()}
|
||||
</div>
|
||||
{shouldScaleElements && (
|
||||
<div style={{ width: "30%", height: "100%" }}>
|
||||
<QueryCopilotSidebar explorer={this.props.collection.container} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Spinner, SpinnerSize, Stack, Text } from "@fluentui/react";
|
||||
import { PoolIdType } from "Common/Constants";
|
||||
import { configContext } from "ConfigContext";
|
||||
import { NotebookWorkspaceConnectionInfo, PostgresFirewallRule } from "Contracts/DataModels";
|
||||
import { NotebookTerminalComponent } from "Explorer/Controls/Notebook/NotebookTerminalComponent";
|
||||
@@ -51,7 +52,7 @@ export const QuickstartTab: React.FC<QuickstartTabProps> = ({ explorer }: Quicks
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
explorer.allocateContainer();
|
||||
explorer.allocateContainer(PoolIdType.DefaultPoolId);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
|
||||
@@ -9,6 +9,7 @@ import { ConnectTab } from "Explorer/Tabs/ConnectTab";
|
||||
import { PostgresConnectTab } from "Explorer/Tabs/PostgresConnectTab";
|
||||
import { QuickstartTab } from "Explorer/Tabs/QuickstartTab";
|
||||
import { userContext } from "UserContext";
|
||||
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
||||
import { useTeachingBubble } from "hooks/useTeachingBubble";
|
||||
import ko from "knockout";
|
||||
import React, { MutableRefObject, useEffect, useRef, useState } from "react";
|
||||
@@ -126,7 +127,12 @@ function TabNav({ tab, active, tabKind }: { tab?: Tab; active: boolean; tabKind?
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
<span className="tabNavText">{useObservable(tab?.tabTitle || getReactTabTitle())}</span>
|
||||
<span
|
||||
className="tabNavText"
|
||||
style={active ? { fontWeight: "bolder", borderBottom: "2px solid rgba(0,120,212,1)" } : {}}
|
||||
>
|
||||
{useObservable(tab?.tabTitle || getReactTabTitle())}
|
||||
</span>
|
||||
{tabKind !== ReactTabKind.Home && (
|
||||
<span className="tabIconSection">
|
||||
<CloseButton tab={tab} active={active} hovering={hovering} tabKind={tabKind} />
|
||||
@@ -158,6 +164,7 @@ const CloseButton = ({
|
||||
onClick={(event: React.MouseEvent<HTMLSpanElement, MouseEvent>) => {
|
||||
event.stopPropagation();
|
||||
tab ? tab.onCloseTabButtonClick() : useTabs.getState().closeReactTab(tabKind);
|
||||
tabKind === ReactTabKind.QueryCopilot && useQueryCopilot.getState().resetQueryCopilotStates();
|
||||
}}
|
||||
tabIndex={active ? 0 : undefined}
|
||||
onKeyPress={({ nativeEvent: e }) => tab.onKeyPressClose(undefined, e)}
|
||||
@@ -251,7 +258,7 @@ const getReactTabContent = (activeReactTab: ReactTabKind, explorer: Explorer): J
|
||||
case ReactTabKind.Quickstart:
|
||||
return <QuickstartTab explorer={explorer} />;
|
||||
case ReactTabKind.QueryCopilot:
|
||||
return <QueryCopilotTab initialInput={useTabs.getState().queryCopilotTabInitialInput} explorer={explorer} />;
|
||||
return <QueryCopilotTab explorer={explorer} />;
|
||||
default:
|
||||
throw Error(`Unsupported tab kind ${ReactTabKind[activeReactTab]}`);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,9 @@ import { useNotebook } from "Explorer/Notebook/useNotebook";
|
||||
import * as ko from "knockout";
|
||||
import * as _ from "underscore";
|
||||
import * as Constants from "../../Common/Constants";
|
||||
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
||||
import * as Logger from "../../Common/Logger";
|
||||
import { fetchPortalNotifications } from "../../Common/PortalNotifications";
|
||||
import { bulkCreateDocument } from "../../Common/dataAccess/bulkCreateDocument";
|
||||
import { createDocument } from "../../Common/dataAccess/createDocument";
|
||||
import { getCollectionUsageSizeInKB } from "../../Common/dataAccess/getCollectionDataUsageSize";
|
||||
@@ -10,19 +13,16 @@ import { readCollectionOffer } from "../../Common/dataAccess/readCollectionOffer
|
||||
import { readStoredProcedures } from "../../Common/dataAccess/readStoredProcedures";
|
||||
import { readTriggers } from "../../Common/dataAccess/readTriggers";
|
||||
import { readUserDefinedFunctions } from "../../Common/dataAccess/readUserDefinedFunctions";
|
||||
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
||||
import * as Logger from "../../Common/Logger";
|
||||
import { fetchPortalNotifications } from "../../Common/PortalNotifications";
|
||||
import * as DataModels from "../../Contracts/DataModels";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import { UploadDetailsRecord } from "../../Contracts/ViewModels";
|
||||
import { useTabs } from "../../hooks/useTabs";
|
||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import { userContext } from "../../UserContext";
|
||||
import { SqlTriggerResource } from "../../Utils/arm/generatedClients/cosmos/types";
|
||||
import { isServerlessAccount } from "../../Utils/CapabilityUtils";
|
||||
import { logConsoleInfo } from "../../Utils/NotificationConsoleUtils";
|
||||
import { SqlTriggerResource } from "../../Utils/arm/generatedClients/cosmos/types";
|
||||
import { useTabs } from "../../hooks/useTabs";
|
||||
import Explorer from "../Explorer";
|
||||
import { useCommandBar } from "../Menus/CommandBar/CommandBarComponentAdapter";
|
||||
import { CassandraAPIDataClient, CassandraTableKey, CassandraTableKeys } from "../Tables/TableDataClient";
|
||||
@@ -526,7 +526,7 @@ export default class Collection implements ViewModels.Collection {
|
||||
|
||||
public onSchemaAnalyzerClick = async () => {
|
||||
if (useNotebook.getState().isPhoenixFeatures) {
|
||||
await this.container.allocateContainer();
|
||||
await this.container.allocateContainer(Constants.PoolIdType.DefaultPoolId);
|
||||
}
|
||||
useSelectedNode.getState().setSelectedNode(this);
|
||||
this.selectedSubnodeKind(ViewModels.CollectionTabKind.SchemaAnalyzer);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"Save": "Save",
|
||||
"Discard": "Discard",
|
||||
"Refresh": "Refesh"
|
||||
"Refresh": "Refresh"
|
||||
}
|
||||
@@ -83,7 +83,7 @@ const App: React.FunctionComponent = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flexContainer">
|
||||
<div className="flexContainer" aria-hidden="false">
|
||||
<div id="divExplorer" className="flexContainer hideOverflows">
|
||||
<div id="freeTierTeachingBubble"> </div>
|
||||
{/* Main Command Bar - Start */}
|
||||
@@ -128,7 +128,7 @@ const App: React.FunctionComponent = () => {
|
||||
{<SQLQuickstartTutorial />}
|
||||
{<MongoQuickstartTutorial />}
|
||||
{<QueryCopilotCarousel isOpen={isCopilotCarouselOpen} explorer={explorer} />}
|
||||
{shouldShowModal && <QueryCopilotFeedbackModal />}
|
||||
{shouldShowModal && <QueryCopilotFeedbackModal explorer={explorer} />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { configContext } from "ConfigContext";
|
||||
import { useDialog } from "Explorer/Controls/Dialog";
|
||||
import promiseRetry, { AbortError } from "p-retry";
|
||||
import { Action } from "Shared/Telemetry/TelemetryConstants";
|
||||
import { userContext } from "UserContext";
|
||||
import { allowedJunoOrigins, validateEndpoint } from "Utils/EndpointValidation";
|
||||
import promiseRetry, { AbortError } from "p-retry";
|
||||
import {
|
||||
Areas,
|
||||
ConnectionStatusType,
|
||||
@@ -12,7 +14,6 @@ import {
|
||||
} from "../Common/Constants";
|
||||
import { getErrorMessage, getErrorStack } from "../Common/ErrorHandlingUtils";
|
||||
import * as Logger from "../Common/Logger";
|
||||
import { configContext } from "../ConfigContext";
|
||||
import {
|
||||
ContainerConnectionInfo,
|
||||
ContainerInfo,
|
||||
@@ -28,7 +29,6 @@ import {
|
||||
} from "../Contracts/DataModels";
|
||||
import { useNotebook } from "../Explorer/Notebook/useNotebook";
|
||||
import * as TelemetryProcessor from "../Shared/Telemetry/TelemetryProcessor";
|
||||
import { userContext } from "../UserContext";
|
||||
import { getAuthorizationHeader } from "../Utils/AuthorizationUtils";
|
||||
|
||||
export class PhoenixClient {
|
||||
|
||||
@@ -36,6 +36,10 @@ export type Features = {
|
||||
readonly enableLegacyMongoShellV2Debug: boolean;
|
||||
readonly loadLegacyMongoShellFromBE: boolean;
|
||||
readonly enableCopilot: boolean;
|
||||
readonly enablePriorityBasedThrottling: boolean;
|
||||
readonly copilotVersion?: string;
|
||||
readonly enableCopilotPhoenixGateaway: boolean;
|
||||
readonly enableCopilotFullSchema: boolean;
|
||||
|
||||
// can be set via both flight and feature flag
|
||||
autoscaleDefault: boolean;
|
||||
@@ -103,7 +107,11 @@ export function extractFeatures(given = new URLSearchParams(window.location.sear
|
||||
enableLegacyMongoShellV2: "true" === get("enablelegacymongoshellv2"),
|
||||
enableLegacyMongoShellV2Debug: "true" === get("enablelegacymongoshellv2debug"),
|
||||
loadLegacyMongoShellFromBE: "true" === get("loadlegacymongoshellfrombe"),
|
||||
enablePriorityBasedThrottling: "true" === get("enableprioritybasedthrottling"),
|
||||
enableCopilot: "true" === get("enablecopilot"),
|
||||
copilotVersion: get("copilotversion") ?? "v1.0",
|
||||
enableCopilotPhoenixGateaway: "true" === get("enablecopilotphoenixgateaway"),
|
||||
enableCopilotFullSchema: "true" === get("enablecopilotfullschema"),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ export const createDefaultSettings = () => {
|
||||
LocalStorageUtility.setEntryNumber(StorageKey.CustomItemPerPage, Constants.Queries.itemsPerPage);
|
||||
LocalStorageUtility.setEntryString(StorageKey.IsCrossPartitionQueryEnabled, "true");
|
||||
LocalStorageUtility.setEntryNumber(StorageKey.MaxDegreeOfParellism, Constants.Queries.DefaultMaxDegreeOfParallelism);
|
||||
LocalStorageUtility.setEntryString(StorageKey.PriorityLevel, Constants.PriorityLevel.Default);
|
||||
};
|
||||
|
||||
export const hasSettingsDefined = (): boolean => {
|
||||
@@ -15,3 +16,9 @@ export const hasSettingsDefined = (): boolean => {
|
||||
LocalStorageUtility.hasItem(StorageKey.MaxDegreeOfParellism)
|
||||
);
|
||||
};
|
||||
|
||||
export const ensurePriorityLevel = () => {
|
||||
if (!LocalStorageUtility.hasItem(StorageKey.PriorityLevel)) {
|
||||
LocalStorageUtility.setEntryString(StorageKey.PriorityLevel, Constants.PriorityLevel.Default);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -16,4 +16,5 @@ export enum StorageKey {
|
||||
SetPartitionKeyUndefined,
|
||||
GalleryCalloutDismissed,
|
||||
VisitedAccounts,
|
||||
PriorityLevel,
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ const userContext: UserContext = {
|
||||
collectionCreationDefaults: CollectionCreationDefaults,
|
||||
};
|
||||
|
||||
function isAccountNewerThanThresholdInMs(createdAt: string, threshold: number) {
|
||||
export function isAccountNewerThanThresholdInMs(createdAt: string, threshold: number) {
|
||||
let createdAtMs: number = Date.parse(createdAt);
|
||||
if (isNaN(createdAtMs)) {
|
||||
createdAtMs = 0;
|
||||
|
||||
@@ -38,7 +38,7 @@ function validateEndpointInternal(
|
||||
return valid;
|
||||
}
|
||||
|
||||
export const allowedArmEndpoints: ReadonlyArray<string> = [
|
||||
export const defaultAllowedArmEndpoints: ReadonlyArray<string> = [
|
||||
"https://management.azure.com",
|
||||
"https://management.usgovcloudapi.net",
|
||||
"https://management.chinacloudapi.cn",
|
||||
@@ -46,7 +46,7 @@ export const allowedArmEndpoints: ReadonlyArray<string> = [
|
||||
|
||||
export const allowedAadEndpoints: ReadonlyArray<string> = ["https://login.microsoftonline.com/"];
|
||||
|
||||
export const allowedBackendEndpoints: ReadonlyArray<string> = [
|
||||
export const defaultAllowedBackendEndpoints: ReadonlyArray<string> = [
|
||||
"https://main.documentdb.ext.azure.com",
|
||||
"https://main.documentdb.ext.azure.cn",
|
||||
"https://main.documentdb.ext.azure.us",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { IChoiceGroupOption, IChoiceGroupProps, IProgressIndicatorProps } from "@fluentui/react";
|
||||
import { Notebook } from "@nteract/commutable";
|
||||
import { NotebookV4 } from "@nteract/commutable/lib/v4";
|
||||
import { HttpStatusCodes } from "../Common/Constants";
|
||||
import { HttpStatusCodes, PoolIdType } from "../Common/Constants";
|
||||
import { getErrorMessage, getErrorStack, handleError } from "../Common/ErrorHandlingUtils";
|
||||
import { TextFieldProps, useDialog } from "../Explorer/Controls/Dialog";
|
||||
import {
|
||||
@@ -229,7 +229,7 @@ export function downloadItem(
|
||||
"Download",
|
||||
async () => {
|
||||
if (useNotebook.getState().isPhoenixNotebooks) {
|
||||
await container.allocateContainer();
|
||||
await container.allocateContainer(PoolIdType.DefaultPoolId);
|
||||
}
|
||||
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
|
||||
if (notebookServerInfo && notebookServerInfo.notebookServerEndpoint !== undefined) {
|
||||
|
||||
59
src/Utils/PriorityBasedExecutionUtils.test.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import * as PriorityBasedExecutionUtils from "./PriorityBasedExecutionUtils";
|
||||
import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility";
|
||||
import { PriorityLevel } from "../Common/Constants";
|
||||
import * as Cosmos from "@azure/cosmos";
|
||||
|
||||
describe("Priority execution utility", () => {
|
||||
it("check default priority level is Low", () => {
|
||||
expect(PriorityBasedExecutionUtils.getPriorityLevel()).toEqual(PriorityLevel.Low);
|
||||
});
|
||||
|
||||
it("check the priority level is returned as present in local storage", () => {
|
||||
LocalStorageUtility.setEntryString(StorageKey.PriorityLevel, PriorityLevel.High);
|
||||
expect(PriorityBasedExecutionUtils.getPriorityLevel()).toEqual(PriorityLevel.High);
|
||||
});
|
||||
|
||||
it("check relevant request based on different resource types", () => {
|
||||
const requestContext1: Cosmos.RequestContext = {
|
||||
resourceType: Cosmos.ResourceType.item,
|
||||
globalEndpointManager: null,
|
||||
connectionPolicy: null,
|
||||
requestAgent: null,
|
||||
method: null,
|
||||
options: null,
|
||||
plugins: null,
|
||||
};
|
||||
const requestContext2: Cosmos.RequestContext = {
|
||||
resourceType: Cosmos.ResourceType.conflicts,
|
||||
globalEndpointManager: null,
|
||||
connectionPolicy: null,
|
||||
requestAgent: null,
|
||||
method: null,
|
||||
options: null,
|
||||
plugins: null,
|
||||
};
|
||||
const requestContext3: Cosmos.RequestContext = {
|
||||
resourceType: Cosmos.ResourceType.sproc,
|
||||
operationType: Cosmos.OperationType.Execute,
|
||||
globalEndpointManager: null,
|
||||
connectionPolicy: null,
|
||||
requestAgent: null,
|
||||
method: null,
|
||||
options: null,
|
||||
plugins: null,
|
||||
};
|
||||
const requestContext4: Cosmos.RequestContext = {
|
||||
resourceType: Cosmos.ResourceType.database,
|
||||
globalEndpointManager: null,
|
||||
connectionPolicy: null,
|
||||
requestAgent: null,
|
||||
method: null,
|
||||
options: null,
|
||||
plugins: null,
|
||||
};
|
||||
expect(PriorityBasedExecutionUtils.isRelevantRequest(requestContext1)).toEqual(true);
|
||||
expect(PriorityBasedExecutionUtils.isRelevantRequest(requestContext2)).toEqual(true);
|
||||
expect(PriorityBasedExecutionUtils.isRelevantRequest(requestContext3)).toEqual(true);
|
||||
expect(PriorityBasedExecutionUtils.isRelevantRequest(requestContext4)).toEqual(false);
|
||||
});
|
||||
});
|
||||
29
src/Utils/PriorityBasedExecutionUtils.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import * as Cosmos from "@azure/cosmos";
|
||||
import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility";
|
||||
import { PriorityLevel } from "../Common/Constants";
|
||||
|
||||
export function isRelevantRequest(requestContext: Cosmos.RequestContext): boolean {
|
||||
return (
|
||||
requestContext.resourceType === Cosmos.ResourceType.item ||
|
||||
requestContext.resourceType === Cosmos.ResourceType.conflicts ||
|
||||
(requestContext.resourceType === Cosmos.ResourceType.sproc &&
|
||||
requestContext.operationType === Cosmos.OperationType.Execute)
|
||||
);
|
||||
}
|
||||
|
||||
export function getPriorityLevel(): PriorityLevel {
|
||||
const priorityLevel = LocalStorageUtility.getEntryString(StorageKey.PriorityLevel);
|
||||
if (priorityLevel && Object.values(PriorityLevel).includes(priorityLevel)) {
|
||||
return priorityLevel as PriorityLevel;
|
||||
} else {
|
||||
return PriorityLevel.Default;
|
||||
}
|
||||
}
|
||||
|
||||
export const requestPlugin: Cosmos.Plugin<any> = async (requestContext, next) => {
|
||||
if (isRelevantRequest(requestContext)) {
|
||||
const priorityLevel: PriorityLevel = getPriorityLevel();
|
||||
requestContext.headers["x-ms-cosmos-priority-level"] = priorityLevel as string;
|
||||
}
|
||||
return next(requestContext);
|
||||
};
|
||||
@@ -294,6 +294,9 @@ async function configurePortal(): Promise<Explorer> {
|
||||
updateContextsFromPortalMessage(inputs);
|
||||
explorer = new Explorer();
|
||||
resolve(explorer);
|
||||
if (userContext.apiType === "Postgres") {
|
||||
explorer.openNPSSurveyDialog();
|
||||
}
|
||||
if (openAction) {
|
||||
handleOpenAction(openAction, useDatabases.getState().databases, explorer);
|
||||
}
|
||||
|
||||
@@ -1,30 +1,172 @@
|
||||
import { MinimalQueryIterator } from "Common/IteratorUtilities";
|
||||
import { QueryResults } from "Contracts/ViewModels";
|
||||
import { CopilotMessage } from "Explorer/QueryCopilot/Shared/QueryCopilotInterfaces";
|
||||
import { guid } from "Explorer/Tables/Utilities";
|
||||
import create, { UseStore } from "zustand";
|
||||
|
||||
interface QueryCopilotState {
|
||||
export interface QueryCopilotState {
|
||||
generatedQuery: string;
|
||||
likeQuery: boolean;
|
||||
userPrompt: string;
|
||||
showFeedbackModal: boolean;
|
||||
hideFeedbackModalForLikedQueries: boolean;
|
||||
correlationId: string;
|
||||
query: string;
|
||||
selectedQuery: string;
|
||||
isGeneratingQuery: boolean;
|
||||
isGeneratingExplanation: boolean;
|
||||
isExecuting: boolean;
|
||||
dislikeQuery: boolean | undefined;
|
||||
showCallout: boolean;
|
||||
showSamplePrompts: boolean;
|
||||
queryIterator: MinimalQueryIterator | undefined;
|
||||
queryResults: QueryResults | undefined;
|
||||
errorMessage: string;
|
||||
isSamplePromptsOpen: boolean;
|
||||
showDeletePopup: boolean;
|
||||
showFeedbackBar: boolean;
|
||||
showCopyPopup: boolean;
|
||||
showErrorMessageBar: boolean;
|
||||
generatedQueryComments: string;
|
||||
wasCopilotUsed: boolean;
|
||||
showWelcomeSidebar: boolean;
|
||||
showCopilotSidebar: boolean;
|
||||
chatMessages: CopilotMessage[];
|
||||
shouldAllocateContainer: boolean;
|
||||
shouldIncludeInMessages: boolean;
|
||||
|
||||
openFeedbackModal: (generatedQuery: string, likeQuery: boolean, userPrompt: string) => void;
|
||||
closeFeedbackModal: () => void;
|
||||
setHideFeedbackModalForLikedQueries: (hideFeedbackModalForLikedQueries: boolean) => void;
|
||||
refreshCorrelationId: () => void;
|
||||
setUserPrompt: (userPrompt: string) => void;
|
||||
setQuery: (query: string) => void;
|
||||
setGeneratedQuery: (generatedQuery: string) => void;
|
||||
setSelectedQuery: (selectedQuery: string) => void;
|
||||
setIsGeneratingQuery: (isGeneratingQuery: boolean) => void;
|
||||
setIsGeneratingExplanation: (isGeneratingExplanation: boolean) => void;
|
||||
setIsExecuting: (isExecuting: boolean) => void;
|
||||
setLikeQuery: (likeQuery: boolean) => void;
|
||||
setDislikeQuery: (dislikeQuery: boolean | undefined) => void;
|
||||
setShowCallout: (showCallout: boolean) => void;
|
||||
setShowSamplePrompts: (showSamplePrompts: boolean) => void;
|
||||
setQueryIterator: (queryIterator: MinimalQueryIterator | undefined) => void;
|
||||
setQueryResults: (queryResults: QueryResults | undefined) => void;
|
||||
setErrorMessage: (errorMessage: string) => void;
|
||||
setIsSamplePromptsOpen: (isSamplePromptsOpen: boolean) => void;
|
||||
setShowDeletePopup: (showDeletePopup: boolean) => void;
|
||||
setShowFeedbackBar: (showFeedbackBar: boolean) => void;
|
||||
setshowCopyPopup: (showCopyPopup: boolean) => void;
|
||||
setShowErrorMessageBar: (showErrorMessageBar: boolean) => void;
|
||||
setGeneratedQueryComments: (generatedQueryComments: string) => void;
|
||||
setWasCopilotUsed: (wasCopilotUsed: boolean) => void;
|
||||
setShowWelcomeSidebar: (showWelcomeSidebar: boolean) => void;
|
||||
setShowCopilotSidebar: (showCopilotSidebar: boolean) => void;
|
||||
setChatMessages: (chatMessages: CopilotMessage[]) => void;
|
||||
|
||||
setShouldAllocateContainer: (shouldAllocateContainer: boolean) => void;
|
||||
setShouldIncludeInMessages: (shouldIncludeInMessages: boolean) => void;
|
||||
|
||||
resetQueryCopilotStates: () => void;
|
||||
}
|
||||
|
||||
export const useQueryCopilot: UseStore<QueryCopilotState> = create((set) => ({
|
||||
type QueryCopilotStore = UseStore<QueryCopilotState>;
|
||||
|
||||
export const useQueryCopilot: QueryCopilotStore = create((set) => ({
|
||||
generatedQuery: "",
|
||||
likeQuery: false,
|
||||
userPrompt: "",
|
||||
showFeedbackModal: false,
|
||||
hideFeedbackModalForLikedQueries: false,
|
||||
correlationId: "",
|
||||
query: "",
|
||||
selectedQuery: "",
|
||||
isGeneratingQuery: false,
|
||||
isGeneratingExplanation: false,
|
||||
isExecuting: false,
|
||||
dislikeQuery: undefined,
|
||||
showCallout: false,
|
||||
showSamplePrompts: false,
|
||||
queryIterator: undefined,
|
||||
queryResults: undefined,
|
||||
errorMessage: "",
|
||||
isSamplePromptsOpen: false,
|
||||
showDeletePopup: false,
|
||||
showFeedbackBar: false,
|
||||
showCopyPopup: false,
|
||||
showErrorMessageBar: false,
|
||||
generatedQueryComments: "",
|
||||
wasCopilotUsed: false,
|
||||
showWelcomeSidebar: true,
|
||||
showCopilotSidebar: false,
|
||||
chatMessages: [],
|
||||
shouldAllocateContainer: true,
|
||||
shouldIncludeInMessages: true,
|
||||
|
||||
openFeedbackModal: (generatedQuery: string, likeQuery: boolean, userPrompt: string) =>
|
||||
set({ generatedQuery, likeQuery, userPrompt, showFeedbackModal: true }),
|
||||
closeFeedbackModal: () => set({ generatedQuery: "", likeQuery: false, userPrompt: "", showFeedbackModal: false }),
|
||||
setHideFeedbackModalForLikedQueries: (hideFeedbackModalForLikedQueries: boolean) =>
|
||||
set({ hideFeedbackModalForLikedQueries }),
|
||||
refreshCorrelationId: () => set({ correlationId: guid() }),
|
||||
setUserPrompt: (userPrompt: string) => set({ userPrompt }),
|
||||
setQuery: (query: string) => set({ query }),
|
||||
setGeneratedQuery: (generatedQuery: string) => set({ generatedQuery }),
|
||||
setSelectedQuery: (selectedQuery: string) => set({ selectedQuery }),
|
||||
setIsGeneratingQuery: (isGeneratingQuery: boolean) => set({ isGeneratingQuery }),
|
||||
setIsGeneratingExplanation: (isGeneratingExplanation: boolean) => set({ isGeneratingExplanation }),
|
||||
setIsExecuting: (isExecuting: boolean) => set({ isExecuting }),
|
||||
setLikeQuery: (likeQuery: boolean) => set({ likeQuery }),
|
||||
setDislikeQuery: (dislikeQuery: boolean | undefined) => set({ dislikeQuery }),
|
||||
setShowCallout: (showCallout: boolean) => set({ showCallout }),
|
||||
setShowSamplePrompts: (showSamplePrompts: boolean) => set({ showSamplePrompts }),
|
||||
setQueryIterator: (queryIterator: MinimalQueryIterator | undefined) => set({ queryIterator }),
|
||||
setQueryResults: (queryResults: QueryResults | undefined) => set({ queryResults }),
|
||||
setErrorMessage: (errorMessage: string) => set({ errorMessage }),
|
||||
setIsSamplePromptsOpen: (isSamplePromptsOpen: boolean) => set({ isSamplePromptsOpen }),
|
||||
setShowDeletePopup: (showDeletePopup: boolean) => set({ showDeletePopup }),
|
||||
setShowFeedbackBar: (showFeedbackBar: boolean) => set({ showFeedbackBar }),
|
||||
setshowCopyPopup: (showCopyPopup: boolean) => set({ showCopyPopup }),
|
||||
setShowErrorMessageBar: (showErrorMessageBar: boolean) => set({ showErrorMessageBar }),
|
||||
setGeneratedQueryComments: (generatedQueryComments: string) => set({ generatedQueryComments }),
|
||||
setWasCopilotUsed: (wasCopilotUsed: boolean) => set({ wasCopilotUsed }),
|
||||
setShowWelcomeSidebar: (showWelcomeSidebar: boolean) => set({ showWelcomeSidebar }),
|
||||
setShowCopilotSidebar: (showCopilotSidebar: boolean) => set({ showCopilotSidebar }),
|
||||
setChatMessages: (chatMessages: CopilotMessage[]) => set({ chatMessages }),
|
||||
setShouldAllocateContainer: (shouldAllocateContainer: boolean) => set({ shouldAllocateContainer }),
|
||||
setShouldIncludeInMessages: (shouldIncludeInMessages: boolean) => set({ shouldIncludeInMessages }),
|
||||
|
||||
resetQueryCopilotStates: () => {
|
||||
set((state) => ({
|
||||
...state,
|
||||
generatedQuery: "",
|
||||
likeQuery: false,
|
||||
userPrompt: "",
|
||||
showFeedbackModal: false,
|
||||
hideFeedbackModalForLikedQueries: false,
|
||||
correlationId: "",
|
||||
query: "",
|
||||
selectedQuery: "",
|
||||
isGeneratingQuery: false,
|
||||
isGeneratingExplanation: false,
|
||||
isExecuting: false,
|
||||
dislikeQuery: undefined,
|
||||
showCallout: false,
|
||||
showSamplePrompts: false,
|
||||
queryIterator: undefined,
|
||||
queryResults: undefined,
|
||||
errorMessage: "",
|
||||
isSamplePromptsOpen: false,
|
||||
showDeletePopup: false,
|
||||
showFeedbackBar: false,
|
||||
showCopyPopup: false,
|
||||
showErrorMessageBar: false,
|
||||
generatedQueryComments: "",
|
||||
wasCopilotUsed: false,
|
||||
showCopilotSidebar: false,
|
||||
chatMessages: [],
|
||||
shouldAllocateContainer: true,
|
||||
shouldIncludeInMessages: true,
|
||||
}));
|
||||
},
|
||||
}));
|
||||
|
||||