Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Asking the user for feedback using custom heuristic #3139

Merged
merged 18 commits into from
May 5, 2017
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()
24 changes: 21 additions & 3 deletions src/command_modules/azure-cli-shell/azclishell/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import os
import subprocess
import sys

import datetime
import jmespath
from six.moves import configparser

Expand All @@ -26,6 +26,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 @@ -50,6 +51,7 @@
PROFILE = Profile()
SELECT_SYMBOL = azclishell.configuration.SELECT_SYMBOL
PART_SCREEN_EXAMPLE = .3
START_TIME = datetime.datetime.now()
CLEAR_WORD = get_os_clear_screen_word()


Expand Down Expand Up @@ -135,7 +137,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 @@ -153,6 +156,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 @@ -189,7 +193,15 @@ def on_input_timeout(self, cli):

self._update_default_info()

settings, empty_space = space_toolbar(_toolbar_info(), cols, empty_space)
delta = datetime.datetime.now() - 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 = _toolbar_info()

settings, empty_space = space_toolbar(toolbar, cols, empty_space)

cli.buffers['description'].reset(
initial_document=Document(self.description_docs, cursor_position=0))
Expand Down Expand Up @@ -521,10 +533,15 @@ def handle_scoping_input(self, continue_flag, cmd, text):
return continue_flag, cmd

def cli_execute(self, cmd):
""" runs a command through the CLI """
try:
args = parse_quotes(cmd)
azlogging.configure_logging(args)

if 'feedback' in args:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if I create a VM with name 'feedback'.
How does this work?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed

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 All @@ -535,6 +552,7 @@ def cli_execute(self, cmd):
config = Configuration()
self.app.initialize(config)
result = self.app.execute(args)

self.last_exit = 0
if result and result.result is not None:
from azure.cli.core._output import OutputProducer
Expand Down
22 changes: 20 additions & 2 deletions 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 @@ -57,12 +58,14 @@ class Configuration(object):
def __init__(self):
self.config = configparser.ConfigParser({
'firsttime': 'yes',
'style': 'default'
'style': 'default',
'given feedback': 'no'
})
self.config.add_section('Help Files')
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 +89,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 +111,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 +155,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,59 @@
# --------------------------------------------------------------------------------------------
# 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 today_format(now):
""" returns the date format """
return now.strftime("%Y-%m-%d")


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.now()
Copy link
Contributor

@troydai troydai May 5, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UTC?

now = today_format(now)
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.now()
date_list = [base - datetime.timedelta(days=x) for x in range(1, DAYS_AGO)]
for day in date_list:
count += 1 if freq.get(today_format(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()