diff --git a/app/assets/javascripts/about.js b/app/assets/javascripts/about.js deleted file mode 100644 index 5857fae75718..000000000000 --- a/app/assets/javascripts/about.js +++ /dev/null @@ -1,21 +0,0 @@ -$(function() { - "use strict"; - $(".compute-status").each(function(index, item) { - var item = $(item); - var url = item.data('url'); - $.ajax({ - type: 'post', - url: url, - success: function(response) { - item.text(__(response.status)); - item.attr('title',response.message); - if(response.status === "OK"){ - item.addClass('label label-success') - }else{ - item.addClass('label label-danger') - } - item.tooltip({html: true}); - } - }); - }); -}); diff --git a/app/helpers/about_helper.rb b/app/helpers/about_helper.rb new file mode 100644 index 000000000000..e4179a48e225 --- /dev/null +++ b/app/helpers/about_helper.rb @@ -0,0 +1,35 @@ +module AboutHelper + def plugins + @plugins.map do |plugin| + {:name => {name: plugin.name, url: plugin.url}, :description => plugin.description, + :author => plugin.author, :version => plugin.version } + end + end + + def proxies + @smart_proxies.map do |proxy| + {:id => {:name => proxy.name, :id => proxy.id}, + :features => proxy.features.map(&:name).to_sentence} + end + end + + def providers + nil unless SETTINGS[:unattended] + @providers.map do |provider| + {:provider => provider[:friendly_name], + :status => provider[:status] == :installed} + end + end + + def compute_resources + nil unless SETTINGS[:unattended] + @compute_resources.map do |compute| + {:id => {:name => compute.name, :id => compute.id}, + :type => compute.provider_friendly_name} + end + end + + def about_data + {:compute => compute_resources, :proxy => proxies, :plugin => plugins, :provider => providers } + end +end diff --git a/app/views/about/index.html.erb b/app/views/about/index.html.erb index 5c37fafe2009..3d94a6ba39fa 100644 --- a/app/views/about/index.html.erb +++ b/app/views/about/index.html.erb @@ -1,122 +1,12 @@ <% title _("About") %> -<%= javascript 'proxy_status', 'charts', 'about' %>

<%=_("System Status")%>

- -
-
- <% if @smart_proxies.empty? %> -

<%= _("No smart proxies to show") %>

- <% else %> - - - - - - - - - - - <% @smart_proxies.each do |proxy| %> - - - - - - - <% end %> - -
<%= _("Name") %><%= _("Features") %><%= _("Status") %><%= _("Version") %>
<%= link_to_if_authorized proxy.name, hash_for_smart_proxy_path(proxy) %><%=h proxy.features.map(&:name).to_sentence %>
<%= spinner %>
<%= spinner %>
- <% end %> -
- <% if SETTINGS[:unattended] %> -
- - - - - - - - - <% @providers.sort_by { |prov| prov[:friendly_name].downcase }.each do |provider| %> - - - <% if provider[:status] == :installed %> - - <% else %> - - <% end %> - - <% end %> - -
<%= _("Provider") %><%= _("Status") %>
<%= provider[:friendly_name] %>
<%= _('Installed') %>
<%= _('Not Installed') %>
- <%= _('To enable a provider, either install the OS package (e.g. foreman-libvirt) or enable the bundler group for development setup (e.g. ovirt).') %> -
-
- <% if @compute_resources.empty? %> -

<%= _("No compute resource to show") %>

- <% else %> - - - - - - - - - - <% @compute_resources.each do |compute| %> - - - - - - <% end %> - -
<%= _("Name") %><%= _("Type") %><%= _("Status") %>
<%= link_to(compute.name, compute) %><%= compute.provider_friendly_name %>
><%= spinner %>
- <% end %> -
- <% end %> -
- <% if @plugins.empty? %> -

<%= _("No plugins found") %>

- <% else %> - - - - - - - - - - - <% @plugins.each do |plugin| %> - - - - - - - <% end %> - -
<%= _("Name") %><%= _("Description") %><%= _("Author") %><%= _("Version") %>
<%= plugin.url.blank? ? plugin.name : link_to(plugin.name, plugin.url, :rel=>'external')%><%= _(plugin.description) %><%= plugin.author_url.blank? ? plugin.author : link_to(plugin.author, plugin.author_url)%><%= plugin.version %>
- <% end %> -
-
+
+ <%= mount_react_component('About', '#tabs', about_data.compact.to_json) %>
@@ -144,5 +34,4 @@
- - + \ No newline at end of file diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb index 1e6d33732aa5..c6c9b04e5d3b 100644 --- a/config/initializers/assets.rb +++ b/config/initializers/assets.rb @@ -51,7 +51,6 @@ class << self subnets hidden_values proxy_status - about parameter_override) javascript += FastGettext.default_available_locales.map { |loc| "locale/#{loc}/app" } diff --git a/config/routes.rb b/config/routes.rb index 263857403ad1..14ebf92a8994 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -405,7 +405,7 @@ post 'instance_type_selected' post 'cluster_selected' get 'resource_pools' - post 'ping' + get 'ping' put 'associate' put 'refresh_cache' end diff --git a/config/webpack.config.js b/config/webpack.config.js index 554096c45ce0..2dd5c8b1422e 100644 --- a/config/webpack.config.js +++ b/config/webpack.config.js @@ -12,7 +12,6 @@ var pluginUtils = require('../script/plugin_webpack_directories'); var vendorEntry = require('./webpack.vendor'); - module.exports = env => { // must match config.webpack.dev_server.port var devServerPort = 3808; diff --git a/test/integration/about_test.rb b/test/integration/about_test.rb index 2f30a2787306..bbcf77ce1ab9 100644 --- a/test/integration/about_test.rb +++ b/test/integration/about_test.rb @@ -17,27 +17,12 @@ class AboutIntegrationTest < IntegrationTestWithJavascript assert page.has_selector?('h4', :text => "System Status"), "System Status was expected in the

tag, but was not found" assert page.has_selector?('h4', :text => "Support"), "Support was expected in the

tag, but was not found" assert page.has_selector?('h4', :text => "System Information"), "System Information was expected in the

tag, but was not found" - assert page.has_link?("Smart Proxies", :href => "#smart_proxies") - assert page.has_link?("Compute Resources", :href => "#compute_resources") + assert page.has_link?("Smart Proxies", :href => "#") + assert page.has_link?("Compute Resources", :href => "#") assert page.has_link?("Foreman Users", :href => "http://groups.google.com/group/foreman-users") assert page.has_link?("Foreman Developers", :href => "http://groups.google.com/group/foreman-dev") assert page.has_link?("issue tracker", :href => "http://projects.theforeman.org/projects/foreman/issues") assert page.has_link?("Wiki", :href => "http://projects.theforeman.org") assert page.has_link?("Ohad Levy", :href => "mailto:ohadlevy@gmail.com") - assert page.has_content?("Version") - end - - test "about page proxies should have version" do - visit about_index_path - wait_for_ajax - assert page.has_selector?('th', :text => "Version") - assert page.has_selector?('div.proxy-version', :text => '1.13.0') - end - - private - - def wait_for_ajax - super - assert page.has_no_selector?('div.spinner'), 'AJAX spinners still active' end end diff --git a/webpack/assets/javascripts/react_app/common/EmptyStates.js b/webpack/assets/javascripts/react_app/common/EmptyStates.js new file mode 100644 index 000000000000..ee55d8d8f296 --- /dev/null +++ b/webpack/assets/javascripts/react_app/common/EmptyStates.js @@ -0,0 +1,32 @@ +export const computeResource = () => ({ + header: __('Compute Resource'), + description: __('Foreman supports creating and managing hosts on a number of virtualization and cloud services - referred to as “compute resources” - as well as bare metal hosts.'), + // eslint-disable-next-line no-undef + docUrl: `https://www.theforeman.org/manuals/${VERSION}/index.html#5.2ComputeResources`, + action: { + title: __('Create a compute resource'), + url: '/compute_resources/new', + }, +}); + +export const plugin = () => ({ + header: __('Plugin'), + description: __('Plugins are tools to extend and modify the functionality of Foreman. Plugins offer custom functions and features so that each user can tailor their environment to their specific needs.'), + // eslint-disable-next-line no-undef + docUrl: `https://www.theforeman.org/manuals/${VERSION}/index.html#Plugins`, + action: { + title: __('Get a plugin'), + url: 'https://projects.theforeman.org/projects/foreman/wiki/List_of_Plugins', + }, +}); + +export const smartProxy = () => ({ + header: __('Smart Proxy'), + description: __('The Smart Proxy provides an easy way to add or extended existing subsystems, via DHCP, DNS, Puppet, etc.'), + // eslint-disable-next-line no-undef + docUrl: `https://www.theforeman.org/manuals/${VERSION}/index.html#Smart-Proxy`, + action: { + title: __('Create a smart proxy'), + url: '/smart_proxies/new', + }, +}); diff --git a/webpack/assets/javascripts/react_app/common/helpers.js b/webpack/assets/javascripts/react_app/common/helpers.js index f35bf235e241..2edbc63f5c75 100644 --- a/webpack/assets/javascripts/react_app/common/helpers.js +++ b/webpack/assets/javascripts/react_app/common/helpers.js @@ -8,4 +8,7 @@ export default { }); }, noop, + urlBuilder(controller, action, id = undefined) { + return `/${controller}/${id ? `${id}/` : ''}${action}`; + }, }; diff --git a/webpack/assets/javascripts/react_app/components/about/__snapshots__/about.test.js.snap b/webpack/assets/javascripts/react_app/components/about/__snapshots__/about.test.js.snap new file mode 100644 index 000000000000..139540a37984 --- /dev/null +++ b/webpack/assets/javascripts/react_app/components/about/__snapshots__/about.test.js.snap @@ -0,0 +1,95 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`about page should render 1`] = ` + + + + + + +`; diff --git a/webpack/assets/javascripts/react_app/components/about/about.fixtures.js b/webpack/assets/javascripts/react_app/components/about/about.fixtures.js new file mode 100644 index 000000000000..d821392c24df --- /dev/null +++ b/webpack/assets/javascripts/react_app/components/about/about.fixtures.js @@ -0,0 +1,30 @@ +export const data = { + compute: [ + { id: { name: 'Libvirt', id: 1 }, type: 'Libvirt' }, + { id: { name: 'Ovirt', id: 2 }, type: 'Ovirt' }, + ], + proxy: [ + { + id: { name: 'regular', id: 1 }, + features: 'Facts, Logs, DNS, DHCP, and Puppet', + }, + ], + plugin: [ + { + name: 'foreman_discovery', + description: 'MaaS Discovery Plugin engine for Foreman', + author: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ex ea difficultate illae fallaciloquae', + version: '11.0.0', + }, + ], + provider: [ + { provider: 'Libvirt', status: true }, + { provider: 'oVirt', status: true }, + { provider: 'EC2', status: true }, + { provider: 'VMware', status: true }, + { provider: 'OpenStack', status: true }, + { provider: 'Rackspace', status: true }, + { provider: 'Google', status: true }, + ], +}; diff --git a/webpack/assets/javascripts/react_app/components/about/about.test.js b/webpack/assets/javascripts/react_app/components/about/about.test.js new file mode 100644 index 000000000000..f5b411a18284 --- /dev/null +++ b/webpack/assets/javascripts/react_app/components/about/about.test.js @@ -0,0 +1,60 @@ +import React from 'react'; +import { mount, shallow } from 'enzyme'; +import toJson from 'enzyme-to-json'; +import { Provider } from 'react-redux'; +import About from './'; +import { data } from './about.fixtures'; +import store from '../../redux'; +import { mockRequest } from '../../mockRequests'; + +const flushAllPromises = () => new Promise(resolve => setImmediate(resolve)); + +describe('about page', () => { + mockRequest({ + url: '/smart_proxies/1/ping', + response: { + success: true, + message: { + version: '1.16.0-develop', + modules: { + facts: '1.16.0', + dns: '1.16.0', + dhcp: '1.16.0', + puppet: '1.16.0', + logs: '1.16.0', + }, + }, + }, + }); + mockRequest({ + url: '/compute_resources/1/ping', + response: { + status: 'OK', + message: '', + }, + }); + mockRequest({ + url: '/compute_resources/2/ping', + response: { + status: 'Error', + message: 'Failed to open TCP connection', + }, + }); + it('should render', () => { + const wrapper = shallow(); + + expect(toJson(wrapper)).toMatchSnapshot(); + }); + it('should have statuses and version fields', async () => { + const wrapper = mount( + + ); + + await flushAllPromises(); + wrapper.update(); + expect(wrapper.find('#proxy1_version').text()).toBe('1.16.0-develop'); + expect(wrapper.find('#proxy1_status .pficon-ok')).toHaveLength(1); + expect(wrapper.find('#compute_resource1_status .pficon-ok')).toHaveLength(1); + expect(wrapper.find('#compute_resource2_status .pficon-error-circle-o')).toHaveLength(1); + }); +}); diff --git a/webpack/assets/javascripts/react_app/components/about/compute/index.js b/webpack/assets/javascripts/react_app/components/about/compute/index.js new file mode 100644 index 000000000000..0c21692c0d14 --- /dev/null +++ b/webpack/assets/javascripts/react_app/components/about/compute/index.js @@ -0,0 +1,13 @@ +import React from 'react'; +import Table from '../../common/table'; +import { columns } from './schema'; +import { computeResource } from '../../../common/EmptyStates'; + +const AboutComputeTable = props => ( + +); + +export default AboutComputeTable; diff --git a/webpack/assets/javascripts/react_app/components/about/compute/schema.js b/webpack/assets/javascripts/react_app/components/about/compute/schema.js new file mode 100644 index 000000000000..9ac44843ab04 --- /dev/null +++ b/webpack/assets/javascripts/react_app/components/about/compute/schema.js @@ -0,0 +1,57 @@ +import React from 'react'; +import ConnectedStatus from '../../common/status'; +import { headerFormat, cellFormat } from '../../common/table'; +import helpers from '../../../common/helpers'; + +export const columns = [ + { + property: 'id', + header: { + label: 'Name', + formatters: [headerFormat], + }, + cell: { + formatters: [ + cell => ( + + ), + ], + }, + }, + { + property: 'type', + header: { + label: 'Type', + formatters: [headerFormat], + }, + cell: { + formatters: [cellFormat], + }, + }, + { + property: 'id', + header: { + label: 'Status', + formatters: [headerFormat], + }, + cell: { + formatters: [ + cell => ( + + ), + ], + }, + }, +]; diff --git a/webpack/assets/javascripts/react_app/components/about/index.js b/webpack/assets/javascripts/react_app/components/about/index.js new file mode 100644 index 000000000000..d261967246b7 --- /dev/null +++ b/webpack/assets/javascripts/react_app/components/about/index.js @@ -0,0 +1,29 @@ +import React from 'react'; +import TabsWrapper from '../common/tabs'; +import AboutComputeTable from './compute'; +import AboutPluginTable from './plugin'; +import AboutProviderTable from './provider'; +import AboutProxyTable from './proxies'; + +const About = ({ data }) => { + const { + compute, proxy, provider, plugin, + } = data; + const tabs = [ + __('Smart Proxies'), + __('Available Providers'), + __('Compute Resources'), + __('Plugins'), + ]; + + return ( + + + + + + + ); +}; + +export default About; diff --git a/webpack/assets/javascripts/react_app/components/about/plugin/index.js b/webpack/assets/javascripts/react_app/components/about/plugin/index.js new file mode 100644 index 000000000000..0a63c4cfe217 --- /dev/null +++ b/webpack/assets/javascripts/react_app/components/about/plugin/index.js @@ -0,0 +1,14 @@ +import React from 'react'; +import Table from '../../common/table'; +import { columns } from './schema'; +import { plugin } from '../../../common/EmptyStates'; + +const AboutPluginTable = props => ( +
+ + {cell.name} + + + +
+); + +export default AboutPluginTable; diff --git a/webpack/assets/javascripts/react_app/components/about/plugin/schema.js b/webpack/assets/javascripts/react_app/components/about/plugin/schema.js new file mode 100644 index 000000000000..9035eac10c0a --- /dev/null +++ b/webpack/assets/javascripts/react_app/components/about/plugin/schema.js @@ -0,0 +1,53 @@ +import React from 'react'; +import { headerFormat, cellFormat, ellipsisFormat } from '../../common/table'; + +export const columns = () => [ + { + property: 'name', + header: { + label: __('Name'), + formatters: [headerFormat], + }, + cell: { + formatters: [ + cell => ( + + ), + ], + }, + }, + { + property: 'description', + header: { + label: __('Description'), + formatters: [headerFormat], + }, + cell: { + formatters: [ellipsisFormat], + }, + }, + { + property: 'author', + header: { + label: __('Author'), + formatters: [headerFormat], + }, + cell: { + formatters: [ellipsisFormat], + }, + }, + { + property: 'version', + header: { + label: __('Version'), + formatters: [headerFormat], + }, + cell: { + formatters: [cellFormat], + }, + }, +]; diff --git a/webpack/assets/javascripts/react_app/components/about/provider/index.js b/webpack/assets/javascripts/react_app/components/about/provider/index.js new file mode 100644 index 000000000000..d2ec7d78a5b8 --- /dev/null +++ b/webpack/assets/javascripts/react_app/components/about/provider/index.js @@ -0,0 +1,13 @@ +import React from 'react'; +import Table from '../../common/table'; +import { columns } from './schema'; +import { computeResource } from '../../../common/EmptyStates'; + +const AboutProviderTable = props => ( +
+ + {cell.name} + +
+); +export default AboutProviderTable; diff --git a/webpack/assets/javascripts/react_app/components/about/provider/schema.js b/webpack/assets/javascripts/react_app/components/about/provider/schema.js new file mode 100644 index 000000000000..c025ae78b55d --- /dev/null +++ b/webpack/assets/javascripts/react_app/components/about/provider/schema.js @@ -0,0 +1,32 @@ +import React from 'react'; +import { Icon } from 'patternfly-react'; +import { headerFormat, cellFormat } from '../../common/table'; + +export const columns = () => [ + { + property: 'provider', + header: { + label: __('Provider'), + formatters: [headerFormat], + }, + cell: { + formatters: [cellFormat], + }, + }, + { + property: 'status', + header: { + label: __('Installed'), + formatters: [headerFormat], + }, + cell: { + formatters: [ + cell => ( + + ), + ], + }, + }, +]; diff --git a/webpack/assets/javascripts/react_app/components/about/proxies/index.js b/webpack/assets/javascripts/react_app/components/about/proxies/index.js new file mode 100644 index 000000000000..f551820bebbf --- /dev/null +++ b/webpack/assets/javascripts/react_app/components/about/proxies/index.js @@ -0,0 +1,14 @@ +import React from 'react'; +import Table from '../../common/table'; +import { columns } from './schema'; +import { smartProxy } from '../../../common/EmptyStates'; + +const AboutProxyTable = props => ( +
+ +
+); + +export default AboutProxyTable; diff --git a/webpack/assets/javascripts/react_app/components/about/proxies/schema.js b/webpack/assets/javascripts/react_app/components/about/proxies/schema.js new file mode 100644 index 000000000000..22654105862b --- /dev/null +++ b/webpack/assets/javascripts/react_app/components/about/proxies/schema.js @@ -0,0 +1,78 @@ +import React from 'react'; +import ConnectedStatus from '../../common/status'; +import { headerFormat, ellipsisFormat } from '../../common/table'; +import helpers from '../../../common/helpers'; + +export const columns = () => [ + { + property: 'id', + header: { + label: __('Name'), + formatters: [headerFormat], + }, + cell: { + formatters: [ + value => ( + + ), + ], + }, + }, + { + property: 'features', + header: { + label: __('Features'), + formatters: [headerFormat], + }, + cell: { + formatters: [ellipsisFormat], + }, + }, + { + property: 'id', + header: { + label: __('Status'), + formatters: [headerFormat], + }, + cell: { + formatters: [ + value => ( + + ), + ], + }, + }, + { + property: 'id', + header: { + label: __('Version'), + formatters: [headerFormat], + }, + cell: { + formatters: [ + value => ( + + ), + ], + }, + }, +]; diff --git a/webpack/assets/javascripts/react_app/components/common/emptyState/index.js b/webpack/assets/javascripts/react_app/components/common/emptyState/index.js new file mode 100644 index 000000000000..ce81d913b452 --- /dev/null +++ b/webpack/assets/javascripts/react_app/components/common/emptyState/index.js @@ -0,0 +1,43 @@ +import React from 'react'; +import { EmptyState as PfEmptyState, Button } from 'patternfly-react'; + +const EmptyState = (props) => { + const { + icon = 'add-circle-o', + header, + description, + customDocumentation, + documentationLabel = 'For more information please see', + documentationButton = 'Documentation', + docUrl, + action, + secondayActions, + } = props; + const defaultDocumantion = __(`${documentationLabel} ${documentationButton}`); + + return ( + + + {header} + {description} + + {customDocumentation || } + + + + + {secondayActions && ( + + {secondayActions.map(item => ( + + ))} + + )} + + ); +}; +export default EmptyState; diff --git a/webpack/assets/javascripts/react_app/components/common/status/__snapshots__/status.test.js.snap b/webpack/assets/javascripts/react_app/components/common/status/__snapshots__/status.test.js.snap new file mode 100644 index 000000000000..8c1b4801e826 --- /dev/null +++ b/webpack/assets/javascripts/react_app/components/common/status/__snapshots__/status.test.js.snap @@ -0,0 +1,58 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Status should render a message 1`] = ` + + message1 + +`; + +exports[`Status status 1 should render error icon 1`] = ` +
+ +
+`; + +exports[`Status status 1 should render ok icon 1`] = ` +
+ +
+`; + +exports[`Status status 2 should render error icon 1`] = ` +
+ +
+`; + +exports[`Status status 2 should render ok icon 1`] = ` +
+ +
+`; + +exports[`Status status 2 should render warning icon 1`] = ` +
+ +
+`; diff --git a/webpack/assets/javascripts/react_app/components/common/status/index.js b/webpack/assets/javascripts/react_app/components/common/status/index.js new file mode 100644 index 000000000000..8b68c6cd0d02 --- /dev/null +++ b/webpack/assets/javascripts/react_app/components/common/status/index.js @@ -0,0 +1,70 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import { get } from 'lodash'; +import { Icon } from 'patternfly-react'; +import { simpleLoader } from '../../common/Loader'; +import * as StatusAction from '../../../redux/actions/status/'; + +export class Status extends React.Component { + componentDidMount() { + const { data: { id, type, url }, getStatus } = this.props; + if (url) { + getStatus({ id, type }, url); + } + } + + render() { + const { + status: { + status, message, error, success, + }, + getMessage, + } = this.props; + + if (message && getMessage) { + return {message[getMessage]}; + } + + if (status || success !== undefined) { + if (message && message.warning) { + return ( +
+ +
+ ); + } + return ( +
+ +
+ ); + } + + if (error) { + return ( +
+ +
+ ); + } + + return simpleLoader('xs'); + } +} + +const mapStateToProps = (state, ownProps) => { + const { type, id } = ownProps.data; + return { + status: get(state.status, `${type}.${id}`) || {}, + }; +}; + +export default connect(mapStateToProps, StatusAction)(Status); diff --git a/webpack/assets/javascripts/react_app/components/common/status/status.fixutres.js b/webpack/assets/javascripts/react_app/components/common/status/status.fixutres.js new file mode 100644 index 000000000000..a6d42e05fa91 --- /dev/null +++ b/webpack/assets/javascripts/react_app/components/common/status/status.fixutres.js @@ -0,0 +1,37 @@ +export const state = { + status1: { + 1: { + id: 1, + type: 'status1', + status: 'OK', + }, + 2: { + id: 1, + type: 'status2', + status: 'Error', + message: 'Error TCP Connection', + }, + }, + status2: { + 1: { + id: 1, + type: 'status2', + success: true, + message: { message1: 'message1' }, + }, + 2: { + id: 2, + type: 'status2', + success: false, + message: 'Error Type 1', + }, + 3: { + id: 3, + type: 'status2', + success: false, + message: { + warning: { message: 'Warning message' }, + }, + }, + }, +}; diff --git a/webpack/assets/javascripts/react_app/components/common/status/status.test.js b/webpack/assets/javascripts/react_app/components/common/status/status.test.js new file mode 100644 index 000000000000..badcafafcf00 --- /dev/null +++ b/webpack/assets/javascripts/react_app/components/common/status/status.test.js @@ -0,0 +1,42 @@ +import { shallow } from 'enzyme'; +import toJson from 'enzyme-to-json'; +import React from 'react'; +import { Status } from './'; +import { state } from './status.fixutres'; + +describe('Status', () => { + it('status 1 should render ok icon', () => { + const wrapper = shallow(); + + expect(toJson(wrapper)).toMatchSnapshot(); + }); + it('status 1 should render error icon', () => { + const wrapper = shallow(); + + expect(toJson(wrapper)).toMatchSnapshot(); + }); + it('status 2 should render ok icon', () => { + const wrapper = shallow(); + + expect(toJson(wrapper)).toMatchSnapshot(); + }); + it('status 2 should render error icon', () => { + const wrapper = shallow(); + + expect(toJson(wrapper)).toMatchSnapshot(); + }); + it('status 2 should render warning icon', () => { + const wrapper = shallow(); + + expect(toJson(wrapper)).toMatchSnapshot(); + }); + it('should render a message', () => { + const wrapper = shallow(); + + expect(toJson(wrapper)).toMatchSnapshot(); + }); +}); diff --git a/webpack/assets/javascripts/react_app/components/common/table/index.js b/webpack/assets/javascripts/react_app/components/common/table/index.js new file mode 100644 index 000000000000..37ed03a0ceba --- /dev/null +++ b/webpack/assets/javascripts/react_app/components/common/table/index.js @@ -0,0 +1,40 @@ +import { Table as PfTable } from 'patternfly-react'; +import React from 'react'; +import EllipsisWithTooltip from 'react-ellipsis-with-tooltip'; +import EmptyState from '../emptyState'; + +export const headerFormat = value => {value}; +export const cellFormat = value => {value}; +export const ellipsisFormat = value => ( + + {value} + +); + +class Table extends React.Component { + isEmpty() { + return this.props.rows.length === 0; + } + + render() { + const { columns, rows, emptyState } = this.props; + return this.isEmpty() ? ( + + ) : ( + + + rowIndex} + /> + + ); + } +} +export default Table; diff --git a/webpack/assets/javascripts/react_app/components/common/tabs/index.js b/webpack/assets/javascripts/react_app/components/common/tabs/index.js new file mode 100644 index 000000000000..229f29ec74d4 --- /dev/null +++ b/webpack/assets/javascripts/react_app/components/common/tabs/index.js @@ -0,0 +1,33 @@ +import React from 'react'; +import { + TabContainer, + Nav, + NavItem, + TabPane, + TabContent, +} from 'patternfly-react'; + +const TabsWrapper = (props) => { + const { tabs, children } = props; + return ( + +
+ + + + {React.Children.map(children, (content, index) => ( + {content} + ))} + +
+
+ ); +}; + +export default TabsWrapper; diff --git a/webpack/assets/javascripts/react_app/components/componentRegistry.js b/webpack/assets/javascripts/react_app/components/componentRegistry.js index 97e12eb870bd..d69f3a0269a2 100644 --- a/webpack/assets/javascripts/react_app/components/componentRegistry.js +++ b/webpack/assets/javascripts/react_app/components/componentRegistry.js @@ -10,6 +10,7 @@ import BookmarkContainer from './bookmarks'; import PasswordStrength from './PasswordStrength'; import BreadcrumbBar from './BreadcrumbBar'; import FactChart from './factCharts'; +import About from './about'; const componentRegistry = { registry: {}, @@ -68,6 +69,7 @@ const coreComponets = [ { name: 'PasswordStrength', type: PasswordStrength }, { name: 'BreadcrumbBar', type: BreadcrumbBar }, { name: 'FactChart', type: FactChart }, + { name: 'About', type: About }, ]; componentRegistry.registerMultiple(coreComponets); diff --git a/webpack/assets/javascripts/react_app/redux/actions/status/index.js b/webpack/assets/javascripts/react_app/redux/actions/status/index.js new file mode 100644 index 000000000000..a671df66ac5a --- /dev/null +++ b/webpack/assets/javascripts/react_app/redux/actions/status/index.js @@ -0,0 +1,12 @@ +import { STATUS_REQUEST, STATUS_SUCCESS, STATUS_FAILURE } from '../../consts'; +import { ajaxRequestAction } from '../common'; + +export const getStatus = ({ id, type }, url) => dispatch => + ajaxRequestAction({ + dispatch, + requestAction: STATUS_REQUEST, + successAction: STATUS_SUCCESS, + failedAction: STATUS_FAILURE, + url, + item: { id, type }, + }); diff --git a/webpack/assets/javascripts/react_app/redux/consts.js b/webpack/assets/javascripts/react_app/redux/consts.js index e03d986ed889..3793b2f04698 100644 --- a/webpack/assets/javascripts/react_app/redux/consts.js +++ b/webpack/assets/javascripts/react_app/redux/consts.js @@ -35,3 +35,6 @@ export const FACT_CHART_DATA_SUCCESS = 'FACT_CHART_DATA_SUCCESS'; export const FACT_CHART_DATA_FAILURE = 'FACT_CHART_DATA_FAILURE'; export const OPEN_FACT_CHART_MODAL = 'OPEN_FACT_CHART_MODAL'; export const CLOSE_FACT_CHART_MODAL = 'CLOSE_FACT_CHART_MODAL'; +export const STATUS_REQUEST = 'STATUS_REQUEST'; +export const STATUS_SUCCESS = 'STATUS_SUCCESS'; +export const STATUS_FAILURE = 'STATUS_FAILURE'; diff --git a/webpack/assets/javascripts/react_app/redux/reducers/index.js b/webpack/assets/javascripts/react_app/redux/reducers/index.js index c79a738a64e3..5f76a9415491 100644 --- a/webpack/assets/javascripts/react_app/redux/reducers/index.js +++ b/webpack/assets/javascripts/react_app/redux/reducers/index.js @@ -8,6 +8,7 @@ import toasts from './toasts'; import { reducers as passwordStrengthReducers } from '../../components/PasswordStrength'; import { reducers as breadcrumbBarReducers } from '../../components/BreadcrumbBar'; import factChart from './factCharts/'; +import status from './status'; export function combineReducersAsync(asyncReducers) { return combineReducers({ @@ -19,6 +20,7 @@ export function combineReducersAsync(asyncReducers) { toasts, ...passwordStrengthReducers, ...breadcrumbBarReducers, + status, ...asyncReducers, factChart, }); diff --git a/webpack/assets/javascripts/react_app/redux/reducers/status/index.js b/webpack/assets/javascripts/react_app/redux/reducers/status/index.js new file mode 100644 index 000000000000..dd0f535bb19f --- /dev/null +++ b/webpack/assets/javascripts/react_app/redux/reducers/status/index.js @@ -0,0 +1,19 @@ +import Immutable from 'seamless-immutable'; +import { STATUS_REQUEST, STATUS_SUCCESS, STATUS_FAILURE } from '../../consts'; + +const initialState = Immutable({}); + +export default (state = initialState, action) => { + const { payload, type } = action; + + switch (type) { + case STATUS_REQUEST: + return state; + case STATUS_SUCCESS: + return state.setIn([payload.type, payload.id], { ...payload }); + case STATUS_FAILURE: + return state.setIn([payload.item.type, payload.item.id], { error: payload.error }); + default: + return state; + } +};
+ + {value.name} + + + + + +