Skip to content
This repository has been archived by the owner on Aug 25, 2021. It is now read-only.

Tagging component #3

Merged
merged 12 commits into from
May 3, 2018
Merged

Conversation

PanSpagetka
Copy link
Contributor

@PanSpagetka PanSpagetka commented Feb 27, 2018

How Does it look

export 5

screenshot from 2018-05-02 11-42-10

screenshot from 2018-05-02 13-55-44
screenshot from 2018-05-02 13-56-14
screenshot from 2018-05-02 13-56-34
screenshot from 2018-05-02 13-57-00

Interaction

screencast from 2018-05-02 14-13-25

How does it work

Tags are made up of two parts, a category and value. Some categories allow for more than one value to be associated. Categories which can have assigned only one value are marked with * character.
Tags can be applied to a single item or multiple items at one time depending on the use case.
By selecting Category and Value from select box, you can assign tags to items. You can also remove assigned values by clicking on cross in Tag View.

How to use it

  1. Create Redux store with taggingAppor Add it to your store.
  2. Dispatch action for loading initial state store.dispatch({ initialState: defaultState, type: "UI-COMPONENTS_TAGGING_LOAD_STATE" });
  3. Render Component, you can use either TaggingConnected or TaggingWithButtonsConnected, those are the components which work with Redux and contains logic(they are working).
  ReactDOM.render(
    <Provider store={store}>
      <TaggingWithButtonsConnected />
    </Provider>,
    document.getElementById('demo-app'),
  );

initialState must be structured like this:

tags : [
  { description: 'Number', id: 2, singleValue: true,
    values: [{ description: '1', id: 21 }, { description: '2', id: 22 }] },
  { description: 'Animal', id: 3,
    values: [
      { description: 'Duck', id: 31 }, { description: 'Cat', id: 32 }, { description: 'Dog', id: 33 }
    ] 
  },
], 
assignedTags:  [
  { 
    tagCategory: { description: 'Animal', id: 3 },
    tagValues: [{ description: 'Cat', id: 32 }] 
  }
],
affectedItems : [123456, 654321]
Name Type Default Mandatory Description
tags Array [] Yes Categories and its Values which can be assigned to items
description String None Yes Text description of each Category, this text be showed in Select.
id Int(but it should work with others) None Yes Unique id used to distinguish Categories between themself.
singleValue Bool false No Only single value for this category can be assigned at one time. These are marked with * in UI.
values Array None Yes Values which can be assigned to this particular Category. Contains string description and Int id
assignedTags Array [] No Categories and it's Values, which are already assigned to affectedItems
affectedItems Array [] No Array of ids identifying items, which tags are edited

TaggingConnected

@ohadlevy
Copy link
Member

Any reason why this shouldn't be part of patternfly react? I think the pattern is generic enough?

@ohadlevy
Copy link
Member

/cc @serenamarie125

Copy link
Member

@ohadlevy ohadlevy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like a great start :), thank you.

I've added a few inline comments from a very quick code scanning.

a few general comments

  • I think we should use patternfly-react as much as we can, this includes components that already available, and move the view/react code layers to that repository.
  • I would suggest you look into how v2v plugin and foreman currently handle redux, I think it would make a lot of sense to be aligned from the beginning vs create technical debt later on.

on a side note, i think it makes sense to send pr's to patternfly-react, as you would get a lot of value out of the review comments while learning react.

.eslintrc Outdated
@@ -12,6 +12,9 @@
"ignoreUrls": true,
"ignoreComments": false
}],
"react/require-default-props": [0],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

while i dont have an opinion, maybe linting rule changes should happen in separate pr's so their impact could be discussed?

Copy link
Contributor Author

@PanSpagetka PanSpagetka Feb 28, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#6

@@ -0,0 +1,29 @@
import * as React from 'react';
import PropTypes from 'prop-types';
import style from './style';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: extra whitespace.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

deleted

import PropTypes from 'prop-types';
import style from './style';

class Tag extends React.Component {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

imho you dont need a class here, you can simply use a const function instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

<span className="label label-info">
{this.props.tagCategory}: {this.props.tagValue}
<a href="#" onClick={this.handleClick}>
<span className="pficon pficon-close" aria-hidden="true" />
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please use the icon component from patternfly-react

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will use patternfly/patternfly-react#223 as soon as it's merged

{this.props.tagCategory}: {this.props.tagValue}
<a href="#" onClick={this.handleClick}>
<span className="pficon pficon-close" aria-hidden="true" />
<span className="sr-only">Remove</span>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all strings should be available as props, so later on you can i18n them.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, strings are available as props with default values.

const tagCategories = Object.keys(props.tags) || [];
return (
<div className="col-lg-6">
<div className="row">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please use col and row components from patternfly-react

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

import TagSelector from './tagSelector';
import ValueSelector from './valueSelector';

const TagModifier = (props) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please extact the strings, so you can do

const TagModifier = ({ tags, selectedTagCategory, onTagCategroyChange... })

and then just use them without the props.<>

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

import { combineReducers } from 'redux';
import { modifySetTags, toggle } from './reducers';

const TAGS = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these should move into fixtures files.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This shouldn't be part of this component, It was there because of testing purposes. I moved it to stories and demo. Now I pass it as a default state, when creating this component

@@ -0,0 +1,24 @@
// state = state.setTags
export const modifySetTags = (state = [], action) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

imho you should use immutable library to avoid changing the existing state.

@PanSpagetka
Copy link
Contributor Author

@ohadlevy I think this could be part of patternfly react in the future, but not yet. First I would like to test it in ManageIQ and see if there are some changes to be made.

After I test it, I would move it to patternfly, preferably to monorepo(when it's done). Because I think this component have very specific usecase and it is not necessary for everybody who uses patternfly to load it.

@PanSpagetka
Copy link
Contributor Author

I have probably should have marked it as WIP, because there is still some work to be done.

@miq-bot add_label WIP

@PanSpagetka PanSpagetka changed the title Tagging component [WIP] Tagging component Feb 27, 2018
@karelhala karelhala added the wip label Feb 27, 2018
@PanSpagetka PanSpagetka mentioned this pull request Feb 27, 2018
@@ -0,0 +1,5 @@
// action creators
export const TOGGLE_TAG_CATEGORY_CHANGE = 'TOGGLE_TAG_CATEGORY_CHANGE';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add prefix for these actions so we don't clash with other action types.

export const TOGGLE_TAG_CATEGORY_CHANGE = 'UI-COMPONENTS_TOGGLE_TAG_CATEGORY_CHANGE';
export const TOGGLE_TAG_VALUE_CHANGE = 'UI-COMPONENTS_TOGGLE_TAG_VALUE_CHANGE';
export const DELETE_SET_TAG = 'UI-COMPONENTS_DELETE_SET_TAG';
export const ADD_SET_TAG = 'UI-COMPONENTS_ADD_SET_TAG';

@PanSpagetka PanSpagetka force-pushed the tagging-component branch 2 times, most recently from b66b4e9 to 546fe8e Compare March 7, 2018 08:43
@serenamarie125
Copy link

@ohadlevy @PanSpagetka I think someone already implemented something in the managieq-ui-service repo in conjunction with UX from a design perspective. @AllenBW I think there was a tag editor modal submitted - I couldn't find a PR to reference. Might you have it handy?

@serenamarie125
Copy link

@ohadlevy re: your PF React suggestion, I agree that this would be a great contribution!

@AllenBW
Copy link
Member

AllenBW commented Mar 7, 2018

@serenamarie125 yeah!! that was a while back... ManageIQ/manageiq-ui-service#402

@serenamarie125
Copy link

Actually, here's the initial piece of it ... although it looks like it never went through a ux review
@beanh66 this is relatively aligned with the design right?

@PanSpagetka
Copy link
Contributor Author

@serenamarie125 Thanks for showing me this, I didn't know that we have official design. But I still think my PR is needed. Even though my knowledge of angular is very poor, I don't think, that somebody can take that Tag editor from ServiceUI and use it in ManageIQ-ui-classic(or anywhere else) easily. It would require some amount of work.

The point of my PR is make reusable component, composed of several simpler components creating some logical structure, using preferable technologies(React, Redux). So you can use this component, if I oversimplify it, with one line of code.

Because logic, data and design are separated, you can change design of the component without affecting it's functionality easily. Or you can simply add another component into it. For example View of Affected Entities, without need for modifying existing code, only adding new.

As I said earlier, I would like to(and will) make PR into Patternfly React, when I have working example in ManageIQ.

If you have some suggestions about design, please let me know.
I hope I have explained why I was(am) making this PR :D

@beanh66
Copy link

beanh66 commented Mar 8, 2018

@serenamarie125 correct, that MIQ implementation is in line with the design we had. Thanks!

@serenamarie125
Copy link

@PanSpagetka
Is the screenshot here from your implementation?

Could we get closer to the design shown here https://manageiq.github.io/manageiq-design/UX/common/Tags/design?

Is it possible to have multiple values assigned to the same category? In the design, we are showing a design where we would show that in the "chip".

@PanSpagetka
Copy link
Contributor Author

PanSpagetka commented Mar 13, 2018

@serenamarie125 Yes it is. And I will make it look more like in the design. @terezanovotna offered me help, so when I am done with coding I will redesigned it according to the https://manageiq.github.io/manageiq-design/UX/common/Tags/design

Multiple values are not possible now, because there weren't possible in ManageIQ, but I can rewrite it so they will be possible.

Copy link
Contributor

@karelhala karelhala left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It needs some refactoring changes so the code is much redable and is less prone to bugs. Not reviewed stories and tests, that will be step 2.


export default function renderApp() {
ReactDOM.render(<cmpA.HelloCmp />, document.getElementById('demo-app'));
const store = createStore(taggingApp);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use module.hot reload so we can change store on the fly:

if (module.hot) {
  const store = createStore(taggingApp);
  module.hot.accept('../src/tagging/reducers', () => {
    const nextRootReducer = taggingApp;
    store.replaceReducer(nextRootReducer);
  });
}
store.dispatch(loadState(defaultState));
ReactDOM.render(
  <TaggingWithButtonsConnected store={store} />,
  document.getElementById('demo-app'),
);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolved

package.json Outdated
@@ -91,12 +95,13 @@
"dependencies": {
"lodash": "^4.17.4",
"patternfly-react": "^1.8.0",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update PF react to newer version "patternfly-react": "^1.16.4",

@@ -31,7 +31,6 @@ module.exports = (env) => {
enforce: 'pre',
test: /\.(js|jsx)$/,
exclude: /node_modules/,
loader: 'eslint-loader',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please turn the eslint rule back? You will have few errors, but these are OK and easy to fix.

const Tag = ({
onTagDeleteClick, tagCategory, tagValue,
}) => (
<li key={`${tagValue.id}`}>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can remove ${..} and use just <li key={tagValue.id}>

<Label
key={tagValue.id}
bsStyle='primary'
onRemoveClick={() => { onTagDeleteClick(tagCategory, tagValue); }}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to wrap function in body

onRemoveClick={() => onTagDeleteClick(tagCategory, tagValue)}

}

onTagValueChange = (selectedTagValue) => {
if (this.props.multiValue) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do not create same object twice

const action = { tagCategory: this.props.selectedTagCategory, tagValue: selectedTagValue };

}
}

onTagDeleteClick = (tagCategory, tagValue) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to wrap it up with {}

onTagDeleteClick = (tagCategory, tagValue) => this.props.onTagDeleteClick({ tagCategory, tagValue });

selectedTagValue={this.props.selectedTagValue}
/>
<Row className={'pull-right'}>
<ButtonGroup>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would put ButtonGroup in seperate component so we have easier way of combining these two components together.



class ValueSelector extends React.Component {
handleChange = (selectedOption) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactor this component to be simpler

handleChange = (selectedOption) => {
    this.props.onTagValueChange({ description: selectedOption.label, id: selectedOption.value });
  };

  value = this.props.selectedOption.id;
  label = this.props.selectedOption.description;
  tagValues = this.props.tagValues.map(tag => ({ value: tag.id, label: tag.description }));

  render() {
    return (
      <Select
        name="form-field-name"
        value={this.value}
        label={this.label}
        onChange={this.handleChange}
        options={this.tagValues}
        clearable={false}
      />
    );
  }

export const modifyassignedTags = (state = [], action) => {
switch (action.type) {
case actionsConstants.DELETE_ASSIGNED_TAG:
return [...state.filter(tag => (tag.tagCategory.id !== action.tag.tagCategory.id)),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please refactor this to use functions and make it simpler.

@PanSpagetka PanSpagetka force-pushed the tagging-component branch 3 times, most recently from 99600ae to d299ee1 Compare April 10, 2018 08:25
@PanSpagetka
Copy link
Contributor Author

@remove_label wip

@PanSpagetka PanSpagetka changed the title [WIP] Tagging component Tagging component Apr 10, 2018
@PanSpagetka
Copy link
Contributor Author

@miq_bot remove_label wip

@miq-bot miq-bot removed the wip label Apr 10, 2018
@PanSpagetka
Copy link
Contributor Author

PanSpagetka commented Apr 30, 2018

@serenamarie125 Thank you for review.

  1. Done
  2. Done
  3. Yes, buttons are optional part of the pattern. You can have exactly same component with or without buttons. First screenshot, with colorful rectangles, is older. I have adjusted button order to be consistent ManageIQ. But the spacing is problematic, I am using button group component which doesn't support spacing. I will look at it later.
  4. Yep, there is.

I have addressed the visual feedback, changed spacing, colors, alignments and updated screenshots/gif.

EDIT:
Use button toolbar for buttons, so they have space between themselves. Update pictures and gif, but for some reason my screenshot tool doesn't capture tooltip. So it is visible only in the gif picture.

@PanSpagetka PanSpagetka force-pushed the tagging-component branch 2 times, most recently from ca989b8 to f798b0c Compare May 2, 2018 12:19
@miq-bot
Copy link
Member

miq-bot commented May 2, 2018

Checked commits PanSpagetka/react-ui-components@fc69c86~...f798b0c with ruby 2.3.3, rubocop 0.52.1, haml-lint 0.20.0, and yamllint 1.10.0
0 files checked, 0 offenses detected
Everything looks fine. 🍰

Copy link

@serenamarie125 serenamarie125 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@PanSpagetka thanks so much for making all the styling changes. This looks really nice, great job!

@miq-bot
Copy link
Member

miq-bot commented May 3, 2018

This pull request is not mergeable. Please rebase and repush.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants