When discussing the migration of your server/data center environment to the Atlassian cloud, it’s crucial to thoroughly assess various aspects before hitting the “Migrate” button. Below, I’ve compiled a list of queries commonly used to analyze the data within your instance and to determine whether any corrective actions are required prior to initiating the migration process.
Please note that this is a generic list of queries and should be adapted to your specific environment and requirements:
- Queries for Data Cleanup
- Identify Pages Using Nested Macros in Confluence
- Identify User Filter Usage
- Find Duplicate Email Addresses with Query
- Limit Attachment Length to 250 Characters
- Calculate Total Attachment Size in MB
- Identify Dashboards with High Gadget Count
- Find Workflows with References to a Specific Custom Field
- Identify Custom Fields with Low Usage
- Identify Custom Fields with Global Context
- Identify Custom Fields with No Value (Empty)
- Identify Custom Fields Not Updated After a Specific Date (YYY-MM-DD)
- Identify Boards Linked to Non-Existing Filters
- Identify Board Columns Linked to Non-Existing Statuses
- Identify Invalid JQL in Quick Filter
- Identify Personal Data (Email Addresses) in Filters
- Identify Boards Owned by Inactive Users
- Identify Filters Owned by Inactive Users
- Identify Boards Owned by Deleted Users
- Identify Filters Owned by Deleted Users
- Queries for Public Access
- Identify Projects with Any Permission Set to “Anyone”
- Identify Projects with Anonymous Access in Browse Project Permission
- Identify Filters with Share Type “Share with Everyone”
- Identify Agile Boards with Share Type “Share with Everyone”
- Identify Dashboards with Share Type “Share with Everyone”
- Check Public Access Settings in Confluence
Queries for Data Cleanup
Identify Pages Using Nested Macros in Confluence
SELECT c.CONTENTID
, replace(replace(cast(b.BODY AS varchar(max)), char(10),' '), char(13),' ') AS body
FROM BODYCONTENT b
JOIN CONTENT c ON (c.CONTENTID = b.CONTENTID)
JOIN SPACES s ON (s.SPACEID = c.SPACEID)
WHERE c.CONTENTTYPE IN ('PAGE', 'BLOGPOST')
AND c.CONTENT_STATUS = 'current'
AND c.PREVVER IS NULL;
#!/bin/bash
[[ -z "$1" ]] && { echo "Provide input file" ; exit 1 ; } || { input_file=$1 ; }
[ -d output ] && { rm -r output ; }
mkdir output;
output_folder="output"
first_output_sed="$input_file.sed"
second_output_sed="$first_output_sed.final"
output_file="results.csv"
sed -r 's/<ac:structured-macro ac:name="[a-zA-Z0-9\-]+" \/>/foobar/g' $input_file > $first_output_sed
sed -r 's/<ac:structured-macro ac:name="[0-9a-zA-Z\-\_]+" ac:schema-version="[0-9]+" ac:macro-id="[0-9a-zA-Z\-]+" \/>/foobar/g' $first_output_sed > $second_output_sed
for lineno in $(grep -Eon '<ac:structured-macro|</ac:structured-macro' $second_output_sed | uniq -c | sed -r 's/^ {2,}//g' | sort -nr | awk '$1 > 1' | cut -d ":" -f 1 | sort -u | cut -d " " -f 2) ; do sed -n "${lineno}p" $second_output_sed | cut -d ";" -f 1 ; done | sort -u > $output_file
mv $output_file $output_folder
rm -f $first_output_sed $second_output_sed
Identify User Filter Usage
SELECT authorname, COUNT(authorname)
FROM searchrequest
GROUP BY authorname
ORDER BY COUNT(authorname) DESC;
Find Duplicate Email Addresses with Query
SELECT lower_email_address, COUNT(lower_email_address), STRING_AGG(user_name, ', ') AS "Users with Dupe E-Mail"
FROM cwd_user
GROUP BY lower_email_address
HAVING COUNT(lower_email_address) > 1;
SELECT lower_email_address, COUNT(lower_email_address), STRING_AGG(user_name, ', ') AS "Users with Dupe E-Mail"
FROM cwd_user
GROUP BY lower_email_address
HAVING COUNT(lower_email_address) > 1;
Limit Attachment Length to 250 Characters
SELECT id, issueid, LEN(filename)
FROM fileattachment
WHERE LEN(filename) > 250;
Calculate Total Attachment Size in MB
SELECT ROUND(SUM(filesize) / (1024 * 1024), 2) AS "Total Attachment Size(MB)"
FROM fileattachment
JOIN jiraissue ON jiraissue.id = fileattachment.issueid;
Identify Dashboards with High Gadget Count
SELECT pc.portalpage, pp.pagename, pp.username, COUNT(pc.portalpage) AS num_gadgets
FROM portletconfiguration pc
JOIN portalpage pp ON pc.portalpage = pp.ID
GROUP BY pc.portalpage, pp.pagename, pp.username
ORDER BY COUNT(pc.portalpage) DESC;
Find Workflows with References to a Specific Custom Field
SELECT *
FROM jiraworkflows wf
WHERE wf.descriptor LIKE '%customfield_10281%';
Identify Custom Fields with Low Usage
SELECT customfield.id, COUNT(*)
FROM customfield
LEFT JOIN customfieldvalue ON customfield.id = customfieldvalue.customfield
GROUP BY customfield.id
HAVING COUNT(*) < 5
ORDER BY COUNT(*) DESC;
Identify Custom Fields with Global Context
SELECT DISTINCT p.pkey, cf.cfname, cf.id, p.pname
FROM CUSTOMFIELDVALUE cfv
LEFT JOIN JIRAISSUE ji ON cfv.ISSUE = ji.id
RIGHT JOIN CUSTOMFIELD cf ON cf.id = cfv.customfield
LEFT JOIN project p ON p.id = ji.project
WHERE cf.id IN ('<globalContextIdsCommaSeparated>')
GROUP BY cf.id, cf.cfname, p.pkey, p.pname
ORDER BY cf.cfname, p.pname;
Identify Custom Fields with No Value (Empty)
SELECT customfield.id
FROM customfield
LEFT JOIN customfieldvalue ON customfield.id = customfieldvalue.customfield
WHERE customfieldvalue.stringvalue IS NULL
AND customfieldvalue.numbervalue IS NULL
AND customfieldvalue.textvalue IS NULL
ORDER BY customfield.id;
Identify Custom Fields Not Updated After a Specific Date (YYY-MM-DD)
SELECT field.id, field.cfname
FROM customfield field
WHERE field.cfname NOT IN (
SELECT item.field
FROM changeitem item
JOIN changegroup cgroup ON item.groupid = cgroup.id
WHERE item.fieldtype = 'custom'
AND cgroup.created > 'YYYY-MM-DD'
)
AND customfieldtypekey NOT LIKE '%com.pyxis.greenhopper%'
AND customfieldtypekey NOT LIKE '%com.atlassian.servicedesk%'
AND customfieldtypekey NOT LIKE '%com.atlassian.bonfire%';
Identify Boards Linked to Non-Existing Filters
SELECT DISTINCT
rv.ID AS "Board ID"
, rv.NAME AS "Board Name"
, rv.OWNER_USER_NAME AS "Board Owner"
, ba.RAPID_VIEW_ID AS "Foreign Key ID from the Board Admins table"
FROM AO_60DB71_RAPIDVIEW rv
LEFT JOIN searchrequest sr ON (rv.SAVED_FILTER_ID = sr.id)
LEFT JOIN AO_60DB71_BOARDADMINS ba ON (rv.ID = ba.RAPID_VIEW_ID)
WHERE NOT EXISTS ( SELECT *
FROM searchrequest
WHERE id = rv.SAVED_FILTER_ID )
ORDER BY rv.ID;
Identify Board Columns Linked to Non-Existing Statuses
SELECT rv.ID AS "Board ID"
, rv.NAME AS "Board Name"
, c.NAME AS "Column Name"
, i.pname AS "Issue Status Name"
FROM AO_60DB71_RAPIDVIEW rv
JOIN AO_60DB71_COLUMN c ON (rv.ID = c.RAPID_VIEW_ID)
JOIN AO_60DB71_COLUMNSTATUS cs ON (c.ID = cs.COLUMN_ID)
LEFT JOIN issuestatus i ON (i.id = cs.STATUS_ID)
WHERE NOT EXISTS ( SELECT id
FROM issuestatus
WHERE id = cs.STATUS_ID );
Identify Invalid JQL in Quick Filter
SELECT rv.NAME AS "Board Name"
, qf.NAME AS "Quick Filter Name"
FROM AO_60DB71_RAPIDVIEW rv
INNER JOIN AO_60DB71_QUICKFILTER qf ON (rv.ID = qf.RAPID_VIEW_ID)
WHERE qf.LONG_QUERY LIKE '%@%';
Identify Personal Data (Email Addresses) in Filters
SELECT * FROM searchrequest WHERE reqcontent LIKE '%@%';
Identify Boards Owned by Inactive Users
SELECT rv.NAME AS "Board name"
, cwu.lower_user_name AS "Board inactive owner username"
, cwu.lower_email_address AS "Board inactive owner email address"
, cwu.display_name AS "Board inactive owner display name"
, au.user_key AS "Board inactive owner user key"
FROM cwd_user cwu
INNER JOIN app_user au ON (cwu.lower_user_name = au.lower_user_name)
JOIN AO_60DB71_RAPIDVIEW rv ON (rv.OWNER_USER_NAME = cwu.lower_user_name OR rv.OWNER_USER_NAME = au.user_key)
WHERE cwu.active = 0
ORDER BY rv.ID;
Identify Filters Owned by Inactive Users
SELECT DISTINCT sr.filtername AS "Filter name"
, sr.username AS "Filter username"
, sr.authorname AS "Filter author"
, cwu.lower_email_address AS "Filter inactive user email address"
, cwu.display_name AS "Filter inactive user display name"
, au.user_key AS "Filter inactive user user key"
, CASE WHEN cwu.active = 0 THEN 'Inactive' ELSE 'Active' END AS "User status"
, CAST(sr.reqcontent AS nvarchar(max)) AS "Filter JQL"
FROM cwd_user cwu
INNER JOIN app_user au ON (cwu.lower_user_name = au.lower_user_name)
JOIN searchrequest sr ON ( (sr.username = cwu.lower_user_name OR sr.username = au.user_key) OR (sr.authorname = cwu.lower_user_name OR sr.authorname = au.user_key) )
WHERE cwu.active = 0 ;
Identify Boards Owned by Deleted Users
SELECT rv.NAME AS "Board Name"
, u.lower_user_name AS "Deleted Username"
FROM AO_60DB71_RAPIDVIEW rv
JOIN app_user u ON (rv.OWNER_USER_NAME = u.user_key)
LEFT JOIN cwd_user cwu ON (u.lower_user_name = cwu.lower_user_name)
WHERE NOT EXISTS ( SELECT *
FROM cwd_user
WHERE lower_user_name = u.lower_user_name );
Identify Filters Owned by Deleted Users
SELECT sr.filtername AS "Filter name"
, u.lower_user_name AS "Username"
FROM searchrequest sr
JOIN app_user u ON (sr.authorname = u.lower_user_name)
LEFT JOIN cwd_user cwu ON (u.lower_user_name = cwu.lower_user_name)
WHERE NOT EXISTS ( SELECT *
FROM cwd_user
WHERE lower_user_name = u.lower_user_name );
Queries for Public Access
Identify Projects with Any Permission Set to “Anyone”
SELECT p.id, p.pname, ps.name, sp.permission_key
FROM project p
INNER JOIN nodeassociation na ON p.id = na.source_node_id
INNER JOIN schemepermissions sp ON na.sink_node_id = sp.scheme
INNER JOIN permissionscheme ps ON na.sink_node_id = ps.id
WHERE na.source_node_entity = 'Project'
AND na.sink_node_entity = 'PermissionScheme'
AND sp.perm_type = 'group'
AND sp.perm_parameter IS NULL;
Identify Projects with Anonymous Access in Browse Project Permission
SELECT p.id, p.pname, ps.name
FROM project p
INNER JOIN nodeassociation na ON p.id = na.source_node_id
INNER JOIN schemepermissions sp ON na.sink_node_id = sp.scheme
INNER JOIN permissionscheme ps ON na.sink_node_id = ps.id
WHERE na.source_node_entity = 'Project'
AND na.sink_node_entity = 'PermissionScheme'
AND sp.permission_key = 'BROWSE_PROJECTS'
AND sp.perm_type = 'group'
AND sp.perm_parameter IS NULL;
Identify Filters with Share Type “Share with Everyone”
SELECT sr.filtername, sp.sharetype AS current_share_state, sr.username AS owner_name, sr.reqcontent AS JQL
FROM searchrequest sr
INNER JOIN sharepermissions sp ON sp.entityid = sr.id
WHERE sp.sharetype = 'global' AND sp.entitytype = 'SearchRequest';
Identify Agile Boards with Share Type “Share with Everyone”
SELECT DISTINCT rv.NAME AS "Board Name", sr.filtername, sp.sharetype AS current_share_state, sr.username AS owner_name
FROM AO_60DB71_RAPIDVIEW AS rv
INNER JOIN searchrequest sr ON sr.id = rv.SAVED_FILTER_ID
INNER JOIN sharepermissions sp ON sp.entityid = sr.id
WHERE sp.sharetype = 'global' AND sp.entitytype = 'SearchRequest';
Identify Dashboards with Share Type “Share with Everyone”
SELECT DISTINCT pp.id as Dashboard_Id, pp.pagename AS Dashboard_name, sp.sharetype AS current_share_state, pp.username AS owner_name
FROM portalpage pp
INNER JOIN sharepermissions sp ON sp.entityid = pp.id
WHERE sp.sharetype = 'global' AND sp.entitytype = 'PortalPage'
ORDER BY pp.id;
Check Public Access Settings in Confluence
SELECT SPACENAME
FROM SPACES
WHERE SPACEID IN (
SELECT SPACEID
FROM SPACEPERMISSIONS
WHERE PERMTYPE = 'VIEWSPACE'
AND PERMGROUPNAME IS NULL
AND PERMUSERNAME IS NULL
AND PERMALLUSERSSUBJECT IS NULL
);
By addressing these queries and ensuring all necessary preparations are made, you can significantly increase the chances of a successful migration to the Atlassian cloud while minimizing disruptions to your operations.