diff --git a/doc/source/refstack.rst b/doc/source/refstack.rst index 62282ea2..2451ce47 100644 --- a/doc/source/refstack.rst +++ b/doc/source/refstack.rst @@ -204,6 +204,17 @@ performed to upgrade the database to the latest revision: Now it should be some revision number other than `None`. +(Optional) Generate About Page Content +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The RefStack About page is populated with HTML templates generated from +our RST documentation files. If you want this information displayed, then +run the following command from the root of the project. + +``./tools/convert-docs.py -o ./refstack-ui/app/components/about/templates ./doc/source/*.rst`` + +Ignore any unknown directive errors. + Start RefStack ^^^^^^^^^^^^^^ diff --git a/refstack-ui/app/app.js b/refstack-ui/app/app.js index 6efa35ea..4d03f18a 100644 --- a/refstack-ui/app/app.js +++ b/refstack-ui/app/app.js @@ -41,7 +41,9 @@ }). state('about', { url: '/about', - templateUrl: '/components/about/about.html' + templateUrl: '/components/about/about.html', + controller: 'AboutController as ctrl' + }). state('guidelines', { url: '/guidelines', diff --git a/refstack-ui/app/assets/css/style.css b/refstack-ui/app/assets/css/style.css index f3480929..eea5de35 100644 --- a/refstack-ui/app/assets/css/style.css +++ b/refstack-ui/app/assets/css/style.css @@ -226,3 +226,26 @@ a.glyphicon { .modal-body .row { margin-bottom: 10px; } + +.about-sidebar { + width: 20%; + float: left; + padding-right: 2px; + padding-top: 25px; +} + +.about-content { + width: 80%; + float: left; + padding-left: 5%; + +} + +.about-option { + padding: 5px 5px 5px 10px; +} + +.about-active { + background: #f2f2f2; + border-left: 2px solid orange; +} \ No newline at end of file diff --git a/refstack-ui/app/components/about/about.html b/refstack-ui/app/components/about/about.html index d6ab9912..348318cd 100644 --- a/refstack-ui/app/components/about/about.html +++ b/refstack-ui/app/components/about/about.html @@ -1,31 +1,13 @@ -

RefStack Documentation

- -

RefStack is a source of tools for interoperability testing of OpenStack clouds.

-

To learn more about RefStack, visit the links below.

- -
    -
  1. - - About RefStack -
  2. -
  3. - - How to upload test results to RefStack -
  4. -
  5. - - Vendor and product management -
  6. -
  7. - - Test result management -
  8. -
+
+
+
+
+ {{data.title}} +
+
+
+
+
+
diff --git a/refstack-ui/app/components/about/aboutController.js b/refstack-ui/app/components/about/aboutController.js new file mode 100644 index 00000000..868762b9 --- /dev/null +++ b/refstack-ui/app/components/about/aboutController.js @@ -0,0 +1,85 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +(function () { + 'use strict'; + + angular + .module('refstackApp') + .controller('AboutController', AboutController); + + AboutController.$inject = ['$location']; + + /** + * RefStack About Controller + * This controller handles the about page and the multiple templates + * associated to the page. + */ + function AboutController($location) { + var ctrl = this; + + ctrl.selectOption = selectOption; + ctrl.getHash = getHash; + + ctrl.options = { + 'about' : { + 'title': 'About RefStack', + 'template': 'components/about/templates/README.html', + 'order': 1 + }, + 'uploading-your-results': { + 'title': 'Uploading Your Results', + 'template': 'components/about/templates/' + + 'uploading_private_results.html', + 'order': 2 + }, + 'managing-results': { + 'title': 'Managing Results', + 'template': 'components/about/templates/' + + 'test_result_management.html', + 'order': 3 + }, + 'vendors-and-products': { + 'title': 'Vendors and Products', + 'template': 'components/about/templates/vendor_product.html', + 'order': 4 + } + }; + + /** + * Given an option key, mark it as selected and set the corresponding + * template and URL hash. + */ + function selectOption(key) { + ctrl.selected = key; + ctrl.template = ctrl.options[key].template; + $location.hash(key); + } + + /** + * Get the hash in the URL and select it if possible. + */ + function getHash() { + var hash = $location.hash(); + if (hash && hash in ctrl.options) { + ctrl.selectOption(hash); + } + else { + ctrl.selectOption('about'); + } + } + + ctrl.getHash(); + } +})(); diff --git a/refstack-ui/app/index.html b/refstack-ui/app/index.html index 5a896c2e..0df25635 100644 --- a/refstack-ui/app/index.html +++ b/refstack-ui/app/index.html @@ -40,6 +40,7 @@ + diff --git a/refstack-ui/tests/unit/ControllerSpec.js b/refstack-ui/tests/unit/ControllerSpec.js index 77f77889..b648493a 100644 --- a/refstack-ui/tests/unit/ControllerSpec.js +++ b/refstack-ui/tests/unit/ControllerSpec.js @@ -58,6 +58,49 @@ describe('Refstack controllers', function () { }); }); + describe('AboutController', function () { + var $location, ctrl; + + beforeEach(inject(function ($controller, _$location_) { + $location = _$location_; + ctrl = $controller('AboutController', {}); + ctrl.options = { + 'about' : { + 'title': 'About RefStack', + 'template': 'about-template' + }, + 'option1' : { + 'title': 'Option One', + 'template': 'template-1' + } + }; + })); + + it('should have a function to select an option', + function () { + ctrl.selectOption('option1'); + expect(ctrl.selected).toBe('option1'); + expect(ctrl.template).toBe('template-1'); + expect($location.hash()).toBe('option1'); + }); + + it('should have a function to get the URL hash and select it', + function () { + // Test existing option. + $location.url('/about#option1'); + ctrl.getHash(); + expect(ctrl.selected).toBe('option1'); + expect(ctrl.template).toBe('template-1'); + + // Test nonexistent option. + $location.url('/about#foobar'); + ctrl.getHash(); + expect(ctrl.selected).toBe('about'); + expect(ctrl.template).toBe('about-template'); + }); + + }); + describe('GuidelinesController', function () { var ctrl; diff --git a/requirements.txt b/requirements.txt index 79b7c745..2acf067b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,9 @@ SQLAlchemy>=0.8.3 alembic==0.5.0 beaker==1.6.5.post1 +beautifulsoup4 cryptography>=1.0,!=1.3.0 # BSD/Apache-2.0 +docutils>=0.11 oslo.config>=1.6.0 # Apache-2.0 oslo.db>=1.4.1 # Apache-2.0 oslo.log>=3.11.0 diff --git a/tools/convert-docs.py b/tools/convert-docs.py new file mode 100755 index 00000000..3678be66 --- /dev/null +++ b/tools/convert-docs.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python +# Copyright (c) 2017 IBM, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +Convert RST files to basic HTML. The primary use case is to provide a way +to display RefStack documentation on the RefStack website. +""" + +import argparse +import glob +import os + +from bs4 import BeautifulSoup +from docutils.core import publish_file + + +def extract_body(html): + """Extract the content of the body tags of an HTML string.""" + soup = BeautifulSoup(html, "html.parser") + return ''.join(['%s' % str(a) for a in soup.body.contents]) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser( + description='Convert RST files to basic HTML template files.' + ) + parser.add_argument('files', + metavar='file', + nargs='+', + help='RST file(s) to be converted to HTML templates.') + parser.add_argument('-o', '--output_dir', + required=False, + help='The directory where template files should be ' + 'output to. Defaults to the current directory.') + args = parser.parse_args() + + if args.output_dir: + output_dir = args.output_dir + # If the output directory doesn't exist, create it. + if not os.path.exists(output_dir): + try: + os.makedirs(output_dir) + except OSError: + if not os.path.isdir(output_dir): + raise + else: + output_dir = os.getcwd() + + for path in args.files: + for file in glob.glob(path): + base_file = os.path.splitext(os.path.basename(file))[0] + + # Calling publish_file will also print to stdout. Destination path + # is set to /dev/null to suppress this. + html = publish_file(source_path=file, + destination_path='/dev/null', + writer_name='html',) + body = extract_body(html) + + output_file = os.path.join(output_dir, base_file + ".html") + with open(output_file, "w") as template_file: + template_file.write(body)