From 3ec7954050405a2d4f8de9af1769eae8038c3e29 Mon Sep 17 00:00:00 2001 From: Quinten Steenhuis Date: Tue, 18 Apr 2023 12:54:13 -0400 Subject: [PATCH 01/27] Update CHANGELOG.md --- CHANGELOG.md | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dad6259..938f516 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,15 +1,3 @@ # CHANGELOG -TODO(brycew): back-port previous releases to this change log from GitHub release notes. - -## Version v0.19.1 - -### Fixed - -Update sqlalchemy by @BryceStevenWilley in #75 - -Docassemble 1.4.33 updated to SQL Alchemy 2.0, which broke the session viewing functionality in the Dashboard. -This patch fixes things to work on the latest docassemble version, all the way back to docassemble version 1.2.68. - -Full Changelog: [v0.19.0...v0.19.1](https://github.com/SuffolkLITLab/docassemble-ALDashboard/compare/v0.19.0...v0.19.1) - +See https://github.com/SuffolkLITLab/docassemble-ALDashboard/releases From 2c7f7eb5121ab357e3fd2ad375978d9957e8ac1a Mon Sep 17 00:00:00 2001 From: Quinten Steenhuis Date: Wed, 3 May 2023 16:07:53 -0400 Subject: [PATCH 02/27] Get this working again, decouple from Docassemble dependencies so we can test easier --- .../data/questions/validate_docx.yml | 8 +- docassemble/ALDashboard/validate_docx.py | 244 ++++++++++++------ 2 files changed, 162 insertions(+), 90 deletions(-) diff --git a/docassemble/ALDashboard/data/questions/validate_docx.yml b/docassemble/ALDashboard/data/questions/validate_docx.yml index 9fd37b0..23bfbad 100644 --- a/docassemble/ALDashboard/data/questions/validate_docx.yml +++ b/docassemble/ALDashboard/data/questions/validate_docx.yml @@ -18,7 +18,7 @@ code: | all_files = [] for file in get_upload: all_files.append({"file": file.filename, - "errors": get_jinja_errors(file, env=Environment(loader=BaseLoader,undefined=DASkipUndefined)) + "errors": get_jinja_errors(file.path()) }) --- question: | @@ -45,11 +45,7 @@ subquestion: | % for f in all_files: % if f["errors"]: #### ${ f["file"] } - % for error in f["errors"]: - ``` - ${ indent(error) } - ``` - % endfor + ${ indent(f["errors"]) } % endif % endfor % endif \ No newline at end of file diff --git a/docassemble/ALDashboard/validate_docx.py b/docassemble/ALDashboard/validate_docx.py index a997458..d7cfac4 100644 --- a/docassemble/ALDashboard/validate_docx.py +++ b/docassemble/ALDashboard/validate_docx.py @@ -1,107 +1,183 @@ # mypy: disable-error-code="override, assignment" +from typing import Callable, Optional from jinja2 import Undefined, DebugUndefined, ChainableUndefined from jinja2.utils import missing from docxtpl import DocxTemplate from jinja2 import Environment, BaseLoader +from jinja2.ext import Extension +from jinja2.lexer import Token import jinja2.exceptions -from docassemble.base.util import DAFile -from docassemble.base.parse import fix_quotes import re -__all__ = ['CallAndDebugUndefined', 'get_jinja_errors', 'Environment', 'BaseLoader', 'DASkipUndefined'] +__all__ = ["CallAndDebugUndefined", "get_jinja_errors", "Environment", "BaseLoader"] + + +class DAIndexError(IndexError): + pass + + +class DAAttributeError(AttributeError): + pass + + +nameerror_match = re.compile( + r"\'(.*)\' (is not defined|referenced before assignment|is undefined)" +) + + +def extract_missing_name(the_error): + m = nameerror_match.search(str(the_error)) + if m: + return m.group(1) + raise the_error + + +class DAEnvironment(Environment): + def from_string(self, source, **kwargs): # pylint: disable=arguments-differ + source = re.sub(r"({[\%\{].*?[\%\}]})", fix_quotes, source) + return super().from_string(source, **kwargs) + + def getitem(self, obj, argument): + try: + return obj[argument] + except (DAAttributeError, DAIndexError) as err: + varname = extract_missing_name(err) + return self.undefined(obj=missing, name=varname) + except (AttributeError, TypeError, LookupError): + return self.undefined(obj=obj, name=argument, accesstype="item") + + def getattr(self, obj, attribute): + try: + return getattr(obj, attribute) + except DAAttributeError as err: + varname = extract_missing_name(err) + return self.undefined(obj=missing, name=varname) + except: + return self.undefined(obj=obj, name=attribute, accesstype="attribute") + + +def fix_quotes(match): + instring = match.group(1) + n = len(instring) + output = "" + i = 0 + while i < n: + if instring[i] == "\u201c" or instring[i] == "\u201d": + output += '"' + elif instring[i] == "\u2018" or instring[i] == "\u2019": + output += "'" + elif instring[i] == "&" and i + 4 < n and instring[i : i + 5] == "&": + output += "&" + i += 4 + else: + output += instring[i] + i += 1 + return output + class CallAndDebugUndefined(DebugUndefined): """Handles Jinja2 undefined errors by printing the name of the undefined variable. Extended to handle callable methods. """ + def __call__(self, *pargs, **kwargs): return self - + def __getattr__(self, _: str) -> "CallAndDebugUndefined": return self - __getitem__ = __getattr__ # type: ignore - -class DASkipUndefined(ChainableUndefined): - """Undefined handler for Jinja2 exceptions that allows rendering most - templates that have undefined variables. It will not fix all broken - templates. For example, if the missing variable is used in a complex - mathematical expression it may still break (but expressions with only two - elements should render as ''). - """ - def __init__(self, *pargs, **kwargs): - # Handle the way Docassemble DAEnvironment triggers attribute errors - pass + __getitem__ = __getattr__ # type: ignore - def __str__(self) -> str: - return '' - # type: ignore - def __call__(self, *args, **kwargs)->"DASkipUndefined": - return self +null_func: Callable = lambda y: y - __getitem__ = __getattr__ = __call__ # type: ignore +registered_jinja_filters: dict = {} - def __eq__(self, *pargs) -> bool: - return False - - # need to return a bool type - __bool__ = __ne__ = __le__ = __lt__ = __gt__ = __ge__ = __nonzero__ = __eq__ # type: ignore +builtin_jinja_filters = { + "ampersand_filter": null_func, + "markdown": null_func, + "add_separators": null_func, + "inline_markdown": null_func, + "paragraphs": null_func, + "manual_line_breaks": null_func, + "RichText": null_func, + "groupby": null_func, + "max": null_func, + "min": null_func, + "sum": null_func, + "unique": null_func, + "join": null_func, + "attr": null_func, + "selectattr": null_func, + "rejectattr": null_func, + "sort": null_func, + "dictsort": null_func, + "nice_number": null_func, + "ordinal": null_func, + "ordinal_number": null_func, + "currency": null_func, + "comma_list": null_func, + "comma_and_list": null_func, + "capitalize": null_func, + "salutation": null_func, + "alpha": null_func, + "roman": null_func, + "word": null_func, + "bold": null_func, + "italic": null_func, + "title_case": null_func, + "single_paragraph": null_func, + "phone_number_formatted": null_func, + "phone_number_in_e164": null_func, + "country_name": null_func, + "fix_punctuation": null_func, + "redact": null_func, + "verbatim": null_func, + "map": null_func, + "chain": null_func, + "any": any, + "all": all, +} - # let undefined variables work in for loops - def __iter__(self, *pargs)->"DASkipUndefined": - return self - - def __next__(self, *pargs)->None: - raise StopIteration - - # need to return an int type - def __int__(self, *pargs)->int: - return 0 - - __len__ = __int__ - - # need to return a float type - def __float__(self, *args)->float: - return 0.0 - - # need to return complex type - def __complex__(self, *args)->complex: - return 0j - - def __add__(self, *args, **kwargs)->str: - return self.__str__() - - # type can be anything. we want it to work with `str()` function though - # and we do not want to silently give wrong math results. - # note that this means 1 + (undefined) or (undefined) + 1 will work but not 1 + (undefined) + 1 - __radd__ = __mul__ = __rmul__ = __div__ = __rdiv__ = \ - __truediv__ = __rtruediv__ = __floordiv__ = __rfloordiv__ = \ - __mod__ = __rmod__ = __pos__ = __neg__ = __pow__ = __rpow__ = \ - __sub__ = __rsub__= __hash__ = __add__ # type: ignore - - -def get_jinja_errors(the_file:DAFile, env=None)->str: - """Just try rendering the DOCX file as a Jinja2 template and catch any errors. - Returns a string with the errors, if any. - """ - if not env: - env = Environment(loader=BaseLoader(),undefined=CallAndDebugUndefined) - - docx_template = DocxTemplate(the_file.path()) - - try: - the_xml = docx_template.get_xml() - #the_xml = re.sub(r'', '\n', the_xml) - the_xml = re.sub(r'({[\%\{].*?[\%\}]})', fix_quotes, the_xml) - the_xml = docx_template.patch_xml(the_xml) - docx_template.render({}, jinja_env=env) - return "" - except jinja2.exceptions.TemplateSyntaxError as the_error: - errmess = str(the_error) - if hasattr(the_error, 'docx_context'): - errmess += "\n\nContext:\n" + "\n".join(map(lambda x: " " + x, the_error.docx_context)) - return errmess - except Exception as the_error: - return str(the_error) - \ No newline at end of file + +class DAExtension(Extension): + def parse(self, parser): + raise NotImplementedError() + + def filter_stream(self, stream): + # in_var = False + met_pipe = False + for token in stream: + if token.type == "variable_begin": + # in_var = True + met_pipe = False + if token.type == "variable_end": + # in_var = False + if not met_pipe: + yield Token(token.lineno, "pipe", None) + yield Token(token.lineno, "name", "ampersand_filter") + # if in_var and token.type == 'pipe': + # met_pipe = True + yield token + + +def get_jinja_errors(the_file: str) -> Optional[str]: + """Just try rendering the DOCX file as a Jinja2 template and catch any errors. + Returns a string with the errors, if any. + """ + env = DAEnvironment(undefined=CallAndDebugUndefined, extensions=[DAExtension]) + env.filters.update(registered_jinja_filters) + env.filters.update(builtin_jinja_filters) + + doc = DocxTemplate(the_file) + try: + doc.render({}, jinja_env=env) + return None + except jinja2.exceptions.TemplateSyntaxError as the_error: + errmess = str(the_error) + extra_context = the_error.docx_context if hasattr(the_error, "docx_context") else [] # type: ignore + if extra_context: + errmess += "\n\nContext:\n" + "\n".join( + map(lambda x: " " + x, extra_context) + ) + return errmess From f27e4d68f7202ec3be818d48f33226f5debcc860 Mon Sep 17 00:00:00 2001 From: Quinten Steenhuis Date: Wed, 3 May 2023 16:22:18 -0400 Subject: [PATCH 03/27] Add tests --- .gitignore | 1 + .../ALDashboard/test/made_up_variables.docx | Bin 0 -> 12223 bytes .../test/valid_word_invalid_jinja.docx | Bin 0 -> 11900 bytes docassemble/ALDashboard/test_validate_docx.py | 21 ++++++++++++++++++ 4 files changed, 22 insertions(+) create mode 100644 .gitignore create mode 100644 docassemble/ALDashboard/test/made_up_variables.docx create mode 100644 docassemble/ALDashboard/test/valid_word_invalid_jinja.docx create mode 100644 docassemble/ALDashboard/test_validate_docx.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bee8a64 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +__pycache__ diff --git a/docassemble/ALDashboard/test/made_up_variables.docx b/docassemble/ALDashboard/test/made_up_variables.docx new file mode 100644 index 0000000000000000000000000000000000000000..807ba7ecb060728c16ca74a91dce1f2e1e47ed71 GIT binary patch literal 12223 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 M6XbYz>3+NWe==vpX8-^I literal 0 HcmV?d00001 diff --git a/docassemble/ALDashboard/test/valid_word_invalid_jinja.docx b/docassemble/ALDashboard/test/valid_word_invalid_jinja.docx new file mode 100644 index 0000000000000000000000000000000000000000..6985bb9108a6b5940087d4de25128f2c1a22c624 GIT binary patch literal 11900 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 T^Y~4`iU8;V2iPLo->&`#{ERtv literal 0 HcmV?d00001 diff --git a/docassemble/ALDashboard/test_validate_docx.py b/docassemble/ALDashboard/test_validate_docx.py new file mode 100644 index 0000000..de043b7 --- /dev/null +++ b/docassemble/ALDashboard/test_validate_docx.py @@ -0,0 +1,21 @@ +import unittest +from typing import Optional +from .validate_docx import get_jinja_errors +from pathlib import Path + + +class TestGetJinjaErrors(unittest.TestCase): + def test_working_template(self): + working_template = Path(__file__).parent / "test/made_up_variables.docx" + result: Optional[str] = get_jinja_errors(working_template) + self.assertIsNone(result) + + def test_failing_template(self): + failing_template = Path(__file__).parent / "test/valid_word_invalid_jinja.docx" + result: Optional[str] = get_jinja_errors(failing_template) + self.assertIsNotNone(result) + self.assertIsInstance(result, str) + + +if __name__ == "__main__": + unittest.main() From 79f19a678424cd730aca9ef2bf9ff1089909be1b Mon Sep 17 00:00:00 2001 From: Quinten Steenhuis Date: Tue, 27 Jun 2023 11:08:09 -0400 Subject: [PATCH 04/27] Fix #9, fix #88 --- .../ALDashboard/data/questions/api_test.yml | 79 +++++++++++++++++++ .../ALDashboard/data/questions/menu.yml | 4 +- 2 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 docassemble/ALDashboard/data/questions/api_test.yml diff --git a/docassemble/ALDashboard/data/questions/api_test.yml b/docassemble/ALDashboard/data/questions/api_test.yml new file mode 100644 index 0000000..7c9577d --- /dev/null +++ b/docassemble/ALDashboard/data/questions/api_test.yml @@ -0,0 +1,79 @@ +--- +include: + - docassemble.ALToolbox:display_template.yml + - nav.yml +--- +metadata: + title: Test API keys + sessions are unique: True + required privileges: + - admin + - developer + temporary session: True +--- +objects: + - my_person: Individual +--- +mandatory: True +code: | + address_to_complete + if my_person.address.address: + my_person.address.geocode() + if my_person.email: + email_success = send_email(to=my_person, subject="Test message from Docassemble", body="Test message body. Thanks for testing with ALDashboard.") + api_results +--- +question: | + Test an API key +fields: + - note: | + --- + Google Maps + + Server API key: `${ get_config("google", {}).get("api key") }` [BR] + HTTP/S API key: `${ get_config("google", {}).get("google maps api key") }` + - Address auto complete: address_to_complete + address autocomplete: True + required: False + - "Address geocoding (enter at least a street address to trigger)": my_person.address.address + required: False + - Unit: my_person.address.unit + required: False + - City: my_person.address.city + required: False + - State: my_person.address.state + required: False + - Zip: my_person.address.zip + required: False + - County: my_person.address.county + required: False + - note: | + --- + Email sending + + ``` + ${ get_config("mail") } + ``` + - Enter an email to get a test message: my_person.email + datatype: email + required: False +--- +event: api_results +question: | + Results +subquestion: | + % if my_person.address.address: + Geocoded: ${ my_person.address.was_geocoded_successfully() } [BR] + % if my_person.address.was_geocoded_successfully(): + Long address: ${ my_person.address.norm_long.on_one_line() } [BR] + % endif + % if hasattr(my_person.address, "county"): + County: ${ my_person.address.county } + % endif + % endif + + % if my_person.email: + Email success: ${ email_success } + % endif + + Use the back button to try again diff --git a/docassemble/ALDashboard/data/questions/menu.yml b/docassemble/ALDashboard/data/questions/menu.yml index 085aa06..43bb4d2 100644 --- a/docassemble/ALDashboard/data/questions/menu.yml +++ b/docassemble/ALDashboard/data/questions/menu.yml @@ -33,8 +33,10 @@ buttons: image: chart-bar - Install assembly line: ${ interview_url(i=user_info().package + ":install_assembly_line.yml", reset=1) } image: tasks + - Verify API keys: ${ interview_url(i=user_info().package + ":api_test.yml", reset=1) } + image: terminal - Install packages: ${ interview_url(i=user_info().package + ":install_packages.yml", reset=1) } - image: cogs + image: cogs - Update packages: ${ interview_url(i=user_info().package + ":update_packages.yml", reset=1) } image: sync - Package scanner: ${ interview_url(i=user_info().package + ":package_scanner.yml", reset=1) } From a8051fb50bb006f83ab71c2ae384e8ca773ef4d5 Mon Sep 17 00:00:00 2001 From: Quinten Steenhuis Date: Fri, 30 Jun 2023 11:18:47 -0400 Subject: [PATCH 05/27] Upgrade to v5.2.3. Note this requires a newer release of node than DA comes with as of this commit --- .../data/questions/compile_bootstrap.yml | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/docassemble/ALDashboard/data/questions/compile_bootstrap.yml b/docassemble/ALDashboard/data/questions/compile_bootstrap.yml index e73dbd4..b9a99f8 100644 --- a/docassemble/ALDashboard/data/questions/compile_bootstrap.yml +++ b/docassemble/ALDashboard/data/questions/compile_bootstrap.yml @@ -13,6 +13,7 @@ metadata: --- objects: - output_file: DAFile + - output_scss: DAFile.using(filename="custom.scss") --- id: interview order block mandatory: True @@ -24,7 +25,7 @@ code: | done --- code: | - bootstrap_dir = "/tmp/bootstrap-5.2.3/" + bootstrap_dir = "/tmp/bootstrap-5.3.0/" --- need: - bootstrap_dir @@ -36,12 +37,13 @@ code: | # https://stackoverflow.com/a/14260592 p = Path(bootstrap_dir) if not p.is_dir(): - r = requests.get("https://github.com/twbs/bootstrap/archive/refs/tags/v5.2.3.zip") + r = requests.get("https://github.com/twbs/bootstrap/archive/refs/tags/v5.3.0.zip") z = zipfile.ZipFile(io.BytesIO(r.content)) z.extractall("/tmp/") z.close() del z subprocess.run(["npm", "install", "--prefix", bootstrap_dir]) + subprocess.run(["npm", "install", "npm-sass", "--prefix", bootstrap_dir]) installed = True --- need: @@ -52,12 +54,13 @@ code: | import shutil, uuid, os, subprocess from pathlib import Path file_name = uuid.uuid4() - full_path = bootstrap_dir + f"scss/{file_name}.scss" + full_path = os.path.join(bootstrap_dir, "scss", f"{file_name}.scss") if upload_choice == "type_out": with open(full_path, "w") as text_to_file: text_to_file.write(file_text) + output_scss.copy_into(full_path) else: # upload_file - shutil.copy(uploaded_file.path(), bootstrap_dir + f"scss/{file_name}.scss") + shutil.copy(uploaded_file.path(), os.path.join(bootstrap_dir, "scss", f"{file_name}.scss")) compile_output = subprocess.run(["npm", "run", "css-compile", "--prefix", bootstrap_dir], capture_output=True) os.remove(full_path) out_path = Path(bootstrap_dir + f"dist/css/{file_name}.css") @@ -85,6 +88,9 @@ question: | What file do you want to make a theme from? subquestion: | It should include an `@import "bootstrap"` in it to actually include all of the bootstrap code. + + You can use [https://huemint.com/bootstrap-basic/](https://huemint.com/bootstrap-basic/) + to generate the SCSS code. fields: - How do you want to provide the file?: upload_choice datatype: radio @@ -124,6 +130,9 @@ fields: "danger": #fa043c, ); @import "bootstrap"; +validation code: | + if upload_choice == "type_out" and not '@import "bootstrap";' in file_text: + validation_error('You need to include the exact text \'@import "bootstrap";\' at the end of your SCSS block', field="file_text") --- template: output_template subject: Bootstrap CSS File @@ -148,4 +157,10 @@ subquestion: | ${ display_template(output_template, copy=True, collapse=True) } - [Right click to 'Save link as...'](${output_file.url_for()}) + [Bootstrap Theme file](${output_file.url_for(attachment=True)}) + + % if upload_choice == "type_out": + [SCSS source file](${ output_scss.url_for(attachment=True) }) + % else: + [SCSS source file](${ uploaded_file.url_for(attachment=True) }) + % endif From 3bebdf9c07af017826d07de5795101e1c4f245cc Mon Sep 17 00:00:00 2001 From: Quinten Steenhuis Date: Fri, 30 Jun 2023 14:28:47 -0400 Subject: [PATCH 06/27] Explain minimum version --- docassemble/ALDashboard/data/questions/compile_bootstrap.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docassemble/ALDashboard/data/questions/compile_bootstrap.yml b/docassemble/ALDashboard/data/questions/compile_bootstrap.yml index b9a99f8..710fc83 100644 --- a/docassemble/ALDashboard/data/questions/compile_bootstrap.yml +++ b/docassemble/ALDashboard/data/questions/compile_bootstrap.yml @@ -43,7 +43,6 @@ code: | z.close() del z subprocess.run(["npm", "install", "--prefix", bootstrap_dir]) - subprocess.run(["npm", "install", "npm-sass", "--prefix", bootstrap_dir]) installed = True --- need: @@ -81,6 +80,9 @@ subquestion: | CSS file that you can include in your docassemble projects. For more information, see [our tutorial on making a custom bootstrap theme](https://suffolklitlab.org/legal-tech-class/docs/practical-guide-docassemble/theming-docassemble#creating-a-custom-theme-from-source-instead-of-with-a-theme-generator). + + You will need to have a recent version of Docassemble's "system" OS to use this tool. (at least 1.4.43). This may require + an upgrade to the [Docker container](https://suffolklitlab.org/legal-tech-class/docs/practical-guide-docassemble/maintaining-docassemble#updates-to-the-docassemble-container). continue button field: start_screen --- id: file-upload From dbfe4f4dd035327fd099e374d9996657cfcb3133 Mon Sep 17 00:00:00 2001 From: Quinten Steenhuis Date: Fri, 30 Jun 2023 14:31:34 -0400 Subject: [PATCH 07/27] Download icons --- docassemble/ALDashboard/data/questions/compile_bootstrap.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docassemble/ALDashboard/data/questions/compile_bootstrap.yml b/docassemble/ALDashboard/data/questions/compile_bootstrap.yml index 710fc83..ec98ca0 100644 --- a/docassemble/ALDashboard/data/questions/compile_bootstrap.yml +++ b/docassemble/ALDashboard/data/questions/compile_bootstrap.yml @@ -162,7 +162,7 @@ subquestion: | [Bootstrap Theme file](${output_file.url_for(attachment=True)}) % if upload_choice == "type_out": - [SCSS source file](${ output_scss.url_for(attachment=True) }) + [:download: SCSS source file](${ output_scss.url_for(attachment=True) }) % else: - [SCSS source file](${ uploaded_file.url_for(attachment=True) }) + [:download: SCSS source file](${ uploaded_file.url_for(attachment=True) }) % endif From 187257d737c7c1b7b2f4d2a6f79631afbfd828a1 Mon Sep 17 00:00:00 2001 From: Quinten Steenhuis Date: Fri, 18 Aug 2023 16:07:11 -0400 Subject: [PATCH 08/27] Add SMS --- .../ALDashboard/data/questions/api_test.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docassemble/ALDashboard/data/questions/api_test.yml b/docassemble/ALDashboard/data/questions/api_test.yml index 7c9577d..c0cffbe 100644 --- a/docassemble/ALDashboard/data/questions/api_test.yml +++ b/docassemble/ALDashboard/data/questions/api_test.yml @@ -21,6 +21,8 @@ code: | my_person.address.geocode() if my_person.email: email_success = send_email(to=my_person, subject="Test message from Docassemble", body="Test message body. Thanks for testing with ALDashboard.") + if my_person.phone_number: + sms_success = send_sms(to=my_person, body="Test message from Docassemble. Thanks for testing with ALDashboard.") api_results --- question: | @@ -57,6 +59,16 @@ fields: - Enter an email to get a test message: my_person.email datatype: email required: False + - note: | + --- + SMS sending + + ``` + ${ get_config("twilio") } + ``` + - Enter a phone number to get a test message: my_person.phone_number + datatype: phone + required: False --- event: api_results question: | @@ -76,4 +88,8 @@ subquestion: | Email success: ${ email_success } % endif + % if my_person.phone_number: + SMS success: ${ sms_success } + % endif + Use the back button to try again From 28a0fff5f3e2093915d9e336ef279a9db6ae782b Mon Sep 17 00:00:00 2001 From: Bryce Willey Date: Fri, 25 Aug 2023 11:48:52 -0400 Subject: [PATCH 09/27] Correct description-file to description_file The deprecation warning from pip: ``` UserWarning: Usage of dash-separated 'description-file' will not be supported in future versions. Please use the underscore name 'description_file' instead ``` --- docassemble/ALDashboard/create_package.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docassemble/ALDashboard/create_package.py b/docassemble/ALDashboard/create_package.py index cf11464..5203338 100644 --- a/docassemble/ALDashboard/create_package.py +++ b/docassemble/ALDashboard/create_package.py @@ -145,7 +145,7 @@ def create_package_zip(pkgname: str, info: dict, author_info: dict, folders_and_ """ setupcfg = """\ [metadata] -description-file = README.md +description_file = README.md """ setuppy = """\ import os From be8005c8f4222be86efdf97014616a4abcf98432 Mon Sep 17 00:00:00 2001 From: Bryce Willey Date: Wed, 20 Sep 2023 14:43:23 -0400 Subject: [PATCH 10/27] Sort sessions and fix users dropdown (#100) Made a few small fixes to the `list_sessions.yml` interview after using it this morning: * The interviews were listed all out of order, which made it hard to know where to start looking: they are now sorted alphabetically * `speedy_get_users` returned Tuples, which didn't seem to be working in the user selection on the first screen of `list_sessions`, and I don't think DA handles very well in general. I changed it to return a List of key-value maps, which is what the `code` section of a combobox expects. Tested both of these changes in apps-dev, and it works great. Might merge these soon, since I've trying to use it to pinpoint more production errors with the ALAffidavitOfIndigency that are appearing. --- docassemble/ALDashboard/aldashboard.py | 4 ++-- docassemble/ALDashboard/data/questions/list_sessions.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docassemble/ALDashboard/aldashboard.py b/docassemble/ALDashboard/aldashboard.py index 753510c..817c819 100644 --- a/docassemble/ALDashboard/aldashboard.py +++ b/docassemble/ALDashboard/aldashboard.py @@ -122,13 +122,13 @@ def da_write_config(data: Dict): restart_all() -def speedy_get_users() -> List[Tuple[int, str]]: +def speedy_get_users() -> List[Dict[int, str]]: """ Return a list of all users in the database. Possibly faster than get_user_list(). """ the_users = UserModel.query.with_entities(UserModel.id, UserModel.email).all() - return [user for user in the_users] + return [{user[0]: user[1]} for user in the_users] def get_users_and_name() -> List[Tuple[int, str, str, str]]: diff --git a/docassemble/ALDashboard/data/questions/list_sessions.yml b/docassemble/ALDashboard/data/questions/list_sessions.yml index 4f2ac2f..acb5427 100644 --- a/docassemble/ALDashboard/data/questions/list_sessions.yml +++ b/docassemble/ALDashboard/data/questions/list_sessions.yml @@ -19,7 +19,7 @@ fields: - Filename: filename required: False code: | - [{interview: interviews[interview].get('title')} for interview in interviews] + sorted([{interview: interviews[interview].get('title')} for interview in interviews], key=lambda y: next(iter(y.values()), '')) - User (leave blank to view all sessions): chosen_user required: False datatype: integer @@ -48,4 +48,4 @@ subquestion: | --- event: view_session_variables code: | - json_response(get_session_variables(filename, action_argument('session_id'), simplify=True)) \ No newline at end of file + json_response(get_session_variables(filename, action_argument('session_id'), simplify=True)) From 1dfb81591fc780e1538015a11677639e638d6ffe Mon Sep 17 00:00:00 2001 From: Bryce Willey Date: Wed, 20 Sep 2023 14:44:15 -0400 Subject: [PATCH 11/27] Bump version to 0.20.1 --- docassemble/ALDashboard/__init__.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docassemble/ALDashboard/__init__.py b/docassemble/ALDashboard/__init__.py index 2f15b8c..abadaef 100644 --- a/docassemble/ALDashboard/__init__.py +++ b/docassemble/ALDashboard/__init__.py @@ -1 +1 @@ -__version__ = '0.20.0' +__version__ = '0.20.1' diff --git a/setup.py b/setup.py index 511683f..218be52 100644 --- a/setup.py +++ b/setup.py @@ -44,7 +44,7 @@ def find_package_data(where='.', package='', exclude=standard_exclude, exclude_d return out setup(name='docassemble.ALDashboard', - version='0.20.0', + version='0.20.1', description=('Dashboard for some admin tasks'), long_description='# Backend Configuration Tool\r\n\r\nA single pane of glass that centralizes some tedious Docassemble admin configuration tasks\r\n\r\n![image](https://user-images.githubusercontent.com/7645641/123702117-bdd7d300-d830-11eb-8c0e-8e204d912ff8.png)\r\n\r\n1. Install the Document Assembly Line packages (support files for [Court Forms Online](https://courtformsonline.org))\r\n1. Searchable user management - reset passwords and change privileges.\r\n1. Installing or updating several packages at once.\r\n1. Listing and viewing the contents of an (unencrypted) interview to facilitate debugging errors on production servers.\r\n1. View analytics/stats captured with store_variable_snapshot.\r\n1. List the files inside a particular package installed on the server.\r\n1. Gather files from a user who left the organization/unknown username and password.\r\n1. Review screen generator\r\n1. validate DOCX Jinja2 templates\r\n\r\nIdeas:\r\n1. Add a link to the dispatch directive for an existing file in an existing package.\r\n1. Generating translation files [TBD].\r\n\r\nTo use, you must create a docassemble API key and add it to your\r\nconfiguration, like this:\r\n\r\n`install packages api key: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx`\r\n\r\n## Some screenshots\r\n\r\n### Main page\r\n![image](https://user-images.githubusercontent.com/7645641/123702117-bdd7d300-d830-11eb-8c0e-8e204d912ff8.png)\r\n\r\n### Manage users\r\n\r\n![image](https://user-images.githubusercontent.com/7645641/123702231-e069ec00-d830-11eb-94dc-5ec0abb86bc9.png)\r\n\r\n### Bulk install packages from GitHub\r\n\r\n![image](https://user-images.githubusercontent.com/7645641/123702290-efe93500-d830-11eb-9fdf-a5935ff4078e.png)\r\n\r\n### Bulk update packages\r\n\r\n![image](https://user-images.githubusercontent.com/7645641/123702362-068f8c00-d831-11eb-9ce4-df7a67ffcfeb.png)\r\n\r\n### View / search sessions by user and interview name\r\n\r\n![image](https://user-images.githubusercontent.com/7645641/123702422-1d35e300-d831-11eb-84d5-5e7385deb901.png)\r\n\r\n![image](https://user-images.githubusercontent.com/7645641/123702464-2cb52c00-d831-11eb-80fc-f2291e824eae.png)\r\n\r\n### View interview stats captured with `store_variables_snapshot()`\r\n\r\n![image](https://user-images.githubusercontent.com/7645641/123702623-5e2df780-d831-11eb-8937-6625df74ab22.png)\r\n\r\n', long_description_content_type='text/markdown', From 2dce9a30a9ce1bb46a3a2dd53365c0a8b884257d Mon Sep 17 00:00:00 2001 From: Bryce Willey Date: Tue, 10 Oct 2023 21:06:49 -0400 Subject: [PATCH 12/27] Better Bootstrap theme default (#103) * Adding a better bootstrap default theme Correcting a mistake I made a while back; the default colors are horrible. That's not a huge issue, but it is a problem because it's impossible to tell before generating the theme, and it's not clear what those values are by default in docassemble. Now, the default colors when you are typing the theme are the same ones from docassemble's base theme. So you'll get what you expect. * Smaller bootstrap button name Co-authored-by: Quinten Steenhuis --- .../data/questions/compile_bootstrap.yml | 20 +++++++++---------- .../ALDashboard/data/questions/menu.yml | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/docassemble/ALDashboard/data/questions/compile_bootstrap.yml b/docassemble/ALDashboard/data/questions/compile_bootstrap.yml index ec98ca0..dedc5db 100644 --- a/docassemble/ALDashboard/data/questions/compile_bootstrap.yml +++ b/docassemble/ALDashboard/data/questions/compile_bootstrap.yml @@ -119,17 +119,17 @@ fields: is: type_out default: | $white: #ffffff; - $blue: #25dec6; - + $blue: #0d6efd; + $theme-colors: ( - "light": #d8e2a5, - "dark": #1b1b1b, - "primary": #25dec6, - "secondary": #375b5a, - "info": #d74d72, - "success": #0cb545, - "warning": #f4ca0b, - "danger": #fa043c, + "light": #f8f9fa, + "dark": #212529, + "primary": #0d6fed, + "secondary": #6c757, + "info": #0dcaf0, + "success": #198754, + "warning": #ffc107, + "danger": #dc3545, ); @import "bootstrap"; validation code: | diff --git a/docassemble/ALDashboard/data/questions/menu.yml b/docassemble/ALDashboard/data/questions/menu.yml index 43bb4d2..dad5fbc 100644 --- a/docassemble/ALDashboard/data/questions/menu.yml +++ b/docassemble/ALDashboard/data/questions/menu.yml @@ -55,5 +55,5 @@ buttons: image: paperclip - PDF tools: ${ interview_url(i=user_info().package + ":pdf_wrangling.yml", reset=1) } image: file-pdf - - Compile a custom Bootstrap theme: ${ interview_url(i=user_info().package + ":compile_bootstrap.yml", reset=1) } + - Compile Bootstrap theme: ${ interview_url(i=user_info().package + ":compile_bootstrap.yml", reset=1) } image: bootstrap \ No newline at end of file From 5bcd39669bc9cb9cda69f3a3a810fa8ac1961881 Mon Sep 17 00:00:00 2001 From: Bryce Willey Date: Wed, 11 Oct 2023 11:19:37 -0400 Subject: [PATCH 13/27] Show preview of what the theme will look like (#104) * Show preview of what the theme will look like Mostly pure HTML from https://bootswatch.com/default/, and using a trick from https://stackoverflow.com/a/13883978/11416267 to add the generated theme to the CSS of the page using Javascript. Works great, might want to consider culling down some of the things shown to be just the docassemble components, since there's a whole lot, and most of it, people won't have to worry about. * Make files a bit clearer on page I went ahead and bolded them, and put one of those horizontal lines between the files at the top and the examples below. Hopefully that makes it a bit clearer. --- .../data/questions/compile_bootstrap.yml | 38 +- .../data/questions/pdf_wrangling.yml | 3 +- .../ALDashboard/data/static/test_html.html | 1513 +++++++++++++++++ 3 files changed, 1548 insertions(+), 6 deletions(-) create mode 100644 docassemble/ALDashboard/data/static/test_html.html diff --git a/docassemble/ALDashboard/data/questions/compile_bootstrap.yml b/docassemble/ALDashboard/data/questions/compile_bootstrap.yml index dedc5db..0db3fb6 100644 --- a/docassemble/ALDashboard/data/questions/compile_bootstrap.yml +++ b/docassemble/ALDashboard/data/questions/compile_bootstrap.yml @@ -14,6 +14,7 @@ metadata: objects: - output_file: DAFile - output_scss: DAFile.using(filename="custom.scss") + - test_html_file: DAStaticFile.using(filename="test_html.html") --- id: interview order block mandatory: True @@ -137,7 +138,7 @@ validation code: | validation_error('You need to include the exact text \'@import "bootstrap";\' at the end of your SCSS block', field="file_text") --- template: output_template -subject: Bootstrap CSS File +subject: Bootstrap CSS Contents content: | ${ output_file.slurp() } --- @@ -159,10 +160,39 @@ subquestion: | ${ display_template(output_template, copy=True, collapse=True) } - [Bootstrap Theme file](${output_file.url_for(attachment=True)}) + **[Bootstrap CSS file](${output_file.url_for(attachment=True)})** % if upload_choice == "type_out": - [:download: SCSS source file](${ output_scss.url_for(attachment=True) }) + **[:download: SCSS source file](${ output_scss.url_for(attachment=True) })** % else: - [:download: SCSS source file](${ uploaded_file.url_for(attachment=True) }) + **[:download: SCSS source file](${ uploaded_file.url_for(attachment=True) })** % endif + +
+ + --- + + Below are examples of all bootstrap components and what they will look like with this + bootstrap theme. + + ${ test_html_file.slurp() } + + --- + +script: | + +css: | + diff --git a/docassemble/ALDashboard/data/questions/pdf_wrangling.yml b/docassemble/ALDashboard/data/questions/pdf_wrangling.yml index 087ac14..8592532 100644 --- a/docassemble/ALDashboard/data/questions/pdf_wrangling.yml +++ b/docassemble/ALDashboard/data/questions/pdf_wrangling.yml @@ -116,7 +116,6 @@ code: | id: rename fields question: | Rename fields -labels above fields: True fields: - code: | [{item[0]: f"rename_fields['{item[0]}']", "default" : item[0], "label above field": True, "required": False} for item in source_pdf[0].get_pdf_fields()] @@ -146,4 +145,4 @@ code: | formfyxer.rename_pdf_fields(in_file=source_pdf[0].path(), out_file=new_pdf.path(), mapping=rename_fields) renamed_fields = True - \ No newline at end of file + diff --git a/docassemble/ALDashboard/data/static/test_html.html b/docassemble/ALDashboard/data/static/test_html.html new file mode 100644 index 0000000..f4ed574 --- /dev/null +++ b/docassemble/ALDashboard/data/static/test_html.html @@ -0,0 +1,1513 @@ + + + +
+ +
+
+
+ + +
+ +
+ +
+ +
+ +
+
+
+ + +
+ + +
+
+

+ + + + + + + + + +

+ +

+ + + + + + + + + +

+ +

+ + + + + + + + +

+ +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+ +
+ + + +
+
+
+
+
+ + +
+
+ +
+
+ + + + + + +
+
+ +
+
+ + + + + + +
+
+ +
+
+ + + + + + +
+
+ +
+
+ + + +
+
+ +
+ +
+
+
+
+ + +
+
+
+ +
+
+ + + +
+
+
+

Heading 1

+

Heading 2

+

Heading 3

+

Heading 4

+
Heading 5
+
Heading 6
+

+ Heading + with faded secondary text +

+

Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor.

+
+
+
+
+

Example body text

+

Nullam quis risus eget urna mollis ornare vel eu leo. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nullam id dolor id nibh ultricies vehicula.

+

This line of text is meant to be treated as fine print.

+

The following is rendered as bold text.

+

The following is rendered as italicized text.

+

An abbreviation of the word attribute is attr.

+
+
+
+
+

Emphasis classes

+

.text-primary

+

.text-primary-emphasis

+

.text-secondary

+

.text-secondary-emphasis

+

.text-success

+

.text-success-emphasis

+

.text-danger

+

.text-danger-emphasis

+

.text-warning

+

.text-warning-emphasis

+

.text-info

+

.text-info-emphasis

+

.text-light

+

.text-light-emphasis

+

.text-dark

+

.text-dark-emphasis

+

.text-body

+

.text-body-emphasis

+

.text-body-secondary

+

.text-body-tertiary

+
+
+
+ + + +
+
+

Blockquotes

+
+
+
+
+
+
+
+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer posuere erat a ante.

+ +
+ +
+
+
+
+
+
+
+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer posuere erat a ante.

+ +
+ +
+
+
+
+
+
+
+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer posuere erat a ante.

+ +
+ +
+
+
+
+
+ + +
+
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TypeColumn headingColumn headingColumn heading
ActiveColumn contentColumn contentColumn content
DefaultColumn contentColumn contentColumn content
PrimaryColumn contentColumn contentColumn content
SecondaryColumn contentColumn contentColumn content
SuccessColumn contentColumn contentColumn content
DangerColumn contentColumn contentColumn content
WarningColumn contentColumn contentColumn content
InfoColumn contentColumn contentColumn content
LightColumn contentColumn contentColumn content
DarkColumn contentColumn contentColumn content
+
+
+
+
+ + +
+
+
+ +
+
+ +
+
+
+
+
+ Legend +
+ +
+ +
+
+
+ + + We'll never share your email with anyone else. +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ Radio buttons +
+ + +
+
+ + +
+
+ + +
+
+
+ Checkboxes +
+ + +
+
+ + +
+
+
+ Switches +
+ + +
+
+ + +
+
+
+ Ranges + + + + + + +
+ +
+
+
+
+
+
+
+
+ + +
+
+ +
+
+ + +
+
+ +
+ + +
Success! You've done it.
+
+ +
+ + +
Sorry, that username's taken. Try another?
+
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ +
+
+ $ + + .00 +
+
+ + +
+
+
+ +
+ +
+ + +
+
+ + +
+
+
+ +
+
+
+ + +
+
+
+ +
+
+ +
+
+ +
+ +
+
+

Raw denim you probably haven't heard of them jean shorts Austin. Nesciunt tofu stumptown aliqua, retro synth master cleanse. Mustache cliche tempor, williamsburg carles vegan helvetica. Reprehenderit butcher retro keffiyeh dreamcatcher synth. Cosby sweater eu banh mi, qui irure terry richardson ex squid. Aliquip placeat salvia cillum iphone. Seitan aliquip quis cardigan american apparel, butcher voluptate nisi qui.

+
+
+

Food truck fixie locavore, accusamus mcsweeney's marfa nulla single-origin coffee squid. Exercitation +1 labore velit, blog sartorial PBR leggings next level wes anderson artisan four loko farm-to-table craft beer twee. Qui photo booth letterpress, commodo enim craft beer mlkshk aliquip jean shorts ullamco ad vinyl cillum PBR. Homo nostrud organic, assumenda labore aesthetic magna delectus mollit.

+
+ + +
+
+
+ +
+ +
+ +
+
+
+ +
+
+
+ +
+
+ +
+ + + +
+
+ +
+

Pagination

+
+
+
    +
  • + « +
  • +
  • + 1 +
  • +
  • + 2 +
  • +
  • + 3 +
  • +
  • + 4 +
  • +
  • + 5 +
  • +
  • + » +
  • +
+
+ +
+
    +
  • + « +
  • +
  • + 1 +
  • +
  • + 2 +
  • +
  • + 3 +
  • +
  • + 4 +
  • +
  • + 5 +
  • +
  • + » +
  • +
+
+ +
+
    +
  • + « +
  • +
  • + 1 +
  • +
  • + 2 +
  • +
  • + 3 +
  • +
  • + 4 +
  • +
  • + 5 +
  • +
  • + » +
  • +
+
+
+
+
+
+ + +
+
+
+ +
+
+ +
+
+

Alerts

+
+
+ +

Warning!

+

Best check yo self, you're not looking too good. Nulla vitae elit libero, a pharetra augue. Praesent commodo cursus magna, vel scelerisque nisl consectetur et.

+
+
+
+
+
+
+
+
+ + Oh snap! Change a few things up and try submitting again. +
+
+
+
+
+
+ + Well done! You successfully read this important alert message. +
+
+
+
+
+
+ + Heads up! This alert needs your attention, but it's not super important. +
+
+
+
+
+
+
+
+ + Oh snap! Change a few things up and try submitting again. +
+
+
+
+
+
+ + Well done! You successfully read this important alert message. +
+
+
+
+
+
+ + Heads up! This alert needs your attention, but it's not super important. +
+
+
+
+
+

Badges

+
+ Primary + Secondary + Success + Danger + Warning + Info + Light + Dark +
+
+ Primary + Secondary + Success + Danger + Warning + Info + Light + Dark +
+
+
+ + +
+
+
+ + +

Basic

+
+
+
+
+
+ +

Contextual alternatives

+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +

Multiple bars

+
+
+
+
+
+
+
+ +

Striped

+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +

Animated

+
+
+
+
+
+
+
+
+ + +
+
+
+ +
+
+ +
+
+

List groups

+
+
+ +
+
+
+
    +
  • + Cras justo odio + 14 +
  • +
  • + Dapibus ac facilisis in + 2 +
  • +
  • + Morbi leo risus + 1 +
  • +
+
+
+
    +
  • + Cras justo odio + 14 +
  • +
  • + Dapibus ac facilisis in + 2 +
  • +
  • + Morbi leo risus + 1 +
  • + Cras justo odio + 5 +
  • +
  • + Dapibus ac facilisis in + 4 +
  • +
  • + Morbi leo risus + 9 +
  • +
  • + Morbi leo risus + 8 +
  • +
  • + Morbi leo risus + 0 +
  • +
+
+
+ + +
+ +
+
+

Cards

+
+
+ +
+
+
+
+
Header
+
+

Primary card title

+

Some quick example text to build on the card title and make up the bulk of the card's content.

+
+
+
+
Header
+
+

Secondary card title

+

Some quick example text to build on the card title and make up the bulk of the card's content.

+
+
+
+
Header
+
+

Success card title

+

Some quick example text to build on the card title and make up the bulk of the card's content.

+
+
+
+
Header
+
+

Danger card title

+

Some quick example text to build on the card title and make up the bulk of the card's content.

+
+
+
+
Header
+
+

Warning card title

+

Some quick example text to build on the card title and make up the bulk of the card's content.

+
+
+
+
Header
+
+

Info card title

+

Some quick example text to build on the card title and make up the bulk of the card's content.

+
+
+
+
Header
+
+

Light card title

+

Some quick example text to build on the card title and make up the bulk of the card's content.

+
+
+
+
Header
+
+

Dark card title

+

Some quick example text to build on the card title and make up the bulk of the card's content.

+
+
+
+
+
+
+
+
Header
+
+

Primary card title

+

Some quick example text to build on the card title and make up the bulk of the card's content.

+
+
+
+
Header
+
+

Secondary card title

+

Some quick example text to build on the card title and make up the bulk of the card's content.

+
+
+
+
Header
+
+

Success card title

+

Some quick example text to build on the card title and make up the bulk of the card's content.

+
+
+
+
Header
+
+

Danger card title

+

Some quick example text to build on the card title and make up the bulk of the card's content.

+
+
+
+
Header
+
+

Warning card title

+

Some quick example text to build on the card title and make up the bulk of the card's content.

+
+
+
+
Header
+
+

Info card title

+

Some quick example text to build on the card title and make up the bulk of the card's content.

+
+
+
+
Header
+
+

Light card title

+

Some quick example text to build on the card title and make up the bulk of the card's content.

+
+
+
+
Header
+
+

Dark card title

+

Some quick example text to build on the card title and make up the bulk of the card's content.

+
+
+
+
+ +
+
+
+

Card header

+
+
Special title treatment
+
Support card subtitle
+
+ + + Image cap + +
+

Some quick example text to build on the card title and make up the bulk of the card's content.

+
+
    +
  • Cras justo odio
  • +
  • Dapibus ac facilisis in
  • +
  • Vestibulum at eros
  • +
+ + +
+
+
+

Card title

+
Card subtitle
+

Some quick example text to build on the card title and make up the bulk of the card's content.

+ Card link + Another link +
+
+
+
+
+ +
+
+

Accordions

+
+
+ +
+
+
+
+
+

+ +

+
+
+ This is the first item's accordion body. It is shown by default, until the collapse plugin adds the appropriate classes that we use to style each element. These classes control the overall appearance, as well as the showing and hiding via CSS transitions. You can modify any of this with custom CSS or overriding our default variables. It's also worth noting that just about any HTML can go within the .accordion-body, though the transition does limit overflow. +
+
+
+
+

+ +

+
+
+ This is the second item's accordion body. It is hidden by default, until the collapse plugin adds the appropriate classes that we use to style each element. These classes control the overall appearance, as well as the showing and hiding via CSS transitions. You can modify any of this with custom CSS or overriding our default variables. It's also worth noting that just about any HTML can go within the .accordion-body, though the transition does limit overflow. +
+
+
+
+

+ +

+
+
+ This is the third item's accordion body. It is hidden by default, until the collapse plugin adds the appropriate classes that we use to style each element. These classes control the overall appearance, as well as the showing and hiding via CSS transitions. You can modify any of this with custom CSS or overriding our default variables. It's also worth noting that just about any HTML can go within the .accordion-body, though the transition does limit overflow. +
+
+
+
+
+
+
+ +
+ + +
+
+
+ +
+
+
+
+

Modals

+
+ +
+

Offcanvas

+
+ + Link with href + + + +
+
+
Offcanvas
+ +
+
+
+ Some text as placeholder. In real life you can have the elements you have chosen. Like, text, images, lists, etc. +
+ +
+
+
+
+
+

Popovers

+
+ + + + + + + +
+

Tooltips

+
+ + + + + + + +
+

Toasts

+
+ +
+
+
+
+ + +
From 268abb3630cd0ea983e0bddac116b040b74e6921 Mon Sep 17 00:00:00 2001 From: Bryce Willey Date: Wed, 11 Oct 2023 11:56:53 -0400 Subject: [PATCH 14/27] Better readme --- README.md | 24 ++++++++++++++---------- setup.cfg | 2 +- setup.py | 4 ++-- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index fc09a63..f567f3d 100644 --- a/README.md +++ b/README.md @@ -2,19 +2,20 @@ [![PyPI version](https://badge.fury.io/py/docassemble.ALDashboard.svg)](https://badge.fury.io/py/docassemble.ALDashboard) -A single pane of glass that centralizes some tedious Docassemble admin configuration tasks +A single tool and interview to centralizes some tedious Docassemble admin configuration tasks -![image](https://user-images.githubusercontent.com/7645641/123702117-bdd7d300-d830-11eb-8c0e-8e204d912ff8.png) +![A screenshot of the ALDashboard menu with choices: "Admin only - manage users", "Admin only - stats", "Install assembly line", "Verify API Keys", "Install packages", "update packages", "Package scanner", "View Answer files", "generate review screen draft", "validate docx template", "validation translation files", "prepare translation files", "validate an attachment fields block", "PDF tools", and "Compile Bootstrap theme"](https://github.com/SuffolkLITLab/docassemble-ALDashboard/assets/6252212/29539eec-3891-476b-b248-dd3db986d899) 1. Install the Document Assembly Line packages (support files for [Court Forms Online](https://courtformsonline.org)) 1. Searchable user management - reset passwords and change privileges. 1. Installing or updating several packages at once. 1. Listing and viewing the contents of an (unencrypted) interview to facilitate debugging errors on production servers. -1. View analytics/stats captured with store_variable_snapshot. +1. View analytics/stats captured with `store_variable_snapshot`. 1. List the files inside a particular package installed on the server. 1. Gather files from a user who left the organization/unknown username and password. 1. Review screen generator 1. validate DOCX Jinja2 templates +1. Generating a [custom bootstrap theme](https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/customization/overview#creating-a-custom-theme-from-source-instead-of-with-a-theme-generator) for your interviews. Ideas: 1. Add a link to the dispatch directive for an existing file in an existing package. @@ -28,27 +29,30 @@ configuration, like this: ## Some screenshots ### Main page -![image](https://user-images.githubusercontent.com/7645641/123702117-bdd7d300-d830-11eb-8c0e-8e204d912ff8.png) +![A screenshot of the ALDashboard menu with choices: "Admin only - manage users", "Admin only - stats", "Install assembly line", "Verify API Keys", "Install packages", "update packages", "Package scanner", "View Answer files", "generate review screen draft", "validate docx template", "validation translation files", "prepare translation files", "validate an attachment fields block", "PDF tools", and "Compile Bootstrap theme"](https://github.com/SuffolkLITLab/docassemble-ALDashboard/assets/6252212/29539eec-3891-476b-b248-dd3db986d899) ### Manage users -![image](https://user-images.githubusercontent.com/7645641/123702231-e069ec00-d830-11eb-94dc-5ec0abb86bc9.png) +![A screenshot that says "Manage users" with the fields User, What do you want want to do? Reset password, Change user permissions, New Password, Verify new Password](https://user-images.githubusercontent.com/7645641/123702231-e069ec00-d830-11eb-94dc-5ec0abb86bc9.png) ### Bulk install packages from GitHub -![image](https://user-images.githubusercontent.com/7645641/123702290-efe93500-d830-11eb-9fdf-a5935ff4078e.png) +![A screenshot that says "What packages do you want to install? Github URL, YAML filename, Shor name or alias (no spaces)"](https://user-images.githubusercontent.com/7645641/123702290-efe93500-d830-11eb-9fdf-a5935ff4078e.png) ### Bulk update packages -![image](https://user-images.githubusercontent.com/7645641/123702362-068f8c00-d831-11eb-9ce4-df7a67ffcfeb.png) +![A screenshot that says "What packages do you want to update? docassemble.209aPlaintiffMotionToModify, docassemble.ALAffidavitOfIndigency" and more](https://user-images.githubusercontent.com/7645641/123702362-068f8c00-d831-11eb-9ce4-df7a67ffcfeb.png) ### View / search sessions by user and interview name -![image](https://user-images.githubusercontent.com/7645641/123702422-1d35e300-d831-11eb-84d5-5e7385deb901.png) +![A screenshot that says "What interview do you want to view sessions for? File name, User (leave blank to view all sessions)"](https://user-images.githubusercontent.com/7645641/123702422-1d35e300-d831-11eb-84d5-5e7385deb901.png) -![image](https://user-images.githubusercontent.com/7645641/123702464-2cb52c00-d831-11eb-80fc-f2291e824eae.png) +![A screenshot that says "Recently generated sessions for docassemble.MA209AProtectiveOrder:data/questions/209a_package.yml" with 5 sessions below](https://user-images.githubusercontent.com/7645641/123702464-2cb52c00-d831-11eb-80fc-f2291e824eae.png) ### View interview stats captured with `store_variables_snapshot()` -![image](https://user-images.githubusercontent.com/7645641/123702623-5e2df780-d831-11eb-8937-6625df74ab22.png) +![A screenshot that says "Stats for Eviction Moratorium: 9 Total submissions: 9 Group by: zip | state | modtime, Excel Download" followed by a map](https://user-images.githubusercontent.com/7645641/123702623-5e2df780-d831-11eb-8937-6625df74ab22.png) +### Generate a bootstrap theme + +![A screenshot that says "Your file is compiled! You can view and copy your file, or download it directly by right clicking the link to save it as a CSS file". Below are examples of Bootstrap components like buttons and nav bars.](https://github.com/SuffolkLITLab/docassemble-ALDashboard/assets/6252212/079e428d-4cae-4f75-8b1b-227c28f32a44) diff --git a/setup.cfg b/setup.cfg index b88034e..08aedd7 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,2 +1,2 @@ [metadata] -description-file = README.md +description_file = README.md diff --git a/setup.py b/setup.py index 218be52..09a6230 100644 --- a/setup.py +++ b/setup.py @@ -46,7 +46,7 @@ def find_package_data(where='.', package='', exclude=standard_exclude, exclude_d setup(name='docassemble.ALDashboard', version='0.20.1', description=('Dashboard for some admin tasks'), - long_description='# Backend Configuration Tool\r\n\r\nA single pane of glass that centralizes some tedious Docassemble admin configuration tasks\r\n\r\n![image](https://user-images.githubusercontent.com/7645641/123702117-bdd7d300-d830-11eb-8c0e-8e204d912ff8.png)\r\n\r\n1. Install the Document Assembly Line packages (support files for [Court Forms Online](https://courtformsonline.org))\r\n1. Searchable user management - reset passwords and change privileges.\r\n1. Installing or updating several packages at once.\r\n1. Listing and viewing the contents of an (unencrypted) interview to facilitate debugging errors on production servers.\r\n1. View analytics/stats captured with store_variable_snapshot.\r\n1. List the files inside a particular package installed on the server.\r\n1. Gather files from a user who left the organization/unknown username and password.\r\n1. Review screen generator\r\n1. validate DOCX Jinja2 templates\r\n\r\nIdeas:\r\n1. Add a link to the dispatch directive for an existing file in an existing package.\r\n1. Generating translation files [TBD].\r\n\r\nTo use, you must create a docassemble API key and add it to your\r\nconfiguration, like this:\r\n\r\n`install packages api key: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx`\r\n\r\n## Some screenshots\r\n\r\n### Main page\r\n![image](https://user-images.githubusercontent.com/7645641/123702117-bdd7d300-d830-11eb-8c0e-8e204d912ff8.png)\r\n\r\n### Manage users\r\n\r\n![image](https://user-images.githubusercontent.com/7645641/123702231-e069ec00-d830-11eb-94dc-5ec0abb86bc9.png)\r\n\r\n### Bulk install packages from GitHub\r\n\r\n![image](https://user-images.githubusercontent.com/7645641/123702290-efe93500-d830-11eb-9fdf-a5935ff4078e.png)\r\n\r\n### Bulk update packages\r\n\r\n![image](https://user-images.githubusercontent.com/7645641/123702362-068f8c00-d831-11eb-9ce4-df7a67ffcfeb.png)\r\n\r\n### View / search sessions by user and interview name\r\n\r\n![image](https://user-images.githubusercontent.com/7645641/123702422-1d35e300-d831-11eb-84d5-5e7385deb901.png)\r\n\r\n![image](https://user-images.githubusercontent.com/7645641/123702464-2cb52c00-d831-11eb-80fc-f2291e824eae.png)\r\n\r\n### View interview stats captured with `store_variables_snapshot()`\r\n\r\n![image](https://user-images.githubusercontent.com/7645641/123702623-5e2df780-d831-11eb-8937-6625df74ab22.png)\r\n\r\n', + long_description='# ALDashboard: a docassemble Admin and Configuration Tool\r\n\r\n[![PyPI version](https://badge.fury.io/py/docassemble.ALDashboard.svg)](https://badge.fury.io/py/docassemble.ALDashboard)\r\n\r\nA single tool and interview to centralizes some tedious Docassemble admin configuration tasks\r\n\r\n![A screenshot of the ALDashboard menu with choices: "Admin only - manage users", "Admin only - stats", "Install assembly line", "Verify API Keys", "Install packages", "update packages", "Package scanner", "View Answer files", "generate review screen draft", "validate docx template", "validation translation files", "prepare translation files", "validate an attachment fields block", "PDF tools", and "Compile Bootstrap theme"](https://github.com/SuffolkLITLab/docassemble-ALDashboard/assets/6252212/29539eec-3891-476b-b248-dd3db986d899)\r\n\r\n1. Install the Document Assembly Line packages (support files for [Court Forms Online](https://courtformsonline.org))\r\n1. Searchable user management - reset passwords and change privileges.\r\n1. Installing or updating several packages at once.\r\n1. Listing and viewing the contents of an (unencrypted) interview to facilitate debugging errors on production servers.\r\n1. View analytics/stats captured with `store_variable_snapshot`.\r\n1. List the files inside a particular package installed on the server.\r\n1. Gather files from a user who left the organization/unknown username and password.\r\n1. Review screen generator\r\n1. validate DOCX Jinja2 templates\r\n1. Generating a [custom bootstrap theme](https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/customization/overview#creating-a-custom-theme-from-source-instead-of-with-a-theme-generator) for your interviews.\r\n\r\nIdeas:\r\n1. Add a link to the dispatch directive for an existing file in an existing package.\r\n1. Generating translation files [TBD].\r\n\r\nTo use, you must create a docassemble API key and add it to your\r\nconfiguration, like this:\r\n\r\n`install packages api key: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx`\r\n\r\n## Some screenshots\r\n\r\n### Main page\r\n![A screenshot of the ALDashboard menu with choices: "Admin only - manage users", "Admin only - stats", "Install assembly line", "Verify API Keys", "Install packages", "update packages", "Package scanner", "View Answer files", "generate review screen draft", "validate docx template", "validation translation files", "prepare translation files", "validate an attachment fields block", "PDF tools", and "Compile Bootstrap theme"](https://github.com/SuffolkLITLab/docassemble-ALDashboard/assets/6252212/29539eec-3891-476b-b248-dd3db986d899)\r\n\r\n### Manage users\r\n\r\n![A screenshot that says "Manage users" with the fields User, What do you want want to do? Reset password, Change user permissions, New Password, Verify new Password](https://user-images.githubusercontent.com/7645641/123702231-e069ec00-d830-11eb-94dc-5ec0abb86bc9.png)\r\n\r\n### Bulk install packages from GitHub\r\n\r\n![A screenshot that says "What packages do you want to install? Github URL, YAML filename, Shor name or alias (no spaces)"](https://user-images.githubusercontent.com/7645641/123702290-efe93500-d830-11eb-9fdf-a5935ff4078e.png)\r\n\r\n### Bulk update packages\r\n\r\n![A screenshot that says "What packages do you want to update? docassemble.209aPlaintiffMotionToModify, docassemble.ALAffidavitOfIndigency" and more](https://user-images.githubusercontent.com/7645641/123702362-068f8c00-d831-11eb-9ce4-df7a67ffcfeb.png)\r\n\r\n### View / search sessions by user and interview name\r\n\r\n![A screenshot that says "What interview do you want to view sessions for? File name, User (leave blank to view all sessions)"](https://user-images.githubusercontent.com/7645641/123702422-1d35e300-d831-11eb-84d5-5e7385deb901.png)\r\n\r\n![A screenshot that says "Recently generated sessions for docassemble.MA209AProtectiveOrder:data/questions/209a_package.yml" with 5 sessions below](https://user-images.githubusercontent.com/7645641/123702464-2cb52c00-d831-11eb-80fc-f2291e824eae.png)\r\n\r\n### View interview stats captured with `store_variables_snapshot()`\r\n\r\n![A screenshot that says "Stats for Eviction Moratorium: 9 Total submissions: 9 Group by: zip | state | modtime, Excel Download" followed by a map](https://user-images.githubusercontent.com/7645641/123702623-5e2df780-d831-11eb-8937-6625df74ab22.png)\r\n\r\n### Generate a bootstrap theme\r\n\r\n![A screenshot that says "Your file is compiled! You can view and copy your file, or download it directly by right clicking the link to save it as a CSS file". Below are examples of Bootstrap components like buttons and nav bars.](https://github.com/SuffolkLITLab/docassemble-ALDashboard/assets/6252212/079e428d-4cae-4f75-8b1b-227c28f32a44)\r\n', long_description_content_type='text/markdown', author='Quinten Steenhuis', author_email='qsteenhuis@gmail.com', @@ -54,7 +54,7 @@ def find_package_data(where='.', package='', exclude=standard_exclude, exclude_d url='https://github.com/SuffolkLITLab/docassemble-ALDashboard', packages=find_packages(), namespace_packages=['docassemble'], - install_requires=['PyGithub>=1.55'], + install_requires=['PyGithub>=2.1.1'], zip_safe=False, package_data=find_package_data(where='docassemble/ALDashboard/', package='docassemble.ALDashboard'), ) From e4cfada15955a836d21f2f59b6843f9227cb60db Mon Sep 17 00:00:00 2001 From: Bryce Willey Date: Wed, 11 Oct 2023 12:01:10 -0400 Subject: [PATCH 15/27] Add ALToolbox dep Fix #101. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 09a6230..e189685 100644 --- a/setup.py +++ b/setup.py @@ -54,7 +54,7 @@ def find_package_data(where='.', package='', exclude=standard_exclude, exclude_d url='https://github.com/SuffolkLITLab/docassemble-ALDashboard', packages=find_packages(), namespace_packages=['docassemble'], - install_requires=['PyGithub>=2.1.1'], + install_requires=['PyGithub>=2.1.1', 'docassemble.ALToolbox'], zip_safe=False, package_data=find_package_data(where='docassemble/ALDashboard/', package='docassemble.ALDashboard'), ) From 25a64361015c358c7a7c36210ae50fdb354f6325 Mon Sep 17 00:00:00 2001 From: Quinten Steenhuis Date: Thu, 12 Oct 2023 11:24:36 -0400 Subject: [PATCH 16/27] Fix #16 - basic font install support --- docassemble/ALDashboard/aldashboard.py | 38 +++++++- .../data/questions/install_fonts.yml | 95 +++++++++++++++++++ .../ALDashboard/data/questions/menu.yml | 4 +- 3 files changed, 134 insertions(+), 3 deletions(-) create mode 100644 docassemble/ALDashboard/data/questions/install_fonts.yml diff --git a/docassemble/ALDashboard/aldashboard.py b/docassemble/ALDashboard/aldashboard.py index 817c819..22d3ffe 100644 --- a/docassemble/ALDashboard/aldashboard.py +++ b/docassemble/ALDashboard/aldashboard.py @@ -1,3 +1,6 @@ +import os +import shutil +import subprocess from docassemble.webapp.users.models import UserModel from docassemble.webapp.db_object import init_sqlalchemy from github import Github # PyGithub @@ -20,7 +23,7 @@ ) from docassemble.base.config import daconfig from docassemble.webapp.backend import cloud -from docassemble.base.util import log, DAFile, DAObject, DAList, word +from docassemble.base.util import log, DAFile, DAObject, DAList, word, DAFileList, get_config from ruamel.yaml import YAML from ruamel.yaml.compat import StringIO import re @@ -39,6 +42,8 @@ "ALPackageInstaller", "get_package_info", "install_from_pypi", + "install_fonts", + "list_installed_fonts", ] @@ -230,7 +235,7 @@ def init(self, *pargs, **kwargs): class ErrorLikeObject(DAObject): """ - An object with a `template_name` that identifieds the DALazyTemplate that will + An object with a `template_name` that identifies the DALazyTemplate that will show its error. It can contain any other attributes so its template can access them as needed. DAObject doesn't seem to be enough to allow template definition. """ @@ -241,6 +246,35 @@ def init(self, *pargs, **kwargs): self.template_name = kwargs.get("template_name", "unknown_error") +def install_fonts(the_font_files: DAFileList): + """ + Install fonts to the server and restart both supervisor and unoconv. + """ + # create the /var/www/.fonts directory if it doesn't exist + if not os.path.exists("/var/www/.fonts"): + os.makedirs("/var/www/.fonts") + + # save the DAFile to /var/www/.fonts + for f in the_font_files: + shutil.copyfile(f.path(), "/var/www/.fonts/" + f.filename) + + output = "" + output += subprocess.run(["fc-cache", "-f", "-v"], capture_output=True, text=True).stdout + output += subprocess.run(["supervisorctl", "restart", "uwsgi"], capture_output=True, text=True).stdout + output += subprocess.run(["supervisorctl", "start", "reset"], capture_output=True, text=True).stdout + if get_config("enable unoconv"): + output += subprocess.run(["supervisorctl", "-s", "http://localhost:9001", "restart", "unoconv"], capture_output=True, text=True).stdout + + return output + +def list_installed_fonts(): + """ + List the fonts installed on the server. + """ + output = subprocess.run(["fc-list"], capture_output=True, text=True).stdout + return output + + # select userdict.filename, num_keys, userdictkeys.user_id, modtime, userdict.key from userdict natural join (select key, max(modtime) as modtime, count(key) as num_keys from userdict group by key) mostrecent left join userdictkeys on userdictkeys.key = userdict.key order by modtime desc; # db.session.query diff --git a/docassemble/ALDashboard/data/questions/install_fonts.yml b/docassemble/ALDashboard/data/questions/install_fonts.yml new file mode 100644 index 0000000..bb82a67 --- /dev/null +++ b/docassemble/ALDashboard/data/questions/install_fonts.yml @@ -0,0 +1,95 @@ +--- +include: + - nav.yml +--- +metadata: + required privileges: + - admin +--- +modules: + - .aldashboard +--- +mandatory: True +code: | + the_font_files + if install_fonts_task.ready(): + ending_screen + else: + waiting_screen +--- +id: waiting screen +event: waiting_screen +question: | + Installing fonts +subquestion: | +
+
+ Installing fonts... +
+
+ + This screen will reload every + ten seconds until the installation is complete. +reload: True +--- +id: ending screen +event: ending_screen +question: | + % if install_fonts_task.failed(): + Installation failed + % else: + Installation successful + % endif +subquestion: | + % if install_fonts_task.failed(): + ${ install_fonts_task.ready() } + % endif +--- +code: | + install_fonts_task = background_action("install_fonts_event") +--- +event: install_fonts_event +code: | + install_fonts(the_font_files) + background_response() +--- +id: install fonts +question: | + Upload the fonts you want to install +subquestion: | + You can upload multiple fonts at once. The fonts will be installed on the server and will be available for use in your interviews. + + ${ action_button_html(url_action("list_fonts_task"), label="View installed fonts", id_tag="view_fonts") } + + [TARGET displayed_fonts] + +fields: + - Font files: the_font_files + datatype: files + #accept: | + # "application/font-sfnt, .TTF, .ttf, font/*" +validation code: | + for f in the_font_files: + if not ".ttf" in f.filename.lower(): + validation_error("You need to upload a TrueType font", field="the_font_files") +script: | + +--- +code: | + list_fonts_task = background_action("display_fonts", 'backgroundresponse') +--- +event: display_fonts +code: | + installed_fonts = f""" +
+

Installed fonts

+ { list_installed_fonts() } +
+ """ + + background_response(target='displayed_fonts', content=installed_fonts) + #background_response(target="displayed_fonts", content="ABC") diff --git a/docassemble/ALDashboard/data/questions/menu.yml b/docassemble/ALDashboard/data/questions/menu.yml index dad5fbc..35e4b49 100644 --- a/docassemble/ALDashboard/data/questions/menu.yml +++ b/docassemble/ALDashboard/data/questions/menu.yml @@ -56,4 +56,6 @@ buttons: - PDF tools: ${ interview_url(i=user_info().package + ":pdf_wrangling.yml", reset=1) } image: file-pdf - Compile Bootstrap theme: ${ interview_url(i=user_info().package + ":compile_bootstrap.yml", reset=1) } - image: bootstrap \ No newline at end of file + image: bootstrap + - Install fonts: ${ interview_url(i=user_info().package + ":install_fonts.yml", reset=1)} + image: font \ No newline at end of file From 12a5d4e01be1ba309d84db04eab8676f5feb34ca Mon Sep 17 00:00:00 2001 From: Quinten Steenhuis Date: Thu, 12 Oct 2023 11:28:19 -0400 Subject: [PATCH 17/27] Remove some unused code --- docassemble/ALDashboard/data/questions/install_fonts.yml | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/docassemble/ALDashboard/data/questions/install_fonts.yml b/docassemble/ALDashboard/data/questions/install_fonts.yml index bb82a67..37b2923 100644 --- a/docassemble/ALDashboard/data/questions/install_fonts.yml +++ b/docassemble/ALDashboard/data/questions/install_fonts.yml @@ -72,12 +72,6 @@ validation code: | for f in the_font_files: if not ".ttf" in f.filename.lower(): validation_error("You need to upload a TrueType font", field="the_font_files") -script: | - --- code: | list_fonts_task = background_action("display_fonts", 'backgroundresponse') @@ -91,5 +85,4 @@ code: | """ - background_response(target='displayed_fonts', content=installed_fonts) - #background_response(target="displayed_fonts", content="ABC") + background_response(target='displayed_fonts', content=installed_fonts) \ No newline at end of file From ee18bea9e8902aa715ca63c44df2c143b6f3ecce Mon Sep 17 00:00:00 2001 From: Quinten Steenhuis Date: Thu, 12 Oct 2023 13:50:38 -0400 Subject: [PATCH 18/27] Update docassemble/ALDashboard/aldashboard.py Co-authored-by: Bryce Willey --- docassemble/ALDashboard/aldashboard.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docassemble/ALDashboard/aldashboard.py b/docassemble/ALDashboard/aldashboard.py index 22d3ffe..bee26e2 100644 --- a/docassemble/ALDashboard/aldashboard.py +++ b/docassemble/ALDashboard/aldashboard.py @@ -271,7 +271,9 @@ def list_installed_fonts(): """ List the fonts installed on the server. """ - output = subprocess.run(["fc-list"], capture_output=True, text=True).stdout + fc_list = subprocess.run(["fc-list"], stdout=subprocess.PIPE) + output = subprocess.run(["sort"], stdin=fc_list.stdout, capture_output=True, text=True).stdout + fc_list.stdout.close() return output From ab11efbb853b09858f8bef5ea3873ec706d25068 Mon Sep 17 00:00:00 2001 From: Quinten Steenhuis Date: Thu, 12 Oct 2023 13:52:49 -0400 Subject: [PATCH 19/27] Update docassemble/ALDashboard/aldashboard.py Co-authored-by: Bryce Willey --- docassemble/ALDashboard/aldashboard.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docassemble/ALDashboard/aldashboard.py b/docassemble/ALDashboard/aldashboard.py index bee26e2..21f6e9f 100644 --- a/docassemble/ALDashboard/aldashboard.py +++ b/docassemble/ALDashboard/aldashboard.py @@ -256,7 +256,7 @@ def install_fonts(the_font_files: DAFileList): # save the DAFile to /var/www/.fonts for f in the_font_files: - shutil.copyfile(f.path(), "/var/www/.fonts/" + f.filename) + shutil.copyfile(f.path(), "/var/www/.fonts/" + werkzeug.secure_filename(f.filename)) output = "" output += subprocess.run(["fc-cache", "-f", "-v"], capture_output=True, text=True).stdout From 39066d43aa7132f2d04d4504ed200283fd7653a1 Mon Sep 17 00:00:00 2001 From: Quinten Steenhuis Date: Thu, 12 Oct 2023 14:13:47 -0400 Subject: [PATCH 20/27] Formatting and correct import --- docassemble/ALDashboard/aldashboard.py | 47 ++++++++++++++++++++------ 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/docassemble/ALDashboard/aldashboard.py b/docassemble/ALDashboard/aldashboard.py index 21f6e9f..0efffb2 100644 --- a/docassemble/ALDashboard/aldashboard.py +++ b/docassemble/ALDashboard/aldashboard.py @@ -23,10 +23,20 @@ ) from docassemble.base.config import daconfig from docassemble.webapp.backend import cloud -from docassemble.base.util import log, DAFile, DAObject, DAList, word, DAFileList, get_config +from docassemble.base.util import ( + log, + DAFile, + DAObject, + DAList, + word, + DAFileList, + get_config, + space_to_underscore, +) from ruamel.yaml import YAML from ruamel.yaml.compat import StringIO import re +import werkzeug db = init_sqlalchemy() @@ -144,7 +154,9 @@ def get_users_and_name() -> List[Tuple[int, str, str, str]]: return users -def speedy_get_sessions(user_id: Optional[int] = None, filename: Optional[str] = None) -> List[Tuple]: +def speedy_get_sessions( + user_id: Optional[int] = None, filename: Optional[str] = None +) -> List[Tuple]: """ Return a lsit of the most recent 500 sessions, optionally tied to a specific user ID. @@ -185,7 +197,7 @@ def speedy_get_sessions(user_id: Optional[int] = None, filename: Optional[str] = user_id = None with db.connect() as con: - rs = con.execute(get_sessions_query, {"user_id":user_id, "filename":filename}) + rs = con.execute(get_sessions_query, {"user_id": user_id, "filename": filename}) sessions = [] for session in rs: sessions.append(session) @@ -253,26 +265,41 @@ def install_fonts(the_font_files: DAFileList): # create the /var/www/.fonts directory if it doesn't exist if not os.path.exists("/var/www/.fonts"): os.makedirs("/var/www/.fonts") - + # save the DAFile to /var/www/.fonts for f in the_font_files: - shutil.copyfile(f.path(), "/var/www/.fonts/" + werkzeug.secure_filename(f.filename)) + shutil.copyfile( + f.path(), "/var/www/.fonts/" + werkzeug.utils.secure_filename(f.filename) + ) output = "" - output += subprocess.run(["fc-cache", "-f", "-v"], capture_output=True, text=True).stdout - output += subprocess.run(["supervisorctl", "restart", "uwsgi"], capture_output=True, text=True).stdout - output += subprocess.run(["supervisorctl", "start", "reset"], capture_output=True, text=True).stdout + output += subprocess.run( + ["fc-cache", "-f", "-v"], capture_output=True, text=True + ).stdout + output += subprocess.run( + ["supervisorctl", "restart", "uwsgi"], capture_output=True, text=True + ).stdout + output += subprocess.run( + ["supervisorctl", "start", "reset"], capture_output=True, text=True + ).stdout if get_config("enable unoconv"): - output += subprocess.run(["supervisorctl", "-s", "http://localhost:9001", "restart", "unoconv"], capture_output=True, text=True).stdout + output += subprocess.run( + ["supervisorctl", "-s", "http://localhost:9001", "restart", "unoconv"], + capture_output=True, + text=True, + ).stdout return output + def list_installed_fonts(): """ List the fonts installed on the server. """ fc_list = subprocess.run(["fc-list"], stdout=subprocess.PIPE) - output = subprocess.run(["sort"], stdin=fc_list.stdout, capture_output=True, text=True).stdout + output = subprocess.run( + ["sort"], stdin=fc_list.stdout, capture_output=True, text=True + ).stdout fc_list.stdout.close() return output From 893bb6e85072615e1c7fbe43e9ca598e132fa4bc Mon Sep 17 00:00:00 2001 From: plocket <52798256+plocket@users.noreply.github.com> Date: Thu, 12 Oct 2023 14:24:22 -0400 Subject: [PATCH 21/27] Fix a couple typos and tweak Sorry, I liked Bryce's fixes. I just meant to fix a typo or two I saw and got carried away enhancing what he'd already done. Not married to the changes, so you can merge or close as you wish. --- README.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index f567f3d..8470bb1 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![PyPI version](https://badge.fury.io/py/docassemble.ALDashboard.svg)](https://badge.fury.io/py/docassemble.ALDashboard) -A single tool and interview to centralizes some tedious Docassemble admin configuration tasks +A single tool and interview to centralize some tedious Docassemble admin configuration tasks. ![A screenshot of the ALDashboard menu with choices: "Admin only - manage users", "Admin only - stats", "Install assembly line", "Verify API Keys", "Install packages", "update packages", "Package scanner", "View Answer files", "generate review screen draft", "validate docx template", "validation translation files", "prepare translation files", "validate an attachment fields block", "PDF tools", and "Compile Bootstrap theme"](https://github.com/SuffolkLITLab/docassemble-ALDashboard/assets/6252212/29539eec-3891-476b-b248-dd3db986d899) @@ -15,11 +15,11 @@ A single tool and interview to centralizes some tedious Docassemble admin config 1. Gather files from a user who left the organization/unknown username and password. 1. Review screen generator 1. validate DOCX Jinja2 templates -1. Generating a [custom bootstrap theme](https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/customization/overview#creating-a-custom-theme-from-source-instead-of-with-a-theme-generator) for your interviews. +1. Generate a [custom bootstrap theme](https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/customization/overview#creating-a-custom-theme-from-source-instead-of-with-a-theme-generator) for your interviews. Ideas: 1. Add a link to the dispatch directive for an existing file in an existing package. -1. Generating translation files [TBD]. +1. Generate translation files [TBD]. To use, you must create a docassemble API key and add it to your configuration, like this: @@ -33,26 +33,26 @@ configuration, like this: ### Manage users -![A screenshot that says "Manage users" with the fields User, What do you want want to do? Reset password, Change user permissions, New Password, Verify new Password](https://user-images.githubusercontent.com/7645641/123702231-e069ec00-d830-11eb-94dc-5ec0abb86bc9.png) +![A screenshot that says "Manage users" with the fields "User", "What do you want want to do? Reset password or Change user permissions", "New Password", and "Verify new Password"](https://user-images.githubusercontent.com/7645641/123702231-e069ec00-d830-11eb-94dc-5ec0abb86bc9.png) ### Bulk install packages from GitHub -![A screenshot that says "What packages do you want to install? Github URL, YAML filename, Shor name or alias (no spaces)"](https://user-images.githubusercontent.com/7645641/123702290-efe93500-d830-11eb-9fdf-a5935ff4078e.png) +![A screenshot that says "What packages do you want to install?" The fields are for "Github URL", "YAML filename", and "Short name or alias (no spaces)"](https://user-images.githubusercontent.com/7645641/123702290-efe93500-d830-11eb-9fdf-a5935ff4078e.png) ### Bulk update packages -![A screenshot that says "What packages do you want to update? docassemble.209aPlaintiffMotionToModify, docassemble.ALAffidavitOfIndigency" and more](https://user-images.githubusercontent.com/7645641/123702362-068f8c00-d831-11eb-9ce4-df7a67ffcfeb.png) +![A screenshot that says "What packages do you want to update?" followed by a list of packages. For example, "docassemble.209aPlaintiffMotionToModify", "docassemble.ALAffidavitOfIndigency", and more.](https://user-images.githubusercontent.com/7645641/123702362-068f8c00-d831-11eb-9ce4-df7a67ffcfeb.png) ### View / search sessions by user and interview name -![A screenshot that says "What interview do you want to view sessions for? File name, User (leave blank to view all sessions)"](https://user-images.githubusercontent.com/7645641/123702422-1d35e300-d831-11eb-84d5-5e7385deb901.png) +![A screenshot that says "What interview do you want to view sessions for?" The fields are "File name" and "User (leave blank to view all sessions)"](https://user-images.githubusercontent.com/7645641/123702422-1d35e300-d831-11eb-84d5-5e7385deb901.png) -![A screenshot that says "Recently generated sessions for docassemble.MA209AProtectiveOrder:data/questions/209a_package.yml" with 5 sessions below](https://user-images.githubusercontent.com/7645641/123702464-2cb52c00-d831-11eb-80fc-f2291e824eae.png) +![A screenshot that says "Recently generated sessions for docassemble.MA209AProtectiveOrder:data/questions/209a_package.yml" with 5 sessions below.](https://user-images.githubusercontent.com/7645641/123702464-2cb52c00-d831-11eb-80fc-f2291e824eae.png) ### View interview stats captured with `store_variables_snapshot()` -![A screenshot that says "Stats for Eviction Moratorium: 9 Total submissions: 9 Group by: zip | state | modtime, Excel Download" followed by a map](https://user-images.githubusercontent.com/7645641/123702623-5e2df780-d831-11eb-8937-6625df74ab22.png) +![A screenshot with the title "Stats for Eviction Moratorium: 9". Below is the text "Total submissions: 9", "Group by: zip | state | modtime", and "Excel Download" followed by a map that can be filtered by state or by date.](https://user-images.githubusercontent.com/7645641/123702623-5e2df780-d831-11eb-8937-6625df74ab22.png) ### Generate a bootstrap theme -![A screenshot that says "Your file is compiled! You can view and copy your file, or download it directly by right clicking the link to save it as a CSS file". Below are examples of Bootstrap components like buttons and nav bars.](https://github.com/SuffolkLITLab/docassemble-ALDashboard/assets/6252212/079e428d-4cae-4f75-8b1b-227c28f32a44) +![A screenshot with the title "Your file is compiled!", below is the text "You can view and copy your file, or download it directly by right clicking the link to save it as a CSS file". Below that are examples of Bootstrap components like buttons and nav bars.](https://github.com/SuffolkLITLab/docassemble-ALDashboard/assets/6252212/079e428d-4cae-4f75-8b1b-227c28f32a44) From 1e7b552cf429a9fdbc1ca9a14326ea1f2c98a5fa Mon Sep 17 00:00:00 2001 From: Mia Bonardi <65858862+miabonardi@users.noreply.github.com> Date: Tue, 17 Oct 2023 15:51:25 -0400 Subject: [PATCH 22/27] Add instructions for how to add Dashboard to drop down to README.md Not sure if this is desired here or the right place for it, but it's what I was looking for in this location. Perhaps also an explanation for how to run the Dashboard without adding it to the menu / how to format the link could be helpful here. --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index 8470bb1..52a508f 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,15 @@ configuration, like this: `install packages api key: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` +If you want the ALDashboard to be a dropdown option for admins and developers, add the following to the configuration before your `install packages api key`: + + administrative interviews: + - interview: docassemble.ALDashboard:data/questions/menu.yml + title: Dashboard + required privileges: + - admin + - developer + ## Some screenshots ### Main page From f675ed7c98f4d02c602e3290efa526072073e521 Mon Sep 17 00:00:00 2001 From: Bryce Willey Date: Tue, 17 Oct 2023 17:24:22 -0400 Subject: [PATCH 23/27] Show a preview of the PDF labels To confirm that you renamed the correct fields, which can be difficult for longer PDFs. --- .../data/questions/pdf_wrangling.yml | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/docassemble/ALDashboard/data/questions/pdf_wrangling.yml b/docassemble/ALDashboard/data/questions/pdf_wrangling.yml index 8592532..23a922c 100644 --- a/docassemble/ALDashboard/data/questions/pdf_wrangling.yml +++ b/docassemble/ALDashboard/data/questions/pdf_wrangling.yml @@ -109,6 +109,10 @@ question: | Here is the new PDF subquestion: | [:file-pdf: ${ new_pdf.filename }](${ new_pdf.url_for() }) +right: | + #### Preview of the new PDF labels + + --- code: | rename_fields = {} @@ -138,7 +142,19 @@ attachment: [ {field[0]: "Yes" if field[4] == "/Btn" else field[0]} for field in source_pdf[0].get_pdf_fields() - ] + ] +--- +attachment: + variable name: new_field_preview_pdf + editable: False + pdf template file: + code: | + new_pdf + code: | + [ + {field[0]: "Yes" if field[4] == "/Btn" else field[0]} + for field in new_pdf.get_pdf_fields() + ] --- code: | new_pdf.initialize(filename=base_name) From 8f61f780d372dcbaaeed9694fd3c277a8e329b7e Mon Sep 17 00:00:00 2001 From: Bryce Willey Date: Tue, 17 Oct 2023 17:26:49 -0400 Subject: [PATCH 24/27] Indicate that the preview will change on the next screen --- docassemble/ALDashboard/data/questions/pdf_wrangling.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docassemble/ALDashboard/data/questions/pdf_wrangling.yml b/docassemble/ALDashboard/data/questions/pdf_wrangling.yml index 23a922c..56d0a3f 100644 --- a/docassemble/ALDashboard/data/questions/pdf_wrangling.yml +++ b/docassemble/ALDashboard/data/questions/pdf_wrangling.yml @@ -128,7 +128,7 @@ right: | - _note_: This preview will not update with live changes. + _note_: This preview will update on the next screen. continue button field: display_rename_field_choices --- From 828cc496f33fc260434f1d979a07527b6f2c13a7 Mon Sep 17 00:00:00 2001 From: Bryce Willey Date: Wed, 18 Oct 2023 17:01:09 -0400 Subject: [PATCH 25/27] Review gen improvements (#113) * Use display template to show the generated review Mainly for the copy button. Also adds some custom CSS to keep the code font, and let people resize it. * Don't re-order block attrs id and question should be at the top, etc. * Skip ALDocument/Bundle, and DAStaticFile objects Shouldn't be a part of the review screen. * Use on review screen if multiline Q Otherwise, will get a syntax error (mako doesn't `**` on the same lines as it's keywords). * Improvements to generated revisit screens * use `continue button field` over `event` * add `add_action()` by default * allow people to edit things added to their tables * Display currency on review screens A lot nicer of a display for currency. * Option to point all sections at review screen Something I do by default in my interviews; it lets people go to the review screen from any section, and see all of their answers, instead of splitting it up by section. --- .../questions/review_screen_generator.yml | 77 +++++++++++++++---- 1 file changed, 60 insertions(+), 17 deletions(-) diff --git a/docassemble/ALDashboard/data/questions/review_screen_generator.yml b/docassemble/ALDashboard/data/questions/review_screen_generator.yml index d5d574d..0b6612e 100644 --- a/docassemble/ALDashboard/data/questions/review_screen_generator.yml +++ b/docassemble/ALDashboard/data/questions/review_screen_generator.yml @@ -1,5 +1,6 @@ --- include: + - docassemble.ALToolbox:display_template.yml - nav.yml --- metadata: @@ -25,6 +26,9 @@ fields: - Create revisit screens for lists: build_revisit_blocks datatype: yesno default: True + - Point section blocks to review screen: point_sections_to_review + datatype: yesno + default: True --- code: | import ruamel.yaml as yaml @@ -42,6 +46,7 @@ code: | attributes_list = {} questions_temp = [] generic_question_blocks = [] + sections_temp = [] for doc in yaml_parsed: if doc: question = {"question": doc.get('question',"").strip() } @@ -115,31 +120,47 @@ code: | fields_temp.append({doc.get('question',""): doc.get('field'), 'datatype': 'radio' }) elif 'objects' in doc: objects_temp.extend(doc["objects"]) + elif 'sections' in doc: + sections_temp.extend([next(iter(sec.keys()), [""]) for sec in doc["sections"]]) question["fields"] = fields_temp if question["fields"]: questions_temp.append(question) objects = objects_temp questions = questions_temp + section_events = sections_temp del objects_temp del questions_temp + del sections_temp --- code: | + REVIEW_EVENT_NAME = "review_form" review_fields_temp = [] revisit_screens = [] tables = [] + sections = [] + if point_sections_to_review: + for sec in section_events: + sections.append({ + "event": sec, + "code": REVIEW_EVENT_NAME, + }) if build_revisit_blocks: for obj in objects: obj_name = next(iter(obj.keys()), [""]) + obj_type = next(iter(obj.values()), [""]) + is_skippable_type = any(map(lambda val: obj_type.startswith(val), ["ALDocument.", "ALDocumentBundle.", "DAStaticFile."])) + if is_skippable_type: + continue review = {} review["Edit"] = f"{ obj_name }.revisit" review["button"] = f"**{ obj_name.replace('_', ' ').title() }**\n\n% for item in { obj_name }:\n- ${{ item }}\n% endfor" review_fields_temp.append(review) revisit_screen = { "id": f"revisit { obj_name }", - "event": f"{ obj_name }.revisit", + "continue button field": f"{ obj_name }.revisit", "question": f"Edit your answers about { obj_name.replace('_', ' ').title() }", } - revisit_screen["subquestion"] = f"${{ {obj_name}.table }}" + revisit_screen["subquestion"] = f"${{ {obj_name}.table }}\n\n${{ {obj_name}.add_action() }}" revisit_screens.append(revisit_screen) if obj_name in attributes_list: tables.append({ @@ -148,7 +169,11 @@ code: | "columns": [ {next(iter(attribute.values())).split('.')[-1] if next(iter(attribute.keys())) == "no label" else next(iter(attribute.keys())): f"row_item.{ next(iter(attribute.values())).split('.')[-1] } if hasattr(row_item, '{next(iter(attribute.values())).split('.')[-1]}') else ''"} for attribute in attributes_list[obj_name] - ] + ], + "edit": [ + next(iter(attribute.values())).split('.')[-1] + for attribute in attributes_list[obj_name] + ], }) for question in questions: if len(question["fields"]): @@ -156,25 +181,29 @@ code: | first_label_pair = next((pair for pair in fields[0].items() if pair[0] not in not_labels), ("","")) review = {} review['Edit'] = first_label_pair[1] # This will be the trigger variable in edit button - review["button"] = f"**{ question['question'] }**\n\n" + # Bolding with `**` over multiple lines doesn't work; use instead + if '\n' in question['question']: + review["button"] = f"{question['question'] }\n\n" + else: + review["button"] = f"**{ question['question'] }**\n\n" for field in fields: label_pair = next((pair for pair in field.items() if pair[0] not in not_labels), None) if label_pair: - if label_pair[0] == "no label": - if field.get('datatype') in ['yesno','yesnoradio','yesnowide']: - review['button'] += f"${{ word(yesno({ label_pair[1] })) }}\n\n" - else: - review['button'] += f"${{ showifdef('{label_pair[1]}') }}\n\n" - else: - if field.get('datatype') in ['yesno','yesnoradio','yesnowide']: - review['button'] += f"{label_pair[0] or ''}: ${{ word(yesno({ label_pair[1] })) }}\n\n" - else: - review['button'] += f"{label_pair[0] or ''}: ${{ showifdef('{label_pair[1]}') }}\n\n" + if label_pair[0] != "no label": + review['button'] += f"{label_pair[0] or ''}: " + + if field.get('datatype') in ['yesno','yesnoradio','yesnowide']: + review['button'] += f"${{ word(yesno({ label_pair[1] })) }}\n\n" + elif field.get('datatype') == 'currency': + review['button'] += f"${{ currency(showifdef('{label_pair[1]}')) }}\n\n" + else: + review['button'] += f"${{ showifdef('{label_pair[1]}') }}\n\n" review["button"] = review["button"].strip() + "\n" review_fields_temp.append(review) - review_yaml = [ + review_yaml = sections + [ { "id": "review screen", + "event": REVIEW_EVENT_NAME, "question": "Review your answers", 'review': review_fields_temp }, @@ -187,10 +216,24 @@ code: | # import pyyaml # from yaml import dump from pyaml import dump_all - review_yaml_dumped = dump_all(review_yaml, string_val_style='|') + review_yaml_dumped = dump_all(review_yaml, string_val_style='|', sort_keys=False) +--- +template: review_yaml_template +content: | + ${ review_yaml_dumped } --- event: results question: | subquestion: | - ${ indent(review_yaml_dumped,4) } + ${ display_template(review_yaml_template, classname="make_code", copy=True) } +css: | + \ No newline at end of file From 09e3d932116e00c0fa95145b75454f916ac8bf33 Mon Sep 17 00:00:00 2001 From: Bryce Willey Date: Mon, 23 Oct 2023 12:39:47 -0400 Subject: [PATCH 26/27] Bump to v0.21.0 --- docassemble/ALDashboard/__init__.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docassemble/ALDashboard/__init__.py b/docassemble/ALDashboard/__init__.py index abadaef..e453371 100644 --- a/docassemble/ALDashboard/__init__.py +++ b/docassemble/ALDashboard/__init__.py @@ -1 +1 @@ -__version__ = '0.20.1' +__version__ = '0.21.0' diff --git a/setup.py b/setup.py index e189685..795a62d 100644 --- a/setup.py +++ b/setup.py @@ -44,7 +44,7 @@ def find_package_data(where='.', package='', exclude=standard_exclude, exclude_d return out setup(name='docassemble.ALDashboard', - version='0.20.1', + version='0.21.0', description=('Dashboard for some admin tasks'), long_description='# ALDashboard: a docassemble Admin and Configuration Tool\r\n\r\n[![PyPI version](https://badge.fury.io/py/docassemble.ALDashboard.svg)](https://badge.fury.io/py/docassemble.ALDashboard)\r\n\r\nA single tool and interview to centralizes some tedious Docassemble admin configuration tasks\r\n\r\n![A screenshot of the ALDashboard menu with choices: "Admin only - manage users", "Admin only - stats", "Install assembly line", "Verify API Keys", "Install packages", "update packages", "Package scanner", "View Answer files", "generate review screen draft", "validate docx template", "validation translation files", "prepare translation files", "validate an attachment fields block", "PDF tools", and "Compile Bootstrap theme"](https://github.com/SuffolkLITLab/docassemble-ALDashboard/assets/6252212/29539eec-3891-476b-b248-dd3db986d899)\r\n\r\n1. Install the Document Assembly Line packages (support files for [Court Forms Online](https://courtformsonline.org))\r\n1. Searchable user management - reset passwords and change privileges.\r\n1. Installing or updating several packages at once.\r\n1. Listing and viewing the contents of an (unencrypted) interview to facilitate debugging errors on production servers.\r\n1. View analytics/stats captured with `store_variable_snapshot`.\r\n1. List the files inside a particular package installed on the server.\r\n1. Gather files from a user who left the organization/unknown username and password.\r\n1. Review screen generator\r\n1. validate DOCX Jinja2 templates\r\n1. Generating a [custom bootstrap theme](https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/customization/overview#creating-a-custom-theme-from-source-instead-of-with-a-theme-generator) for your interviews.\r\n\r\nIdeas:\r\n1. Add a link to the dispatch directive for an existing file in an existing package.\r\n1. Generating translation files [TBD].\r\n\r\nTo use, you must create a docassemble API key and add it to your\r\nconfiguration, like this:\r\n\r\n`install packages api key: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx`\r\n\r\n## Some screenshots\r\n\r\n### Main page\r\n![A screenshot of the ALDashboard menu with choices: "Admin only - manage users", "Admin only - stats", "Install assembly line", "Verify API Keys", "Install packages", "update packages", "Package scanner", "View Answer files", "generate review screen draft", "validate docx template", "validation translation files", "prepare translation files", "validate an attachment fields block", "PDF tools", and "Compile Bootstrap theme"](https://github.com/SuffolkLITLab/docassemble-ALDashboard/assets/6252212/29539eec-3891-476b-b248-dd3db986d899)\r\n\r\n### Manage users\r\n\r\n![A screenshot that says "Manage users" with the fields User, What do you want want to do? Reset password, Change user permissions, New Password, Verify new Password](https://user-images.githubusercontent.com/7645641/123702231-e069ec00-d830-11eb-94dc-5ec0abb86bc9.png)\r\n\r\n### Bulk install packages from GitHub\r\n\r\n![A screenshot that says "What packages do you want to install? Github URL, YAML filename, Shor name or alias (no spaces)"](https://user-images.githubusercontent.com/7645641/123702290-efe93500-d830-11eb-9fdf-a5935ff4078e.png)\r\n\r\n### Bulk update packages\r\n\r\n![A screenshot that says "What packages do you want to update? docassemble.209aPlaintiffMotionToModify, docassemble.ALAffidavitOfIndigency" and more](https://user-images.githubusercontent.com/7645641/123702362-068f8c00-d831-11eb-9ce4-df7a67ffcfeb.png)\r\n\r\n### View / search sessions by user and interview name\r\n\r\n![A screenshot that says "What interview do you want to view sessions for? File name, User (leave blank to view all sessions)"](https://user-images.githubusercontent.com/7645641/123702422-1d35e300-d831-11eb-84d5-5e7385deb901.png)\r\n\r\n![A screenshot that says "Recently generated sessions for docassemble.MA209AProtectiveOrder:data/questions/209a_package.yml" with 5 sessions below](https://user-images.githubusercontent.com/7645641/123702464-2cb52c00-d831-11eb-80fc-f2291e824eae.png)\r\n\r\n### View interview stats captured with `store_variables_snapshot()`\r\n\r\n![A screenshot that says "Stats for Eviction Moratorium: 9 Total submissions: 9 Group by: zip | state | modtime, Excel Download" followed by a map](https://user-images.githubusercontent.com/7645641/123702623-5e2df780-d831-11eb-8937-6625df74ab22.png)\r\n\r\n### Generate a bootstrap theme\r\n\r\n![A screenshot that says "Your file is compiled! You can view and copy your file, or download it directly by right clicking the link to save it as a CSS file". Below are examples of Bootstrap components like buttons and nav bars.](https://github.com/SuffolkLITLab/docassemble-ALDashboard/assets/6252212/079e428d-4cae-4f75-8b1b-227c28f32a44)\r\n', long_description_content_type='text/markdown', From 0ec1b5714aefc5ea4dfa93af470a5a7cd7bd1ba7 Mon Sep 17 00:00:00 2001 From: Bryce Willey Date: Mon, 30 Oct 2023 16:44:15 -0400 Subject: [PATCH 27/27] Added link to Github Feedback interview `browse_feedback_sessions.yml` is now a button on the menu. Fix #114. --- docassemble/ALDashboard/data/questions/menu.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docassemble/ALDashboard/data/questions/menu.yml b/docassemble/ALDashboard/data/questions/menu.yml index 35e4b49..9725e20 100644 --- a/docassemble/ALDashboard/data/questions/menu.yml +++ b/docassemble/ALDashboard/data/questions/menu.yml @@ -58,4 +58,6 @@ buttons: - Compile Bootstrap theme: ${ interview_url(i=user_info().package + ":compile_bootstrap.yml", reset=1) } image: bootstrap - Install fonts: ${ interview_url(i=user_info().package + ":install_fonts.yml", reset=1)} - image: font \ No newline at end of file + image: font + - Feedback on server: ${ interview_url(i="docassemble.GithubFeedbackForm:browse_feedback_sessions.yml", reset=1)} + image: comments \ No newline at end of file