Adding user feedback webpage. (#18099)
* Adding user surveys * Fixing lang to point to mssql * Adding loc files * adding temp userSurvey controller * adding loc files * adding boilerplate code * Adding more boilerplate code * Adding command * Finishing survey * Adding loc files * Adding option to pass in user id * Fixing cancelling panel * Fixing some strings * fixing stuff * Refactor user survey page to use record for user answers * Adding privacy statement * Adding localization * Fixing stuff and adding privacy statement link * remove subtitle * Making a helper function to get standard form * Adding standard nps question to a bunch of important features * Fixing nps prompts
This commit is contained in:
Родитель
9cab31d1f5
Коммит
0c71cd2946
|
@ -188,6 +188,7 @@ async function generateReactWebviewsBundle() {
|
|||
'tableDesigner': 'src/reactviews/pages/TableDesigner/index.tsx',
|
||||
'objectExplorerFilter': 'src/reactviews/pages/ObjectExplorerFilter/index.tsx',
|
||||
'queryResult': 'src/reactviews/pages/QueryResult/index.tsx',
|
||||
'userSurvey': 'src/reactviews/pages/UserSurvey/index.tsx',
|
||||
},
|
||||
bundle: true,
|
||||
outdir: 'out/src/reactviews/assets',
|
||||
|
|
|
@ -238,6 +238,16 @@
|
|||
"Expand All": "Expand All",
|
||||
"Collapse All": "Collapse All",
|
||||
"Filter for any field...": "Filter for any field...",
|
||||
"Microsoft would like your feedback": "Microsoft would like your feedback",
|
||||
"Overall, how satisfied are you with the MSSQL extension?": "Overall, how satisfied are you with the MSSQL extension?",
|
||||
"Very Satisfied": "Very Satisfied",
|
||||
"Satisfied": "Satisfied",
|
||||
"Dissatisfied": "Dissatisfied",
|
||||
"Very Dissatisfied": "Very Dissatisfied",
|
||||
"Submit": "Submit",
|
||||
"Not likely at all": "Not likely at all",
|
||||
"Extremely likely": "Extremely likely",
|
||||
"Privacy Statement": "Privacy Statement",
|
||||
"Object Explorer Filter": "Object Explorer Filter",
|
||||
"{0} (filtered)": "{0} (filtered)",
|
||||
"View More": "View More",
|
||||
|
@ -676,6 +686,26 @@
|
|||
"{1} is the subscription id"
|
||||
]
|
||||
},
|
||||
"How likely it is that you would recommend the MSSQL extension to a friend or colleague?": "How likely it is that you would recommend the MSSQL extension to a friend or colleague?",
|
||||
"What can we do to improve?": "What can we do to improve?",
|
||||
"Take Survey": "Take Survey",
|
||||
"Remind Me Later": "Remind Me Later",
|
||||
"Don't Show Again": "Don't Show Again",
|
||||
"Do you mind taking a quick feedback survey about the MSSQL Extension for VS Code?": "Do you mind taking a quick feedback survey about the MSSQL Extension for VS Code?",
|
||||
"MSSQL Feedback": "MSSQL Feedback",
|
||||
"Microsoft reviews your feedback to improve our products, so don't share any personal data or confidential/proprietary content.": "Microsoft reviews your feedback to improve our products, so don't share any personal data or confidential/proprietary content.",
|
||||
"Overall, how satisfied are you with {0}?/{0} is the feature name": {
|
||||
"message": "Overall, how satisfied are you with {0}?",
|
||||
"comment": [
|
||||
"{0} is the feature name"
|
||||
]
|
||||
},
|
||||
"How likely it is that you would recommend {0} to a friend or colleague?/{0} is the feature name": {
|
||||
"message": "How likely it is that you would recommend {0} to a friend or colleague?",
|
||||
"comment": [
|
||||
"{0} is the feature name"
|
||||
]
|
||||
},
|
||||
"Azure sign in failed.": "Azure sign in failed.",
|
||||
"Error loading Azure subscriptions.": "Error loading Azure subscriptions.",
|
||||
"No subscriptions set in VS Code's Azure account filter.": "No subscriptions set in VS Code's Azure account filter.",
|
||||
|
|
|
@ -366,6 +366,15 @@
|
|||
<trans-unit id="++CODE++7bba5bcc44de61b37dcadb5824fac7130483b749da0c5e9eea5e02765e06e37d">
|
||||
<source xml:lang="en">Displays the unified data type (including length, scale and precision) for the column</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="++CODE++7a417a747e385113d9c481ed4937ff3d5b31de744c4db4312464f6f50729b616">
|
||||
<source xml:lang="en">Dissatisfied</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="++CODE++5ce32ed24291b6ee178d1069ca31842e8ead67e82f070a284cec1ab95498476c">
|
||||
<source xml:lang="en">Do you mind taking a quick feedback survey about the MSSQL Extension for VS Code?</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="++CODE++1d60d0cdcb5408b8e39ed364f675c9cad8a2f6174a413324d1e34437bd41269b">
|
||||
<source xml:lang="en">Don't Show Again</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="++CODE++464c4ffd019e1e9691dcf0537c797353ef2b1c1d4833d3d463e5b74ae4547344">
|
||||
<source xml:lang="en">Edit</source>
|
||||
</trans-unit>
|
||||
|
@ -464,6 +473,9 @@
|
|||
<trans-unit id="++CODE++c67415bcff328a59fd399e2a7ca9691e0044192fb7480ae501644339965d046d">
|
||||
<source xml:lang="en">Expression</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="++CODE++0556bfb755c73e8c60da1230c38f26ce0eaf79dc7bd5f5368371d561e5eab52f">
|
||||
<source xml:lang="en">Extremely likely</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="++CODE++031a8f0f659df890dfd53c92e45295b0f14c997185bae46e168831e403b273f7">
|
||||
<source xml:lang="en">Failed</source>
|
||||
</trans-unit>
|
||||
|
@ -562,6 +574,13 @@
|
|||
<trans-unit id="++CODE++881cda150f08bbb74d0fa62b161f8a953cf0e98bf734b60d0e059b408b75522b">
|
||||
<source xml:lang="en">Highlight Expensive Operation</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="++CODE++cdcc527989217aea79f51128ad3c86fa74d6c9def496f199f6b8d9728791d3b4">
|
||||
<source xml:lang="en">How likely it is that you would recommend the MSSQL extension to a friend or colleague?</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="++CODE++31a89f9bfc36d6f9ecf0a7e2e82cb5a9a0f1e7b1702f08bd3aced239c4f48a0b">
|
||||
<source xml:lang="en">How likely it is that you would recommend {0} to a friend or colleague?</source>
|
||||
<note>{0} is the feature name</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="++CODE++9638b09db2ce74e3d600f9a512a6116df9ba9b3a147569930a5d5b4682ac5a20">
|
||||
<source xml:lang="en">I have read the summary and understand the potential risks.</source>
|
||||
</trans-unit>
|
||||
|
@ -627,6 +646,9 @@
|
|||
<trans-unit id="++CODE++aaaeb8d7872b0c49ed19d951314516d2dcd84ca23411320f05fac3d87bac8a32">
|
||||
<source xml:lang="en">MSSQL</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="++CODE++9f67971ab8a605f2aedf0b33a5fce37d2b135aef48f886231da5d8163aa488a6">
|
||||
<source xml:lang="en">MSSQL Feedback</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="++CODE++614e5389e87ce465fe548a0665aeaa67a52a06ff792f2f791615cd37fb610178">
|
||||
<source xml:lang="en">Manage Connection Profiles</source>
|
||||
</trans-unit>
|
||||
|
@ -667,6 +689,12 @@
|
|||
<source xml:lang="en">Microsoft Entra account {0} successfully added.</source>
|
||||
<note>{0} is the account name</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="++CODE++951f4af90cfde827d87f358b3d8d275ee60a3306d9ddb5c4615483cff067697a">
|
||||
<source xml:lang="en">Microsoft reviews your feedback to improve our products, so don't share any personal data or confidential/proprietary content.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="++CODE++7d4b99d7fea1f8fb56040b0f388071d3f886f709000859485129c0ce1930fce9">
|
||||
<source xml:lang="en">Microsoft would like your feedback</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="++CODE++b58330ac25057a441365f4f4f1be20daba2d6d940142c891231e0eac66843ff4">
|
||||
<source xml:lang="en">Move Down</source>
|
||||
</trans-unit>
|
||||
|
@ -741,6 +769,9 @@
|
|||
<trans-unit id="++CODE++544330f40460cbf3595ddb51445d81c643d5cd9dc72d601db887c01169b5f388">
|
||||
<source xml:lang="en">Not Starts With</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="++CODE++972711d90594be0ec9340bc56950dc455b2764187dcad7093ae641df2cbc10c5">
|
||||
<source xml:lang="en">Not likely at all</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="++CODE++ba35f0c47d862763dafa955d6716942f79b8bfe1d01d5968520db6f9ba665f6f">
|
||||
<source xml:lang="en">Not started</source>
|
||||
</trans-unit>
|
||||
|
@ -789,6 +820,13 @@
|
|||
<trans-unit id="++CODE++da53ba1a285ffae9c6528e235b57336fa85b4f05e9fc00a51ee922069c3d9865">
|
||||
<source xml:lang="en">Optional (False)</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="++CODE++eabaa5ba70b7871bd005170e9a540a993456433cdaad54eacc4e4c07a13c71bb">
|
||||
<source xml:lang="en">Overall, how satisfied are you with the MSSQL extension?</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="++CODE++cd184ad9e92906aa47b1a2a184c7d63d4c3642b0ee74079f2d428841ff17036c">
|
||||
<source xml:lang="en">Overall, how satisfied are you with {0}?</source>
|
||||
<note>{0} is the feature name</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="++CODE++e68b36b17cbd990802f57741cb75cf3a73fa66a61999b0cd70e3cf7d26cfb25f">
|
||||
<source xml:lang="en">Parameters</source>
|
||||
</trans-unit>
|
||||
|
@ -826,6 +864,9 @@
|
|||
<trans-unit id="++CODE++a0de89c19964a6454c6d6b4f4205b8c8fcb6c1bfe9370b6d3183226ce8009141">
|
||||
<source xml:lang="en">Primary Key Columns</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="++CODE++25f4fe8cd149e57de765fa487f6e70395ed29ad8d1f2b9c116f9efa24262b420">
|
||||
<source xml:lang="en">Privacy Statement</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="++CODE++a423b47777783386516c79baa19b0dfcd12cb40e4fba79348ad14ab0169402cd">
|
||||
<source xml:lang="en">Profile Name</source>
|
||||
</trans-unit>
|
||||
|
@ -883,6 +924,9 @@
|
|||
<trans-unit id="++CODE++0cd1ba1d31d508fcf599096e647eb0c1a60819928248e404a3f71ea9f7aafad8">
|
||||
<source xml:lang="en">Reload Visual Studio Code</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="++CODE++b69ef66763bd411348c2bf030ab4cd0881dc36c538ce1a05966e92b927d2675c">
|
||||
<source xml:lang="en">Remind Me Later</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="++CODE++c3812fc4acb861d5182fc2b8155f327f736fbe5e5eb86a7bd7afcb6dc5497282">
|
||||
<source xml:lang="en">Remove</source>
|
||||
</trans-unit>
|
||||
|
@ -917,6 +961,9 @@
|
|||
<trans-unit id="++CODE++bcb563c464628dce28a629cdda03742239a5b0f9e25da0a87aea6e68ad25933a">
|
||||
<source xml:lang="en">SQL Login</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="++CODE++be3bac2c67dcc1b486b10add2ba1bf56e6e49fd17d6187fe6b6c012b08cedbfc">
|
||||
<source xml:lang="en">Satisfied</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="++CODE++caf4128d5bf679fef30d60d545684f44efcc2e9a7098df16fcbd0b625580c89f">
|
||||
<source xml:lang="en">Save Password</source>
|
||||
</trans-unit>
|
||||
|
@ -1032,6 +1079,9 @@
|
|||
<trans-unit id="++CODE++72927b6fdb5388115d478bb5e0e69c203231f35ad2e0721d77750626ea4fe4db">
|
||||
<source xml:lang="en">Starts With</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="++CODE++155f816c0407310c0dab222493370773e045ee7fe04e6c9a951b07f495531264">
|
||||
<source xml:lang="en">Submit</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="++CODE++4999c6c6c7badf456f5b0053b09a2076bdb2391a09ab10c9064a680f8f9a0b30">
|
||||
<source xml:lang="en">Subscription</source>
|
||||
</trans-unit>
|
||||
|
@ -1059,6 +1109,9 @@
|
|||
<trans-unit id="++CODE++529667eb9a218f074e24ec63181bb6b3bd4e5ea744e64f71262b6323251ed743">
|
||||
<source xml:lang="en">Table name</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="++CODE++9981cdae853624ee8dffbae9510a8f8b9d588788aab84587374f6dd6bc7eabdd">
|
||||
<source xml:lang="en">Take Survey</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="++CODE++e23969d284c3424c8014c6e5b1b85ebc275bc5c74321e7677a21d023e6ea154c">
|
||||
<source xml:lang="en">Tenant</source>
|
||||
</trans-unit>
|
||||
|
@ -1195,12 +1248,21 @@
|
|||
<trans-unit id="++CODE++8e37953d23daca5ff01b8282c33f4e0a2152f1d1885f94c06418617e3ee1d24e">
|
||||
<source xml:lang="en">Value</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="++CODE++7ef8939f723e18dae31afe6a66699cdd093848ce10e84fcb5d34e15950cc2a06">
|
||||
<source xml:lang="en">Very Dissatisfied</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="++CODE++421600005b3f0bef9188978d2e890f5c5ff1cdaafa4da92f07a52cdc6295c1c6">
|
||||
<source xml:lang="en">Very Satisfied</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="++CODE++15435b311c9371437109bd5323292504915507b034803118517fee44e9547a3c">
|
||||
<source xml:lang="en">View More</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="++CODE++ff01d2362e483ddaca44f0e7f02d280bef382c1a3209776e5a11ca21acf2cea4">
|
||||
<source xml:lang="en">View mssql for Visual Studio Code release notes?</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="++CODE++bf3cd82434efadb9ea501ff44c7d52b0dd98b3d11c0097ba88a10b7fd14d9b54">
|
||||
<source xml:lang="en">What can we do to improve?</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="++CODE++4aa3356437232c6d45b29802f09eee6e201660a67cf78e145f39ad1fada6feab">
|
||||
<source xml:lang="en">Width cannot be 0 or negative</source>
|
||||
</trans-unit>
|
||||
|
@ -1463,6 +1525,9 @@
|
|||
<trans-unit id="mssql.showGettingStarted">
|
||||
<source xml:lang="en">Getting Started Guide</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="mssql.userFeedback">
|
||||
<source xml:lang="en">Give Feedback</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="mssql.Configuration">
|
||||
<source xml:lang="en">MSSQL configuration</source>
|
||||
</trans-unit>
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
<svg id="b5bfbf48-10dd-4d21-8324-df7de0afa790" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<path d="M7.484,10.375a5.6,5.6,0,0,1,1.438.828,5.376,5.376,0,0,1,1.109,1.2,5.682,5.682,0,0,1,.711,1.461A5.5,5.5,0,0,1,11,15.5V16H10v-.5a4.385,4.385,0,0,0-.352-1.75,4.459,4.459,0,0,0-.968-1.43,4.654,4.654,0,0,0-1.43-.961A4.458,4.458,0,0,0,5.5,11a4.385,4.385,0,0,0-1.75.352,4.459,4.459,0,0,0-1.43.968,4.654,4.654,0,0,0-.961,1.43A4.458,4.458,0,0,0,1,15.5V16H0v-.5a5.391,5.391,0,0,1,.969-3.1,5.7,5.7,0,0,1,1.109-1.187,5.614,5.614,0,0,1,1.438-.836,3.359,3.359,0,0,1-.633-.563,3.551,3.551,0,0,1-.477-.687,3.387,3.387,0,0,1-.3-.781A3.858,3.858,0,0,1,2,7.5a3.391,3.391,0,0,1,.273-1.359,3.525,3.525,0,0,1,1.86-1.86A3.5,3.5,0,0,1,5.5,4a3.391,3.391,0,0,1,1.359.273,3.525,3.525,0,0,1,1.86,1.86A3.5,3.5,0,0,1,9,7.5a3.424,3.424,0,0,1-.1.836,3.311,3.311,0,0,1-.3.781,4.035,4.035,0,0,1-.477.695A3.076,3.076,0,0,1,7.484,10.375ZM5.5,10a2.424,2.424,0,0,0,.969-.2,2.523,2.523,0,0,0,.789-.532,2.6,2.6,0,0,0,.539-.8,2.478,2.478,0,0,0,.008-1.946,2.46,2.46,0,0,0-.539-.789,2.746,2.746,0,0,0-.8-.539A2.348,2.348,0,0,0,5.5,5a2.424,2.424,0,0,0-.969.2A2.537,2.537,0,0,0,3.2,6.531a2.505,2.505,0,0,0,0,1.938,2.631,2.631,0,0,0,.532.8,2.436,2.436,0,0,0,.8.539A2.484,2.484,0,0,0,5.5,10ZM16,0V8H14l-3,3V8H10V7h2V8.586L13.586,7H15V1H5V2.8c-.167.021-.333.047-.5.078a2.926,2.926,0,0,0-.5.141V0Z" fill="white" />
|
||||
</svg>
|
После Ширина: | Высота: | Размер: 1.4 KiB |
|
@ -0,0 +1,3 @@
|
|||
<svg id="b5bfbf48-10dd-4d21-8324-df7de0afa790" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<path d="M7.484,10.375a5.6,5.6,0,0,1,1.438.828,5.376,5.376,0,0,1,1.109,1.2,5.682,5.682,0,0,1,.711,1.461A5.5,5.5,0,0,1,11,15.5V16H10v-.5a4.385,4.385,0,0,0-.352-1.75,4.459,4.459,0,0,0-.968-1.43,4.654,4.654,0,0,0-1.43-.961A4.458,4.458,0,0,0,5.5,11a4.385,4.385,0,0,0-1.75.352,4.459,4.459,0,0,0-1.43.968,4.654,4.654,0,0,0-.961,1.43A4.458,4.458,0,0,0,1,15.5V16H0v-.5a5.391,5.391,0,0,1,.969-3.1,5.7,5.7,0,0,1,1.109-1.187,5.614,5.614,0,0,1,1.438-.836,3.359,3.359,0,0,1-.633-.563,3.551,3.551,0,0,1-.477-.687,3.387,3.387,0,0,1-.3-.781A3.858,3.858,0,0,1,2,7.5a3.391,3.391,0,0,1,.273-1.359,3.525,3.525,0,0,1,1.86-1.86A3.5,3.5,0,0,1,5.5,4a3.391,3.391,0,0,1,1.359.273,3.525,3.525,0,0,1,1.86,1.86A3.5,3.5,0,0,1,9,7.5a3.424,3.424,0,0,1-.1.836,3.311,3.311,0,0,1-.3.781,4.035,4.035,0,0,1-.477.695A3.076,3.076,0,0,1,7.484,10.375ZM5.5,10a2.424,2.424,0,0,0,.969-.2,2.523,2.523,0,0,0,.789-.532,2.6,2.6,0,0,0,.539-.8,2.478,2.478,0,0,0,.008-1.946,2.46,2.46,0,0,0-.539-.789,2.746,2.746,0,0,0-.8-.539A2.348,2.348,0,0,0,5.5,5a2.424,2.424,0,0,0-.969.2A2.537,2.537,0,0,0,3.2,6.531a2.505,2.505,0,0,0,0,1.938,2.631,2.631,0,0,0,.532.8,2.436,2.436,0,0,0,.8.539A2.484,2.484,0,0,0,5.5,10ZM16,0V8H14l-3,3V8H10V7h2V8.586L13.586,7H15V1H5V2.8c-.167.021-.333.047-.5.078a2.926,2.926,0,0,0-.5.141V0Z" fill="black" />
|
||||
</svg>
|
После Ширина: | Высота: | Размер: 1.4 KiB |
|
@ -794,6 +794,11 @@
|
|||
"command": "mssql.clearAzureAccountTokenCache",
|
||||
"title": "%mssql.clearAzureAccountTokenCache%",
|
||||
"category": "MS SQL"
|
||||
},
|
||||
{
|
||||
"command": "mssql.userFeedback",
|
||||
"title": "%mssql.userFeedback%",
|
||||
"category": "MS SQL"
|
||||
}
|
||||
],
|
||||
"keybindings": [
|
||||
|
|
|
@ -148,5 +148,6 @@
|
|||
"mssql.filterNode":"Filter",
|
||||
"mssql.clearFilters":"Clear Filters",
|
||||
"mssql.openExecutionPlanFile":"Open Execution Plan File",
|
||||
"mssql.enableExperimentalFeatures.description":"Enables experimental features in the MSSQL extension. The features are not production-ready and may have bugs or issues. Restart Visual Studio Code after changing this setting."
|
||||
"mssql.enableExperimentalFeatures.description":"Enables experimental features in the MSSQL extension. The features are not production-ready and may have bugs or issues. Restart Visual Studio Code after changing this setting.",
|
||||
"mssql.userFeedback":"Give Feedback"
|
||||
}
|
||||
|
|
|
@ -48,6 +48,7 @@ import {
|
|||
} from "@azure/arm-resources";
|
||||
import { getErrorMessage, listAllIterator } from "../utils/utils";
|
||||
import { l10n } from "vscode";
|
||||
import { UserSurvey } from "../nps/userSurvey";
|
||||
|
||||
export class ConnectionDialogWebviewController extends ReactWebviewPanelController<
|
||||
ConnectionDialogWebviewState,
|
||||
|
@ -933,6 +934,7 @@ export class ConnectionDialogWebviewController extends ReactWebviewPanelControll
|
|||
expand: true,
|
||||
});
|
||||
await this.panel.dispose();
|
||||
await UserSurvey.getInstance().promptUserForNPSFeedback();
|
||||
} catch (error) {
|
||||
this.state.connectionStatus = ApiStatus.Error;
|
||||
return state;
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
// Collection of Non-localizable Constants
|
||||
export const languageId = "sql";
|
||||
export const extensionId = "ms-mssql.mssql";
|
||||
export const extensionName = "mssql";
|
||||
export const extensionConfigSectionName = "mssql";
|
||||
export const telemetryConfigSectionName = "telemetry";
|
||||
|
@ -81,6 +82,7 @@ export const cmdClearAzureTokenCache = "mssql.clearAzureAccountTokenCache";
|
|||
export const cmdNewTable = "mssql.newTable";
|
||||
export const cmdEditTable = "mssql.editTable";
|
||||
export const cmdEditConnection = "mssql.editConnection";
|
||||
export const cmdLaunchUserFeedback = "mssql.userFeedback";
|
||||
export const piiLogging = "piiLogging";
|
||||
export const mssqlPiiLogging = "mssql.piiLogging";
|
||||
export const enableSqlAuthenticationProvider =
|
||||
|
@ -210,6 +212,8 @@ export const scriptSelectText = "SELECT TOP (1000) * FROM ";
|
|||
export const tenantDisplayName = "Microsoft";
|
||||
export const windowsResourceClientPath = "SqlToolsResourceProviderService.exe";
|
||||
export const unixResourceClientPath = "SqlToolsResourceProviderService";
|
||||
export const microsoftPrivacyStatementUrl =
|
||||
"https://www.microsoft.com/en-us/privacy/privacystatement";
|
||||
|
||||
export enum Platform {
|
||||
Windows = "win32",
|
||||
|
|
|
@ -677,3 +677,39 @@ export class ConnectionDialog {
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class UserSurvey {
|
||||
public static overallHowSatisfiedAreYouWithMSSQLExtension = l10n.t(
|
||||
"Overall, how satisfied are you with the MSSQL extension?",
|
||||
);
|
||||
public static howlikelyAreYouToRecommendMSSQLExtension = l10n.t(
|
||||
"How likely it is that you would recommend the MSSQL extension to a friend or colleague?",
|
||||
);
|
||||
public static whatCanWeDoToImprove = l10n.t("What can we do to improve?");
|
||||
public static takeSurvey = l10n.t("Take Survey");
|
||||
public static remindMeLater = l10n.t("Remind Me Later");
|
||||
public static dontShowAgain = l10n.t("Don't Show Again");
|
||||
public static doYouMindTakingAQuickFeedbackSurvey = l10n.t(
|
||||
"Do you mind taking a quick feedback survey about the MSSQL Extension for VS Code?",
|
||||
);
|
||||
public static mssqlFeedback = l10n.t("MSSQL Feedback");
|
||||
public static privacyDisclaimer = l10n.t(
|
||||
"Microsoft reviews your feedback to improve our products, so don't share any personal data or confidential/proprietary content.",
|
||||
);
|
||||
public static overallHowStatisfiedAreYouWithFeature = (
|
||||
featureName: string,
|
||||
) =>
|
||||
l10n.t({
|
||||
message: "Overall, how satisfied are you with {0}?",
|
||||
args: [featureName],
|
||||
comment: ["{0} is the feature name"],
|
||||
});
|
||||
|
||||
public static howLikelyAreYouToRecommendFeature = (featureName: string) =>
|
||||
l10n.t({
|
||||
message:
|
||||
"How likely it is that you would recommend {0} to a friend or colleague?",
|
||||
args: [featureName],
|
||||
comment: ["{0} is the feature name"],
|
||||
});
|
||||
}
|
||||
|
|
|
@ -57,6 +57,7 @@ import { ExecutionPlanWebviewController } from "./executionPlanWebviewController
|
|||
import { QueryResultWebviewController } from "../queryResult/queryResultWebViewController";
|
||||
import { MssqlProtocolHandler } from "../mssqlProtocolHandler";
|
||||
import { isIConnectionInfo } from "../utils/utils";
|
||||
import { UserSurvey } from "../nps/userSurvey";
|
||||
|
||||
/**
|
||||
* The main controller class that initializes the extension
|
||||
|
@ -109,6 +110,7 @@ export default class MainController implements vscode.Disposable {
|
|||
this._vscodeWrapper,
|
||||
);
|
||||
this.configuration = vscode.workspace.getConfiguration();
|
||||
UserSurvey.createInstance(this._context);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -183,6 +185,7 @@ export default class MainController implements vscode.Disposable {
|
|||
});
|
||||
this.registerCommand(Constants.cmdRunQuery);
|
||||
this._event.on(Constants.cmdRunQuery, () => {
|
||||
UserSurvey.getInstance().promptUserForNPSFeedback();
|
||||
this.onRunQuery();
|
||||
});
|
||||
this.registerCommand(Constants.cmdManageConnectionProfiles);
|
||||
|
@ -209,6 +212,10 @@ export default class MainController implements vscode.Disposable {
|
|||
this._event.on(Constants.cmdChooseLanguageFlavor, () => {
|
||||
this.runAndLogErrors(this.onChooseLanguageFlavor());
|
||||
});
|
||||
this.registerCommand(Constants.cmdLaunchUserFeedback);
|
||||
this._event.on(Constants.cmdLaunchUserFeedback, async () => {
|
||||
await UserSurvey.getInstance().promptUserForNPSFeedback();
|
||||
});
|
||||
this.registerCommand(Constants.cmdCancelQuery);
|
||||
this._event.on(Constants.cmdCancelQuery, () => {
|
||||
this.onCancelQuery();
|
||||
|
@ -969,6 +976,7 @@ export default class MainController implements vscode.Disposable {
|
|||
Constants.cmdScriptSelect,
|
||||
async (node: TreeNodeInfo) => {
|
||||
await this.scriptNode(node, ScriptOperation.Select, true);
|
||||
await UserSurvey.getInstance().promptUserForNPSFeedback();
|
||||
},
|
||||
),
|
||||
);
|
||||
|
|
|
@ -0,0 +1,275 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from "vscode";
|
||||
import * as constants from "../constants/constants";
|
||||
import { ReactWebviewPanelController } from "../controllers/reactWebviewController";
|
||||
import {
|
||||
UserSurveyReducers,
|
||||
UserSurveyState,
|
||||
} from "../sharedInterfaces/userSurvey";
|
||||
import * as locConstants from "../constants/locConstants";
|
||||
import { sendActionEvent } from "../telemetry/telemetry";
|
||||
import {
|
||||
TelemetryActions,
|
||||
TelemetryViews,
|
||||
} from "../sharedInterfaces/telemetry";
|
||||
|
||||
const PROBABILITY = 0.15;
|
||||
const SESSION_COUNT_KEY = "nps/sessionCount";
|
||||
const LAST_SESSION_DATE_KEY = "nps/lastSessionDate";
|
||||
const SKIP_VERSION_KEY = "nps/skipVersion";
|
||||
const IS_CANDIDATE_KEY = "nps/isCandidate";
|
||||
|
||||
export class UserSurvey {
|
||||
private static _instance: UserSurvey;
|
||||
private _webviewController: UserSurveyWebviewController;
|
||||
private constructor(private _context: vscode.ExtensionContext) {}
|
||||
public static createInstance(_context: vscode.ExtensionContext): void {
|
||||
UserSurvey._instance = new UserSurvey(_context);
|
||||
}
|
||||
public static getInstance(): UserSurvey {
|
||||
return UserSurvey._instance;
|
||||
}
|
||||
|
||||
public async promptUserForNPSFeedback(): Promise<void> {
|
||||
const globalState = this._context.globalState;
|
||||
const skipVersion = globalState.get(SKIP_VERSION_KEY, "");
|
||||
if (skipVersion) {
|
||||
return;
|
||||
}
|
||||
|
||||
const date = new Date().toDateString();
|
||||
const lastSessionDate = globalState.get(
|
||||
LAST_SESSION_DATE_KEY,
|
||||
new Date(0).toDateString(),
|
||||
);
|
||||
|
||||
if (date === lastSessionDate) {
|
||||
return;
|
||||
}
|
||||
|
||||
const sessionCount = globalState.get(SESSION_COUNT_KEY, 0) + 1;
|
||||
await globalState.update(LAST_SESSION_DATE_KEY, date);
|
||||
await globalState.update(SESSION_COUNT_KEY, sessionCount);
|
||||
|
||||
// don't prompt for feedback from users until they've had a chance to use the extension a few times
|
||||
if (sessionCount < 5) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isCandidate =
|
||||
globalState.get(IS_CANDIDATE_KEY, false) ||
|
||||
Math.random() < PROBABILITY;
|
||||
|
||||
await globalState.update(IS_CANDIDATE_KEY, isCandidate);
|
||||
|
||||
const extensionVersion =
|
||||
vscode.extensions.getExtension(constants.extensionId).packageJSON
|
||||
.version || "unknown";
|
||||
if (!isCandidate) {
|
||||
await globalState.update(SKIP_VERSION_KEY, extensionVersion);
|
||||
return;
|
||||
}
|
||||
|
||||
const take = {
|
||||
title: locConstants.UserSurvey.takeSurvey,
|
||||
run: async () => {
|
||||
const state: UserSurveyState = getStandardNPSQuestions();
|
||||
if (
|
||||
!this._webviewController ||
|
||||
this._webviewController.isDisposed
|
||||
) {
|
||||
this._webviewController = new UserSurveyWebviewController(
|
||||
this._context,
|
||||
state,
|
||||
);
|
||||
} else {
|
||||
this._webviewController.updateState(state);
|
||||
}
|
||||
this._webviewController.revealToForeground();
|
||||
|
||||
const answers = await new Promise<Record<string, string>>(
|
||||
(resolve) => {
|
||||
this._webviewController.onSubmit((e) => {
|
||||
resolve(e);
|
||||
});
|
||||
|
||||
this._webviewController.onCancel(() => {
|
||||
resolve({});
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
sendActionEvent(
|
||||
TelemetryViews.UserSurvey,
|
||||
TelemetryActions.SurverySubmit,
|
||||
{
|
||||
surveyId: "nps",
|
||||
...answers,
|
||||
},
|
||||
);
|
||||
await globalState.update(IS_CANDIDATE_KEY, false);
|
||||
await globalState.update(SKIP_VERSION_KEY, extensionVersion);
|
||||
},
|
||||
};
|
||||
const remind = {
|
||||
title: locConstants.UserSurvey.remindMeLater,
|
||||
run: async () => {
|
||||
await globalState.update(SESSION_COUNT_KEY, sessionCount - 3);
|
||||
},
|
||||
};
|
||||
const never = {
|
||||
title: locConstants.UserSurvey.dontShowAgain,
|
||||
isSecondary: true,
|
||||
run: async () => {
|
||||
await globalState.update(IS_CANDIDATE_KEY, false);
|
||||
await globalState.update(SKIP_VERSION_KEY, extensionVersion);
|
||||
},
|
||||
};
|
||||
|
||||
const button = await vscode.window.showInformationMessage(
|
||||
locConstants.UserSurvey.doYouMindTakingAQuickFeedbackSurvey,
|
||||
take,
|
||||
remind,
|
||||
never,
|
||||
);
|
||||
await (button || remind).run();
|
||||
}
|
||||
|
||||
public async launchSurvey(
|
||||
surveyId: string,
|
||||
survey: UserSurveyState,
|
||||
): Promise<Record<string, string>> {
|
||||
const state: UserSurveyState = survey;
|
||||
if (!this._webviewController || this._webviewController.isDisposed) {
|
||||
this._webviewController = new UserSurveyWebviewController(
|
||||
this._context,
|
||||
state,
|
||||
);
|
||||
} else {
|
||||
this._webviewController.updateState(state);
|
||||
}
|
||||
this._webviewController.revealToForeground();
|
||||
|
||||
const answers = await new Promise<Record<string, string>>((resolve) => {
|
||||
this._webviewController.onSubmit((e) => {
|
||||
resolve(e);
|
||||
});
|
||||
|
||||
this._webviewController.onCancel(() => {
|
||||
resolve({});
|
||||
});
|
||||
});
|
||||
|
||||
sendActionEvent(
|
||||
TelemetryViews.UserSurvey,
|
||||
TelemetryActions.SurverySubmit,
|
||||
{
|
||||
surveyId: surveyId,
|
||||
...answers,
|
||||
},
|
||||
);
|
||||
return answers;
|
||||
}
|
||||
}
|
||||
|
||||
class UserSurveyWebviewController extends ReactWebviewPanelController<
|
||||
UserSurveyState,
|
||||
UserSurveyReducers
|
||||
> {
|
||||
private _onSubmit: vscode.EventEmitter<Record<string, string>> =
|
||||
new vscode.EventEmitter<Record<string, string>>();
|
||||
public readonly onSubmit: vscode.Event<Record<string, string>> =
|
||||
this._onSubmit.event;
|
||||
|
||||
private _onCancel: vscode.EventEmitter<void> =
|
||||
new vscode.EventEmitter<void>();
|
||||
public readonly onCancel: vscode.Event<void> = this._onCancel.event;
|
||||
|
||||
constructor(context: vscode.ExtensionContext, state?: UserSurveyState) {
|
||||
super(
|
||||
context,
|
||||
locConstants.UserSurvey.mssqlFeedback,
|
||||
"userSurvey",
|
||||
state,
|
||||
undefined,
|
||||
{
|
||||
dark: vscode.Uri.joinPath(
|
||||
context.extensionUri,
|
||||
"media",
|
||||
"feedback_dark.svg",
|
||||
),
|
||||
light: vscode.Uri.joinPath(
|
||||
context.extensionUri,
|
||||
"media",
|
||||
"feedback_light.svg",
|
||||
),
|
||||
},
|
||||
);
|
||||
|
||||
this.registerReducer("submit", async (state, payload) => {
|
||||
this._onSubmit.fire(payload.answers);
|
||||
this.panel.dispose();
|
||||
return state;
|
||||
});
|
||||
|
||||
this.registerReducer("cancel", async (state) => {
|
||||
this._onCancel.fire();
|
||||
this.panel.dispose();
|
||||
return state;
|
||||
});
|
||||
|
||||
this.registerReducer("openPrivacyStatement", async (state) => {
|
||||
vscode.env.openExternal(
|
||||
vscode.Uri.parse(constants.microsoftPrivacyStatementUrl),
|
||||
);
|
||||
return state;
|
||||
});
|
||||
this.panel.onDidDispose(() => {
|
||||
this._onCancel.fire();
|
||||
});
|
||||
}
|
||||
|
||||
updateState(state: UserSurveyState): void {
|
||||
this.state = state;
|
||||
}
|
||||
}
|
||||
|
||||
export function getStandardNPSQuestions(featureName?: string): UserSurveyState {
|
||||
return {
|
||||
questions: [
|
||||
{
|
||||
label: featureName
|
||||
? locConstants.UserSurvey.howLikelyAreYouToRecommendFeature(
|
||||
featureName,
|
||||
)
|
||||
: locConstants.UserSurvey
|
||||
.howlikelyAreYouToRecommendMSSQLExtension,
|
||||
type: "nps",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
label: featureName
|
||||
? locConstants.UserSurvey.overallHowStatisfiedAreYouWithFeature(
|
||||
featureName,
|
||||
)
|
||||
: locConstants.UserSurvey
|
||||
.overallHowSatisfiedAreYouWithMSSQLExtension,
|
||||
type: "nsat",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
type: "divider",
|
||||
},
|
||||
{
|
||||
label: locConstants.UserSurvey.whatCanWeDoToImprove,
|
||||
type: "textarea",
|
||||
required: false,
|
||||
placeholder: locConstants.UserSurvey.privacyDisclaimer,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
|
@ -205,6 +205,26 @@ export class LocConstants {
|
|||
filterAnyField: l10n.t("Filter for any field..."),
|
||||
};
|
||||
}
|
||||
|
||||
public get userFeedback() {
|
||||
return {
|
||||
microsoftWouldLikeYourFeedback: l10n.t(
|
||||
"Microsoft would like your feedback",
|
||||
),
|
||||
overallHowSatisfiedAreYouWithMSSQLExtension: l10n.t(
|
||||
"Overall, how satisfied are you with the MSSQL extension?",
|
||||
),
|
||||
verySatisfied: l10n.t("Very Satisfied"),
|
||||
satisfied: l10n.t("Satisfied"),
|
||||
dissatisfied: l10n.t("Dissatisfied"),
|
||||
veryDissatisfied: l10n.t("Very Dissatisfied"),
|
||||
submit: l10n.t("Submit"),
|
||||
cancel: l10n.t("Cancel"),
|
||||
notLikelyAtAll: l10n.t("Not likely at all"),
|
||||
extremelyLikely: l10n.t("Extremely likely"),
|
||||
privacyStatement: l10n.t("Privacy Statement"),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export let locConstants = LocConstants.getInstance();
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import ReactDOM from "react-dom/client";
|
||||
import { VscodeWebviewProvider } from "../../common/vscodeWebviewProvider";
|
||||
import { UserSurveyStateProvider } from "./userSurveryStateProvider";
|
||||
import { UserSurveyPage } from "./userSurveyPage";
|
||||
import "../../index.css";
|
||||
|
||||
ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||
<VscodeWebviewProvider>
|
||||
<UserSurveyStateProvider>
|
||||
<UserSurveyPage />
|
||||
</UserSurveyStateProvider>
|
||||
</VscodeWebviewProvider>,
|
||||
);
|
|
@ -0,0 +1,53 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import React, { createContext } from "react";
|
||||
import { useVscodeWebview } from "../../common/vscodeWebviewProvider";
|
||||
import {
|
||||
UserSurveyContextProps,
|
||||
UserSurveyState,
|
||||
UserSurveyReducers,
|
||||
} from "../../../sharedInterfaces/userSurvey";
|
||||
|
||||
const UserSurveyContext = createContext<UserSurveyContextProps | undefined>(
|
||||
undefined,
|
||||
);
|
||||
|
||||
interface UserSurveyProviderProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const UserSurveyStateProvider: React.FC<UserSurveyProviderProps> = ({
|
||||
children,
|
||||
}) => {
|
||||
const vscodeWebviewProvider = useVscodeWebview<
|
||||
UserSurveyState,
|
||||
UserSurveyReducers
|
||||
>();
|
||||
return (
|
||||
<UserSurveyContext.Provider
|
||||
value={{
|
||||
state: vscodeWebviewProvider.state,
|
||||
submit: async (answers: Record<string, string>) => {
|
||||
await vscodeWebviewProvider.extensionRpc.action("submit", {
|
||||
answers: answers,
|
||||
});
|
||||
},
|
||||
cancel: async () => {
|
||||
await vscodeWebviewProvider.extensionRpc.action("cancel");
|
||||
},
|
||||
openPrivacyStatement: async () => {
|
||||
await vscodeWebviewProvider.extensionRpc.action(
|
||||
"openPrivacyStatement",
|
||||
);
|
||||
},
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</UserSurveyContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export { UserSurveyContext, UserSurveyStateProvider };
|
|
@ -0,0 +1,326 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import {
|
||||
Button,
|
||||
Divider,
|
||||
Field,
|
||||
Link,
|
||||
makeStyles,
|
||||
Radio,
|
||||
RadioGroup,
|
||||
Text,
|
||||
Textarea,
|
||||
} from "@fluentui/react-components";
|
||||
import { useContext, useState } from "react";
|
||||
import { UserSurveyContext } from "./userSurveryStateProvider";
|
||||
import { locConstants } from "../../common/locConstants";
|
||||
import {
|
||||
BaseQuestion,
|
||||
NpsQuestion,
|
||||
NsatQuestion,
|
||||
TextareaQuestion,
|
||||
} from "../../../sharedInterfaces/userSurvey";
|
||||
|
||||
const useStyles = makeStyles({
|
||||
root: {
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
width: "800px",
|
||||
maxWidth: "100%",
|
||||
"> *": {
|
||||
marginBottom: "15px",
|
||||
},
|
||||
padding: "10px",
|
||||
},
|
||||
title: {
|
||||
marginBottom: "30px",
|
||||
},
|
||||
footer: {
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
},
|
||||
buttonsContainer: {
|
||||
display: "flex",
|
||||
"> *": {
|
||||
marginRight: "10px",
|
||||
},
|
||||
},
|
||||
privacyDisclaimer: {
|
||||
marginLeft: "auto",
|
||||
},
|
||||
});
|
||||
|
||||
export const UserSurveyPage = () => {
|
||||
const classes = useStyles();
|
||||
const userSurveryProvider = useContext(UserSurveyContext);
|
||||
const [isSubmitDisabled, setIsSubmitDisabled] = useState(true);
|
||||
const [userAnswers, setUserAnswers] = useState<Record<string, string>>({});
|
||||
|
||||
const updateSubmitButtonState = () => {
|
||||
for (let i = 0; i < userSurveryProvider!.state!.questions.length; i++) {
|
||||
const question = userSurveryProvider!.state!.questions[i];
|
||||
// if question is not divider and not required, skip
|
||||
if (question.type === "divider") {
|
||||
continue;
|
||||
}
|
||||
if (!(question as BaseQuestion)?.required) {
|
||||
continue;
|
||||
}
|
||||
if (!userAnswers[question.label]) {
|
||||
setIsSubmitDisabled(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
setIsSubmitDisabled(false);
|
||||
};
|
||||
|
||||
const onAnswerChange = (label: string, answer: string) => {
|
||||
userAnswers[label] = answer;
|
||||
setUserAnswers(userAnswers);
|
||||
updateSubmitButtonState();
|
||||
};
|
||||
|
||||
if (!userSurveryProvider?.state) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<h2
|
||||
style={{
|
||||
marginBottom: "30px",
|
||||
}}
|
||||
>
|
||||
{userSurveryProvider.state.title ??
|
||||
locConstants.userFeedback.microsoftWouldLikeYourFeedback}
|
||||
</h2>
|
||||
{userSurveryProvider.state.subtitle && (
|
||||
<p>{userSurveryProvider.state.subtitle}</p>
|
||||
)}
|
||||
|
||||
{userSurveryProvider.state.questions.map((question, index) => {
|
||||
switch (question.type) {
|
||||
case "nsat":
|
||||
return (
|
||||
<NSATQuestion
|
||||
key={index}
|
||||
question={question}
|
||||
onChange={(d) =>
|
||||
onAnswerChange(question.label, d)
|
||||
}
|
||||
/>
|
||||
);
|
||||
case "nps":
|
||||
return (
|
||||
<NPSQuestion
|
||||
key={index}
|
||||
question={question}
|
||||
onChange={(d) =>
|
||||
onAnswerChange(question.label, d)
|
||||
}
|
||||
/>
|
||||
);
|
||||
case "textarea":
|
||||
return (
|
||||
<TextAreaQuestion
|
||||
key={index}
|
||||
question={question}
|
||||
onChange={(d) =>
|
||||
onAnswerChange(question.label, d)
|
||||
}
|
||||
/>
|
||||
);
|
||||
case "divider":
|
||||
return <Divider key={index} />;
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
})}
|
||||
<div className={classes.footer}>
|
||||
<div className={classes.buttonsContainer}>
|
||||
<Button
|
||||
appearance="primary"
|
||||
disabled={isSubmitDisabled}
|
||||
onClick={() => userSurveryProvider.submit(userAnswers)}
|
||||
>
|
||||
{userSurveryProvider.state.submitButtonText ??
|
||||
locConstants.userFeedback.submit}
|
||||
</Button>
|
||||
<Button onClick={() => userSurveryProvider.cancel()}>
|
||||
{userSurveryProvider.state.cancelButtonText ??
|
||||
locConstants.userFeedback.cancel}
|
||||
</Button>
|
||||
</div>
|
||||
<Link
|
||||
onClick={() => {
|
||||
userSurveryProvider.openPrivacyStatement();
|
||||
}}
|
||||
>
|
||||
{locConstants.userFeedback.privacyStatement}
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export interface QuestionProps<T> {
|
||||
question: T;
|
||||
onChange: (data: string) => void;
|
||||
}
|
||||
|
||||
export const NSATQuestion = ({
|
||||
question,
|
||||
onChange,
|
||||
}: QuestionProps<NsatQuestion>) => {
|
||||
const userSurveryProvider = useContext(UserSurveyContext);
|
||||
if (!userSurveryProvider) {
|
||||
return undefined;
|
||||
}
|
||||
return (
|
||||
<Field
|
||||
label={
|
||||
<Text weight="bold">
|
||||
{question.label ??
|
||||
locConstants.userFeedback
|
||||
.overallHowSatisfiedAreYouWithMSSQLExtension}
|
||||
</Text>
|
||||
}
|
||||
required={question.required ?? false}
|
||||
>
|
||||
<RadioGroup
|
||||
layout="horizontal-stacked"
|
||||
onChange={(_e, d) => onChange(d.value)}
|
||||
>
|
||||
<Radio
|
||||
value={locConstants.userFeedback.veryDissatisfied}
|
||||
label={locConstants.userFeedback.veryDissatisfied}
|
||||
/>
|
||||
<Radio
|
||||
value={locConstants.userFeedback.dissatisfied}
|
||||
label={locConstants.userFeedback.dissatisfied}
|
||||
/>
|
||||
<Radio
|
||||
value={locConstants.userFeedback.satisfied}
|
||||
label={locConstants.userFeedback.satisfied}
|
||||
/>
|
||||
<Radio
|
||||
value={locConstants.userFeedback.verySatisfied}
|
||||
label={locConstants.userFeedback.verySatisfied}
|
||||
/>
|
||||
</RadioGroup>
|
||||
</Field>
|
||||
);
|
||||
};
|
||||
|
||||
export const NPSQuestion = ({
|
||||
question,
|
||||
onChange,
|
||||
}: QuestionProps<NpsQuestion>) => {
|
||||
const userSurveryProvider = useContext(UserSurveyContext);
|
||||
if (!userSurveryProvider) {
|
||||
return undefined;
|
||||
}
|
||||
return (
|
||||
<Field
|
||||
label={<Text weight="bold">{question.label}</Text>}
|
||||
required={question.required ?? false}
|
||||
style={{
|
||||
marginBottom: "25px",
|
||||
}}
|
||||
>
|
||||
<RadioGroup
|
||||
layout="horizontal-stacked"
|
||||
onChange={(_e, d) => onChange(d.value)}
|
||||
>
|
||||
<Radio
|
||||
value={"0"}
|
||||
label={
|
||||
<div
|
||||
style={{
|
||||
position: "relative",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
{"0"}
|
||||
<br />
|
||||
<Text
|
||||
style={{
|
||||
position: "absolute",
|
||||
width: "100px",
|
||||
top: "30px",
|
||||
left: "0px",
|
||||
fontSize: "10px",
|
||||
}}
|
||||
size={200}
|
||||
>
|
||||
{locConstants.userFeedback.notLikelyAtAll}
|
||||
</Text>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
<Radio value={"1"} label={"1"} />
|
||||
<Radio value={"2"} label={"2"} />
|
||||
<Radio value={"3"} label={"3"} />
|
||||
<Radio value={"4"} label={"4"} />
|
||||
<Radio value={"5"} label={"5"} />
|
||||
<Radio value={"6"} label={"6"} />
|
||||
<Radio value={"7"} label={"7"} />
|
||||
<Radio value={"8"} label={"8"} />
|
||||
<Radio value={"9"} label={"9"} />
|
||||
<Radio
|
||||
value={"10"}
|
||||
label={
|
||||
<div
|
||||
style={{
|
||||
position: "relative",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
{"10"}
|
||||
<br />
|
||||
<Text
|
||||
style={{
|
||||
position: "absolute",
|
||||
width: "max-content",
|
||||
top: "30px",
|
||||
right: "0px",
|
||||
fontSize: "10px",
|
||||
}}
|
||||
size={200}
|
||||
>
|
||||
{locConstants.userFeedback.extremelyLikely}
|
||||
</Text>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</RadioGroup>
|
||||
</Field>
|
||||
);
|
||||
};
|
||||
|
||||
export const TextAreaQuestion = ({
|
||||
question,
|
||||
onChange,
|
||||
}: QuestionProps<TextareaQuestion>) => {
|
||||
const userSurveryProvider = useContext(UserSurveyContext);
|
||||
if (!userSurveryProvider) {
|
||||
return undefined;
|
||||
}
|
||||
return (
|
||||
<Field
|
||||
required={question.required ?? false}
|
||||
label={<Text weight="bold">{question.label}</Text>}
|
||||
hint={question.placeholder}
|
||||
>
|
||||
<Textarea
|
||||
onChange={(_e, data) => onChange(data.value)}
|
||||
resize="vertical"
|
||||
/>
|
||||
</Field>
|
||||
);
|
||||
};
|
|
@ -13,6 +13,7 @@ export enum TelemetryViews {
|
|||
WebviewController = "WebviewController",
|
||||
ObjectExplorerFilter = "ObjectExplorerFilter",
|
||||
TableDesigner = "TableDesigner",
|
||||
UserSurvey = "UserSurvey",
|
||||
}
|
||||
|
||||
export enum TelemetryActions {
|
||||
|
@ -39,6 +40,7 @@ export enum TelemetryActions {
|
|||
Publish = "Publish",
|
||||
ContinueEditing = "ContinueEditing",
|
||||
Close = "Close",
|
||||
SurverySubmit = "SurveySubmit",
|
||||
}
|
||||
|
||||
export interface WebviewTelemetryActionEvent {
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
export interface UserSurveyState {
|
||||
/**
|
||||
* The title of the survey. By default, it is "Microsoft would like your feedback".
|
||||
*/
|
||||
title?: string;
|
||||
/**
|
||||
* The subtitle of the survey. By default, it is empty.
|
||||
*/
|
||||
subtitle?: string;
|
||||
/**
|
||||
* The text of the submit button. By default, it is "Submit".
|
||||
*/
|
||||
submitButtonText?: string;
|
||||
/**
|
||||
* The text of the cancel button. By default, it is "Cancel".
|
||||
*/
|
||||
cancelButtonText?: string;
|
||||
/**
|
||||
* The questions of the survey.
|
||||
*/
|
||||
questions: Question[];
|
||||
}
|
||||
|
||||
export type Question = NpsQuestion | NsatQuestion | TextareaQuestion | Divider;
|
||||
|
||||
export interface BaseQuestion {
|
||||
/**
|
||||
* The label of the question.
|
||||
*/
|
||||
label: string;
|
||||
/**
|
||||
* The required field of the question.
|
||||
*/
|
||||
required?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* A question with a radio button with 0 to 10 options.
|
||||
*/
|
||||
export interface NpsQuestion extends BaseQuestion {
|
||||
type: "nps";
|
||||
}
|
||||
|
||||
/**
|
||||
* A question with a radio button with 'Very Satisfied', 'Satisfied', 'Dissatisfied', 'Very Dissatisfied' options.
|
||||
*/
|
||||
export interface NsatQuestion extends BaseQuestion {
|
||||
type: "nsat";
|
||||
}
|
||||
|
||||
export interface TextareaQuestion extends BaseQuestion {
|
||||
type: "textarea";
|
||||
/**
|
||||
* The placeholder for the textarea.
|
||||
*/
|
||||
placeholder?: string;
|
||||
}
|
||||
|
||||
export interface Divider {
|
||||
type: "divider";
|
||||
}
|
||||
|
||||
export interface UserSurveyContextProps {
|
||||
state: UserSurveyState;
|
||||
submit(answers: Record<string, string>): void;
|
||||
cancel(): void;
|
||||
openPrivacyStatement(): void;
|
||||
}
|
||||
|
||||
export interface UserSurveyReducers {
|
||||
submit: {
|
||||
answers: Record<string, string>;
|
||||
};
|
||||
cancel: {};
|
||||
openPrivacyStatement: {};
|
||||
}
|
|
@ -17,6 +17,7 @@ import {
|
|||
TelemetryViews,
|
||||
} from "../sharedInterfaces/telemetry";
|
||||
import { scriptCopiedToClipboard } from "../constants/locConstants";
|
||||
import { UserSurvey } from "../nps/userSurvey";
|
||||
|
||||
export class TableDesignerWebviewController extends ReactWebviewPanelController<
|
||||
designer.TableDesignerWebviewState,
|
||||
|
@ -258,6 +259,7 @@ export class TableDesignerWebviewController extends ReactWebviewPanelController<
|
|||
},
|
||||
};
|
||||
this.panel.title = state.tableInfo.title;
|
||||
await UserSurvey.getInstance().promptUserForNPSFeedback();
|
||||
return state;
|
||||
});
|
||||
|
||||
|
@ -287,6 +289,7 @@ export class TableDesignerWebviewController extends ReactWebviewPanelController<
|
|||
},
|
||||
};
|
||||
await this._untitledSqlDocumentService.newQuery(script);
|
||||
await UserSurvey.getInstance().promptUserForNPSFeedback();
|
||||
return state;
|
||||
});
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче