Summary
- Referenced Team Sharing ensures that complex Shared Entities are fully shareable
- Not only is the object shared but so are any dependent objects (e.g. Calculated Column in a Layout)
- When sharing an Extended Layout all Tagged Objects are included in the Share
Sometimes when using Team Sharing, a Shared Entity might contain references to other objects.
When this happens, AdapTable will:
- find all the associated dependencies
- upload them into Team Share together with the main Shared Entity
Deep Dive
How Referenced Sharing Works
AdapTable performs referenced sharing automatically.
It is able to handle transitive dependencies which can be multiple levels deep.
For example, if a shared Layout included a Calculated Column with a BooleanExpression which referenced a Named Query which itself referenced another Named Query, then all 4 objects would be uploaded to Team Share.
Similarly if a shared Layout is referenced in Object Tags then these Objects will also be uploaded to the Team Share.
If the Sharing is Active and the uploaded Shared Entity has dependencies, these will all be Active as well
Sharing Objects
The most common referencing use case is when one Adaptable Object references a second AdapTable Object.
For instance sharing a Format Column which references a Named Query.
AdapTable will ensure that all dependencies are shared.
- This demo illustrated Referenced Sharing of AdapTable Objects:
- Alice has 2 related objects:
- a Named Query called
Hottest JavaScript - a Format Column which references the Named Query to style rows that match
- a Named Query called
- As Alice share the Format Column
- Switch to Bob and open TeamShare
- You will note that not only is the Format Column there but so is the Named Query which it references
import {AdaptableOptions, InitialState} from '@adaptabletools/adaptable'; import {WebFramework} from './rowData'; // use a common Team Sharing state key for both users const TEAM_SHARING_STATE_KEY = 'ReferencedObjectsTeamSharing_TeamSharingState'; export const getAdaptableOptionsForUser = ( userName: 'Alice' | 'Bob', updateCurrentUser: (userName: 'Alice' | 'Bob') => void ): AdaptableOptions<WebFramework> => { const commonInitialState: InitialState = { Dashboard: { Revision: Date.now(), Tabs: [ { Name: 'Select Current User', Toolbars: ['CurrentUser', 'Layout'], }, ], ModuleButtons: ['FormatColumn', 'TeamSharing', 'SettingsPanel'], }, Layout: { CurrentLayout: 'Standard Layout', Layouts: [ { TableColumns: [], Name: 'Standard Layout', }, ], }, }; const userSpecificInitialState: InitialState = userName === 'Alice' ? { StatusBar: { StatusBars: [ { Key: 'Center Panel', StatusBarPanels: ['FormatColumn', 'TeamSharing'], }, ], }, FormatColumn: { FormatColumns: [ { Name: 'formatColumn-all', Style: { BackColor: '#8fd3fe', }, Scope: { All: true, }, Rule: { BooleanExpression: 'QUERY("Hottest JavaScript") ', }, }, ], }, NamedQuery: { NamedQueries: [ { Name: 'Hottest JavaScript', BooleanExpression: '[language]="JavaScript" AND ([github_watchers] > 2000 OR [github_stars] > 14500)', }, ], }, Layout: { CurrentLayout: 'Standard Layout', Layouts: [ { TableColumns: [ 'name', 'language', 'pushed_at', 'github_watchers', 'week_issue_change', ], Name: 'Standard Layout', AutoSizeColumns: true, }, ], }, } : // Bob { StatusBar: { StatusBars: [ { Key: 'Center Panel', StatusBarPanels: ['FormatColumn', 'TeamSharing'], }, ], }, Layout: { CurrentLayout: 'Standard Layout', Layouts: [ { TableColumns: [ 'name', 'language', 'updated_at', 'pushed_at', 'github_watchers', 'week_issue_change', 'github_stars', 'license', 'has_projects', 'has_pages', 'created_at', 'has_wiki', ], Name: 'Standard Layout', AutoSizeColumns: true, }, ], }, }; return { primaryKey: 'id', adaptableId: 'Referenced Objects Team Sharing', adaptableStateKey: `ReferencedObjectsTeamSharing_${userName}`, userName, initialState: { ...commonInitialState, ...userSpecificInitialState, }, teamSharingOptions: { enableTeamSharing: true, loadSharedEntities: async () => { const data = localStorage.getItem(TEAM_SHARING_STATE_KEY); if (!data) { return []; } try { return JSON.parse(data); } catch (e) { return []; } }, persistSharedEntities: entries => { localStorage.setItem(TEAM_SHARING_STATE_KEY, JSON.stringify(entries)); return Promise.resolve(); }, }, dashboardOptions: { customToolbars: [ { name: 'CurrentUser', title: 'Current User', toolbarButtons: [ { label: 'Alice', buttonStyle: () => { return { tone: userName === 'Alice' ? 'success' : 'neutral', variant: userName === 'Alice' ? 'raised' : 'outlined', }; }, onClick: () => { updateCurrentUser('Alice'); }, }, { label: 'Bob', buttonStyle: () => { return { tone: userName === 'Bob' ? 'success' : 'neutral', variant: userName === 'Bob' ? 'raised' : 'outlined', }; }, onClick: () => { updateCurrentUser('Bob'); }, }, ], }, ], customDashboardButtons: [ { tooltip: 'Reset state', icon: { name: 'refresh', }, buttonStyle: { tone: 'error', }, onClick: (button, context) => { localStorage.removeItem(TEAM_SHARING_STATE_KEY); ['Alice', 'Bob'].forEach(user => { localStorage.removeItem(`ReferencedObjectsTeamSharing_${user}`); }); context.adaptableApi.stateApi.reloadInitialState(); }, }, ], }, }; };
Sharing Layouts
Another common use case for Referenced Team Sharing is when a Layout is shared.
A Layout can include references to other objects in 2 ways:
- by containing Special Columns - e.g. Calculated Columns or FreeText Columns
- by using Object Tags to extend the Layout to to include other objects (like Format or Styled Columns)
Special Columns
Any Layout which contains references to Calculated Columns or FreeText Columns will automaticallyf include them in Referenced Sharing.
- In this demo we provided Alice with a Layout entitled
Special Column Viewwhich contains:- a Calculated Column called
Subscribers Ratio - a FreeText Column called
Comments
- a Calculated Column called
- We have also provided a Custom Dashboard Button for Alice to team-share the
Special Column ViewLayout
- As Alice shares the Layout by clicking the 'Share Special Column View' button in the Dashboard
- Switch to Bob and open TeamShare
- Note that not only is the Layout there but so are the Calculated and FreeText Columns
- Select the 'Special Column View' Layout and see how the Shared Layout automatically includes the 2 Columns
import { AdaptableOptions, DashboardOptions, InitialState, } from '@adaptabletools/adaptable'; import {WebFramework} from './rowData'; // use a common Team Sharing state key for both users const TEAM_SHARING_STATE_KEY = 'ReferencedSpecialColumnTeamSharing_TeamSharingState'; export const getAdaptableOptionsForUser = ( userName: 'Alice' | 'Bob', updateCurrentUser: (userName: 'Alice' | 'Bob') => void ): AdaptableOptions => { const commonAdaptableOptions: AdaptableOptions<WebFramework> = { primaryKey: 'id', adaptableId: 'Referenced Special Columns Team Sharing', adaptableStateKey: `ReferencedSpecialColumnsTeamSharing_${userName}`, userName, teamSharingOptions: { enableTeamSharing: true, loadSharedEntities: async adaptableId => { const data = localStorage.getItem(TEAM_SHARING_STATE_KEY); if (!data) { return []; } try { return JSON.parse(data); } catch (e) { return []; } }, persistSharedEntities: entries => { localStorage.setItem(TEAM_SHARING_STATE_KEY, JSON.stringify(entries)); return Promise.resolve(); }, }, initialState: { Layout: { CurrentLayout: 'Special Column View', Layouts: [ { Name: 'Special Column View', TableColumns: [ 'name', 'language', 'github_stars', 'github_watchers', 'subscribersRatio', 'comments', 'open_pr_count', 'closed_issues_count', 'created_at', 'updated_at', 'pushed_at', 'license', 'created_at', 'has_wiki', ], AutoSizeColumns: true, }, ], }, }, }; const changeUserState: InitialState['Dashboard'] = { Revision: Date.now(), Tabs: [ { Name: 'Select Current User', Toolbars: ['CurrentUser', 'Layout'], }, ], ModuleButtons: ['Layout', 'TeamSharing'], }; const changeUserDashboardOptions: DashboardOptions = { customToolbars: [ { name: 'CurrentUser', title: 'Current User', toolbarButtons: [ { label: 'Alice', buttonStyle: () => { return { tone: userName === 'Alice' ? 'success' : 'neutral', variant: userName === 'Alice' ? 'raised' : 'outlined', }; }, onClick: () => { updateCurrentUser('Alice'); }, }, { label: 'Bob', buttonStyle: () => { return { tone: userName === 'Bob' ? 'success' : 'neutral', variant: userName === 'Bob' ? 'raised' : 'outlined', }; }, onClick: () => { updateCurrentUser('Bob'); }, }, ], }, ], }; const userSpecificAdaptableOptions: Partial<AdaptableOptions<WebFramework>> = userName === 'Alice' ? { dashboardOptions: { ...changeUserDashboardOptions, customDashboardButtons: [ { label: 'Share Layout: "Special Column View" ', buttonStyle: { tone: 'neutral', variant: 'raised', }, onClick: (button, {adaptableApi}) => { const currentLayout = adaptableApi.layoutApi.getCurrentLayout(); adaptableApi.teamSharingApi.shareAdaptableEntity( currentLayout, 'Layout', { description: 'Share Layout Special Column View', type: 'Snapshot', } ); }, }, { tooltip: 'Reset state', icon: { name: 'refresh', }, buttonStyle: { tone: 'error', }, onClick: (button, context) => { localStorage.removeItem(TEAM_SHARING_STATE_KEY); ['Alice', 'Bob'].forEach(user => { localStorage.removeItem( `ReferencedSpecialColumnsTeamSharing_${user}` ); }); context.adaptableApi.stateApi.reloadInitialState(); }, }, ], }, initialState: { Dashboard: changeUserState, StatusBar: { StatusBars: [ { Key: 'Center Panel', StatusBarPanels: ['Layout', 'TeamSharing'], }, ], }, Layout: { CurrentLayout: 'Special Column View', Layouts: [ { Name: 'Special Column View', TableColumns: [ 'name', 'language', 'github_stars', 'github_watchers', 'subscribersRatio', 'comments', 'open_pr_count', 'closed_issues_count', 'created_at', 'updated_at', 'pushed_at', 'license', 'created_at', 'has_wiki', ], AutoSizeColumns: true, }, ], }, CalculatedColumn: { CalculatedColumns: [ { FriendlyName: 'Subscribers Ratio', ColumnId: 'subscribersRatio', Query: { ScalarExpression: '[github_stars] / [github_watchers]', }, CalculatedColumnSettings: { DataType: 'number', }, }, ], }, FreeTextColumn: { FreeTextColumns: [ { ColumnId: 'comments', FriendlyName: 'Comments', FreeTextColumnSettings: { DataType: 'text', }, FreeTextStoredValues: [ {PrimaryKey: 24195339, FreeText: 'Used by the US team'}, ], }, ], }, }, } : // Bob { dashboardOptions: { ...changeUserDashboardOptions, customDashboardButtons: [ { label: 'Import Layout', buttonStyle: { tone: 'neutral', variant: 'raised', }, hidden: (button, {adaptableApi}) => { const sharedEntries = adaptableApi.teamSharingApi.getLoadedAdaptableSharedEntities(); // show if there is a layout object shared if (sharedEntries?.length) { return !sharedEntries.some( sharedEntry => sharedEntry.Module === 'Layout' ); } return true; }, onClick: async (button, {adaptableApi}) => { await adaptableApi.teamSharingApi.loadSharedEntities(); const sharedObjects = adaptableApi.teamSharingApi.getLoadedAdaptableSharedEntities(); const layout = sharedObjects.find( sharedObject => sharedObject.Module === 'Layout' ); if (!layout) { return; } adaptableApi.teamSharingApi.importSharedEntry(layout); }, }, { tooltip: 'Reset state', icon: { name: 'refresh', }, buttonStyle: { tone: 'error', }, onClick: (button, context) => { localStorage.removeItem(TEAM_SHARING_STATE_KEY); ['Alice', 'Bob'].forEach(user => { localStorage.removeItem( `ReferencedSpecialColumnsTeamSharing_${user}` ); }); context.adaptableApi.stateApi.reloadInitialState(); }, }, ], }, initialState: { Dashboard: changeUserState, StatusBar: { StatusBars: [ { Key: 'Center Panel', StatusBarPanels: ['Layout', 'TeamSharing'], }, ], }, Layout: { CurrentLayout: 'Default', Layouts: [ { Name: 'Default', TableColumns: [ 'name', 'language', 'github_stars', 'github_watchers', 'open_pr_count', 'closed_issues_count', 'created_at', 'updated_at', 'pushed_at', 'license', 'created_at', 'has_wiki', ], AutoSizeColumns: true, }, ], }, }, }; return { ...commonAdaptableOptions, ...userSpecificAdaptableOptions, }; };
Extended Layouts
Referenced Sharing will also take into consideration any Extended Layouts that leverage Object Tags.
If a Layout contains objects (e.g. Format Columns) which have Object Tags that reference the Layout, they will be inlcuded in the Referenced Share.
Caution
- This requires
autoCheckTagsForLayoutsto be set to true in Layout Options - Alternatively, an implementation can be provided for
isObjectExtendedInLayout(also in Layout Options)
Find Out More
See Extending Layouts for detailed instructions on how to scope Adaptable Objects to particular Layouts
- This demo illustrated Referenced Sharing of Extended Layouts which contain Object Tags
- Alice has 2 Layouts - each with a different set of Format Columns tagged to that Layout:
First View-Name,LanguageandDatecolumnsSecond View-Github Stars,Closed Issuesand alsoDatecolumns, plus a Condition where Language is 'HTML'
- There is a button in the Dashboard for Alice to share the
First ViewLayout
- As Alice shares the
First ViewLayout by clicking the 'Share Layout: First View' button in the Dashboard - Switch to Bob and open TeamShare
- You will note that not only is the Layout there but so are the associated Format Columns
- Upload the
First ViewLayout and you will see that the Shared Layout automatically includes all the tagged Objects
import { AdaptableOptions, DashboardOptions, InitialState, } from '@adaptabletools/adaptable'; import {WebFramework} from './rowData'; // use a common Team Sharing state key for both users const TEAM_SHARING_STATE_KEY = 'ExtendedLayoutTeamSharing_TeamSharingState'; export const getAdaptableOptionsForUser = ( userName: 'Alice' | 'Bob', updateCurrentUser: (userName: 'Alice' | 'Bob') => void ): AdaptableOptions<WebFramework> => { const commonAdaptableOptions: AdaptableOptions<WebFramework> = { primaryKey: 'id', adaptableId: 'Extended Layout Team Sharing', adaptableStateKey: `ExtendedLayoutTeamSharing_${userName}`, userName, layoutOptions: { layoutTagOptions: { autoCheckTagsForLayouts: true, autoGenerateTagsForLayouts: true, }, }, teamSharingOptions: { enableTeamSharing: true, loadSharedEntities: async adaptableId => { const data = localStorage.getItem(TEAM_SHARING_STATE_KEY); if (!data) { return []; } try { return JSON.parse(data); } catch (e) { return []; } }, persistSharedEntities: entries => { localStorage.setItem(TEAM_SHARING_STATE_KEY, JSON.stringify(entries)); return Promise.resolve(); }, }, initialState: { Layout: { CurrentLayout: 'First View', Layouts: [ { Name: 'First View', TableColumns: [ 'name', 'language', 'github_stars', 'open_pr_count', 'closed_issues_count', 'created_at', 'updated_at', 'pushed_at', 'license', 'created_at', 'has_wiki', 'github_watchers', 'description', 'open_issues_count', 'closed_pr_count', 'has_projects', 'has_pages', 'week_issue_change', ], AutoSizeColumns: true, }, ], }, }, }; const changeUserState: InitialState['Dashboard'] = { Revision: Date.now(), Tabs: [ { Name: 'Select Current User', Toolbars: ['CurrentUser', 'Layout'], }, ], ModuleButtons: ['Layout', 'TeamSharing'], }; const changeUserDashboardOptions: DashboardOptions = { customToolbars: [ { name: 'CurrentUser', title: 'Current User', toolbarButtons: [ { label: 'Alice', buttonStyle: () => { return { tone: userName === 'Alice' ? 'success' : 'neutral', variant: userName === 'Alice' ? 'raised' : 'outlined', }; }, onClick: () => { updateCurrentUser('Alice'); }, }, { label: 'Bob', buttonStyle: () => { return { tone: userName === 'Bob' ? 'success' : 'neutral', variant: userName === 'Bob' ? 'raised' : 'outlined', }; }, onClick: () => { updateCurrentUser('Bob'); }, }, ], }, ], }; const userSpecificAdaptableOptions: Partial<AdaptableOptions<WebFramework>> = userName === 'Alice' ? { dashboardOptions: { ...changeUserDashboardOptions, customDashboardButtons: [ { label: 'Share Layout: "First View" ', buttonStyle: { tone: 'neutral', variant: 'raised', }, onClick: (button, {adaptableApi}) => { const currentLayout = adaptableApi.layoutApi.getCurrentLayout(); adaptableApi.teamSharingApi.shareAdaptableEntity( currentLayout, 'Layout', { description: 'Share Layout First View', type: 'Snapshot', } ); }, }, { tooltip: 'Reset state', icon: { name: 'refresh', }, buttonStyle: { tone: 'error', }, onClick: (button, context) => { localStorage.removeItem(TEAM_SHARING_STATE_KEY); ['Alice', 'Bob'].forEach(user => { localStorage.removeItem( `ExtendedLayoutTeamSharing_${user}` ); }); context.adaptableApi.stateApi.reloadInitialState(); }, }, ], }, initialState: { Dashboard: changeUserState, StatusBar: { StatusBars: [ { Key: 'Center Panel', StatusBarPanels: ['Layout', 'TeamSharing'], }, ], }, Layout: { CurrentLayout: 'First View', Layouts: [ { Name: 'First View', TableColumns: [ 'name', 'language', 'github_stars', 'open_pr_count', 'closed_issues_count', 'created_at', 'updated_at', 'pushed_at', 'license', 'created_at', 'has_wiki', 'github_watchers', 'description', 'open_issues_count', 'closed_pr_count', 'has_projects', 'has_pages', 'week_issue_change', ], AutoSizeColumns: true, }, ], }, FormatColumn: { FormatColumns: [ { Name: 'formatColumn-name', Scope: { ColumnIds: ['name'], }, Style: { FontWeight: 'Bold', }, Tags: ['First View'], }, { Name: 'formatColumn-github_stars', Scope: { ColumnIds: ['github_stars'], }, Style: { ForeColor: '#c2bb00', BackColor: '#f5f5f5', }, DisplayFormat: { Formatter: 'NumberFormatter', Options: { Prefix: 'Stars: ', }, }, Tags: ['Second View'], }, { Name: 'formatColumn-date', Scope: { DataTypes: ['date'], }, Style: { FontStyle: 'Italic', Alignment: 'Center', }, DisplayFormat: { Formatter: 'DateFormatter', Options: { Pattern: 'MMM do yyyy', }, }, Tags: ['First View', 'Second View'], }, { Name: 'formatColumn-language', Style: { BackColor: '#87cefa', }, Scope: { ColumnIds: ['language'], }, Rule: { Predicates: [ { PredicateId: 'Is', Inputs: ['TypeScript'], }, ], }, Tags: ['First View'], }, { Name: 'formatColumn-all', Style: { BackColor: 'orange', }, Scope: { All: true, }, Rule: { BooleanExpression: '[language] = "HTML"', }, Tags: ['Second View'], }, ], }, StyledColumn: { StyledColumns: [ { ColumnId: 'open_pr_count', GradientStyle: { CellRanges: [ { Min: 0, Max: 254, Color: '#a52a2a', }, ], }, Tags: ['First View'], }, { ColumnId: 'closed_issues_count', PercentBarStyle: { RangeValueType: 'Number', CellRanges: [ { Min: 0, Max: 21706, Color: 'purple', }, ], CellText: ['PercentageValue'], }, Tags: ['Second View'], }, ], }, }, } : // Bob { dashboardOptions: { ...changeUserDashboardOptions, customDashboardButtons: [ { label: 'Import Layout', buttonStyle: { tone: 'neutral', variant: 'raised', }, hidden: (button, {adaptableApi}) => { const sharedEntries = adaptableApi.teamSharingApi.getLoadedAdaptableSharedEntities(); // show if there is a layout object shared if (sharedEntries?.length) { return !sharedEntries.some( sharedEntry => sharedEntry.Module === 'Layout' ); } return true; }, onClick: async (button, {adaptableApi}) => { await adaptableApi.teamSharingApi.loadSharedEntities(); const sharedObjects = adaptableApi.teamSharingApi.getLoadedAdaptableSharedEntities(); const layout = sharedObjects.find( sharedObject => sharedObject.Module === 'Layout' ); if (!layout) { return; } adaptableApi.teamSharingApi.importSharedEntry(layout); }, }, { tooltip: 'Reset state', icon: { name: 'refresh', }, buttonStyle: { tone: 'error', }, onClick: (button, context) => { localStorage.removeItem(TEAM_SHARING_STATE_KEY); ['Alice', 'Bob'].forEach(user => { localStorage.removeItem( `ExtendedLayoutTeamSharing_${user}` ); }); context.adaptableApi.stateApi.reloadInitialState(); }, }, ], }, initialState: { Dashboard: changeUserState, StatusBar: { StatusBars: [ { Key: 'Center Panel', StatusBarPanels: ['Layout', 'TeamSharing'], }, ], }, Layout: { CurrentLayout: 'First View', Layouts: [ { Name: 'First View', TableColumns: [ 'name', 'language', 'github_stars', 'open_pr_count', 'closed_issues_count', 'created_at', 'updated_at', 'pushed_at', 'license', 'created_at', 'has_wiki', 'github_watchers', 'description', 'open_issues_count', 'closed_pr_count', 'has_projects', 'has_pages', 'week_issue_change', ], AutoSizeColumns: true, }, ], }, }, }; return { ...commonAdaptableOptions, ...userSpecificAdaptableOptions, }; };