Create, query and edit a specific server version using service geodatabase.
Use case
Workflows often progress in discrete stages, with each stage requiring the allocation of a different set of resources and business rules. Typically, each stage in the overall process represents a single unit of work, such as a work order or job. To manage these, you can create a separate, isolated version and modify it. Once this work is complete, you can integrate the changes into the default version.
How to use the sample
Once loaded, the map will zoom to the extent of the feature layer. The current version is indicated at the top of the map. Click "Create Version" to open a dialog to specify the version information (name, access, and description). See the Additional information section for restrictions on the version name.
Click "Create" to create the version with the information that you specified. Select a feature to edit an attribute and/or click a second time to relocate the point.
Click the button in the top left corner to switch back and forth between the version you created and the default version. Edits will automatically be applied to your version when switching to the default version.
How it works
- Create and load a
ServiceGeodatabase
with a feature service URL that has enabled Version Management. - Get the
ServiceFeatureTable
from the service geodatabase. - Create a
FeatureLayer
from the service feature table. - Create
ServiceVersionParameters
with a unique name,VersionAccess
, and description.- Note - See the additional information section for more restrictions on the version name.
- Create a new version calling
ServiceGeodatabase.createVersion
passing in the service version parameters. - Switch to the version you have just created using
ServiceGeodatabase.switchVersion
, passing in the version name obtained fromServiceGeodatabase.createVersionResult
. - Select a
Feature
from the map to edit its "TYPDAMAGE" attribute and location. - Apply these edits to your version by calling
ServiceGeodatabase.applyEdits
. - Switch back and forth between your version and the default version to see how the two versions differ.
Relevant API
- FeatureLayer
- ServiceFeatureTable
- ServiceGeodatabase
- ServiceGeodatabase.applyEdits
- ServiceGeodatabase.createVersion
- ServiceGeodatabase.createVersionResult
- ServiceGeodatabase.switchVersion
- ServiceVersionInfo
- ServiceVersionParameters
- VersionAccess
About the data
The feature layer used in this sample is Damage to commercial buildings located in Naperville, Illinois.
Additional information
Credentials:
- Username: editor01
- Password: S7#i2LWmYH75
The name of the version must meet the following criteria:
- Must not exceed 62 characters
- Must not include: Period (.), Semicolon (;), Single quotation mark ('), Double quotation mark (")
- Must not include a space for the first character
- Note - the version name will have the username and a period (.) prepended to it. E.g "editor01.MyNewUniqueVersionName"
Branch versioning access permission:
- VersionAccess.Public - Any portal user can view and edit the version.
- VersionAccess.Protected - Any portal user can view, but only the version owner, feature layer owner, and portal administrator can edit the version.
- VersionAccess.Private - Only the version owner, feature layer owner, and portal administrator can view and edit the version.
Tags
branch versioning, edit, version control, version management server
Sample Code
// [WriteFile Name=EditWithBranchVersioning, Category=EditData]
// [Legal]
// Copyright 2020 Esri.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// [Legal]
import QtQuick 2.6
import Esri.ArcGISRuntime 100.15
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12
import QtGraphicalEffects 1.0
import Esri.ArcGISRuntime.Toolkit 100.15
Rectangle {
id: rootRectangle
clip: true
width: 800
height: 600
readonly property var featAttributes: ["Affected", "Destroyed", "Inaccessible", "Minor", "Major"]
readonly property var versionAccessModel: ["Public", "Protected", "Private"]
property var serviceFeatureTable: null
property var featureLayer: null
property var selectedFeature: null
property string featureName: ""
property string currentTypeDamage: ""
property string currentSgdbVersion: ""
property string createdVersionName: ""
property bool sgdbVersionIsDefault: sgdb.defaultVersionName === sgdb.versionName ? true : false
MapView {
id: mapView
anchors.fill: parent
Component.onCompleted: {
// Set the focus on MapView to initially enable keyboard navigation
forceActiveFocus();
}
onViewpointChanged: {
clearSelectedFeature();
callout.dismiss();
}
onMouseClicked: {
busyIndicator.visible = true;
featureLayer.clearSelection();
// if feature is already selected, then move to new location
if (selectedFeature) {
const clickedPoint = mapView.screenToLocation(mouse.x, mouse.y);
moveFeature(clickedPoint);
} else {
// call identify on the map view
mapView.identifyLayerWithMaxResults(featureLayer, mouse.x, mouse.y, 5, false, 1);
}
}
onIdentifyLayerStatusChanged: {
if (identifyLayerStatus === Enums.TaskStatusCompleted) {
// clear any previous selections
clearSelectedFeature();
if (identifyLayerResult.geoElements.length > 0) {
selectedFeature = identifyLayerResult.geoElements[0];
featureLayer.selectFeature(selectedFeature);
} else {
busyIndicator.visible = false;
return;
}
featureName = selectedFeature.attributes.attributeValue("PLACENAME");
currentTypeDamage = selectedFeature.attributes.attributeValue("TYPDAMAGE");
callout.showCallout();
busyIndicator.visible = false;
}
}
calloutData {
// HTML to style the title text by centering it, increase pt size,
// and bolding it.
title: "<br><font size=\"+2\">%1</font>".arg(featureName)
location: selectedFeature ? selectedFeature.geometry : null
}
Callout {
id: callout
borderWidth: 1
borderColor: "lightgrey"
calloutData: parent.calloutData
leaderPosition: leaderPositionEnum.Automatic
onAccessoryButtonClicked: {
for (let i=0; i < featAttributes.length; i++) {
if (currentTypeDamage === featAttributes[i]) {
typeDmgCombo.currentIndex = i;
break;
}
}
updateWindow.visible = true;
}
}
Map {
Basemap {
initStyle: Enums.BasemapStyleArcGISStreets
}
}
}
ServiceGeodatabase {
id: sgdb
url: "https://sampleserver7.arcgisonline.com/server/rest/services/DamageAssessment/FeatureServer"
credential: Credential {
username: "editor01"
password: "S7#i2LWmYH75"
}
onLoadStatusChanged: {
if (loadStatus !== Enums.LoadStatusLoaded)
return;
currentSgdbVersion = sgdb.versionName;
serviceFeatureTable = sgdb.table(0);
serviceFeatureTable.updateFeatureStatusChanged.connect(()=> {
if (serviceFeatureTable.updateFeatureStatus === Enums.TaskStatusCompleted)
busyIndicator.visible = false;
});
serviceFeatureTable.loadStatusChanged.connect(()=> {
if (serviceFeatureTable.loadStatus !== Enums.LoadStatusLoaded)
return;
mapView.setViewpointGeometry(featureLayer.fullExtent);
busyIndicator.visible = false;
});
featureLayer = ArcGISRuntimeEnvironment.createObject("FeatureLayer");
featureLayer.featureTable = serviceFeatureTable;
mapView.map.operationalLayers.append(featureLayer);
}
onCreateVersionStatusChanged: {
if (createVersionStatus === Enums.TaskStatusCompleted) {
createdVersionName = createVersionResult.name;
createVersionBtn.text = qsTr("Switch to default version");
switchVersions();
} else if (createVersionStatus === Enums.TaskStatusErrored) {
errorText.text = qsTr("%1 - %2".arg(error.message).arg(error.additionalMessage));
errorDialog.visible = true;
busyIndicator.visible = false;
}
}
onSwitchVersionStatusChanged: {
if (switchVersionStatus === Enums.TaskStatusCompleted) {
currentSgdbVersion = sgdb.versionName;
} else if (switchVersionStatus === Enums.TaskStatusErrored) {
errorText.text = qsTr("%1 - %2".arg(error.message).arg(error.additionalMessage));
errorDialog.visible = true;
}
busyIndicator.visible = false;
}
onApplyEditsStatusChanged: {
if (applyEditsStatus === Enums.TaskStatusCompleted) {
switchVersions();
applyEditsDialog.visible = false;
} else if (applyEditsStatus === Enums.TaskStatusErrored) {
errorText.text = qsTr("%1 - %2".arg(error.message).arg(error.additionalMessage));
errorDialog.visible = true;
return;
}
}
onComponentCompleted: {
load();
busyIndicator.visible = true;
}
}
ServiceVersionParameters {
id: params
}
Button {
id: createVersionBtn
text: qsTr("Create Version")
anchors {
left: parent.left
top: parent.top
margins: 3
}
enabled: !busyIndicator.visible
onClicked: {
if (text === qsTr("Create Version")) {
createVersionWindow.visible = true;
callout.dismiss();
updateWindow.visible = false;
} else if (text === qsTr("Switch to default version")) {
text = qsTr("Switch to created version")
busyIndicator.visible = true;
if (sgdb.hasLocalEdits()) {
sgdb.applyEdits();
applyEditsDialog.visible = true;
} else {
switchVersions();
}
callout.dismiss();
updateWindow.visible = false;
} else if (text === qsTr("Switch to created version")) {
text = qsTr("Switch to default version")
switchVersions();
callout.dismiss();
updateWindow.visible = false;
}
clearSelectedFeature();
}
}
Rectangle {
id: currentVersionRect
anchors{
top: parent.top
horizontalCenter: parent.horizontalCenter
margins: 3
}
width: childrenRect.width;
height: childrenRect.height;
color: "#000000"
visible: currentVersionText.text !== "" ? true : false
ColumnLayout {
spacing: 3
Text {
text: qsTr("Current version:")
Layout.alignment: Qt.AlignHCenter
color: "white"
}
Text {
id: currentVersionText
Layout.alignment: Qt.AlignHCenter
text: currentSgdbVersion
color: "white"
}
}
}
Rectangle {
id: createVersionWindow
anchors.centerIn: parent
width: childrenRect.width
height: childrenRect.height
radius: 10
visible: false
GaussianBlur {
anchors.fill: createVersionWindow
source: mapView
radius: 40
samples: 20
}
MouseArea {
anchors.fill: parent
onClicked: mouse.accepted = true;
onWheel: wheel.accepted = true;
}
ColumnLayout {
anchors.margins: 5
TextField {
id: versionNameTextField
placeholderText: qsTr("Name must be unique")
Layout.alignment: Qt.AlignHCenter
Layout.margins: 5
validator: RegExpValidator { regExp: /\w{0,50}/ }
}
ComboBox {
id: accessComboBox
model: versionAccessModel
Layout.fillWidth: true
Layout.alignment: Qt.AlignHCenter
Layout.margins: 5
}
TextField {
id: descriptionTextField
placeholderText: qsTr("Enter description")
Layout.alignment: Qt.AlignHCenter
Layout.margins: 5
}
Row {
Layout.alignment: Qt.AlignRight
Layout.margins: 5
height: childrenRect.height
spacing: 5
Button {
Layout.margins: 5
Layout.alignment: Qt.AlignRight
text: qsTr("Create")
onClicked: {
createVersion(versionNameTextField.text, accessComboBox.currentValue, descriptionTextField.text);
busyIndicator.visible = true;
resetCreateVersionWindow();
}
}
Button {
Layout.alignment: Qt.AlignRight
Layout.margins: 5
text: qsTr("Cancel")
onClicked: {
resetCreateVersionWindow();
}
}
}
}
}
// Update Window
Rectangle {
id: updateWindow
anchors.centerIn: parent
width: childrenRect.width
height: childrenRect.height
radius: 10
visible: false
GaussianBlur {
anchors.fill: updateWindow
source: mapView
radius: 40
samples: 20
}
MouseArea {
anchors.fill: parent
onClicked: mouse.accepted = true;
onWheel: wheel.accepted = true;
}
GridLayout {
columns: 2
anchors.margins: 5
Text {
Layout.columnSpan: 2
Layout.margins: 5
Layout.alignment: Qt.AlignHCenter
text: qsTr("Update Attributes")
font.pixelSize: 16
}
Text {
text: "TYPDAMAGE:"
Layout.margins: 5
}
ComboBox {
id: typeDmgCombo
property int modelWidth: 0
Layout.minimumWidth: modelWidth + leftPadding + rightPadding + indicator.width
Layout.margins: 5
Layout.fillWidth: true
model: featAttributes
enabled: !sgdbVersionIsDefault
}
Row {
Layout.alignment: Qt.AlignRight
Layout.columnSpan: 2
height: childrenRect.height
spacing: 5
Button {
Layout.margins: 5
Layout.alignment: Qt.AlignRight
text: qsTr("Update")
enabled: !sgdbVersionIsDefault
// once the update button is clicked, hide the windows, and fetch the currently selected features
onClicked: {
updateAttribute(typeDmgCombo.currentValue);
updateWindow.visible = false;
callout.dismiss();
busyIndicator.visible = true;
}
}
Button {
Layout.alignment: Qt.AlignRight
Layout.margins: 5
text: qsTr("Cancel")
// once the cancel button is clicked, hide the window
onClicked: {
clearSelectedFeature();
updateWindow.visible = false;
callout.dismiss();
}
}
}
}
}
function switchVersions() {
if (!sgdbVersionIsDefault) {
sgdb.switchVersion(sgdb.defaultVersionName);
createVersionBtn.text = qsTr("Switch to created version");
} else {
sgdb.switchVersion(createdVersionName);
}
}
function moveFeature(mapPoint) {
if (!sgdbVersionIsDefault) {
selectedFeature.geometry = mapPoint;
selectedFeature.featureTable.updateFeature(selectedFeature);
}
busyIndicator.visible = false;
clearSelectedFeature();
}
function clearSelectedFeature() {
selectedFeature = null;
if (featureLayer)
featureLayer.clearSelection();
callout.dismiss();
}
function createVersion(versionName, versionAccess, description) {
params.name = versionName;
params.description = description;
if (versionAccess === "Private") {
params.access = Enums.VersionAccessPrivate;
} else if (versionAccess === "Protected") {
params.access = Enums.VersionAccessProtected;
} else if (versionAccess == "Public") {
params.access = Enums.VersionAccessPublic;
}
sgdb.createVersion(params);
}
function resetCreateVersionWindow() {
createVersionWindow.visible = false;
versionNameTextField.text = "";
descriptionTextField.text = "";
accessComboBox.currentIndex = 0;
}
function updateAttribute(attributeValue) {
busyIndicator.visible = true;
if (!selectedFeature)
return;
if (sgdbVersionIsDefault) {
clearSelectedFeature();
return;
}
// update the attirbute with the selection from the combo box
selectedFeature.attributes.replaceAttribute("TYPDAMAGE", attributeValue);
selectedFeature.featureTable.updateFeature(selectedFeature);
}
// Declare AuthenticationView to handle any authentication challenges
AuthenticationView {
anchors.fill: parent
}
Dialog {
id: errorDialog
anchors.centerIn: parent
standardButtons: Dialog.Ok
Text {
id: errorText
}
}
Dialog {
id: applyEditsDialog
x: Math.round(parent.width - width) / 2
y: Math.round(parent.height - height) - mapView.attributionRect.height;
Text {
id: applyEditsText
text: qsTr("Applying Edits")
}
}
BusyIndicator {
id: busyIndicator
anchors.centerIn: parent
visible: false
}
}