text:
package com.evolveum.midpoint.web.component.assignment;

import com.evolveum.midpoint.common.refinery.RefinedObjectClassDefinition;
import com.evolveum.midpoint.common.refinery.RefinedResourceSchema;
import com.evolveum.midpoint.common.refinery.RefinedResourceSchemaImpl;
import com.evolveum.midpoint.gui.api.GuiStyleConstants;
import com.evolveum.midpoint.gui.api.component.BasePanel;
import com.evolveum.midpoint.gui.api.component.objecttypeselect.ObjectTypeSelectPanel;
import com.evolveum.midpoint.gui.api.component.togglebutton.ToggleIconButton;
import com.evolveum.midpoint.gui.api.model.LoadableModel;
import com.evolveum.midpoint.gui.api.page.PageBase;
import com.evolveum.midpoint.gui.api.util.WebComponentUtil;
import com.evolveum.midpoint.gui.api.util.WebModelServiceUtils;
import com.evolveum.midpoint.model.api.ModelService;
import com.evolveum.midpoint.prism.*;
import com.evolveum.midpoint.prism.path.ItemPath;
import com.evolveum.midpoint.prism.query.ObjectQuery;
import com.evolveum.midpoint.prism.query.builder.QueryBuilder;
import com.evolveum.midpoint.schema.GetOperationOptions;
import com.evolveum.midpoint.schema.SelectorOptions;
import com.evolveum.midpoint.schema.constants.RelationTypes;
import com.evolveum.midpoint.schema.constants.SchemaConstants;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.security.enforcer.api.ItemSecurityConstraints;
import com.evolveum.midpoint.task.api.Task;
import com.evolveum.midpoint.util.exception.CommunicationException;
import com.evolveum.midpoint.util.exception.ConfigurationException;
import com.evolveum.midpoint.util.exception.ExpressionEvaluationException;
import com.evolveum.midpoint.util.exception.ObjectNotFoundException;
import com.evolveum.midpoint.util.exception.SchemaException;
import com.evolveum.midpoint.util.exception.SecurityViolationException;
import com.evolveum.midpoint.util.logging.LoggingUtils;
import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;
import com.evolveum.midpoint.web.component.DateInput;
import com.evolveum.midpoint.web.component.input.DropDownChoicePanel;
import com.evolveum.midpoint.web.component.input.RelationDropDownChoicePanel;
import com.evolveum.midpoint.web.component.util.VisibleEnableBehaviour;
import com.evolveum.midpoint.web.page.admin.PageAdminFocus;
import com.evolveum.midpoint.web.page.admin.configuration.component.ChooseTypePanel;
import com.evolveum.midpoint.web.page.admin.dto.ObjectViewDto;
import com.evolveum.midpoint.web.page.admin.users.component.AssignmentInfoDto;
import com.evolveum.midpoint.web.page.admin.users.dto.UserDtoStatus;
import com.evolveum.midpoint.web.page.self.PageAssignmentDetails;
import com.evolveum.midpoint.web.page.self.PageAssignmentsList;
import com.evolveum.midpoint.xml.ns._public.common.common_3.*;
import com.evolveum.prism.xml.ns._public.types_3.ItemPathType;

import org.apache.wicket.AttributeModifier;
import org.apache.wicket.Component;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.markup.html.AjaxLink;
import org.apache.wicket.ajax.markup.html.form.AjaxCheckBox;
import org.apache.wicket.markup.head.CssHeaderItem;
import org.apache.wicket.markup.head.IHeaderResponse;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.TextArea;
import org.apache.wicket.markup.html.list.ListItem;
import org.apache.wicket.markup.html.list.ListView;
import org.apache.wicket.model.AbstractReadOnlyModel;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.LoadableDetachableModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.model.PropertyModel;
import org.apache.wicket.request.resource.PackageResourceReference;

import javax.xml.namespace.QName;

import java.util.*;

/**
 * @author lazyman
 */
public class AssignmentEditorPanel extends BasePanel<AssignmentEditorDto> {
	private static final long serialVersionUID = 1L;

	private static final Trace LOGGER = TraceManager.getTrace(AssignmentEditorPanel.class);

	private static final String DOT_CLASS = AssignmentEditorPanel.class.getName() + ".";
	private static final String OPERATION_LOAD_OBJECT = DOT_CLASS + "loadObject";
	private static final String OPERATION_LOAD_RESOURCE = DOT_CLASS + "loadResource";
	private static final String OPERATION_LOAD_ATTRIBUTES = DOT_CLASS + "loadAttributes";
	private static final String OPERATION_LOAD_TARGET_OBJECT = DOT_CLASS + "loadItemSecurityDecisions";
	private static final String OPERATION_LOAD_RELATION_DEFINITIONS = DOT_CLASS + "loadRelationDefinitions";

	private static final String ID_HEADER_ROW = "headerRow";
	private static final String ID_SELECTED = "selected";
	private static final String ID_TYPE_IMAGE = "typeImage";
	private static final String ID_NAME_LABEL = "nameLabel";
	private static final String ID_NAME = "name";
	private static final String ID_ACTIVATION = "activation";
	private static final String ID_ACTIVATION_BLOCK = "activationBlock";
	private static final String ID_EXPAND = "expand";
	protected static final String ID_BODY = "body";
	private static final String ID_DESCRIPTION = "description";
	private static final String ID_RELATION_CONTAINER = "relationContainer";
	private static final String ID_FOCUS_TYPE = "focusType";
	private static final String ID_FOCUS_TYPE_CONTAINER = "focusTypeContainer";
	protected static final String ID_RELATION = "relation";
	private static final String ID_RELATION_LABEL = "relationLabel";
	private static final String ID_ADMINISTRATIVE_STATUS = "administrativeStatus";
	private static final String ID_VALID_FROM = "validFrom";
	private static final String ID_VALID_TO = "validTo";
	private static final String ID_SHOW_EMPTY = "showEmpty";
	private static final String ID_SHOW_EMPTY_LABEL = "showEmptyLabel";
	private static final String ID_ATTRIBUTES = "attributes";
	private static final String ID_ATTRIBUTE = "attribute";
	private static final String ID_AC_ATTRIBUTE = "acAttribute";
	private static final String ID_TARGET = "target";
	private static final String ID_TARGET_CONTAINER = "targetContainer";
	private static final String ID_CONSTRUCTION_CONTAINER = "constructionContainer";
	private static final String ID_CONTAINER_TENANT_REF = "tenantRefContainer";
	private static final String ID_TENANT_CHOOSER = "tenantRefChooser";
	private static final String ID_CONTAINER_ORG_REF = "orgRefContainer";
	private static final String ID_ORG_CHOOSER = "orgRefChooser";
	private static final String ID_BUTTON_SHOW_MORE = "errorLink";
	private static final String ID_ERROR_ICON = "errorIcon";
	private static final String ID_METADATA_CONTAINER = "metadataContainer";
	private static final String ID_PROPERTY_CONTAINER = "propertyContainer";
	private static final String ID_DESCRIPTION_CONTAINER = "descriptionContainer";
	private static final String ID_ADMIN_STATUS_CONTAINER = "administrativeStatusContainer";
	private static final String ID_VALID_FROM_CONTAINER = "validFromContainer";
	private static final String ID_VALID_TO_CONTAINER = "validToContainer";

	private IModel<List<ACAttributeDto>> attributesModel;
	protected WebMarkupContainer headerRow;
	protected IModel<List<AssignmentInfoDto>> privilegesListModel;
	protected boolean delegatedToMe;
	private LoadableDetachableModel<ItemSecurityConstraints> itemSecurityConstraintsModel;

	public AssignmentEditorPanel(String id, IModel<AssignmentEditorDto> model, boolean delegatedToMe,
			LoadableModel<List<AssignmentInfoDto>> privilegesListModel) {
		super(id, model);
		this.privilegesListModel = privilegesListModel;
		this.delegatedToMe = delegatedToMe;

	}

	public AssignmentEditorPanel(String id, IModel<AssignmentEditorDto> model) {
		super(id, model);

		attributesModel = new LoadableModel<List<ACAttributeDto>>(false) {
			@Override
			protected List<ACAttributeDto> load() {
				return loadAttributes();
			}
		};
	}

	@Override
	protected void onInitialize(){
		super.onInitialize();
		initDecisionsModel();
		initLayout();
	}

	@Override
	public void renderHead(IHeaderResponse response) {
		super.renderHead(response);

		response.render(CssHeaderItem.forReference(
				new PackageResourceReference(AssignmentEditorPanel.class, "AssignmentEditorPanel.css")));
	}

	protected void initLayout() {
		setOutputMarkupId(true);
		headerRow = new WebMarkupContainer(ID_HEADER_ROW);
		headerRow.add(AttributeModifier.append("class", createHeaderClassModel(getModel())));
		headerRow.setOutputMarkupId(true);
		add(headerRow);

		initHeaderRow();

		WebMarkupContainer body = new WebMarkupContainer(ID_BODY);
		body.setOutputMarkupId(true);
		body.add(new VisibleEnableBehaviour() {

			@Override
			public boolean isVisible() {
				AssignmentEditorDto editorDto = AssignmentEditorPanel.this.getModel().getObject();
				return !editorDto.isMinimized();
			}
		});
		add(body);

		initBodyLayout(body);
	}

	protected void initHeaderRow(){
		AjaxCheckBox selected = new AjaxCheckBox(ID_SELECTED,
				new PropertyModel<>(getModel(), AssignmentEditorDto.F_SELECTED)) {
			private static final long serialVersionUID = 1L;

			@Override
			protected void onUpdate(AjaxRequestTarget target) {
				// do we want to update something?
			}
		};
		selected.add(new VisibleEnableBehaviour(){
			@Override
			public boolean isVisible(){
				return !getModel().getObject().isSimpleView();
			}
		});
		headerRow.add(selected);

		WebMarkupContainer typeImage = new WebMarkupContainer(ID_TYPE_IMAGE);
		typeImage.add(AttributeModifier.append("class", createImageTypeModel(getModel())));
		headerRow.add(typeImage);

		Label errorIcon = new Label(ID_ERROR_ICON);
		errorIcon.add(new VisibleEnableBehaviour() {
			private static final long serialVersionUID = 1L;

			@Override
			public boolean isVisible() {
				return !isTargetValid();
			}
		});
		headerRow.add(errorIcon);

		AjaxLink name = new AjaxLink(ID_NAME) {
			private static final long serialVersionUID = 1L;

			@Override
			public void onClick(AjaxRequestTarget target) {
				nameClickPerformed(target);
			}
		};
		headerRow.add(name);

		AjaxLink errorLink = new AjaxLink(ID_BUTTON_SHOW_MORE) {
			private static final long serialVersionUID = 1L;

			@Override
			public void onClick(AjaxRequestTarget target) {
				showErrorPerformed(target);
			}
		};
		errorLink.add(new VisibleEnableBehaviour() {
			private static final long serialVersionUID = 1L;

			@Override
			public boolean isVisible() {
				return !isTargetValid();
			}
		});
		headerRow.add(errorLink);

		Label nameLabel = new Label(ID_NAME_LABEL, createAssignmentNameLabelModel(false));
		nameLabel.setOutputMarkupId(true);
		name.add(nameLabel);

		Label activation = new Label(ID_ACTIVATION, AssignmentsUtil.createActivationTitleModel(getModel().getObject().getActivation(), "-", AssignmentEditorPanel.this));
		headerRow.add(activation);

		ToggleIconButton expandButton = new ToggleIconButton(ID_EXPAND, GuiStyleConstants.CLASS_ICON_EXPAND,
				GuiStyleConstants.CLASS_ICON_COLLAPSE) {
			private static final long serialVersionUID = 1L;

			@Override
			public void onClick(AjaxRequestTarget target) {
				nameClickPerformed(target);
			}

			@Override
			public boolean isOn() {
				return !AssignmentEditorPanel.this.getModelObject().isMinimized();
			}
		};
		expandButton.add(new VisibleEnableBehaviour(){

			private static final long serialVersionUID = 1L;

			@Override
			public boolean isVisible(){
				return !getModel().getObject().isSimpleView();
			}
		});
		headerRow.add(expandButton);
	}

	protected IModel<String> createAssignmentNameLabelModel(final boolean isManager) {
		return new AbstractReadOnlyModel<String>() {

			@Override
			public String getObject() {
				if (getModel() != null && getModel().getObject() != null) {
					AssignmentEditorDto dto = getModelObject();

					if (dto.getName() != null) {
						StringBuilder name = new StringBuilder(dto.getName());
						if (isManager) {
							name.append(" - Manager");
						}
						return name.toString();
					}

					if (dto.getAltName() != null) {
						return getString("AssignmentEditorPanel.name.focus");
					}
				}

				return getString("AssignmentEditorPanel.name.noTarget");
			}
		};
	}

	private boolean isTargetValid() {

		if (getModel() != null && getModel().getObject() != null) {
			AssignmentEditorDto dto = getModelObject();

			if (dto.getName() == null && dto.getAltName() == null) {
				return false;
			}
		}

		return true;
	}

	protected IModel<String> createHeaderClassModel(final IModel<AssignmentEditorDto> model) {
		return AssignmentsUtil.createAssignmentStatusClassModel(model.getObject().getStatus());
	}

	protected void initBodyLayout(WebMarkupContainer body) {
		WebMarkupContainer propertyContainer = new WebMarkupContainer(ID_PROPERTY_CONTAINER);
		propertyContainer.setOutputMarkupId(true);
		body.add(propertyContainer);

		WebMarkupContainer descriptionContainer = new WebMarkupContainer(ID_DESCRIPTION_CONTAINER);
		descriptionContainer.setOutputMarkupId(true);
		descriptionContainer.add(new VisibleEnableBehaviour(){
			private static final long serialVersionUID = 1L;

			@Override
			public boolean isVisible(){
				return isItemAllowed(new ItemPath(FocusType.F_ASSIGNMENT, AssignmentType.F_DESCRIPTION));
			}
		});
		body.add(descriptionContainer);

		TextArea<String> description = new TextArea<>(ID_DESCRIPTION,
				new PropertyModel<String>(getModel(), AssignmentEditorDto.F_DESCRIPTION));
		description.add(new VisibleEnableBehaviour() {
			private static final long serialVersionUID = 1L;

			@Override
			public boolean isEnabled() {
				return getModel().getObject().isEditable();
			}
		});
		descriptionContainer.add(description);

		WebMarkupContainer relationContainer = new WebMarkupContainer(ID_RELATION_CONTAINER);
		relationContainer.setOutputMarkupId(true);
		relationContainer.setOutputMarkupPlaceholderTag(true);
		relationContainer.add(new VisibleEnableBehaviour() {

			@Override
			public boolean isVisible() {
				if (!isItemAllowed(new ItemPath(FocusType.F_ASSIGNMENT, AssignmentType.F_TARGET_REF,
						ObjectReferenceType.F_RELATION))){
					return false;
				}
				AssignmentEditorDto dto = getModel().getObject();
				if (dto != null) {
					if (AssignmentEditorDtoType.ORG_UNIT.equals(dto.getType()) ||
                            AssignmentEditorDtoType.SERVICE.equals(dto.getType()) ||
                            AssignmentEditorDtoType.ROLE.equals(dto.getType())) {
						return true;
					}
				}

				return false;
			}
		});
		body.add(relationContainer);
		addRelationDropDown(relationContainer);

		WebMarkupContainer focusTypeContainer = new WebMarkupContainer(ID_FOCUS_TYPE_CONTAINER);
		focusTypeContainer.setOutputMarkupId(true);
		focusTypeContainer.add(new VisibleEnableBehaviour(){
			private static final long serialVersionUID = 1L;

			@Override
			public boolean isVisible(){
				return isItemAllowed(new ItemPath(FocusType.F_ASSIGNMENT, AssignmentType.F_FOCUS_TYPE));
			}
		});
		body.add(focusTypeContainer);

		ObjectTypeSelectPanel<FocusType> focusType = new ObjectTypeSelectPanel<>(ID_FOCUS_TYPE,
				new PropertyModel<>(getModel(), AssignmentEditorDto.F_FOCUS_TYPE), FocusType.class);
		focusTypeContainer.add(focusType);

		Label relationLabel = new Label(ID_RELATION_LABEL, new AbstractReadOnlyModel<String>() {

			@Override
			public String getObject() {
				if (getModel() == null || getModel().getObject() == null) {
					return getString("AssignmentEditorPanel.relation.notSpecified");
				}

				AssignmentEditorDto object = getModel().getObject();
				if (object.getTargetRef() != null) {
					QName relation = object.getTargetRef() != null ? object.getTargetRef().getRelation() : null;
					String propertyKey = WebComponentUtil.getRelationHeaderLabelKey(relation);
					return createStringResource(propertyKey).getString();
				} else {
					return "";
				}
			}
		});
		relationLabel.setOutputMarkupId(true);
		relationLabel.setOutputMarkupPlaceholderTag(true);
		relationLabel.add(new VisibleEnableBehaviour() {

			@Override
			public boolean isVisible() {
				return !isCreatingNewAssignment();
			}
		});
		relationContainer.add(relationLabel);

		WebMarkupContainer tenantRefContainer = createTenantContainer();
		tenantRefContainer.add(new VisibleEnableBehaviour(){
			private static final long serialVersionUID = 1L;

			@Override
			public boolean isVisible(){
				return isItemAllowed(new ItemPath(FocusType.F_ASSIGNMENT, AssignmentType.F_TENANT_REF));
			}
		});
		body.add(tenantRefContainer);

		WebMarkupContainer orgRefContainer = createOrgContainer();
		orgRefContainer.add(new VisibleEnableBehaviour(){
			private static final long serialVersionUID = 1L;

			@Override
			public boolean isVisible(){
				return isItemAllowed(new ItemPath(FocusType.F_ASSIGNMENT, AssignmentType.F_ORG_REF));
			}
		});
		body.add(orgRefContainer);
		propertyContainer.add(new VisibleEnableBehaviour(){
			private static final long serialVersionUID = 1L;

			@Override
			public boolean isVisible(){
				return isItemAllowed(new ItemPath(FocusType.F_ASSIGNMENT, AssignmentType.F_DESCRIPTION))
						|| isItemAllowed(new ItemPath(FocusType.F_ASSIGNMENT, AssignmentType.F_TARGET_REF,
						ObjectReferenceType.F_RELATION))
						|| isItemAllowed(new ItemPath(FocusType.F_ASSIGNMENT, AssignmentType.F_FOCUS_TYPE))
						|| isItemAllowed(new ItemPath(FocusType.F_ASSIGNMENT, AssignmentType.F_TENANT_REF))
						|| isItemAllowed(new ItemPath(FocusType.F_ASSIGNMENT, AssignmentType.F_ORG_REF));
			}

		});

		WebMarkupContainer activationBlock = new WebMarkupContainer(ID_ACTIVATION_BLOCK);
		body.add(activationBlock);

		WebMarkupContainer adminStatusContainer = new WebMarkupContainer(ID_ADMIN_STATUS_CONTAINER);
		adminStatusContainer.setOutputMarkupId(true);
		adminStatusContainer.add(new VisibleEnableBehaviour() {
			private static final long serialVersionUID = 1L;

			@Override
			public boolean isVisible() {
				return isItemAllowed(new ItemPath(FocusType.F_ASSIGNMENT, AssignmentType.F_ACTIVATION,
						ActivationType.F_ADMINISTRATIVE_STATUS));
			}
		});
		activationBlock.add(adminStatusContainer);

		DropDownChoicePanel administrativeStatus = WebComponentUtil.createEnumPanel(
				ActivationStatusType.class, ID_ADMINISTRATIVE_STATUS,
				new PropertyModel<ActivationStatusType>(getModel(), AssignmentEditorDto.F_ACTIVATION + "."
						+ ActivationType.F_ADMINISTRATIVE_STATUS.getLocalPart()),
				this);
		administrativeStatus.add(new VisibleEnableBehaviour(){
			private static final long serialVersionUID = 1L;

			@Override
			public boolean isEnabled(){
				return getModel().getObject().isEditable();
			}
		});
		adminStatusContainer.add(administrativeStatus);

		WebMarkupContainer validFromContainer = new WebMarkupContainer(ID_VALID_FROM_CONTAINER);
		validFromContainer.setOutputMarkupId(true);
		validFromContainer.add(new VisibleEnableBehaviour() {
			private static final long serialVersionUID = 1L;

			@Override
			public boolean isVisible() {
				return isItemAllowed(new ItemPath(FocusType.F_ASSIGNMENT, AssignmentType.F_ACTIVATION,
						ActivationType.F_VALID_FROM));
			}
		});
		activationBlock.add(validFromContainer);

		DateInput validFrom = new DateInput(ID_VALID_FROM,
				AssignmentsUtil.createDateModel(new PropertyModel<>(getModel(),
						AssignmentEditorDto.F_ACTIVATION + ".validFrom")));
		validFrom.add(new VisibleEnableBehaviour(){
			private static final long serialVersionUID = 1L;

			@Override
			public boolean isEnabled(){
				return getModel().getObject().isEditable();
			}
		});
		validFromContainer.add(validFrom);

		WebMarkupContainer validToContainer = new WebMarkupContainer(ID_VALID_TO_CONTAINER);
		validToContainer.setOutputMarkupId(true);
		validToContainer.add(new VisibleEnableBehaviour() {
			private static final long serialVersionUID = 1L;

			@Override
			public boolean isVisible() {
				return isItemAllowed(new ItemPath(FocusType.F_ASSIGNMENT, AssignmentType.F_ACTIVATION,
						ActivationType.F_VALID_TO));
			}
		});
		activationBlock.add(validToContainer);

		DateInput validTo = new DateInput(ID_VALID_TO,
				AssignmentsUtil.createDateModel(new PropertyModel<>(getModel(),
						AssignmentEditorDto.F_ACTIVATION + ".validTo")));
		validTo.add(new VisibleEnableBehaviour(){
			private static final long serialVersionUID = 1L;

			@Override
			public boolean isEnabled(){
				return getModel().getObject().isEditable();
			}
		});
		validToContainer.add(validTo);

		activationBlock.add(new VisibleEnableBehaviour() {

			@Override
			public boolean isVisible() {
				// enabled activation in assignments for now.
				return true;
			}
		});

		WebMarkupContainer targetContainer = new WebMarkupContainer(ID_TARGET_CONTAINER);
		targetContainer.add(new VisibleEnableBehaviour() {

			@Override
			public boolean isVisible() {
				if (!isItemAllowed(new ItemPath(FocusType.F_ASSIGNMENT, AssignmentType.F_TARGET))){
					return false;
				}
				AssignmentEditorDto dto = getModel().getObject();
				return !AssignmentEditorDtoType.CONSTRUCTION.equals(dto.getType());
			}
		});
		body.add(targetContainer);

		Label target = new Label(ID_TARGET, createTargetModel());
		targetContainer.add(target);

		WebMarkupContainer constructionContainer = new WebMarkupContainer(ID_CONSTRUCTION_CONTAINER);
		constructionContainer.add(new VisibleEnableBehaviour() {

			@Override
			public boolean isVisible() {
				AssignmentEditorDto dto = getModel().getObject();
				return AssignmentEditorDtoType.CONSTRUCTION.equals(dto.getType());
			}
		});
		body.add(constructionContainer);

		AjaxLink showEmpty = new AjaxLink(ID_SHOW_EMPTY) {

			@Override
			public void onClick(AjaxRequestTarget target) {
				showEmptyPerformed(target);
			}
		};
		constructionContainer.add(showEmpty);

		Label showEmptyLabel = new Label(ID_SHOW_EMPTY_LABEL, createShowEmptyLabel());
		showEmptyLabel.setOutputMarkupId(true);
		showEmpty.add(showEmptyLabel);

		initAttributesLayout(constructionContainer);

		Component metadataPanel;
        if (UserDtoStatus.ADD.equals(getModel().getObject().getStatus()) ||
                getModelObject().getOldValue().asContainerable() == null){
            metadataPanel = new WebMarkupContainer(ID_METADATA_CONTAINER);
        } else {
            metadataPanel = new MetadataPanel(ID_METADATA_CONTAINER, new AbstractReadOnlyModel<MetadataType>() {
                @Override
                public MetadataType getObject() {
                    return getModelObject().getOldValue().getValue().getMetadata();
                }
            }, "", "row");
        }
        metadataPanel.setOutputMarkupId(true);
		metadataPanel.add(new VisibleEnableBehaviour(){
			@Override
			public boolean isVisible(){
				return !UserDtoStatus.ADD.equals(getModel().getObject().getStatus());
			}
		});
		body.add(metadataPanel);

		WebComponentUtil.addAjaxOnUpdateBehavior(body);
	}

	private void updateAssignmentName(AjaxRequestTarget target, Boolean isManager){

		Label nameLabel = new Label(ID_NAME_LABEL, createAssignmentNameLabelModel(isManager));
		nameLabel.setOutputMarkupId(true);
		AjaxLink name = (AjaxLink) get(createComponentPath(ID_HEADER_ROW, ID_NAME));
		name.addOrReplace(nameLabel);
		target.add(name);
	}

	private WebMarkupContainer createTenantContainer() {
		WebMarkupContainer tenantRefContainer = new WebMarkupContainer(ID_CONTAINER_TENANT_REF);
		ChooseTypePanel tenantRef = new ChooseTypePanel(ID_TENANT_CHOOSER,
				new PropertyModel<ObjectViewDto>(getModel(), AssignmentEditorDto.F_TENANT_REF)) {

			@Override
			protected ObjectQuery getChooseQuery() {
				return QueryBuilder.queryFor(OrgType.class, getPageBase().getPrismContext())
						.item(OrgType.F_TENANT).eq(true)
						.build();
			}

			@Override
			protected boolean isSearchEnabled() {
				return true;
			}

			@Override
			protected QName getSearchProperty() {
				return OrgType.F_NAME;
			}
		};
		tenantRef.setPanelEnabled(getModel().getObject().isEditable());
		tenantRefContainer.add(tenantRef);
		tenantRefContainer.add(new VisibleEnableBehaviour() {

			@Override
			public boolean isVisible() {
				AssignmentEditorDto dto = getModel().getObject();
				if (dto != null) {
					if (AssignmentEditorDtoType.ROLE.equals(dto.getType())) {
						return true;
					}
				}

				return false;
			}
		});
		return tenantRefContainer;
	}

	private WebMarkupContainer createOrgContainer() {
		WebMarkupContainer tenantRefContainer = new WebMarkupContainer(ID_CONTAINER_ORG_REF);
		ChooseTypePanel tenantRef = new ChooseTypePanel(ID_ORG_CHOOSER,
				new PropertyModel<ObjectViewDto>(getModel(), AssignmentEditorDto.F_ORG_REF)) {

			@Override
			protected ObjectQuery getChooseQuery() {
				return QueryBuilder.queryFor(OrgType.class, getPageBase().getPrismContext())
						.item(OrgType.F_TENANT).eq(false)
						.or().item(OrgType.F_TENANT).isNull()
						.build();
			}

			@Override
			protected boolean isSearchEnabled() {
				return true;
			}

			@Override
			protected QName getSearchProperty() {
				return OrgType.F_NAME;
			}
		};
		tenantRefContainer.add(tenantRef);
		tenantRef.setEnabled(getModel().getObject().isEditable());
		tenantRefContainer.add(new VisibleEnableBehaviour() {

			@Override
			public boolean isVisible() {
				AssignmentEditorDto dto = getModel().getObject();
				if (dto != null) {
					if (AssignmentEditorDtoType.ROLE.equals(dto.getType())) {
						return true;
					}
				}

				return false;
			}
		});
		return tenantRefContainer;
	}

	private void initAttributesLayout(WebMarkupContainer constructionContainer) {
		WebMarkupContainer attributes = new WebMarkupContainer(ID_ATTRIBUTES);
		attributes.setOutputMarkupId(true);
		attributes.add(new VisibleEnableBehaviour() {

			@Override
			public boolean isVisible() {
				AssignmentEditorDto dto = getModel().getObject();
				return AssignmentEditorDtoType.CONSTRUCTION.equals(dto.getType());
			}
		});
		attributes.setEnabled(getModel().getObject().isEditable());
		constructionContainer.add(attributes);

		ListView<ACAttributeDto> attribute = new ListView<ACAttributeDto>(ID_ATTRIBUTE, attributesModel) {

			@Override
			protected void populateItem(ListItem<ACAttributeDto> listItem) {
				final IModel<ACAttributeDto> attrModel = listItem.getModel();
				ACAttributePanel acAttribute = new ACAttributePanel(ID_AC_ATTRIBUTE, attrModel, ignoreMandatoryAttributes());
				acAttribute.setRenderBodyOnly(true);
				listItem.add(acAttribute);
				listItem.setOutputMarkupId(true);

				listItem.add(new VisibleEnableBehaviour() {

					@Override
					public boolean isVisible() {
						AssignmentEditorDto editorDto = AssignmentEditorPanel.this.getModel().getObject();
						if (editorDto.isShowEmpty()) {
							return true;
						}

						ACAttributeDto dto = attrModel.getObject();
						return !dto.isEmpty();
					}
				});
			}
		};
		attributes.add(attribute);
		// todo extension
	}

	private IModel<String> createShowEmptyLabel() {
		return new AbstractReadOnlyModel<String>() {

			@Override
			public String getObject() {
				AssignmentEditorDto dto = AssignmentEditorPanel.this.getModel().getObject();

				if (dto.isShowEmpty()) {
					return getString("AssignmentEditorPanel.hideEmpty");
				} else {
					return getString("AssignmentEditorPanel.showEmpty");
				}
			}
		};
	}

	private void showEmptyPerformed(AjaxRequestTarget target) {
		AssignmentEditorDto dto = AssignmentEditorPanel.this.getModel().getObject();
		dto.setShowEmpty(!dto.isShowEmpty());

		WebMarkupContainer parent = (WebMarkupContainer) get(
				createComponentPath(ID_BODY, ID_CONSTRUCTION_CONTAINER));

		target.add(parent.get(ID_ATTRIBUTES),
				parent.get(createComponentPath(ID_SHOW_EMPTY, ID_SHOW_EMPTY_LABEL)),
				getPageBase().getFeedbackPanel());
	}

	private List<ACAttributeDto> loadAttributes() {
		AssignmentEditorDto dto = getModel().getObject();

		OperationResult result = new OperationResult(OPERATION_LOAD_ATTRIBUTES);
		List<ACAttributeDto> attributes = new ArrayList<>();
		try {
			ConstructionType construction = WebComponentUtil.getContainerValue(dto.getOldValue(),
					AssignmentType.F_CONSTRUCTION, ConstructionType.class);

			if (construction == null) {
				return attributes;
			}

			PrismObject<ResourceType> resource = construction.getResource() != null
					? construction.getResource().asPrismObject() : null;
			if (resource == null) {
				resource = getReference(construction.getResourceRef(), result);
			}

			PrismContext prismContext = getPageBase().getPrismContext();
			RefinedResourceSchema refinedSchema = RefinedResourceSchemaImpl.getRefinedSchema(resource,
					LayerType.PRESENTATION, prismContext);
			RefinedObjectClassDefinition objectClassDefinition = refinedSchema
					.getRefinedDefinition(ShadowKindType.ACCOUNT, construction.getIntent());

			if (objectClassDefinition == null) {
				return attributes;
			}

			PrismContainerDefinition definition = objectClassDefinition
					.toResourceAttributeContainerDefinition();

			if (LOGGER.isTraceEnabled()) {
				LOGGER.trace("Refined definition for {}\n{}", construction, definition.debugDump());
			}

			List<ResourceAttributeDefinitionType> attrConstructions = construction.getAttribute();

			Collection<ItemDefinition> definitions = definition.getDefinitions();
			for (ItemDefinition attrDef : definitions) {
				if (!(attrDef instanceof PrismPropertyDefinition)) {
					// log skipping or something...
					continue;
				}

				PrismPropertyDefinition propertyDef = (PrismPropertyDefinition) attrDef;
				if (propertyDef.isOperational() || propertyDef.isIgnored()) {
					continue;
				}
				attributes.add(ACAttributeDto.createACAttributeDto(propertyDef,
						findOrCreateValueConstruction(propertyDef, attrConstructions), prismContext));
			}
			result.recordSuccess();
		} catch (Exception ex) {
			LoggingUtils.logUnexpectedException(LOGGER, "Exception occurred during assignment attribute loading", ex);
			result.recordFatalError("Exception occurred during assignment attribute loading.", ex);
		} finally {
			result.recomputeStatus();
		}

		Collections.sort(attributes, new Comparator<ACAttributeDto>() {

			@Override
			public int compare(ACAttributeDto a1, ACAttributeDto a2) {
				return String.CASE_INSENSITIVE_ORDER.compare(a1.getName(), a2.getName());
			}
		});

		if (dto.getAttributes() != null && !dto.getAttributes().isEmpty()) {
			for (ACAttributeDto assignmentAttribute : dto.getAttributes()) {
				for (ACAttributeDto attributeDto : attributes){
					if (attributeDto.getName().equals(assignmentAttribute.getName())){
						attributes.set(attributes.indexOf(attributeDto), assignmentAttribute);
						continue;
					}
				}
			}
		}

		dto.setAttributes(attributes);

		getPageBase().showResult(result, false);

		return dto.getAttributes();
	}

	private PrismObject getReference(ObjectReferenceType ref, OperationResult result) {
		OperationResult subResult = result.createSubresult(OPERATION_LOAD_RESOURCE);
		subResult.addParam("targetRef", ref.getOid());
		PrismObject target = null;
		try {
			Task task = getPageBase().createSimpleTask(OPERATION_LOAD_RESOURCE);
			Class type = ObjectType.class;
			if (ref.getType() != null) {
				type = getPageBase().getPrismContext().getSchemaRegistry()
						.determineCompileTimeClass(ref.getType());
			}
			target = getPageBase().getModelService().getObject(type, ref.getOid(), null, task, subResult);
			subResult.recordSuccess();
		} catch (Exception ex) {
			LoggingUtils.logUnexpectedException(LOGGER, "Couldn't get account construction resource ref", ex);
			subResult.recordFatalError("Couldn't get account construction resource ref.", ex);
		}

		return target;
	}

	private ResourceAttributeDefinitionType findOrCreateValueConstruction(PrismPropertyDefinition attrDef,
			List<ResourceAttributeDefinitionType> attrConstructions) {
		for (ResourceAttributeDefinitionType construction : attrConstructions) {
			if (attrDef.getName().equals(construction.getRef())) {
				return construction;
			}
		}

		ResourceAttributeDefinitionType construction = new ResourceAttributeDefinitionType();
		construction.setRef(new ItemPathType(new ItemPath(attrDef.getName())));

		return construction;
	}

	protected IModel<String> createImageTypeModel(final IModel<AssignmentEditorDto> model) {
		return new AbstractReadOnlyModel<String>() {
			private static final long serialVersionUID = 1L;

			@Override
			public String getObject() {
				AssignmentEditorDto assignmentEditorDto = model.getObject();

				PrismObject targetObject = null;
				try {
					targetObject = getTargetObject(assignmentEditorDto);
				} catch (Exception ex) {
					LoggingUtils.logUnexpectedException(LOGGER, "Couldn't load object", ex);
					// Otherwise ignore, will be pocessed by the fallback code
					// below
				}

				if (targetObject == null) {
					AssignmentEditorDtoType type = assignmentEditorDto.getType();
					return type.getIconCssClass();
				} else {
					return WebComponentUtil.createDefaultIcon(targetObject);
				}
			}
		};
	}

	protected void nameClickPerformed(AjaxRequestTarget target) {
		AssignmentEditorDto dto = getModel().getObject();
		boolean minimized = dto.isMinimized();
		dto.setMinimized(!minimized);

		target.add(this);
	}

	protected IModel<String> createTargetModel() {
		return new LoadableModel<String>(false) {
			private static final long serialVersionUID = 1L;

			@Override
			protected String load() {
				AssignmentEditorDto dto = getModel().getObject();

				PrismObject targetObject;
				try {
					targetObject = getTargetObject(dto);
				} catch (Exception ex) {
					LoggingUtils.logUnexpectedException(LOGGER, "Couldn't load object", ex);
					return getString("AssignmentEditorPanel.loadError");
				}

				if (targetObject == null) {
					return getString("AssignmentEditorPanel.undefined");
				}

				return WebComponentUtil.getName(targetObject);
			}
		};
	}

	private void addRelationDropDown(WebMarkupContainer relationContainer){
		QName assignmentRelation = getModelObject().getTargetRef() != null ? getModelObject().getTargetRef().getRelation() : null;

		RelationDropDownChoicePanel relationDropDown = new RelationDropDownChoicePanel(ID_RELATION,
				assignmentRelation != null ? assignmentRelation : WebComponentUtil.getDefaultRelationOrFail(), getSupportedRelations(), false){
			private static final long serialVersionUID = 1L;

			@Override
			protected void onValueChanged(AjaxRequestTarget target){
				ObjectReferenceType ref = AssignmentEditorPanel.this.getModelObject().getTargetRef();
				if (ref != null){
					ref.setRelation(getRelationValue());
				}
			}
		};		relationDropDown.setEnabled(getModel().getObject().isEditable());
		relationDropDown.add(new VisibleEnableBehaviour() {

			private static final long serialVersionUID = 1L;

			@Override
			public boolean isVisible() {
				return isCreatingNewAssignment();
			}
		});
		relationContainer.add(relationDropDown);

	}

	private List<QName> getSupportedRelations() {
		OperationResult result = new OperationResult("Relations for self service area");
		AssignmentConstraintsType constraints = AssignmentEditorPanel.this.getModelObject().getDefaultAssignmentConstraints();
		if (constraints == null ||
				constraints.isAllowSameTarget() && constraints.isAllowSameRelation()){
			return WebComponentUtil.getCategoryRelationChoices(AreaCategoryType.SELF_SERVICE, getPageBase());
		} else {
			return getModelObject().getNotAssignedRelationsList();
		}
	}

//	protected IModel<RelationTypes> getRelationModel() {
//		return new IModel<RelationTypes>() {
//			private static final long serialVersionUID = 1L;
//
//			@Override
//			public RelationTypes getObject() {
//				if (getModelObject().getTargetRef() == null) {
//					return RelationTypes.MEMBER;
//				}
//				return RelationTypes.getRelationType(getModelObject().getTargetRef().getRelation());
//			}
//
//			@Override
//			public void setObject(RelationTypes newValue) {
//				ObjectReferenceType ref = getModelObject().getTargetRef();
//				if (ref != null){
//					ref.setRelation(newValue.getRelation());
//				}
//			}
//
//			@Override
//			public void detach() {
//
//			}
//		};
//	}

	private <O extends ObjectType> PrismObject<O> getTargetObject(AssignmentEditorDto dto)
			throws ObjectNotFoundException, SchemaException, SecurityViolationException,
			CommunicationException, ConfigurationException, ExpressionEvaluationException {
		PrismContainerValue<AssignmentType> assignment = dto.getOldValue();

		PrismReference targetRef = assignment.findReference(AssignmentType.F_TARGET_REF);
		if (targetRef == null) {
			return null;
		}

		PrismReferenceValue refValue = targetRef.getValue();
		if (refValue != null && refValue.getObject() != null) {
			PrismObject object = refValue.getObject();
			return object;
		}

		String oid = targetRef.getOid();
		OperationResult result = new OperationResult(OPERATION_LOAD_OBJECT);

		PageBase page = getPageBase();
		ModelService model = page.getMidpointApplication().getModel();
		Task task = page.createSimpleTask(OPERATION_LOAD_OBJECT);

		Collection<SelectorOptions<GetOperationOptions>> options = SelectorOptions
				.createCollection(GetOperationOptions.createNoFetch());
		Class<O> type = (Class<O>) ObjectType.class;
		if (refValue.getTargetType() != null) {
			type = getPageBase().getPrismContext().getSchemaRegistry()
					.determineCompileTimeClass(refValue.getTargetType());
		}
		PrismObject<O> object = model.getObject(type, oid, options, task, result);
		refValue.setObject(object);
		return object;
	}

	private void showErrorPerformed(AjaxRequestTarget target) {
		error(getString("AssignmentEditorPanel.targetError"));
		target.add(getPageBase().getFeedbackPanel());
	}

	/**
	 * Override to provide the information if object that contains this
	 * assignment is being edited or created.
	 */
	protected boolean isCreatingNewAssignment() {
		if (getModelObject() == null) {
			return false;
		}

		return UserDtoStatus.ADD.equals(getModelObject().getStatus());
	}

	protected boolean ignoreMandatoryAttributes(){
		return false;
	}

	private void initDecisionsModel() {
		itemSecurityConstraintsModel = new LoadableDetachableModel<ItemSecurityConstraints>() {
			@Override
			protected ItemSecurityConstraints load() {
				return loadSecurityConstraints();
			}
		};

	}

	private boolean isItemAllowed(ItemPath itemPath) {
		ItemSecurityConstraints constraints = itemSecurityConstraintsModel.getObject();
		if (itemPath == null || constraints == null) {
			return true;
		}
		AuthorizationDecisionType decision = constraints.findItemDecision(itemPath);
		return AuthorizationDecisionType.ALLOW.equals(decision);
	}

	private ItemSecurityConstraints loadSecurityConstraints() {
		PageBase pageBase = getPageBase();
		if (pageBase == null || getModelObject().getTargetRef() == null){
			return null;
		}
		PrismObject<? extends FocusType> operationObject = null;
		if (pageBase instanceof PageAdminFocus){
			operationObject = ((PageAdminFocus)pageBase).getObjectWrapper().getObject();
		} else if ((pageBase instanceof PageAssignmentDetails || pageBase instanceof PageAssignmentsList) //shopping cart assignment details panels
				&& !pageBase.getSessionStorage().getRoleCatalog().isMultiUserRequest()){
			List<UserType> targetUserList = pageBase.getSessionStorage().getRoleCatalog().getTargetUserList();
			if (targetUserList == null || targetUserList.size() == 0){
				operationObject = pageBase.loadUserSelf();
			} else {
				operationObject = targetUserList.get(0).asPrismObject();
			}
		}
		if (operationObject == null){
			return null;
		}
		String targetObjectOid = getModelObject().getTargetRef().getOid();

		Task task = pageBase.createSimpleTask(OPERATION_LOAD_TARGET_OBJECT);
		OperationResult result = new OperationResult(OPERATION_LOAD_TARGET_OBJECT);
		PrismObject<AbstractRoleType> targetRefObject = WebModelServiceUtils.loadObject(AbstractRoleType.class,
				targetObjectOid, pageBase, task, result);
		ItemSecurityConstraints constraints = null;
		try{
			constraints =
					pageBase.getModelInteractionService().getAllowedRequestAssignmentItems(operationObject, targetRefObject, task, result);

		} catch (SchemaException | SecurityViolationException | ObjectNotFoundException | ExpressionEvaluationException | CommunicationException | ConfigurationException ex){
			LoggingUtils.logUnexpectedException(LOGGER, "Couldn't load security constraints for assignment items.", ex);
		}
		return constraints;
	}

}


text:
import {runBenchmark, verifyNoBrowserErrors} from 'e2e_util/perf_util';
import {$, browser} from 'protractor';

import {Benchmark, Benchmarks, CreateBtn, DestroyBtn, DetectChangesBtn, RootEl} from './tree_data';

describe('tree benchmark perf', () => {

  let _oldRootEl: any;
  beforeEach(() => _oldRootEl = browser.rootEl);

  afterEach(() => {
    browser.rootEl = _oldRootEl;
    verifyNoBrowserErrors();
  });

  Benchmarks.forEach(benchmark => {
    describe(benchmark.id, () => {
      // This is actually a destroyOnly benchmark
      it('should work for createOnly', (done) => {
        runTreeBenchmark({
          id: 'createOnly',
          benchmark,
          prepare: () => $(CreateBtn).click(),
          work: () => $(DestroyBtn).click()
        }).then(done, done.fail);
      });

      it('should work for createOnlyForReal', (done) => {
        runTreeBenchmark({
          id: 'createOnlyForReal',
          benchmark,
          prepare: () => $(DestroyBtn).click(),
          work: () => $(CreateBtn).click()
        }).then(done, done.fail);
      });

      it('should work for createDestroy', (done) => {
        runTreeBenchmark({
          id: 'createDestroy',
          benchmark,
          work: () => {
            $(DestroyBtn).click();
            $(CreateBtn).click();
          }
        }).then(done, done.fail);
      });

      it('should work for update', (done) => {
        runTreeBenchmark({id: 'update', benchmark, work: () => $(CreateBtn).click()})
            .then(done, done.fail);
      });

      if (benchmark.buttons.indexOf(DetectChangesBtn) !== -1) {
        it('should work for detectChanges', (done) => {
          runTreeBenchmark({
            id: 'detectChanges',
            benchmark,
            work: () => $(DetectChangesBtn).click(),
            setup: () => $(DestroyBtn).click()
          }).then(done, done.fail);
        });
      }

    });
  });
});

function runTreeBenchmark({id, benchmark, prepare, setup, work}: {
  id: string; benchmark: Benchmark, prepare ? () : void; setup ? () : void; work(): void;
}) {
  let params = [{name: 'depth', value: 11}];
  if (benchmark.extraParams) {
    params = params.concat(benchmark.extraParams);
  }
  browser.rootEl = RootEl;
  return runBenchmark({
    id: `${benchmark.id}.${id}`,
    url: benchmark.url,
    ignoreBrowserSynchronization: benchmark.ignoreBrowserSynchronization,
    params: params,
    work: work,
    prepare: prepare,
    setup: setup
  });
}

redpajama_set_name: RedPajamaGithub


text:
package state_test

import (
	. "github.com/cloudfoundry/bosh-init/deployment/instance/state"

	mock_blobstore "github.com/cloudfoundry/bosh-init/blobstore/mocks"
	mock_deployment_release "github.com/cloudfoundry/bosh-init/deployment/release/mocks"
	mock_state_job "github.com/cloudfoundry/bosh-init/state/job/mocks"
	mock_template "github.com/cloudfoundry/bosh-init/templatescompiler/mocks"
	"github.com/golang/mock/gomock"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"

	biac "github.com/cloudfoundry/bosh-agent/agentclient"
	bias "github.com/cloudfoundry/bosh-agent/agentclient/applyspec"
	bideplmanifest "github.com/cloudfoundry/bosh-init/deployment/manifest"
	bireljob "github.com/cloudfoundry/bosh-init/release/job"
	birelpkg "github.com/cloudfoundry/bosh-init/release/pkg"
	bistatejob "github.com/cloudfoundry/bosh-init/state/job"
	boshlog "github.com/cloudfoundry/bosh-utils/logger"
	biproperty "github.com/cloudfoundry/bosh-utils/property"

	fakebiui "github.com/cloudfoundry/bosh-init/ui/fakes"
)

var _ = Describe("Builder", describeBuilder)

func describeBuilder() {
	var mockCtrl *gomock.Controller

	BeforeEach(func() {
		mockCtrl = gomock.NewController(GinkgoT())
	})

	AfterEach(func() {
		mockCtrl.Finish()
	})

	var (
		logger boshlog.Logger

		mockReleaseJobResolver *mock_deployment_release.MockJobResolver
		mockDependencyCompiler *mock_state_job.MockDependencyCompiler
		mockJobListRenderer    *mock_template.MockJobListRenderer
		mockCompressor         *mock_template.MockRenderedJobListCompressor
		mockBlobstore          *mock_blobstore.MockBlobstore

		stateBuilder Builder
	)

	BeforeEach(func() {
		logger = boshlog.NewLogger(boshlog.LevelNone)

		mockReleaseJobResolver = mock_deployment_release.NewMockJobResolver(mockCtrl)
		mockDependencyCompiler = mock_state_job.NewMockDependencyCompiler(mockCtrl)
		mockJobListRenderer = mock_template.NewMockJobListRenderer(mockCtrl)
		mockCompressor = mock_template.NewMockRenderedJobListCompressor(mockCtrl)
		mockBlobstore = mock_blobstore.NewMockBlobstore(mockCtrl)
	})

	Describe("BuildInitialState", func() {
		var (
			jobName            string
			instanceID         int
			deploymentManifest bideplmanifest.Manifest
		)

		BeforeEach(func() {
			jobName = "fake-deployment-job-name"
			instanceID = 0

			deploymentManifest = bideplmanifest.Manifest{
				Name: "fake-deployment-name",
				Jobs: []bideplmanifest.Job{
					{
						Name: "fake-deployment-job-name",
						Networks: []bideplmanifest.JobNetwork{
							{
								Name:      "fake-network-name",
								StaticIPs: []string{"1.2.3.4"},
							},
						},
						Templates: []bideplmanifest.ReleaseJobRef{
							{
								Name:    "fake-release-job-name",
								Release: "fake-release-name",
								Properties: &biproperty.Map{
									"fake-template-property": "fake-template-property-value",
								},
							},
						},
						Properties: biproperty.Map{
							"fake-job-property": "fake-job-property-value",
						},
					},
				},
				Networks: []bideplmanifest.Network{
					{
						Name: "fake-network-name",
						Type: "fake-network-type",
						CloudProperties: biproperty.Map{
							"fake-network-cloud-property": "fake-network-cloud-property-value",
						},
					},
				},
				Properties: biproperty.Map{
					"fake-job-property": "fake-global-property-value", //overridden by job property value
				},
			}

			stateBuilder = NewBuilder(
				mockReleaseJobResolver,
				mockDependencyCompiler,
				mockJobListRenderer,
				mockCompressor,
				mockBlobstore,
				logger,
			)
		})

		It("generates an initial apply spec", func() {
			state, err := stateBuilder.BuildInitialState(jobName, instanceID, deploymentManifest)
			Expect(err).ToNot(HaveOccurred())

			Expect(state.ToApplySpec()).To(Equal(bias.ApplySpec{
				Deployment: "fake-deployment-name",
				Index:      0,
				Job: bias.Job{
					Name:      "fake-deployment-job-name",
					Templates: []bias.Blob{},
				},
				Packages: map[string]bias.Blob{},
				Networks: map[string]interface{}{
					"fake-network-name": map[string]interface{}{
						"type":    "fake-network-type",
						"default": []bideplmanifest.NetworkDefault{"dns", "gateway"},
						"ip":      "1.2.3.4",
						"cloud_properties": biproperty.Map{
							"fake-network-cloud-property": "fake-network-cloud-property-value",
						},
					},
				},
			}))
		})
	})

	Describe("Build", func() {
		var (
			mockRenderedJobList        *mock_template.MockRenderedJobList
			mockRenderedJobListArchive *mock_template.MockRenderedJobListArchive

			jobName            string
			instanceID         int
			deploymentManifest bideplmanifest.Manifest
			fakeStage          *fakebiui.FakeStage

			releasePackageLibyaml *birelpkg.Package
			releasePackageRuby    *birelpkg.Package
			releasePackageCPI     *birelpkg.Package

			agentState biac.AgentState
			expectedIP string

			expectCompile *gomock.Call
		)

		BeforeEach(func() {
			mockRenderedJobList = mock_template.NewMockRenderedJobList(mockCtrl)
			mockRenderedJobListArchive = mock_template.NewMockRenderedJobListArchive(mockCtrl)

			jobName = "fake-deployment-job-name"
			instanceID = 0
			expectedIP = "1.2.3.4"

			deploymentManifest = bideplmanifest.Manifest{
				Name: "fake-deployment-name",
				Jobs: []bideplmanifest.Job{
					{
						Name: "fake-deployment-job-name",
						Networks: []bideplmanifest.JobNetwork{
							{
								Name:      "fake-network-name",
								StaticIPs: []string{"1.2.3.4"},
							},
						},
						Templates: []bideplmanifest.ReleaseJobRef{
							{
								Name:    "fake-release-job-name",
								Release: "fake-release-name",
								Properties: &biproperty.Map{
									"fake-template-property": "fake-template-property-value",
								},
							},
						},
						Properties: biproperty.Map{
							"fake-job-property": "fake-job-property-value",
						},
					},
				},
				Networks: []bideplmanifest.Network{
					{
						Name: "fake-network-name",
						Type: "fake-network-type",
						CloudProperties: biproperty.Map{
							"fake-network-cloud-property": "fake-network-cloud-property-value",
						},
					},
				},
				Properties: biproperty.Map{
					"fake-job-property": "fake-global-property-value", //overridden by job property value
				},
			}

			agentState = biac.AgentState{
				NetworkSpecs: map[string]biac.NetworkSpec{
					"fake-network-name": biac.NetworkSpec{
						IP: "1.2.3.5",
					},
				},
			}

			fakeStage = fakebiui.NewFakeStage()

			stateBuilder = NewBuilder(
				mockReleaseJobResolver,
				mockDependencyCompiler,
				mockJobListRenderer,
				mockCompressor,
				mockBlobstore,
				logger,
			)

			releasePackageLibyaml = &birelpkg.Package{
				Name:         "libyaml",
				Fingerprint:  "fake-package-source-fingerprint-libyaml",
				SHA1:         "fake-package-source-sha1-libyaml",
				Dependencies: []*birelpkg.Package{},
				ArchivePath:  "fake-package-archive-path-libyaml", // only required by compiler...
			}
			releasePackageRuby = &birelpkg.Package{
				Name:         "ruby",
				Fingerprint:  "fake-package-source-fingerprint-ruby",
				SHA1:         "fake-package-source-sha1-ruby",
				Dependencies: []*birelpkg.Package{releasePackageLibyaml},
				ArchivePath:  "fake-package-archive-path-ruby", // only required by compiler...
			}
			releasePackageCPI = &birelpkg.Package{
				Name:         "cpi",
				Fingerprint:  "fake-package-source-fingerprint-cpi",
				SHA1:         "fake-package-source-sha1-cpi",
				Dependencies: []*birelpkg.Package{releasePackageRuby},
				ArchivePath:  "fake-package-archive-path-cpi", // only required by compiler...
			}
		})

		JustBeforeEach(func() {
			releaseJob := bireljob.Job{
				Name:        "fake-release-job-name",
				Fingerprint: "fake-release-job-source-fingerprint",
				Packages:    []*birelpkg.Package{releasePackageCPI, releasePackageRuby},
			}
			mockReleaseJobResolver.EXPECT().Resolve("fake-release-job-name", "fake-release-name").Return(releaseJob, nil)

			releaseJobs := []bireljob.Job{releaseJob}
			compiledPackageRefs := []bistatejob.CompiledPackageRef{
				{
					Name:        "libyaml",
					Version:     "fake-package-source-fingerprint-libyaml",
					BlobstoreID: "fake-package-compiled-archive-blob-id-libyaml",
					SHA1:        "fake-package-compiled-archive-sha1-libyaml",
				},
				{
					Name:        "ruby",
					Version:     "fake-package-source-fingerprint-ruby",
					BlobstoreID: "fake-package-compiled-archive-blob-id-ruby",
					SHA1:        "fake-package-compiled-archive-sha1-ruby",
				},
				{
					Name:        "cpi",
					Version:     "fake-package-source-fingerprint-cpi",
					BlobstoreID: "fake-package-compiled-archive-blob-id-cpi",
					SHA1:        "fake-package-compiled-archive-sha1-cpi",
				},
			}
			expectCompile = mockDependencyCompiler.EXPECT().Compile(releaseJobs, fakeStage).Return(compiledPackageRefs, nil).AnyTimes()

			releaseJobProperties := map[string]*biproperty.Map{
				"fake-release-job-name": &biproperty.Map{
					"fake-template-property": "fake-template-property-value",
				},
			}

			jobProperties := biproperty.Map{
				"fake-job-property": "fake-job-property-value",
			}
			globalProperties := biproperty.Map{
				"fake-job-property": "fake-global-property-value",
			}

			mockJobListRenderer.EXPECT().Render(releaseJobs, releaseJobProperties, jobProperties, globalProperties, "fake-deployment-name", expectedIP).Return(mockRenderedJobList, nil)

			mockRenderedJobList.EXPECT().DeleteSilently()

			mockCompressor.EXPECT().Compress(mockRenderedJobList).Return(mockRenderedJobListArchive, nil)

			mockRenderedJobListArchive.EXPECT().DeleteSilently()

			mockRenderedJobListArchive.EXPECT().Path().Return("fake-rendered-job-list-archive-path")
			mockRenderedJobListArchive.EXPECT().SHA1().Return("fake-rendered-job-list-archive-sha1")
			mockRenderedJobListArchive.EXPECT().Fingerprint().Return("fake-rendered-job-list-fingerprint")

			mockBlobstore.EXPECT().Add("fake-rendered-job-list-archive-path").Return("fake-rendered-job-list-archive-blob-id", nil)
		})

		It("compiles the dependencies of the jobs", func() {
			expectCompile.Times(1)

			_, err := stateBuilder.Build(jobName, instanceID, deploymentManifest, fakeStage, agentState)
			Expect(err).ToNot(HaveOccurred())
		})

		It("builds a new instance state with zero-to-many networks", func() {
			state, err := stateBuilder.Build(jobName, instanceID, deploymentManifest, fakeStage, agentState)
			Expect(err).ToNot(HaveOccurred())

			Expect(state.NetworkInterfaces()).To(ContainElement(NetworkRef{
				Name: "fake-network-name",
				Interface: map[string]interface{}{
					"type":    "fake-network-type",
					"default": []bideplmanifest.NetworkDefault{"dns", "gateway"},
					"ip":      "1.2.3.4",
					"cloud_properties": biproperty.Map{
						"fake-network-cloud-property": "fake-network-cloud-property-value",
					},
				},
			}))
			Expect(state.NetworkInterfaces()).To(HaveLen(1))
		})

		Context("dynamic network without IP address", func() {
			Context("single network", func() {
				BeforeEach(func() {
					deploymentManifest.Jobs[0].Networks[0].StaticIPs = nil
					deploymentManifest.Networks[0].Type = "dynamic"
					expectedIP = "1.2.3.5"
				})

				It("should not fail", func() {
					state, err := stateBuilder.Build(jobName, instanceID, deploymentManifest, fakeStage, agentState)
					Expect(err).ToNot(HaveOccurred())

					Expect(state.NetworkInterfaces()).To(ContainElement(NetworkRef{
						Name: "fake-network-name",
						Interface: map[string]interface{}{
							"type":    "dynamic",
							"default": []bideplmanifest.NetworkDefault{"dns", "gateway"},
							"cloud_properties": biproperty.Map{
								"fake-network-cloud-property": "fake-network-cloud-property-value",
							},
						},
					}))
					Expect(state.NetworkInterfaces()).To(HaveLen(1))
				})
			})

			Context("multiple networks", func() {
				BeforeEach(func() {
					expectedIP = "1.2.3.6"
					deploymentManifest.Networks = append(
						deploymentManifest.Networks,
						bideplmanifest.Network{
							Name: "fake-dynamic-network-name",
							Type: "dynamic",
							CloudProperties: biproperty.Map{
								"fake-network-cloud-property": "fake-network-cloud-property-value",
							},
						},
					)
					deploymentManifest.Jobs[0].Networks = append(
						deploymentManifest.Jobs[0].Networks,
						bideplmanifest.JobNetwork{
							Name:     "fake-dynamic-network-name",
							Defaults: []bideplmanifest.NetworkDefault{bideplmanifest.NetworkDefaultDNS, bideplmanifest.NetworkDefaultGateway},
						},
					)
					agentState.NetworkSpecs["fake-dynamic-network-name"] = biac.NetworkSpec{
						IP: "1.2.3.6",
					}
				})

				It("should not fail", func() {
					state, err := stateBuilder.Build(jobName, instanceID, deploymentManifest, fakeStage, agentState)
					Expect(err).ToNot(HaveOccurred())

					Expect(state.NetworkInterfaces()).To(ContainElement(NetworkRef{
						Name: "fake-dynamic-network-name",
						Interface: map[string]interface{}{
							"type":    "dynamic",
							"default": []bideplmanifest.NetworkDefault{"dns", "gateway"},
							"cloud_properties": biproperty.Map{
								"fake-network-cloud-property": "fake-network-cloud-property-value",
							},
						},
					}))
					Expect(state.NetworkInterfaces()).To(HaveLen(2))
				})
			})
		})

		It("builds a new instance state with zero-to-many rendered jobs from one or more releases", func() {
			state, err := stateBuilder.Build(jobName, instanceID, deploymentManifest, fakeStage, agentState)
			Expect(err).ToNot(HaveOccurred())

			Expect(state.RenderedJobs()).To(ContainElement(JobRef{
				Name:    "fake-release-job-name",
				Version: "fake-release-job-source-fingerprint",
			}))

			// multiple jobs are rendered in a single archive
			Expect(state.RenderedJobListArchive()).To(Equal(BlobRef{
				BlobstoreID: "fake-rendered-job-list-archive-blob-id",
				SHA1:        "fake-rendered-job-list-archive-sha1",
			}))
			Expect(state.RenderedJobs()).To(HaveLen(1))
		})

		It("prints ui stages for compiling packages and rendering job templates", func() {
			_, err := stateBuilder.Build(jobName, instanceID, deploymentManifest, fakeStage, agentState)
			Expect(err).ToNot(HaveOccurred())

			Expect(fakeStage.PerformCalls).To(Equal([]*fakebiui.PerformCall{
				// compile stages not produced by mockDependencyCompiler
				{Name: "Rendering job templates"},
			}))
		})

		It("builds a new instance state with the compiled packages required by the release jobs", func() {
			state, err := stateBuilder.Build(jobName, instanceID, deploymentManifest, fakeStage, agentState)
			Expect(err).ToNot(HaveOccurred())

			Expect(state.CompiledPackages()).To(ContainElement(PackageRef{
				Name:    "cpi",
				Version: "fake-package-source-fingerprint-cpi",
				Archive: BlobRef{
					SHA1:        "fake-package-compiled-archive-sha1-cpi",
					BlobstoreID: "fake-package-compiled-archive-blob-id-cpi",
				},
			}))
			Expect(state.CompiledPackages()).To(ContainElement(PackageRef{
				Name:    "ruby",
				Version: "fake-package-source-fingerprint-ruby",
				Archive: BlobRef{
					SHA1:        "fake-package-compiled-archive-sha1-ruby",
					BlobstoreID: "fake-package-compiled-archive-blob-id-ruby",
				},
			}))
		})

		It("builds a new instance state that includes transitively dependent compiled packages", func() {
			state, err := stateBuilder.Build(jobName, instanceID, deploymentManifest, fakeStage, agentState)
			Expect(err).ToNot(HaveOccurred())

			Expect(state.CompiledPackages()).To(ContainElement(PackageRef{
				Name:    "cpi",
				Version: "fake-package-source-fingerprint-cpi",
				Archive: BlobRef{
					SHA1:        "fake-package-compiled-archive-sha1-cpi",
					BlobstoreID: "fake-package-compiled-archive-blob-id-cpi",
				},
			}))
			Expect(state.CompiledPackages()).To(ContainElement(PackageRef{
				Name:    "ruby",
				Version: "fake-package-source-fingerprint-ruby",
				Archive: BlobRef{
					SHA1:        "fake-package-compiled-archive-sha1-ruby",
					BlobstoreID: "fake-package-compiled-archive-blob-id-ruby",
				},
			}))
			Expect(state.CompiledPackages()).To(ContainElement(PackageRef{
				Name:    "libyaml",
				Version: "fake-package-source-fingerprint-libyaml",
				Archive: BlobRef{
					SHA1:        "fake-package-compiled-archive-sha1-libyaml",
					BlobstoreID: "fake-package-compiled-archive-blob-id-libyaml",
				},
			}))
			Expect(state.CompiledPackages()).To(HaveLen(3))
		})

		Context("when multiple packages have the same dependency", func() {
			BeforeEach(func() {
				releasePackageRuby.Dependencies = append(releasePackageRuby.Dependencies, releasePackageLibyaml)
			})

			It("does not recompile dependant packages", func() {
				state, err := stateBuilder.Build(jobName, instanceID, deploymentManifest, fakeStage, agentState)
				Expect(err).ToNot(HaveOccurred())

				Expect(state.CompiledPackages()).To(ContainElement(PackageRef{
					Name:    "cpi",
					Version: "fake-package-source-fingerprint-cpi",
					Archive: BlobRef{
						SHA1:        "fake-package-compiled-archive-sha1-cpi",
						BlobstoreID: "fake-package-compiled-archive-blob-id-cpi",
					},
				}))
				Expect(state.CompiledPackages()).To(ContainElement(PackageRef{
					Name:    "ruby",
					Version: "fake-package-source-fingerprint-ruby",
					Archive: BlobRef{
						SHA1:        "fake-package-compiled-archive-sha1-ruby",
						BlobstoreID: "fake-package-compiled-archive-blob-id-ruby",
					},
				}))
				Expect(state.CompiledPackages()).To(ContainElement(PackageRef{
					Name:    "libyaml",
					Version: "fake-package-source-fingerprint-libyaml",
					Archive: BlobRef{
						SHA1:        "fake-package-compiled-archive-sha1-libyaml",
						BlobstoreID: "fake-package-compiled-archive-blob-id-libyaml",
					},
				}))
				Expect(state.CompiledPackages()).To(HaveLen(3))
			})
		})

		It("builds an instance state that can be converted to an ApplySpec", func() {
			state, err := stateBuilder.Build(jobName, instanceID, deploymentManifest, fakeStage, agentState)
			Expect(err).ToNot(HaveOccurred())

			Expect(state.ToApplySpec()).To(Equal(bias.ApplySpec{
				Deployment: "fake-deployment-name",
				Index:      0,
				Networks: map[string]interface{}{
					"fake-network-name": map[string]interface{}{
						"type":    "fake-network-type",
						"default": []bideplmanifest.NetworkDefault{"dns", "gateway"},
						"ip":      "1.2.3.4",
						"cloud_properties": biproperty.Map{
							"fake-network-cloud-property": "fake-network-cloud-property-value",
						},
					},
				},
				Job: bias.Job{
					Name: "fake-deployment-job-name",
					Templates: []bias.Blob{
						{
							Name:    "fake-release-job-name",
							Version: "fake-release-job-source-fingerprint",
						},
					},
				},
				Packages: map[string]bias.Blob{
					"cpi": bias.Blob{
						Name:        "cpi",
						Version:     "fake-package-source-fingerprint-cpi",
						SHA1:        "fake-package-compiled-archive-sha1-cpi",
						BlobstoreID: "fake-package-compiled-archive-blob-id-cpi",
					},
					"ruby": bias.Blob{
						Name:        "ruby",
						Version:     "fake-package-source-fingerprint-ruby",
						SHA1:        "fake-package-compiled-archive-sha1-ruby",
						BlobstoreID: "fake-package-compiled-archive-blob-id-ruby",
					},
					"libyaml": bias.Blob{
						Name:        "libyaml",
						Version:     "fake-package-source-fingerprint-libyaml",
						SHA1:        "fake-package-compiled-archive-sha1-libyaml",
						BlobstoreID: "fake-package-compiled-archive-blob-id-libyaml",
					},
				},
				RenderedTemplatesArchive: bias.RenderedTemplatesArchiveSpec{
					BlobstoreID: "fake-rendered-job-list-archive-blob-id",
					SHA1:        "fake-rendered-job-list-archive-sha1",
				},
				ConfigurationHash: "fake-rendered-job-list-fingerprint",
			}))
		})
	})
}


text:
const returnsUndefined = () => void 0;
const returnsNull = () => null;
const returnsFalse = () => false;
const returnsFunction = () => () => {};

expect(() => class { @returnsNull m() {} }).toThrow("method decorators must return a function or void 0")
expect(() => class { @returnsFalse m() {} }).toThrow("method decorators must return a function or void 0")
expect(() => class { @returnsFunction m() {} }).not.toThrow();
expect(() => class { @returnsUndefined m() {} }).not.toThrow();


text:
package com.microsoft.azure.cognitiveservices.search.imagesearch.models;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeName;
import com.fasterxml.jackson.annotation.JsonSubTypes;

/**
 * Defines a media object.
 */
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "_type", defaultImpl = MediaObject.class)
@JsonTypeName("MediaObject")
@JsonSubTypes({
    @JsonSubTypes.Type(name = "ImageObject", value = ImageObject.class)
})
public class MediaObject extends CreativeWork {
    /**
     * Original URL to retrieve the source (file) for the media object (e.g the
     * source URL for the image).
     */
    @JsonProperty(value = "contentUrl", access = JsonProperty.Access.WRITE_ONLY)
    private String contentUrl;

    /**
     * URL of the page that hosts the media object.
     */
    @JsonProperty(value = "hostPageUrl", access = JsonProperty.Access.WRITE_ONLY)
    private String hostPageUrl;

    /**
     * Size of the media object content (use format "value unit" e.g "1024 B").
     */
    @JsonProperty(value = "contentSize", access = JsonProperty.Access.WRITE_ONLY)
    private String contentSize;

    /**
     * Encoding format (e.g mp3, mp4, jpeg, etc).
     */
    @JsonProperty(value = "encodingFormat", access = JsonProperty.Access.WRITE_ONLY)
    private String encodingFormat;

    /**
     * Display URL of the page that hosts the media object.
     */
    @JsonProperty(value = "hostPageDisplayUrl", access = JsonProperty.Access.WRITE_ONLY)
    private String hostPageDisplayUrl;

    /**
     * The width of the source media object, in pixels.
     */
    @JsonProperty(value = "width", access = JsonProperty.Access.WRITE_ONLY)
    private Integer width;

    /**
     * The height of the source media object, in pixels.
     */
    @JsonProperty(value = "height", access = JsonProperty.Access.WRITE_ONLY)
    private Integer height;

    /**
     * Get the contentUrl value.
     *
     * @return the contentUrl value
     */
    public String contentUrl() {
        return this.contentUrl;
    }

    /**
     * Get the hostPageUrl value.
     *
     * @return the hostPageUrl value
     */
    public String hostPageUrl() {
        return this.hostPageUrl;
    }

    /**
     * Get the contentSize value.
     *
     * @return the contentSize value
     */
    public String contentSize() {
        return this.contentSize;
    }

    /**
     * Get the encodingFormat value.
     *
     * @return the encodingFormat value
     */
    public String encodingFormat() {
        return this.encodingFormat;
    }

    /**
     * Get the hostPageDisplayUrl value.
     *
     * @return the hostPageDisplayUrl value
     */
    public String hostPageDisplayUrl() {
        return this.hostPageDisplayUrl;
    }

    /**
     * Get the width value.
     *
     * @return the width value
     */
    public Integer width() {
        return this.width;
    }

    /**
     * Get the height value.
     *
     * @return the height value
     */
    public Integer height() {
        return this.height;
    }

}


text:
<?php

namespace War_Api;

use \War_Api\Security\War_User				as War_User;
use \War_Api\Security\War_User_Roles		as War_User_Roles;

/**
 * These methods should run, well, automatically; at the right time
 **/
class War_Auto_Config {

	private $war_config;

	public function __construct( $config = array() ){
		$this->war_config = $config;
	}

	public function manage_admin_toolbar(){
		if( ! is_bool( $this->war_config->admin_toolbar ) ) $this->war_config->admin_toolbar = false;
        show_admin_bar( $this->war_config->admin_toolbar );
	}

	public function set_user_roles(){
		if( ! property_exists( $this->war_config, 'user_roles' ) ) return;
		$user_roles = new War_User_Roles;
		$user_roles->update_roles( $this->war_config->user_roles );
	}

	public function add_war_object( $war_object ){
		$wu = new War_User;

		$war_object = array(
			'warPath' => get_template_directory_uri(),
			'childPath' => get_stylesheet_directory_uri(),
			'nonce' => $this->war_config->nonce,
			'permalink' => preg_replace('/\%.+\%/',':slug', get_option( 'permalink_structure' ) ),
			'category_base' => preg_replace('/\%.+\%/',':slug', get_option( 'category_base' ) ),
			'api_prefix' => rest_get_url_prefix(),
			'api_namespace' => $this->war_config->namespace,
			'user' => $wu->get_war_user(),
			'root' => esc_url_raw( rest_url() )
		);

		return $war_object;
	}

	public function war_localize(){
		wp_register_script('war_site_details', null);

        $war_object = apply_filters( 'war_object', [] );
        wp_localize_script('war_site_details','warObject',$war_object);
        wp_enqueue_script('war_site_details');
	}

	public function set_api_prefix( $prefix ){
        return $this->war_config->api_prefix;
    }

}


text:
<!DOCTYPE html>

    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>AJAX POST with multiple values and ReactJS</title>
    <link rel="icon" type="image/ico" href="/images/favicon.ico">
    <link rel="stylesheet" href="//fonts.googleapis.com/css?family=Source+Sans+Pro:300,300i,600">
    <link href="/resources/font-awesome/css/font-awesome.min.css" rel="stylesheet">
    <link rel="stylesheet" href="/style.css">
    <link rel="alternate" type="application/rss+xml" title="RSS Feed for Vy" href="/feed.xml" />

Vy

Software Engineer in San Francisco

    Vy

AJAX POST with multiple values and ReactJS

    <time>13 Sep 2015</time>

My team and I are building an app in ReactJS, along with a Postgres Database, and even though there are moments when I want to bang my head against a wall, I'm loving React. Here is the most recent problem that made a dent in the wall: Sending multiple values in an AJAX POST request (specifically, values from a text-input and a drop-down menu).

Our app in a nutshell: Habitude, where users can keep track of their progress in learning a new habit.

The React component that we're working on requires multiple nested components. One of the child components is the form that will take the user's input from the text-field and drop-down menu and send it off to the database. What's a good way to do this? May I introduce to you, the ref attribute.

What ever elements that will take the user's input values, in this case: the text-field and the drop-down, stick unique ref attributes on them. In my example, lines 4 and 6.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

  render: function() {
    return (
      <form className="habitForm" onSubmit={this.handleSubmit}>
      <input type="text" placeholder="Enter text" ref="habit" />
      <div>
        <select name="Categories" id='something' ref="category">
        <option value="null">Please select a category for your new Habitude</option>
          <option value="Health">Health</option>
          <option value="Fitness">Fitness</option>
          <option value="Addiction">Addiction</option>
          <option value="Overall Cool Catness">Overall Cool Catness</option>
        </select>
      </div>
      <input type="submit" value="Post" />
      </form>
      );
  }

Notice that this form is a component and its onSubmit event will fire the this.handleSubmit function that is also in this form component. React has an awesome (and convenient!) method React.findDOMNode that will find the DOM node with the ref attribute value you give it. Grab its value and trim its whitespace and there you have your strings of habit and category.

1
2
3
4
5
6
7
8
9
10

handleSubmit: function(e) {
  e.preventDefault();
  var habit = React.findDOMNode(this.refs.habit).value.trim();
  var category = React.findDOMNode(this.refs.category).value.trim();
  if (!habit) {
    return;
  }
  this.props.onHabitSubmit({habit: habit, category: category});
  React.findDOMNode(this.refs.habit).value = '';
},

What now? One line, 10 you can see there's another event listener of "onHabitSubmit". This props method will send a JSON object to my AJAX POST request. This JSON object will have the 2 key-value pairs that I need to send to the database. A value for "habit" and a value for "category". If you want more info about the this.props property in ReactJS,

check out the docs.

In the parent component that will render everything, the "onHabitSubmit" prop is an event listener we've created. When the "onHabitSubmit" event happens, it will fire the "handleHabitSubmit" AJAX call.

1
2
3
4
5
6
7
8
9

  render: function() {
    return (
      <div className="habitBox">
        <h1>Habit Tracker</h1>
        <HabitList data={this.state.data} />
        <HabitForm onHabitSubmit={this.handleHabitSubmit} />
      </div>
    );
  }

This AJAX call takes in the JSON object that is ever-so-nicely put together by the props.onHabitSubmit:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

handleHabitSubmit: function(habitCategory) {

  var habits = this.state.data;
  var newHabits = habits.concat([habitCategory]);
  this.setState({data: newHabits});

  $.ajax({
    url: '/api/habits',
    dataType: 'json',
    type: 'POST',
    data: habitCategory,
    success: function(data) {
      this.setState({data: data});
    }.bind(this),
    error: function(xhr, status, err) {
      console.error(this.props.url, status, err.toString());
    }.bind(this)
  });
},

On line 11, stick that beautifully wrapped JSON object as the value of the data property in the AJAX POST.

On lines 3-5, pay no attention to the man behind the curtain. There's logic for other parts of our app. Basically, we have to organise the data for other calls in this component that make a GET request for all the habits (including the new one the user just entered) to render to the page in real time, i.e. without refreshing the page. We take the existing habits in the current state of the data object. Then concatenate the new JSON object to the tail end of it, and set the state to this entire array of objects, that hold all the previous habits plus our new one.

All this work to send off mutiple key-value pairs to the database. Now if you'll excuse me, I need a shower after that excruciating workout.

Please enable JavaScript to view the comments powered by Disqus.


text:
package stream.vis.view;

import stream.vis.data.*;
import java.awt.*;
import javax.swing.*;
import javax.swing.table.*;
import java.util.List;

public class NamedTableList extends JTable {

    /// The table name column
    private static final int NAME_COLUMN = 0;

    /// The column that indicates if the table is a stream/relation
    private static final int TYPE_COLUMN = 1;

    /// The column that shows the attributes of the named table
    private static final int ATTR_COLUMN = 2;

    /// The width of the table as a fraction of the client view width
    private static final double WIDTH = 0.3;

    /// The height of the table as a fraction of the client view height
    private static final double HEIGHT = 0.38;

    /// List of tables we need to show
    List tables;

    /// parent
    ClientView clientView;
    Client client;

    /// We will set the widths of the table columns manually since that
    /// auto setting does not use the horizontal scroll bar at all
    boolean colSizeSet = false;

    public NamedTableList (Client client, ClientView clientView) {
	super ();

	this.clientView = clientView;

	tables = client.getRegisteredTables ();

	// Create the table
	TableModel model = new TableModel (tables);
	client.addListener (model);

	setModel (model);

	setColumnHeaders ();
	setColumnSelectionAllowed (false);
	setBackground (Color.white);

	// Turn off auto resize
	setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
    }

    protected void paintComponent(Graphics g) {
	// If this is the first invocation, set the column widths
	if (!colSizeSet) {
	    setColumnWidths (g);
	    colSizeSet = true;
	}

	super.paintComponent (g);
    }

    private void setColumnWidths (Graphics g) {
	TableColumnModel colModel;
	TableColumn col;
	int margin;
	int nameColWidth, typeColWidth, attrColWidth;

	// Used in getXXXColWidth methods to determine the width
	// of the strings
	FontMetrics metrics = g.getFontMetrics ();

	colModel = getColumnModel();
	margin = 2 * colModel.getColumnMargin ();

	// Determine the column widths
	nameColWidth = getNameColWidth (metrics, margin);
	typeColWidth = getTypeColWidth (metrics, margin);
	attrColWidth = getAttrColWidth (metrics, margin);

	// If the column widths use up less than the available width, scale
	// to use up all the width
	JScrollPane parent = clientView.getMainPane ();
	int scrollPaneWidth = parent.getViewportBorderBounds().width;
	int totalWidth = nameColWidth + typeColWidth + attrColWidth;

	if (totalWidth < scrollPaneWidth) {
	    nameColWidth =
		((nameColWidth * scrollPaneWidth) / totalWidth);
	    typeColWidth =
		((typeColWidth * scrollPaneWidth) / totalWidth);
	    attrColWidth =
		((attrColWidth * scrollPaneWidth) / totalWidth);
	}

	col = colModel.getColumn (NAME_COLUMN);
	col.setPreferredWidth (nameColWidth);

	col = colModel.getColumn (TYPE_COLUMN);
	col.setPreferredWidth (typeColWidth);

	col = colModel.getColumn (ATTR_COLUMN);
	col.setPreferredWidth (attrColWidth);
    }

    private void setColumnHeaders() {
	TableColumn col;
	TableColumnModel colModel;

	colModel = getColumnModel();
	col = colModel.getColumn (NAME_COLUMN);
	col.setHeaderValue ("Name");

	col = colModel.getColumn (TYPE_COLUMN);
	col.setHeaderValue ("Type");

	col = colModel.getColumn (ATTR_COLUMN);
	col.setHeaderValue ("Attributes");
    }

    private int getNameColWidth (FontMetrics metrics, int margin) {
	// We want the minimum width to be twice as much needed to
	// display name (since it looks nice :))
	int maxWidth = metrics.stringWidth ("Name") * 2 + margin;

	// Maximum over all the table names
	for (int t = 0 ; t < tables.size() ; t++) {
	    NamedTable table = (NamedTable)tables.get (t);
	    int width = metrics.stringWidth (table.getTableName ());
	    if (width > maxWidth)
		maxWidth = width;
	}

	// Without the extra nudge of 10 units, the display is not what
	// we want :(
	return maxWidth + margin + 10;
    }

    private int getTypeColWidth (FontMetrics metrics, int margin) {
	return metrics.stringWidth ("Relation") * 2 + margin;
    }

    private int getAttrColWidth (FontMetrics metrics, int margin) {
	int maxWidth = metrics.stringWidth ("Attributes")*2 + margin;

	for (int t = 0 ; t < tables.size() ; t++) {
	    NamedTable table = (NamedTable)tables.get (t);
	    int width =
		metrics.stringWidth (table.getAttrEncoding());
	    if (width > maxWidth)
		maxWidth = width;
	}

	return maxWidth + margin + 10;
    }

    private class TableModel extends AbstractTableModel
	implements ClientListener {
	/// List of tables
	List tables;

	TableModel (List tables) {
	    this.tables = tables;
	}

	public int getRowCount () {
	    return tables.size();
	}

	public int getColumnCount () {
	    return 3;
	}

	public Object getValueAt (int row, int col) {
	    NamedTable table = (NamedTable)tables.get(row);

	    // Stream name
	    if (col == 0)
		return table.getTableName();

	    // type
	    if (col == 1)
		if (table.isStream())
		    return "Stream";
		else
		    return "Relation";

	    // Attr
	    if (col == 2) {
		return table.getAttrEncoding ();
	    }

	    // should never come
	    return "";
	}

	public void stateChanged (int oldState, int newState) {}
	public void queryAdded (Query query, UnnamedTable outSchema) {}
	public void queryResultAvailable (QueryResult result) {}
	public void planGenerated (QueryPlan plan) {}
	public void monitorAdded (Monitor mon, Query qry,
				  QueryResult res, QueryPlan plan) {}

	public void baseTableAdded (NamedTable table) {
	    colSizeSet = false;
	    fireTableRowsInserted (tables.size() - 1, tables.size() - 1);
	    clientView.showTableList();
	}
	public void viewAdded (Query query, NamedTable table) {
	    colSizeSet = false;
	    fireTableRowsInserted (tables.size() - 1, tables.size() - 1);
	}

	public void resetEvent () {
	    colSizeSet = false;
	    fireTableDataChanged ();
	}

	public void closeEvent () {

	}
    }
}


text:
package com.hpe.caf.worker.core;

import com.hpe.caf.api.worker.InvalidTaskException;
import com.hpe.caf.api.worker.TaskRejectedException;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RunnableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

final class StreamingWorkerThreadPool implements WorkerThreadPool {

    private final BlockingQueue<Runnable> workQueue;
    private final PrivateWorkerThreadPoolExecutor threadPoolExecutor;

    public StreamingWorkerThreadPool
    (
        final int nThreads,
        final Runnable handler
    ) {
        workQueue = new LinkedBlockingQueue<>();
        threadPoolExecutor = new PrivateWorkerThreadPoolExecutor(
            nThreads, workQueue, handler);
    }

    @Override
    public void shutdown() {
        threadPoolExecutor.shutdown();
    }

    @Override
    public void awaitTermination(long timeout, TimeUnit unit)
        throws InterruptedException
    {
        threadPoolExecutor.awaitTermination(timeout, unit);
    }

    /**
     * Returns whether or not any threads are active
     * @return true if there are no active threads
     */
    @Override
    public boolean isIdle() {
        return threadPoolExecutor.getActiveCount() == 0;
    }

    @Override
    public int getBacklogSize() {
        return workQueue.size();
    }

    /**
     * Execute the specified task at some point in the future
     * @param workerTask the task to be run
     * @throws TaskRejectedException if no more tasks can be accepted
     */
    @Override
    public void submitWorkerTask(final WorkerTaskImpl workerTask)
        throws TaskRejectedException
    {
        try {
            StreamingWorkerWrapper wrapper = new StreamingWorkerWrapper(workerTask);
            threadPoolExecutor.submit(wrapper);
        } catch (InvalidTaskException e) {
            workerTask.setResponse(e);
        }
    }

    @Override
    public int abortTasks() {
        return threadPoolExecutor.abortTasks();
    }

    private static class PrivateWorkerThreadPoolExecutor extends ThreadPoolExecutor {

        private static final Logger LOG = LoggerFactory.getLogger(PrivateWorkerThreadPoolExecutor.class);

        private final Runnable throwableHandler;
        private final Map<RunnableFuture<?>, Runnable> tasks;

        public PrivateWorkerThreadPoolExecutor
        (
            final int nThreads,
            final BlockingQueue<Runnable> workQueue,
            final Runnable handler
        ) {
            super(nThreads,
                  nThreads,
                  0L,
                  TimeUnit.MILLISECONDS,
                  workQueue);

            throwableHandler = Objects.requireNonNull(handler);
            tasks = new ConcurrentHashMap<>();
        }

        @Override
        public void afterExecute(Runnable r, Throwable t) {
            try {
                super.afterExecute(r, t);
                if ( t != null ) {
                    LOG.error("Worker thread terminated with unhandled throwable, terminating service", t);
                    throwableHandler.run();
                }
            }
            finally {
                tasks.remove(r);
            }
        }

        @Override
        protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
            RunnableFuture<T> runnableFuture = super.newTaskFor(runnable, value);
            tasks.put(runnableFuture, runnable);
            return runnableFuture;
        }

        public int abortTasks() {
            AtomicInteger count = new AtomicInteger();

            tasks.forEach((key, value) -> {
                key.cancel(true);
                count.incrementAndGet();
            });
            tasks.clear();

            return count.get();
        }
    }
}


text:
module.exports = {
  name: 'inject-live-reload-config',

  // Define the config for live-reload so it's always based on the current URL.
  //
  // In combination with the .ember-cli "liveReloadJsUrl" config, this takes
  // into account the HTTPS proxy that's in front of the ember development
  // server.
  contentFor(type) {
    let liveReloadPort = process.env.EMBER_CLI_INJECT_LIVE_RELOAD_PORT;
    if(liveReloadPort && type === 'head') {
      return '<script type="text/javascript">\n' +
        '  window.LiveReloadOptions = {\n' +
        '    https: (location.protocol === \'https:\'),\n' +
        '    host: location.hostname,\n' +
        '    port: (location.port) ? location.port : ((location.protocol === \'https:\') ? 443 : 80)\n' +
        '  };\n' +
        '</script>';
    }
  },
};


text:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Melas.Structures
{
    public enum Status
    {
        Offline,
        Online,
        Active,
        Away,
        In_Game,
        In_Lobby
    }

    public class Friend
    {
        public int ID { get; private set; }
        public String Name { get; private set; }
        public Status Status { get; set; }

        public Friend(int ID, String Name)
        {
            this.ID = ID;
            this.Name = Name;
        }
    }
}


text:
=============
Cayenne Agent
=============
The Cayenne agent is a full featured client for the `Cayenne IoT project builder <https://mydevices.com>`_. It sends system information as well as sensor and actuator data and responds to actuator messages initiated from the Cayenne dashboard and mobile apps. The Cayenne agent currently supports Rasbian on the Raspberry Pi but it can be extended to support additional Linux flavors and other platforms.

************
Requirements
************
* `Python 3.3 or newer <https://www.python.org/downloads/>`_.
* pip3 - Python 3 package manager. This should already be available in Python 3.4+ and above. If not it can be installed using the system package manager. Via `apt-get` this would be:
  ::

    sudo apt-get install python3-pip

* python3-dev -  Python 3 header files and static library package. Via `apt-get` this can be installed with:
  ::

    sudo apt-get install python3-dev

* python3-setuptools - Python 3 setuptools package. This should already be available in Python 3.4+ and above. If not it can be installed using the system package manager. Via `apt-get` this would be:
  ::

    sudo apt-get install python3-setuptools

All of the above packages can be installed at once via `apt-get` by running:
::

  sudo apt-get install python3-pip python3-dev python3-setuptools

***************
Getting Started
***************

Installation
============
The agent can be installed by navigating to the root directory of the agent code and running::

  sudo python3 setup.py install

Launching the Agent
===================
After install the agent can be launched by running the myDevices module from python::

  python3 -m myDevices

If you have not installed the Raspberry Pi on the device before you will be prompted to enter an invite code when the agent is first run. You can get the invite code from the Cayenne dashboard with the following steps.

1. Create an account at https://cayenne.mydevices.com, if you do not already have one.
2. Select **Add New...->Device/Widget->Single Board Computer->Raspberry Pi**.
3. Under **Option 2** you will see some Linux commands. You do not need to run these commands. Instead you can get the invite code from the script name: rpi_[invitecode].sh.
4. Enter that invite code at the agent prompt and press Enter.
5. The agent will run and connect to Cayenne and your device will show up in the device list.

Potential conflicts with the Cayenne service
--------------------------------------------
If you have already have installed the Cayenne agent service via the Cayenne dashboard you might see some conflicts if you also try running this agent manually. To get around this you can do one of the following:

1. Uninstall the Cayenne agent service.
   ::

     sudo /etc/myDevices/uninstall/uninstall.sh

2. Shut down the Cayenne agent service.

   a) Open crontab for editing.
      ::

        sudo crontab -e

   b) Comment out or remove the myDevices cron job to prevent the service from automatically restarting, then save and exit.
   c) Stop the Cayenne service.
      ::

        sudo service myDevices stop

   d) Optionally, disable the Cayenne service from starting at boot.
      ::

        sudo systemctl disable myDevices

3. Run this agent module in place of the Cayenne agent service, instead of from the command line.
   ::

     sudo service myDevices start

Other potential issues caused by running this agent along side the Cayenne service:

* File/folder permissions issues - The permissions for files and folders used by this agent, including ``/var/log/myDevices``, ``/etc/myDevices``, ``/var/run/myDevices`` and files inside those folders, could conflict with the permissions set by the agent service installed from Cayenne. To get around this issue you can manually modify the file and folder permissions, reinstall the agent, or run the agent as a service as explained above.
* Agent update - The Cayenne agent will automatically update itself if a new agent becomes available which can overwrite the installation of this agent. You may need to reinstall this agent if that happens. To completely disable updates you can add the line ``DoUpdates = false`` to ``/etc/myDevices/AppSettings.ini``.

***********
Development
***********
The agent can be installed for doing development in place by navigating to the root directory of the agent code and running::

  sudo python3 setup.py develop

When running the agent logs information to stderr as well as ``/var/log/myDevices/cayenne.log``. To enable additional debug logging use the ``-d`` option on the command line:
::

  python3 -m myDevices -d

Currently the agent has code for interfacing with several sensors and actuators and support for the Raspberry Pi. If you would like to extend that you can add support for additional sensors, actuators and boards as described below.

Supporting Additional Sensors/Actuators
=======================================
To add support for additional sensors/actuators you may need to create new plugins for the specific sensors/actuators. Information about adding plugins can be found in the `Example Plugin <https://github.com/myDevicesIoT/cayenne-plugin-example>`_ README file. Additional plugin repos you can look at for examples are `here <https://github.com/topics/cayenne-plugin>`_.

Creating a new sensor/actuator plugin
-------------------------------------

1. Create a new Python module with a class to read data from and, if applicable, to write data to the sensor/actuator.
2. Add a ``.plugin`` file under the ``/etc/myDevices/plugins`` directory or a subfolder within that directory. This file should describe the Python module, class and functions to use for reading/writing data. See the `Example Plugin <https://github.com/myDevicesIoT/cayenne-plugin-example>`_ README file for information about the ``.plugin`` file format.
3. Restart the agent to load the plugin and start sending data. Temporary widgets for the plugin should now show up in the `Cayenne Dashboard <https://cayenne.mydevices.com>`_. You can make them permanent by clicking the plus sign. If the widgets do not show up try refreshing the Dashboard or restarting the agent again.

*Note:* For security reasons the Cayenne agent is designed to be able to run from an account without root privileges. If any of your sensor/actuator code requires root access consider running just that portion of your code via a separate process that can be launched using sudo. For an example see the `Sense HAT Plugin <https://github.com/myDevicesIoT/cayenne-plugin-sensehat>`_ which installs a service to access Sense HAT data.

Supporting Additional Boards
============================
To add support for additional boards beyond the Raspberry Pi you may need to modify I/O, system info and/or settings modules as required for the board.

Pin and Bus I/O
---------------
Current support for pin and bus I/O is based on the Linux filesystem drivers used on the Raspberry Pi. To support a different board you may need to update the agent code for the following items, depending on what functionality the board has:

Native GPIO Pins
  Native GPIO pin support is provided in ``myDevices.devices.digital.gpio.py``. This uses the Linux file system drivers under ``/sys/class/gpio`` for reading and writing to GPIO pins. It also uses the ``/dev/gpiomem`` memory map to determine pin modes. If your board is a Linux based board that supports the same filesystem drivers at the same location you may be able to use this code as-is. Otherwise you may need to modify the filesystem driver location or replace the drivers with a some other method or library for reading and writing GPIO values. If your board doesn't support the ``/dev/gpiomem`` memory mapped file you may be able to get the same pin mode info from ``/dev/mem`` or perhaps another GPIO library. Or just fallback to using the filesystem drivers and only get basic pin modes.

SPI Bus
  SPI bus support is provided in ``myDevices.devices.spi.py``. This uses the Linux file system drivers under ``/dev/spidev0.*``. If your board is a Linux based board that supports the same filesystem drivers at the same location you may be able to use this code as-is. Otherwise you may need to modify the filesystem driver location or replace the drivers with a some other method or library for reading and writing SPI values.

I²C Bus
  I²C bus support is provided in ``myDevices.devices.i2c.py``. This uses the Linux file system drivers under ``/dev/i2c-*``. If your board is a Linux based board that supports the same filesystem drivers at the same location you may be able to use this code as-is. Otherwise you may need to modify the filesystem driver location or replace the drivers with a some other method or library for reading and writing I²C values.

1-Wire Bus
  1-Wire bus support is provided in ``myDevices.devices.onewire.py``. This uses the Linux file system drivers under ``/sys/bus/w1/devices``. If your board is a Linux based board that supports the same filesystem drivers at the same location you may be able to use this code as-is. Otherwise you may need to modify the filesystem driver location or replace the drivers with a some other method or library for reading and writing 1-Wire values.

Serial Bus
  Serial bus support is provided in ``myDevices.devices.serial.py`` This uses the Linux file system drivers under ``/dev/ttyAMA0``. Currently Cayenne doesn't support any sensors or actuators using the serial bus so you probably don't need to support this, unless you add some sensor or actuator that requires it.

Loading/Unloading Bus Kernel Modules
  Support for loading/unloading bus kernel modules is provided in ``myDevices.devices.bus.py``. This uses the Linux program ``modprobe``. If your board uses the same bus kernel modules and supports ``modprobe`` you may be able to use this code as-is. Otherwise you may need to update the modules listed in ``BUSLIST`` and/or modify the code to load the kernel modules. If you don't need to support loading the bus kernel modules you can stub out this code.

System info
-----------
Information about the device, including CPU, RAM, etc., is currently retrieved via a few different modules. To support a different board you may need to update the agent code for the following items, if applicable:

General System Info
  General system info, including CPU, RAM, memory, etc. is retrieved via ``myDevices.system.systeminfo.py`` and ``myDevices.system.cpu.py``. These are mostly implemented using cross platform libraries so they may already provide support for your board. If not, they should be modified or overridden to provide the appropriate system info. If your board does not support all the data values currently implemented you can just provide default values where necessary, though this may affect the data display in the Cayenne dashboard.

Hardware Info
  Hardware info, including make, model, etc. is retrieved via ``myDevices.system.hardware.py``. This should be modified or overridden to provide the appropriate hardware info for your board.

Pin Mapping
  The mapping of the on-board pins is provided in ``myDevices.devices.digital.gpio.py`` with the ``MAPPING`` list. This list provides the available GPIO pin numbers as well as the voltage ("V33", "V50"), ground ("GND") and do-not-connect ("DNC") pins. This should be updated with the mapping for your board. However, the Cayenne dashboard is currently built to display the Raspberry Pi GPIO layout so if your board's pin layout is significantly different it may not display correctly in the GPIO tab.

Settings
--------
Currently the Raspberry Pi agent has settings for enabling/disabling the device tree, SPI, I²C, serial and camera. These are set via the ``myDevices.system.raspiconfig`` module which runs a separate Bash script at ``/etc/myDevices/scripts/config.sh``. If any of these settings are available on your board and you would like to support them you can override or replace ``myDevices.system.raspiconfig.py``. Otherwise the settings functionality can be ignored.

*Note:* For security reasons the Cayenne agent is designed to be able to run from an account without root privileges. If any of your I/O, system info or settings code requires root access consider running it via a separate process that can be launched using sudo. For example, the ``myDevices.system.raspiconfig`` module uses this method to update config settings.

************
Contributing
************
The Cayenne agent is an open source project and welcomes your contributions, including:

* Bug reports and fixes
* Documentation improvements
* Additional sensor/actuator support
* Additional board support
* Other code enhancements

*****************
Cayenne Community
*****************
Join us on Slack at `slack.mydevices.com <http://slack.mydevices.com/>`_ or in the `Cayenne Community <http://community.mydevices.com>`_.

*******
License
*******
`MIT <LICENSE>`_