feat: Deprecate In App Help (#6801)

* [feat] Removed help from toolbar.js

* [feat] Removed help from global search

* [feat] redirected search in help to docs website

[] Url needs to be configurable from hooks

* [Feat] Removed search functions for help

* [fix] Help links not redirecting

* [feat] Modified "Help" menu on desk toolbar

* [feat] Removed docs search from desk toolbar

* Removed Search from toolbar.js

* Removed /search_docs from website

* Removed help from migrate

* Deprecated help from command utils

* Removed help.py

* Removed help setup from travis.yml

* [fix] Fixed formatting issues

* [fix] Deleted commented code from toolbar.js

* [fix] Minor link fix

* [Fix] Fixed a small typo

* Removed docs_app dependency from pathces

* [fix] Removed commented code

* [fix] Typo in function name

* fix: remove commented code

* [fix] Changed 'Deprecated' message to 'Removed'

Removed will be the correct term instead of deprecated
This commit is contained in:
Shivam Mishra 2019-01-29 14:59:39 +05:30 committed by Nabin Hait
parent e4224ea5d8
commit dad1c04aae
13 changed files with 15 additions and 546 deletions

View file

@ -35,8 +35,6 @@ before_script:
- cd ~/frappe-bench
- bench use test_site
- bench reinstall --yes
- bench setup-help
- bench setup-global-help --mariadb_root_password travis
- bench scheduler disable
- sed -i 's/9000/9001/g' sites/common_site_config.json
- bench start &

View file

@ -1,2 +1,2 @@
### User Permissions
- User Permission is now a DocType, a new UX for the existing Role and User Permission managers to make it easy to enter permissions. For more details please check <a href="https://erpnext.org/docs/user/manual/en/setting-up/users-and-permissions/user-permissions">User Permissions</a>
- User Permission is now a DocType, a new UX for the existing Role and User Permission managers to make it easy to enter permissions. For more details please check <a href="https://erpnext.com/docs/user/manual/en/setting-up/users-and-permissions/user-permissions">User Permissions</a>

View file

@ -571,54 +571,27 @@ def get_version():
@click.command('setup-global-help')
@click.option('--mariadb_root_password')
def setup_global_help(mariadb_root_password=None):
'''setup help table in a separate database that will be
'''Removed: setup help table in a separate database that will be
shared by the whole bench and set `global_help_setup` as 1 in
common_site_config.json'''
from frappe.installer import update_site_config
frappe.local.flags = frappe._dict()
frappe.local.flags.in_setup_help = True
frappe.local.flags.in_install = True
frappe.local.lang = 'en'
frappe.local.conf = frappe.get_site_config(sites_path='.')
update_site_config('global_help_setup', 1,
site_config_path=os.path.join('.', 'common_site_config.json'))
if mariadb_root_password:
frappe.local.conf.root_password = mariadb_root_password
from frappe.utils.help import sync
sync()
print_in_app_help_deprecation()
@click.command('get-docs-app')
@click.argument('app')
def get_docs_app(app):
'''Get the docs app for given app'''
from frappe.utils.help import setup_apps_for_docs
setup_apps_for_docs(app)
'''Removed: Get the docs app for given app'''
print_in_app_help_deprecation()
@click.command('get-all-docs-apps')
def get_all_docs_apps():
'''Get docs apps for all apps'''
from frappe.utils.help import setup_apps_for_docs
for app in frappe.get_installed_apps():
setup_apps_for_docs(app)
'''Removed: Get docs apps for all apps'''
print_in_app_help_deprecation()
@click.command('setup-help')
@pass_context
def setup_help(context):
'''Setup help table in the current site (called after migrate)'''
from frappe.utils.help import sync
for site in context.sites:
try:
frappe.init(site)
frappe.connect()
sync()
finally:
frappe.destroy()
'''Removed: Setup help table in the current site (called after migrate)'''
print_in_app_help_deprecation()
@click.command('rebuild-global-search')
@pass_context
@ -681,6 +654,10 @@ def auto_deploy(context, app, migrate=False, restart=False, remote='upstream'):
else:
print('No Updates')
def print_in_app_help_deprecation():
print("In app help has been removed.\nYou can access the documentation on erpnext.com/docs or frappe.io/docs")
return
commands = [
build,
clear_cache,
@ -709,8 +686,6 @@ commands = [
watch,
_bulk_rename,
add_to_email_queue,
setup_global_help,
setup_help,
rebuild_global_search,
auto_deploy
]

View file

@ -14,7 +14,6 @@ from frappe.website import render, router
from frappe.desk.doctype.desktop_icon.desktop_icon import sync_desktop_icons
from frappe.core.doctype.language.language import sync_languages
from frappe.modules.utils import sync_customizations
import frappe.utils.help
def migrate(verbose=True, rebuild_website=False):
'''Migrate all apps to the latest version, will:
@ -60,10 +59,6 @@ def migrate(verbose=True, rebuild_website=False):
frappe.db.commit()
if not frappe.conf.get('global_help_setup'):
# sync help if not set as global
frappe.utils.help.sync()
clear_notifications()
frappe.publish_realtime("version-update")

View file

@ -222,7 +222,6 @@ frappe.patches.v11_0.rename_workflow_action_to_workflow_action_master #13-06-201
frappe.patches.v11_0.rename_email_alert_to_notification #13-06-2018
frappe.patches.v11_0.delete_duplicate_user_permissions
frappe.patches.v11_0.set_dropbox_file_backup
frappe.patches.v11_0.get_docs_apps_if_not_present
frappe.patches.v10_0.set_default_locking_time
frappe.patches.v11_0.rename_google_maps_doctype
frappe.patches.v10_0.modify_smallest_currency_fraction

View file

@ -1,7 +0,0 @@
from __future__ import unicode_literals
import frappe
from frappe.utils.help import setup_apps_for_docs
def execute():
for app in frappe.get_installed_apps():
setup_apps_for_docs(app)

View file

@ -47,16 +47,8 @@
style="padding: 50% 7px; font-size: 17px; background-color: #fafbfc; font-weight: 100;">?</span>
</a>
<ul class="dropdown-menu" role="menu">
<div class="input-group" style="border-bottom: 1px solid #d1d8dd;">
<input id="input-help" type="text" placeholder="{{ __("What do you need help with?") }}" autofocus>
<span class="input-group-btn"><button class="btn btn-default">{{ __("Go") }}</button></span>
</div>
<li id="help-links"></li>
<li class="divider"></li>
<li>
<a data-link-type="documentation"
data-path="/documentation/index" target="_blank" rel="noopener noreferrer">{{ __("Documentation") }}</a>
</li>
<li class="divider documentation-links"></li>
<li><a href="#" onclick="return frappe.ui.toolbar.show_about();">
{%= __("About") %}</a></li>

View file

@ -122,7 +122,7 @@ frappe.search.SearchDialog = Class.extend({
});
// Help results
this.$modal_body.on('click', 'a[data-path]', frappe.help.show_results);
// this.$modal_body.on('click', 'a[data-path]', frappe.help.show_results);
this.bind_keyboard_events();
},
@ -366,35 +366,12 @@ frappe.search.SearchDialog = Class.extend({
frappe.search.utils.get_global_results(keywords, start, limit)
.then(function(global_results) {
results = results.concat(global_results);
return frappe.search.utils.get_help_results(keywords);
}).then(function(help_results) {
results = results.concat(help_results);
callback(results, keywords);
}, function (err) {
console.error(err);
});
}
},
help: {
input_placeholder: __("Search Help"),
empty_state_text: __("Search the docs"),
no_results_status: (keyword) => __("<p>No results found for '" + keyword +
"' in Help</p><p>Would you like to search <a class='switch-to-global-search text-muted' "+
"style='text-decoration: underline;'>globally</a>" +
" or the <a href='https://discuss.erpnext.com' class='forum-link text-muted' " +
"style='text-decoration: underline;'>forums</a> instead?</p>"),
get_results: function(keywords, callback) {
var results = [];
frappe.search.utils.get_help_results(keywords)
.then(function(help_results) {
results = results.concat(help_results);
callback(results, keywords);
}, function (err) {
console.error(err);
});
}
}
},
});

View file

@ -407,46 +407,6 @@ frappe.search.utils = {
});
},
get_help_results: function(keywords) {
function get_results_set(data) {
var result;
var set = {
title: "Help",
fetch_type: "Help",
results: []
}
data.forEach(function(d) {
// more properties
result = {
label: d[0],
value: d[0],
description: d[1],
data_path: d[2],
onclick: function() {
}
}
set.results.push(result);
});
return [set];
}
return new Promise(function(resolve, reject) {
frappe.call({
method: "frappe.utils.help.get_help",
args: {
text: keywords
},
callback: function(r) {
if(r.message) {
resolve(get_results_set(r.message));
} else {
resolve([]);
}
}
});
});
},
get_nav_results: function(keywords) {
function sort_uniques(array) {
var routes = [], out = [];

View file

@ -110,17 +110,10 @@ frappe.ui.toolbar.Toolbar = Class.extend({
$("#input-help").on("keydown", function (e) {
if(e.which == 13) {
var keywords = $(this).val();
show_help_results(keywords);
$(this).val("");
}
});
$("#input-help + span").on("click", function () {
var keywords = $("#input-help").val();
show_help_results(keywords);
$(this).val("");
});
$(document).on("page-change", function () {
var $help_links = $(".dropdown-help #help-links");
$help_links.html("");
@ -146,18 +139,10 @@ frappe.ui.toolbar.Toolbar = Class.extend({
for (var i = 0; i < links.length; i++) {
var link = links[i];
var url = link.url;
var app_name = url.split('//', 2)[1].split('/', 2)[1];
var data_path = url.slice(url.indexOf('/user'));
if(data_path.lastIndexOf('.')){
data_path = data_path.slice(0, data_path.lastIndexOf('.'));
}
data_path = data_path.replace('user', app_name);
$("<a>", {
href: link.url,
text: link.label,
target: "_blank",
"data-path": data_path
target: "_blank"
}).appendTo($help_links);
}
@ -169,11 +154,6 @@ frappe.ui.toolbar.Toolbar = Class.extend({
$(document).on("click", ".help-modal a", show_results);
var me = this;
function show_help_results(keywords) {
me.search.init_search(keywords, "help");
}
function show_results(e) {
//edit links
var href = e.target.href;
@ -183,20 +163,6 @@ frappe.ui.toolbar.Toolbar = Class.extend({
var path = $(e.target).attr("data-path");
if(path) {
e.preventDefault();
frappe.call({
method: "frappe.utils.help.get_help_content",
args: {
path: path
},
callback: function (r) {
if(r.message && r.message.title) {
$result_modal.find('.modal-title').html("<span>"
+ r.message.title + "</span>");
$result_modal.find('.modal-body').html(r.message.content);
$result_modal.modal('show');
}
}
});
}
}
},

View file

@ -1,346 +0,0 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals, print_function
import frappe
import hashlib
from frappe.model.db_schema import DbManager
from frappe.installer import get_root_connection
from frappe.database import Database
import os, subprocess
from bs4 import BeautifulSoup
import jinja2.exceptions
import io
def sync():
# make table
print('Syncing help database...')
help_db = HelpDatabase()
help_db.make_database()
help_db.connect()
help_db.make_table()
help_db.sync_pages()
help_db.build_index()
@frappe.whitelist()
def get_help(text):
return HelpDatabase().search(text)
@frappe.whitelist()
def get_installed_app_help(text):
return HelpDatabase().app_docs_search(text)
@frappe.whitelist()
def get_help_content(path):
return HelpDatabase().get_content(path)
def get_improve_page_html(app_name, target):
docs_config = frappe.get_module(app_name + ".config.docs")
source_link = docs_config.source_link
branch = getattr(docs_config, "branch", "master")
html = '''<div class="page-container">
<div class="page-content">
<div class="edit-container text-center">
<i class="fa fa-smile text-muted"></i>
<a class="edit text-muted" href="{source_link}/blob/{branch}/{target}">
Improve this page
</a>
</div>
</div>
</div>'''.format(source_link=source_link, app_name=app_name, target=target, branch=branch)
return html
class HelpDatabase(object):
def __init__(self):
self.global_help_setup = frappe.conf.get('global_help_setup')
if self.global_help_setup:
bench_name = os.path.basename(os.path.abspath(frappe.get_app_path('frappe')).split('/apps/')[0])
self.help_db_name = hashlib.sha224(bench_name.encode('utf-8')).hexdigest()[:15]
def make_database(self):
'''make database for global help setup'''
if not self.global_help_setup:
return
dbman = DbManager(get_root_connection())
dbman.drop_database(self.help_db_name)
# make database
if not self.help_db_name in dbman.get_database_list():
try:
dbman.create_user(self.help_db_name, self.help_db_name)
except Exception as e:
# user already exists
if e.args[0] != 1396: raise
dbman.create_database(self.help_db_name)
dbman.grant_all_privileges(self.help_db_name, self.help_db_name)
dbman.flush_privileges()
def connect(self):
if self.global_help_setup:
self.db = Database(user=self.help_db_name, password=self.help_db_name)
else:
self.db = frappe.db
def make_table(self):
if not 'help' in self.db.get_tables():
self.db.sql('''create table help(
path varchar(255),
content text,
title text,
intro text,
full_path text,
fulltext(title),
fulltext(content),
index (path))
COLLATE=utf8mb4_unicode_ci
ENGINE=MyISAM
CHARACTER SET=utf8mb4''')
def search(self, words):
self.connect()
return self.db.sql('''
select title, intro, path from help where title like %s union
select title, intro, path from help where match(content) against (%s) limit 10''', ('%'+words+'%', words))
def app_docs_search(self, words):
self.connect()
frappe_path = '%' + 'apps/frappe' + '%'
return self.db.sql('''
select
title, intro, full_path
from
help
where
title like %s
and
full_path not like %s
union
select
title, intro, full_path
from
help
where
match(content) against (%s)
and
full_path not like %s
limit
10
''', ('%'+words+'%', frappe_path, words, frappe_path))
def get_content(self, path):
self.connect()
query = '''select title, content from help
where path like "{path}%" order by path desc limit 1'''
result = None
if not path.endswith('index'):
result = self.db.sql(query.format(path=os.path.join(path, 'index')))
if not result:
result = self.db.sql(query.format(path=path))
return {'title':result[0][0], 'content':result[0][1]} if result else {}
def sync_pages(self):
self.db.sql('truncate help')
doc_contents = '<ol>'
apps = os.listdir('../apps') if self.global_help_setup else frappe.get_installed_apps()
for app in apps:
# Expect handling of cloning docs apps in bench
docs_app = frappe.get_hooks('docs_app', app, app)[0]
web_folder = 'www/' if docs_app != app else ''
docs_folder = '../apps/{docs_app}/{docs_app}/{web_folder}docs/user'.format(
docs_app=docs_app, web_folder=web_folder)
self.out_base_path = '../apps/{docs_app}/{docs_app}/{web_folder}docs'.format(
docs_app=docs_app, web_folder=web_folder)
if os.path.exists(docs_folder):
app_name = getattr(frappe.get_module(app), '__title__', None) or app.title()
doc_contents += '<li><a data-path="/{app}/index">{app_name}</a></li>'.format(
app=app, app_name=app_name)
for basepath, folders, files in os.walk(docs_folder):
files = self.reorder_files(files)
for fname in files:
if fname.rsplit('.', 1)[-1] in ('md', 'html'):
fpath = os.path.join(basepath, fname)
with io.open(fpath, 'r', encoding = 'utf-8') as f:
try:
content = frappe.render_template(f.read(),
{'docs_base_url': '/assets/{app}_docs'.format(app=app)}, safe_render=False)
relpath = self.get_out_path(fpath)
relpath = relpath.replace("user", app)
content = frappe.utils.md_to_html(content)
title = self.make_title(basepath, fname, content)
intro = self.make_intro(content)
content = self.make_content(content, fpath, relpath, app, docs_app)
self.db.sql('''insert into help(path, content, title, intro, full_path)
values (%s, %s, %s, %s, %s)''', (relpath, content, title, intro, fpath))
except jinja2.exceptions.TemplateSyntaxError:
print("Invalid Jinja Template for {0}. Skipping".format(fpath))
doc_contents += "</ol>"
self.db.sql('''insert into help(path, content, title, intro, full_path) values (%s, %s, %s, %s, %s)''',
('/documentation/index', doc_contents, 'Documentation', '', ''))
def make_title(self, basepath, filename, html):
if '<h1>' in html:
title = html.split("<h1>", 1)[1].split("</h1>", 1)[0]
elif 'index' in filename:
title = basepath.rsplit('/', 1)[-1].title().replace("-", " ")
else:
title = filename.rsplit('.', 1)[0].title().replace("-", " ")
return title
def make_intro(self, html):
intro = ""
if '<p>' in html:
intro = html.split('<p>', 1)[1].split('</p>', 1)[0]
if 'Duration' in html:
intro = "Help Video: " + intro
return intro
def make_content(self, html, path, relpath, app_name, doc_app):
if '<h1>' in html:
html = html.split('</h1>', 1)[1]
if '{next}' in html:
html = html.replace('{next}', '')
soup = BeautifulSoup(html, 'html.parser')
self.fix_links(soup, app_name)
self.fix_images(soup, doc_app)
parent = self.get_parent(relpath)
if parent:
parent_tag = soup.new_tag('a')
parent_tag.string = parent['title']
parent_tag['class'] = 'parent-link'
parent_tag['data-path'] = parent['path']
soup.find().insert_before(parent_tag)
return soup.prettify()
def fix_links(self, soup, app_name):
for link in soup.find_all('a'):
if link.has_attr('href'):
url = link['href']
if '/user' in url:
data_path = url[url.index('/user'):]
if '.' in data_path:
data_path = data_path[: data_path.rindex('.')]
if data_path:
link['data-path'] = data_path.replace("user", app_name)
def fix_images(self, soup, app_name):
for img in soup.find_all('img'):
if img.has_attr('src'):
url = img['src']
if '/docs/' in url:
img['src'] = url.replace('/docs/', '/assets/{0}_docs/'.format(app_name))
def build_index(self):
for data in self.db.sql('select path, full_path, content from help'):
self.make_index(data[0], data[1], data[2])
def make_index(self, original_path, full_path, content):
'''Make index from index.txt'''
if '{index}' in content:
path = os.path.dirname(full_path)
files = []
# get files from index.txt
index_path = os.path.join(path, "index.txt")
if os.path.exists(index_path):
with open(index_path, 'r') as f:
files = f.read().splitlines()
# files not in index.txt
for f in os.listdir(path):
if not os.path.isdir(os.path.join(path, f)) and len(f.rsplit('.', 1)) == 2:
name, extn = f.rsplit('.', 1)
if name not in files \
and name != 'index' and extn in ('md', 'html'):
files.append(name)
links_html = "<ol class='index-links'>"
for line in files:
fpath = os.path.join(os.path.dirname(original_path), line)
title = self.db.sql('select title from help where path like %s',
os.path.join(fpath, 'index') + '%')
if not title:
title = self.db.sql('select title from help where path like %s',
fpath + '%')
if title:
title = title[0][0]
links_html += "<li><a data-path='{fpath}'> {title} </a></li>".format(
fpath=fpath, title=title)
# else:
# bad entries in .txt files
# print fpath
links_html += "</ol>"
html = content.replace('{index}', links_html)
self.db.sql('update help set content=%s where path=%s', (html, original_path))
def get_out_path(self, path):
return '/' + os.path.relpath(path, self.out_base_path)
def get_parent(self, child_path):
if 'index' in child_path:
child_path = child_path[: child_path.rindex('index')]
if child_path[-1] == '/':
child_path = child_path[:-1]
child_path = child_path[: child_path.rindex('/')]
out = None
if child_path:
parent_path = child_path + "/index"
out = self.get_content(parent_path)
#if parent is documentation root
else:
parent_path = "/documentation/index"
out = {}
out['title'] = "Documentation"
if not out:
return None
out['path'] = parent_path
return out
def reorder_files(self, files):
pos = 0
if 'index.md' in files:
pos = files.index('index.md')
elif 'index.html' in files:
pos = files.index('index.html')
if pos:
files[0], files[pos] = files[pos], files[0]
return files
def setup_apps_for_docs(app):
docs_app = frappe.get_hooks('docs_app', app, app)[0]
if docs_app and not os.path.exists(frappe.get_app_path(app)):
print("Getting {docs_app} required by {app}".format(docs_app=docs_app, app=app))
subprocess.check_output(['bench', 'get-app', docs_app], cwd = '..')
else:
if docs_app:
print("{docs_app} required by {app} already present".format(docs_app=docs_app, app=app))

View file

@ -1,7 +0,0 @@
{% extends "templates/web.html" %}
{% block page_content %}
{% include "templates/includes/search_template.html" %}
{% endblock %}

View file

@ -1,33 +0,0 @@
from __future__ import unicode_literals
import frappe
from frappe import _
from jinja2 import utils
from html2text import html2text
from frappe.utils import sanitize_html
from frappe.utils.help import get_installed_app_help
def get_context(context):
context.no_cache = 1
if frappe.form_dict.q:
query = str(utils.escape(sanitize_html(frappe.form_dict.q)))
context.title = _('Documentation Results for "{0}"').format(query)
context.route = '/search_docs'
d = frappe._dict()
d.results = get_docs_results(query)
context.update(d)
else:
context.title = _('Docs Search')
@frappe.whitelist(allow_guest = True)
def get_docs_results(text):
out = []
results = get_installed_app_help(text)
for d in results:
full_path = d[2]
out.append(frappe._dict({
'title': d[0],
'preview': html2text(d[1]),
'route': full_path[full_path.index('docs/'):]
}))
return out