Skip to content

Commit

Permalink
Asking the user for feedback using custom heuristic (Azure#3139)
Browse files Browse the repository at this point in the history
* ask user for feedback

* change toolbar for 20 seconds

* feedback in toolbar

*  remove dead code

* flake8

* flake8

* small changes

* wording

* change to cli config

* feedback as command

* utc

* remove from shell config

* flake8
  • Loading branch information
Courtney (CJ) Oka authored May 5, 2017
1 parent e065618 commit 274a9fc
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 7 deletions.
10 changes: 9 additions & 1 deletion src/command_modules/azure-cli-shell/azclishell/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from azclishell.az_completer import AzCompleter
from azclishell.az_lexer import AzLexer
from azclishell.color_styles import style_factory
from azclishell.frequency_heuristic import frequent_user

from azure.cli.core.application import APPLICATION
from azure.cli.core._session import ACCOUNT, CONFIG, SESSION
Expand Down Expand Up @@ -52,12 +53,19 @@ def main(style=None):
print("When in doubt, ask for 'help'")
config.firsttime()

ask_feedback = False
if not config.has_feedback() and frequent_user:
print("\n\nAny comments or concerns? You can use the \'feedback\' command!" +
" We would greatly appreciate it.\n")
ask_feedback = True

shell_app = Shell(
completer=AZCOMPLETER,
lexer=AzLexer,
history=FileHistory(
os.path.join(shell_config_dir(), config.get_history())),
app=APPLICATION,
styles=style_obj
styles=style_obj,
user_feedback=ask_feedback
)
shell_app.run()
27 changes: 22 additions & 5 deletions src/command_modules/azure-cli-shell/azclishell/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import os
import subprocess
import sys
import datetime
import threading

import jmespath
Expand All @@ -27,6 +28,7 @@
import azclishell.configuration
from azclishell.az_lexer import AzLexer, ExampleLexer, ToolbarLexer
from azclishell.command_tree import in_tree
from azclishell.frequency_heuristic import DISPLAY_TIME
from azclishell.gather_commands import add_random_new_lines
from azclishell.key_bindings import registry, get_section, sub_section
from azclishell.layout import create_layout, create_tutorial_layout, set_scope
Expand All @@ -53,6 +55,7 @@
PROFILE = Profile()
SELECT_SYMBOL = azclishell.configuration.SELECT_SYMBOL
PART_SCREEN_EXAMPLE = .3
START_TIME = datetime.datetime.utcnow()
CLEAR_WORD = get_os_clear_screen_word()


Expand Down Expand Up @@ -118,7 +121,8 @@ class Shell(object):
# pylint: disable=too-many-arguments
def __init__(self, completer=None, styles=None,
lexer=None, history=InMemoryHistory(),
app=None, input_custom=sys.stdout, output_custom=None):
app=None, input_custom=sys.stdout, output_custom=None,
user_feedback=False):
self.styles = styles
if styles:
self.lexer = lexer or AzLexer
Expand All @@ -136,6 +140,7 @@ def __init__(self, completer=None, styles=None,
self._env = os.environ
self.last = None
self.last_exit = 0
self.user_feedback = user_feedback
self.input = input_custom
self.output = output_custom
self.config_default = ""
Expand Down Expand Up @@ -169,6 +174,7 @@ def on_input_timeout(self, cli):
self.example_docs = u'{}'.format(example)

self._update_default_info()

cli.buffers['description'].reset(
initial_document=Document(self.description_docs, cursor_position=0))
cli.buffers['parameter'].reset(
Expand All @@ -190,10 +196,17 @@ def _update_toolbar(self):
for _ in range(cols):
empty_space += " "

settings = self._toolbar_info()
settings, empty_space = space_toolbar(settings, cols, empty_space)
delta = datetime.datetime.utcnow() - START_TIME
if self.user_feedback and delta.seconds < DISPLAY_TIME:
toolbar = [
' Try out the \'feedback\' command',
'If refreshed disappear in: {}'.format(str(DISPLAY_TIME - delta.seconds))]
else:
toolbar = self._toolbar_info()

toolbar, empty_space = space_toolbar(toolbar, cols, empty_space)
cli.buffers['bottom_toolbar'].reset(
initial_document=Document(u'{}{}{}'.format(NOTIFICATIONS, settings, empty_space)))
initial_document=Document(u'{}{}{}'.format(NOTIFICATIONS, toolbar, empty_space)))

def _toolbar_info(self):
sub_name = ""
Expand All @@ -211,7 +224,6 @@ def _toolbar_info(self):
"[F3]Keys",
"[Ctrl+D]Quit",
tool_val
# tool_val2
]
return settings_items

Expand Down Expand Up @@ -534,10 +546,15 @@ def handle_scoping_input(self, continue_flag, cmd, text):

def cli_execute(self, cmd):
""" sends the command to the CLI to be executed """

try:
args = parse_quotes(cmd)
azlogging.configure_logging(args)

if len(args) > 0 and args[0] == 'feedback':
SHELL_CONFIGURATION.set_feedback('yes')
self.user_feedback = False

azure_folder = get_config_dir()
if not os.path.exists(azure_folder):
os.makedirs(azure_folder)
Expand Down
19 changes: 18 additions & 1 deletion src/command_modules/azure-cli-shell/azclishell/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
SELECT_SYMBOL['query'] + "[path]": "query previous command using jmespath syntax",
"[cmd] " + SELECT_SYMBOL['example'] + " [num]": "do a step by step tutorial of example",
SELECT_SYMBOL['exit_code']: "get the exit code of the previous command",
SELECT_SYMBOL['scope'] + '[cmd]': "set a scope",
SELECT_SYMBOL['scope'] + '[cmd]': "set a scope, and scopes can be chained with spaces",
SELECT_SYMBOL['scope'] + ' ' + SELECT_SYMBOL['unscope']: "go back a scope",
"Ctrl+N": "Scroll down the documentation",
"Ctrl+Y": "Scroll up the documentation"
Expand All @@ -37,6 +37,7 @@


def help_text(values):
""" reformats the help text """
result = ""
for key in values:
result += key + ' '.join('' for x in range(GESTURE_LENGTH - len(key))) +\
Expand All @@ -63,6 +64,7 @@ def __init__(self):
self.config.add_section('Layout')
self.config.set('Help Files', 'command', 'help_dump.json')
self.config.set('Help Files', 'history', 'history.txt')
self.config.set('Help Files', 'frequency', 'frequency.json')
self.config.set('Layout', 'command_description', 'yes')
self.config.set('Layout', 'param_description', 'yes')
self.config.set('Layout', 'examples', 'yes')
Expand All @@ -86,6 +88,10 @@ def get_help_files(self):
""" returns where the command table is cached """
return self.config.get('Help Files', 'command')

def get_frequency(self):
""" returns the name of the frequency file """
return self.config.get('Help Files', 'frequency')

def load(self, path):
""" loads the configuration settings """
self.config.read(path)
Expand All @@ -104,6 +110,14 @@ def get_style(self):
""" gets the last style they used """
return self.config.get('DEFAULT', 'style')

def has_feedback(self):
""" returns whether user has given feedback """
return az_config.getboolean('core', 'given feedback')

def set_feedback(self, value):
""" sets the feedback in the config """
set_global_config_value('core', 'given feedback', value)

def set_style(self, val):
""" sets the style they used """
self.set_val('DEFAULT', 'style', val)
Expand Down Expand Up @@ -140,3 +154,6 @@ def ask_user_for_telemetry():


CONFIGURATION = Configuration()

if not az_config.has_option('core', 'given feedback'):
set_global_config_value('core', 'given feedback', 'no')
Original file line number Diff line number Diff line change
@@ -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 os
import datetime
import json

from azure.cli.core._config import set_global_config_value

from azclishell.configuration import CONFIGURATION, get_config_dir as shell_config

SHELL_CONFIG = CONFIGURATION
DAYS_AGO = 28
ACTIVE_STATUS = 5
DISPLAY_TIME = 20


def update_frequency():
""" updates the frequency from files """
with open(os.path.join(shell_config(), SHELL_CONFIG.get_frequency()), 'r') as freq:
try:
frequency = json.load(freq)
except ValueError:
frequency = {}

with open(os.path.join(shell_config(), SHELL_CONFIG.get_frequency()), 'w') as freq:
now = datetime.datetime.utcnow()
val = frequency.get(now)
frequency[now] = val + 1 if val else 1
json.dump(frequency, freq)

return frequency


def frequency_measurement():
""" measures how many times a user has used this program in the last calendar week """
freq = update_frequency()
count = 0
base = datetime.datetime.utcnow()
date_list = [base - datetime.timedelta(days=x) for x in range(1, DAYS_AGO)]
for day in date_list:
count += 1 if freq.get(day, 0) > 0 else 0
return count


def frequency_heuristic():
""" decides whether user meets requirements for frequency """
return frequency_measurement() >= ACTIVE_STATUS


frequent_user = frequency_heuristic()

0 comments on commit 274a9fc

Please sign in to comment.