import { getJSON } from '../helpers/request.js';
+
+/**
+ * Return events for a component
+ * @param {string} componentKey
+ * @param {string} [categories]
+ * @returns {Promise}
+ */
export function getEvents (componentKey, categories) {
let url = baseUrl + '/api/events';
- let data = { resource: componentKey, categories };
+ let data = { resource: componentKey };
+ if (categories) {
+ data.categories = categories;
+ }
return getJSON(url, data);
}
--- /dev/null
+import React from 'react';
+import moment from 'moment';
+
+import { TooltipsMixin } from '../../../components/mixins/tooltips-mixin';
+
+
+export const Event = React.createClass({
+ mixins: [TooltipsMixin],
+
+ propTypes: {
+ event: React.PropTypes.shape({
+ id: React.PropTypes.string.isRequired,
+ date: React.PropTypes.object.isRequired,
+ type: React.PropTypes.string.isRequired,
+ name: React.PropTypes.string.isRequired,
+ text: React.PropTypes.string
+ })
+ },
+
+ render () {
+ const { event } = this.props;
+ return <li className="spacer-top">
+ <p data-toggle="tooltip" title={event.text}>
+ <strong className="js-event-type">{window.t('event.category', event.type)}</strong>
+ :
+ <span className="js-event-name">{event.name}</span>
+ </p>
+ <p className="note little-spacer-top js-event-date">{moment(event.date).format('LL')}</p>
+ </li>;
+ }
+});
--- /dev/null
+import React from 'react';
+
+
+const TYPES = ['All', 'Version', 'Alert', 'Profile', 'Other'];
+
+
+export const EventsListFilter = React.createClass({
+ propTypes: {
+ onFilter: React.PropTypes.func.isRequired,
+ currentFilter: React.PropTypes.string.isRequired
+ },
+
+ handleChange() {
+ const value = this.refs.select.value;
+ this.props.onFilter(value);
+ },
+
+ render () {
+ const options = TYPES.map(type => <option key={type} value={type}>{window.t('event.category', type)}</option>);
+ return <select ref="select" onChange={this.handleChange} value={this.props.currentFilter}>{options}</select>;
+ }
+});
--- /dev/null
+import React from 'react';
+
+import { Event } from './event';
+import { EventsListFilter } from './events-list-filter';
+
+
+const LIMIT = 5;
+
+
+export const EventsList = React.createClass({
+ propTypes: {
+ events: React.PropTypes.arrayOf(React.PropTypes.shape({
+ id: React.PropTypes.string.isRequired,
+ date: React.PropTypes.object.isRequired,
+ type: React.PropTypes.string.isRequired,
+ name: React.PropTypes.string.isRequired,
+ text: React.PropTypes.string
+ }).isRequired).isRequired
+ },
+
+ getInitialState() {
+ return { limited: true, filter: 'All' };
+ },
+
+ limitEvents(events) {
+ return this.state.limited ? events.slice(0, LIMIT) : events;
+ },
+
+ filterEvents(events) {
+ if (this.state.filter === 'All') {
+ return events;
+ } else {
+ return events.filter(event => event.type === this.state.filter);
+ }
+ },
+
+ handleClick(e) {
+ e.preventDefault();
+ this.setState({ limited: !this.state.limited });
+ },
+
+ handleFilter(filter) {
+ this.setState({ filter });
+ },
+
+ renderMoreLink(filteredEvents) {
+ if (filteredEvents.length > LIMIT) {
+ const text = this.state.limited ? window.t('widget.events.show_all') : window.t('hide');
+ return <p className="spacer-top note">
+ <a onClick={this.handleClick} href="#">{text}</a>
+ </p>;
+ } else {
+ return null;
+ }
+ },
+
+ renderList (events) {
+ if (events.length) {
+ return <ul>{events.map(event => <Event key={event.id} event={event}/>)}</ul>;
+ } else {
+ return <p className="spacer-top note">{window.t('no_results')}</p>;
+ }
+ },
+
+ render () {
+ const filteredEvents = this.filterEvents(this.props.events);
+ const events = this.limitEvents(filteredEvents);
+ return <div className="overview-meta-card">
+ <div className="clearfix">
+ <h4 className="pull-left overview-meta-header">{window.t('widget.events.name')}</h4>
+ <div className="pull-right">
+ <EventsListFilter currentFilter={this.state.filter} onFilter={this.handleFilter}/>
+ </div>
+ </div>
+ {this.renderList(events)}
+ {this.renderMoreLink(filteredEvents)}
+ </div>;
+ }
+});
import _ from 'underscore';
+import moment from 'moment';
import React from 'react';
+
import { QualityProfileLink } from './../../components/shared/quality-profile-link';
import { QualityGateLink } from './../../components/shared/quality-gate-link';
+import { getEvents } from '../../api/events';
+import { EventsList } from './components/events-list';
+
export default React.createClass({
+ componentDidMount() {
+ this.requestEvents();
+ },
+
+ requestEvents () {
+ return getEvents(this.props.component.key).then(events => {
+ const nextEvents = events.map(event => {
+ return {
+ id: event.id,
+ date: moment(event.dt).toDate(),
+ type: event.c,
+ name: event.n,
+ text: event.ds
+ };
+ });
+ this.setState({ events: nextEvents });
+ });
+ },
+
isView() {
return this.props.component.qualifier === 'VW' || this.props.component.qualifier === 'SVW';
},
return this.props.component.qualifier === 'DEV';
},
+ renderEvents() {
+ if (this.state && this.state.events) {
+ return <EventsList component={this.props.component} events={this.state.events}/>;
+ } else {
+ return null;
+ }
+ },
+
render() {
let profiles = (this.props.component.profiles || []).map(profile => {
return (
{linksCard}
{gateCard}
{profilesCard}
+ {this.renderEvents()}
</div>
);
}
@import (reference) "variables";
-.clearfix() {
+.clearfix {
&:before, &:after { display: table; content: ""; line-height: 0; }
&:after { clear: both; }
}
--- /dev/null
+import { expect } from 'chai';
+import React from 'react';
+import { findDOMNode } from 'react-dom';
+import TestUtils from 'react-addons-test-utils';
+
+import { Event } from '../../../../src/main/js/apps/overview/components/event';
+
+
+describe('Overview :: Event', function () {
+ it('should render event', function () {
+ let output = TestUtils.renderIntoDocument(
+ <Event event={{ id: '1', name: '1.5', type: 'Version', date: new Date(2015, 0, 1) }}/>);
+ expect(
+ findDOMNode(TestUtils.findRenderedDOMComponentWithClass(output, 'js-event-date')).textContent
+ ).to.include('2015');
+ expect(
+ findDOMNode(TestUtils.findRenderedDOMComponentWithClass(output, 'js-event-name')).textContent
+ ).to.include('1.5');
+ expect(
+ findDOMNode(TestUtils.findRenderedDOMComponentWithClass(output, 'js-event-type')).textContent
+ ).to.include('Version');
+ });
+});
--- /dev/null
+import { expect } from 'chai';
+import React from 'react';
+import { findDOMNode } from 'react-dom';
+import TestUtils from 'react-addons-test-utils';
+import sinon from 'sinon';
+
+import { EventsListFilter } from '../../../../src/main/js/apps/overview/components/events-list-filter';
+
+
+describe('Overview :: EventsListFilter', function () {
+ it('should render options', function () {
+ let spy = sinon.spy();
+ let output = TestUtils.renderIntoDocument(
+ <EventsListFilter onFilter={spy} currentFilter="All"/>);
+ let options = TestUtils.scryRenderedDOMComponentsWithTag(output, 'option');
+ expect(options).to.have.length(5);
+ });
+});
#
#------------------------------------------------------------------------------
+event.category.All=All
event.category.Version=Version
event.category.Alert=Quality Gate
event.category.Profile=Quality Profile