Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revision Previous revision
Next revision
Previous revision
devel:documentation:identities:dev:user-type [2020/04/03 05:59]
tomiskar [Admin guide]
devel:documentation:identities:dev:user-type [2020/08/14 10:43] (current)
tomiskar [Configurable form in product]
Line 11: Line 11:
     * Form projection content is placed on path ''src/content/identity/projection/IdentityProjection.js''     * Form projection content is placed on path ''src/content/identity/projection/IdentityProjection.js''
     * Route (= frontent target) ''form/identity-projection/:entityId''' is registed in ''routes.js''.     * Route (= frontent target) ''form/identity-projection/:entityId''' is registed in ''routes.js''.
-    * Component ''identity-projection'' with route usage is registered in ''component-descriptor.js'' 
   * Backend:   * Backend:
 +    * Route component ''IdentityFormProjectionRoute'' with route usage is registered.
     * ''IdmIdentityProjectionDto'' was added - contains all projection data sent between backend and frontend, contains:     * ''IdmIdentityProjectionDto'' was added - contains all projection data sent between backend and frontend, contains:
       * **identity** - identity.       * **identity** - identity.
Line 18: Line 18:
       * **otherContracts** - all other contracts.       * **otherContracts** - all other contracts.
       * **otherPositions** - all other positions.       * **otherPositions** - all other positions.
-      * **identityRoles** - all assigned identity roles.+      * **identityRoles** - all assigned identity roles (loaded by projection configuration property ''load-assigned-roles'' is enabled).
     * ''IdentityProjectionManager'' was added and support **get and save** identity **projection**. Projection is processed by event and processors (see ''IdentityProjectionSaveProcessor''). Processors can be registed to process ''IdmIdentityProjectionDto'' content (custom validations, generators etc. can be added non invasively). Manager contains overridable protected method to get and save all parts of projection (methods can be overidden invasively to add or change product behavior).     * ''IdentityProjectionManager'' was added and support **get and save** identity **projection**. Projection is processed by event and processors (see ''IdentityProjectionSaveProcessor''). Processors can be registed to process ''IdmIdentityProjectionDto'' content (custom validations, generators etc. can be added non invasively). Manager contains overridable protected method to get and save all parts of projection (methods can be overidden invasively to add or change product behavior).
     * ''IdmIdentityProjectionController'' is exposed on url '<server api>/identity-projection' and support get and post ''IdmIdentityProjectionDto'' - calls ''IdentityProjectionManager''methods.     * ''IdmIdentityProjectionController'' is exposed on url '<server api>/identity-projection' and support get and post ''IdmIdentityProjectionDto'' - calls ''IdentityProjectionManager''methods.
 +
 +<note tip>If projection works with role requests only, then loading of all assigned identity roles could be disabled - by projection configuration property ''load-assigned-roles''</note>
 +
 +==== Default user detail ====
 +
 +Default user detail is still available and is used for users without projection is specified. Default user detail can be used as projection with route ''/form/identity'' too:
 +  * Localization can be provided,
 +  * authorization policies can be configured,
 +  * projectin doesn't provide configuration - default iuser detail is show (redirect) only.
  
 ==== How to register new form ==== ==== How to register new form ====
  
-New projection can be added with the same structure in custom module. Is needed to add new content representing form projection (e.g. copy or reuse form projection from product), register new route in ''routes.js'', register new component in ''component-descriptor.js'' and expose new edpoint on backend if needed (if provided ''IdmIdentityProjectionDto'' is insuffiscient).+New projection can be added with the same structure in custom module. Is needed to add new content representing form projection (e.g. copy or reuse form projection from product), register new route in ''routes.js'', register new route component (e.g. by generalize prepared ''AbstractFormProjectionRoute''and expose new edpoint on backend if needed (if provided ''IdmIdentityProjectionDto'' is insuffiscient).
  
 Projection will obtain ''entityId'' route parameter with identity codeable identifier (username or uuid).  Projection will obtain ''entityId'' route parameter with identity codeable identifier (username or uuid). 
Line 34: Line 43:
 Create new form ''src/content/identity/projection/ExampleIdentityProjection.js'' with content: Create new form ''src/content/identity/projection/ExampleIdentityProjection.js'' with content:
  
-<code javascript> +[[https://github.com/bcvsolutions/CzechIdMng/blob/develop/Realization/frontend/czechidm-example/src/content/identity/projection/ExampleIdentityProjection.js|ExampleIdentityProjection.js]]
-import React from 'react'; +
-import PropTypes from 'prop-types'; +
-import { connect } from 'react-redux'; +
-import Helmet from 'react-helmet'; +
-// +
-import { Basic, Advanced, Utils, Managers, Content } from 'czechidm-core'; +
- +
-const identityManager = new Managers.IdentityManager(); +
-const identityProjectionManager = new Managers.IdentityProjectionManager(); +
-const formProjectionManager = new Managers.FormProjectionManager(); +
- +
- +
-/** +
- * Example  form for identity projection. +
- */ +
-class ExampleIdentityProjection extends Basic.AbstractContent { +
- +
- +
-  constructor(props, context) { +
-    super(props, context); +
-    this.state = { +
-      generatePassword: false, +
-      generatePasswordShowLoading: false +
-    }; +
-  } +
- +
-  getContentKey() { +
-    return 'content.identity.projection'; +
-  } +
- +
-  getNavigationKey() { +
-    return 'identities'; +
-  } +
- +
-  componentDidMount() { +
-    super.componentDidMount(); +
-    // +
-    const { entityId } = this.props.match.params; +
-    const { identityProjection, location } = this.props; +
-    const isNew = !!Utils.Ui.getUrlParameter(location, 'new'); +
-    // +
-    if (isNew) { +
-      let formProjectionId; +
-      if (identityProjection && identityProjection.identity && identityProjection.identity.formProjection) { +
-        formProjectionId = identityProjection.identity.formProjection; +
-      } else { +
-        formProjectionId = Utils.Ui.getUrlParameter(this.props.location, 'projection'); +
-      } +
-      if (!formProjectionId) { +
-        // form projection not found - default will be shown +
-        this._initProjection(entityId, identityProjection, {}); +
-      } else { +
-        // fetch projection definition +
-        this.context.store.dispatch(formProjectionManager.autocompleteEntityIfNeeded(formProjectionId, null, (entity, error) => { +
-          if (error) { +
-            this.addError(error); +
-          } else { +
-            this._initProjection(entityId, identityProjection, entity); +
-          } +
-        })); +
-      } +
-    } else { +
-      this.getLogger().debug(`[FormDetail] loading entity detail [id:${ entityId }]`); +
-      this.context.store.dispatch(identityProjectionManager.fetchProjection(entityId, null, (entity, error) => { +
-        if (error) { +
-          this.addError(error); +
-        } else { +
-          this._initProjection(entityId, entity); +
-        } +
-      })); +
-    } +
-  } +
- +
-  _initProjection(entityId, identityProjection = null, formProjection = null) { +
-    // +
-    // prepare form friendly projection +
-    let _identityProjection = null; +
-    if (identityProjection) { +
-      if (!formProjection && identityProjection.identity && identityProjection.identity._embedded) { +
-        formProjection = identityProjection.identity._embedded.formProjection; +
-      } +
-      // +
-      _identityProjection = { +
-        ...identityProjection.identity +
-      }; +
-    } else { +
-      // new projection +
-      _identityProjection = { +
-        id: entityId, +
-        identity: { +
-          id: entityId, +
-          formProjection: formProjection ? formProjection.id : Utils.Ui.getUrlParameter(this.props.location, 'projection'+
-        } +
-      }; +
-    } +
-    // +
-    this.setState({ +
-      identityProjection: _identityProjection, +
-      formProjection +
-    }, () => { +
-      if (this.refs.username) { +
-        this.refs.username.focus(); +
-      } +
-    }); +
-  } +
- +
-  save(event) { +
-    if (event) { +
-      event.preventDefault(); +
-    } +
-    if (!this.refs.form.isFormValid()) { +
-      return; +
-    } +
-    const { identityProjection } = this.state; +
-    /+
-    this.refs.form.processStarted(); +
-    const data = this.refs.form.getData(); +
-    // construct projection +
-    const _identityProjection = { +
-      ...identityProjection, +
-      id: data.id, +
-      identity: data +
-    }; +
-    // +
-    // post => save +
-    this.context.store.dispatch(identityProjectionManager.saveProjection(_identityProjection, null, this._afterSave.bind(this))); +
-  } +
- +
-  /** +
-  * Just set showloading to false and set processEnded to form. +
-  * Call after save/create +
-  */ +
-  _afterSave(identityProjection, error) { +
-    if (error) { +
-      this.setState({ +
-        validationError: error, +
-        validationDefinition: false +
-      }, () => { +
-        this.addError(error); +
-        if (this.refs.form) { +
-          this.refs.form.processEnded(); +
-        } +
-      }); +
-    } else { +
-      this.addMessage({ +
-        message: this.i18n('action.save.success', { record: identityProjection.identity.username, count: 1 }) +
-      }); +
-      this.context.history.replace(identityManager.getDetailLink(identityProjection.identity)); +
-      if (this.refs.form) { +
-        this.refs.form.processEnded(); +
-      } +
-    } +
-  } +
- +
-  render() { +
-    const { +
-      match, +
-      location, +
-      showLoading, +
-      _imageUrl +
-    } = this.props; +
-    const { entityId } = match.params; +
-    const { +
-      identityProjection, +
-      formProjection +
-    } = this.state; +
-    const isNew = !!Utils.Ui.getUrlParameter(location, 'new'); +
-    // +
-    return ( +
-      <Basic.Div> +
-        <Helmet title={ isNew ? this.i18n('create.title') : this.i18n('edit.title') } /> +
-        <Basic.Row> +
-          <Basic.Div className="col-lg-offset-2 col-lg-8"> +
-            <Advanced.DetailHeader +
-              entity={ isNew ? null : identityProjection } +
-              back="/identities" +
-              buttons={[ +
-                <Basic.Icon +
-                  value="fa:angle-double-right" +
-                  style={{ marginRight: 5, cursor: 'pointer' }} +
-                  title={ this.i18n('component.advanced.IdentityInfo.link.detail.default.label') } +
-                  onClick={ () => this.context.history.push(`/identity/${ encodeURIComponent(identityProjection.username) }/profile`) } +
-                  rendered={ !isNew }/> +
-              ]}> +
-              { +
-                _imageUrl +
-                ? +
-                <img src={ _imageUrl } alt="profile" className="img-circle img-thumbnail" style={{ height: 40, padding: 0 }} /+
-                : +
-                <Basic.Icon +
-                  icon={ formProjection ? formProjectionManager.getLocalization(formProjection, 'icon', 'component:identity') : 'component:identity'+
-                  identity={ identityProjection }/+
-              } +
-              { ' ' } +
-              { identityProjectionManager.getNiceLabel(identityProjection) } +
-              <small> +
-                { ' ' } +
-                { isNew ? this.i18n('create.title') : this.i18n('edit.title') } +
-              </small> +
-            </Advanced.DetailHeader> +
- +
-            <Content.OrganizationPosition identity={ entityId } rendered={ !isNew }/+
- +
-            <form onSubmit={ this.save.bind(this) }> +
- +
-              <Basic.Panel rendered={ identityProjection === null || identityProjection === undefined }> +
-                <Basic.Loading isStatic show/+
-              </Basic.Panel> +
- +
-              <Basic.Panel className={ identityProjection === null || identityProjection === undefined ? 'hidden' : 'last' }> +
- +
-                <Basic.PanelBody> +
-                  <Basic.AbstractForm +
-                    ref="form" +
-                    data={ identityProjection } +
-                    readOnly={ !identityProjectionManager.canSave(isNew ? null : identityProjection) } +
-                    style={{ padding: 0 }}> +
- +
-                    <Basic.TextField +
-                      ref="username" +
-                      label={ this.i18n('identity.username.label') } +
-                      max={ 255 }/> +
- +
-                  </Basic.AbstractForm> +
-                </Basic.PanelBody> +
-                <Basic.PanelFooter> +
-                  <Basic.Button type="button" level="link" onClick={ this.context.history.goBack }> +
-                    { this.i18n('button.back') } +
-                  </Basic.Button> +
-                  <Basic.Button +
-                    type="submit" +
-                    level="success" +
-                    showLoading={ showLoading } +
-                    showLoadingIcon +
-                    showLoadingText={ this.i18n('button.saving') } +
-                    rendered={ identityProjectionManager.canSave(isNew ? null : identityProjection) }> +
-                    { this.i18n('button.save') } +
-                  </Basic.Button> +
-                </Basic.PanelFooter> +
-              </Basic.Panel> +
-            </form> +
-          </Basic.Div> +
-        </Basic.Row> +
-      </Basic.Div> +
-    ); +
-  } +
-+
- +
-ExampleIdentityProjection.propTypes = { +
-  identityProjection: PropTypes.object +
-}; +
-ExampleIdentityProjection.defaultProps = { +
-  identityProjection: null, +
-  _imageUrl: null +
-}; +
- +
-function select(state, component) { +
-  const { entityId } = component.match.params; +
-  const profileUiKey = identityManager.resolveProfileUiKey(entityId); +
-  const profile = Managers.DataManager.getData(state, profileUiKey); +
-  const identityProjection = identityProjectionManager.getEntity(state, entityId); +
-  // +
-  return { +
-    identityProjection, +
-    showLoading: identityProjectionManager.isShowLoading(state, null, !identityProjection ? entityId : identityProjection.id), +
-    _imageUrl: profile ? profile.imageUrl : null, +
-    passwordChangeType: Managers.ConfigurationManager.getPublicValue(state, 'idm.pub.core.identity.passwordChange'+
-  }; +
-+
- +
-export default connect(select)(ExampleIdentityProjection); +
-</code>+
  
 Register new route in 'routes.js': Register new route in 'routes.js':
Line 318: Line 55:
 </code> </code>
  
-Register new component in ''component-descriptor.js''+Register new component:
  
-<code javascript+<code java
-{ +/** 
-  id: 'example-identity-projection', + * Example identity form projection. 
-  type: 'form-projection', + *  
-  ownerType: 'IdmIdentity', + * @since 10.3.0 
-  route: '/example/form/identity-projection'+ */ 
 +@Component(ExampleIdentityFormProjectionRoute.PROJECTION_NAME) 
 +public class ExampleIdentityFormProjectionRoute extends AbstractFormProjectionRoute<IdmIdentity> { 
 +  
 + public static final String PROJECTION_NAME = "/example/form/identity-projection"; 
 +  
 + @Override 
 + public String getName() { 
 + return PROJECTION_NAME; 
 + }
 } }
 </code> </code>
Line 365: Line 111:
  
   * Projection could be implemented by [[https://github.com/bcvsolutions/CzechIdMng/tree/graphql|graphgl]] usage on backend (~extendable dto resource).   * Projection could be implemented by [[https://github.com/bcvsolutions/CzechIdMng/tree/graphql|graphgl]] usage on backend (~extendable dto resource).
 +  * Save button can be shown, if any section can be edited - update identity permission is needed now.
 +  * Assigned role attributes cannot be defined, when identity is created - add new assigned roles face mode (support projection properties).
  
 ===== Admin guide ===== ===== Admin guide =====
  • by tomiskar