Delete
diff --git a/app/views/maps/_mapinfobox.html.erb b/app/views/maps/_mapinfobox.html.erb
index 8e6b2dba..9ded6a02 100644
--- a/app/views/maps/_mapinfobox.html.erb
+++ b/app/views/maps/_mapinfobox.html.erb
@@ -9,7 +9,7 @@
<% if current_user %>
- Click here to name this map!
+ Click here to name this map
<% end %>
@@ -77,6 +77,11 @@
Created by: <%= @map.user == user ? "You" : @map.user.name %> on <%= @map.created_at.strftime("%m/%d/%Y") %>
Last edited: <%= @map.updated_at.strftime("%m/%d/%Y") %>
+
+
+
Update Thumbnail
+ Thumb
+
Delete
diff --git a/config/environments/development.rb b/config/environments/development.rb
index 8fef2145..381bca7b 100644
--- a/config/environments/development.rb
+++ b/config/environments/development.rb
@@ -29,4 +29,7 @@ Rails.application.configure do
# Expands the lines which load the assets
config.assets.debug = false
config.assets.quiet = true
+
+ # S3 file storage
+ config.paperclip_defaults = {} # store on local machine for dev
end
diff --git a/config/initializers/mime_types.rb b/config/initializers/mime_types.rb
index 6e1d16f0..2e0132a6 100644
--- a/config/initializers/mime_types.rb
+++ b/config/initializers/mime_types.rb
@@ -3,3 +3,6 @@
# Add new mime types for use in respond_to blocks:
# Mime::Type.register "text/richtext", :rtf
+
+# RDF export
+Mime::Type.register 'text/turtle', :ttl
diff --git a/config/initializers/rack-attack.rb b/config/initializers/rack-attack.rb
deleted file mode 100644
index 0fd76889..00000000
--- a/config/initializers/rack-attack.rb
+++ /dev/null
@@ -1,61 +0,0 @@
-# frozen_string_literal: true
-class Rack::Attack
- Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new
-
- # Throttle all requests by IP (60rpm)
- #
- # Key: "rack::attack:#{Time.now.to_i/:period}:req/ip:#{req.ip}"
- # throttle('req/ip', :limit => 300, :period => 5.minutes) do |req|
- # req.ip # unless req.path.start_with?('/assets')
- # end
-
- # Throttle POST requests to /login by IP address
- #
- # Key: "rack::attack:#{Time.now.to_i/:period}:logins/ip:#{req.ip}"
- throttle('logins/ip', limit: 5, period: 20.seconds) do |req|
- req.ip if req.path == '/login' && req.post?
- end
-
- # Throttle POST requests to /login by email param
- #
- # Key: "rack::attack:#{Time.now.to_i/:period}:logins/email:#{req.email}"
- #
- # Note: This creates a problem where a malicious user could intentionally
- # throttle logins for another user and force their login requests to be
- # denied, but that's not very common and shouldn't happen to you. (Knock
- # on wood!)
- throttle('logins/email', limit: 5, period: 20.seconds) do |req|
- if req.path == '/login' && req.post?
- # return the email if present, nil otherwise
- req.params['email'].presence
- end
- end
-
- throttle('load_url_title/req/5mins/ip', limit: 300, period: 5.minutes) do |req|
- req.ip if req.path == 'hacks/load_url_title'
- end
- throttle('load_url_title/req/1s/ip', limit: 5, period: 1.second) do |req|
- # If the return value is truthy, the cache key for the return value
- # is incremented and compared with the limit. In this case:
- # "rack::attack:#{Time.now.to_i/1.second}:load_url_title/req/ip:#{req.ip}"
- #
- # If falsy, the cache key is neither incremented nor checked.
-
- req.ip if req.path == 'hacks/load_url_title'
- end
-
- self.throttled_response = lambda do |env|
- now = Time.now
- match_data = env['rack.attack.match_data']
- period = match_data[:period]
- limit = match_data[:limit]
-
- headers = {
- 'X-RateLimit-Limit' => limit.to_s,
- 'X-RateLimit-Remaining' => '0',
- 'X-RateLimit-Reset' => (now + (period - now.to_i % period)).to_s
- }
-
- [429, headers, ['']]
- end
-end
diff --git a/config/initializers/rack_attack.rb b/config/initializers/rack_attack.rb
new file mode 100644
index 00000000..b79f0d10
--- /dev/null
+++ b/config/initializers/rack_attack.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+module Rack
+ class Attack
+ Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new
+
+ # Throttle all requests by IP (60rpm)
+ #
+ # Key: "rack::attack:#{Time.now.to_i/:period}:req/ip:#{req.ip}"
+ # throttle('req/ip', :limit => 300, :period => 5.minutes) do |req|
+ # req.ip # unless req.path.start_with?('/assets')
+ # end
+
+ # Throttle POST requests to /login by IP address
+ #
+ # Key: "rack::attack:#{Time.now.to_i/:period}:logins/ip:#{req.ip}"
+ throttle('logins/ip', limit: 5, period: 20.seconds) do |req|
+ req.ip if req.path == '/login' && req.post?
+ end
+
+ # Throttle POST requests to /login by email param
+ #
+ # Key: "rack::attack:#{Time.now.to_i/:period}:logins/email:#{req.email}"
+ #
+ # Note: This creates a problem where a malicious user could intentionally
+ # throttle logins for another user and force their login requests to be
+ # denied, but that's not very common and shouldn't happen to you. (Knock
+ # on wood!)
+ throttle('logins/email', limit: 5, period: 20.seconds) do |req|
+ if req.path == '/login' && req.post?
+ # return the email if present, nil otherwise
+ req.params['email'].presence
+ end
+ end
+
+ throttle('load_url_title/req/5mins/ip', limit: 300, period: 5.minutes) do |req|
+ req.ip if req.path == 'hacks/load_url_title'
+ end
+ throttle('load_url_title/req/1s/ip', limit: 5, period: 1.second) do |req|
+ # If the return value is truthy, the cache key for the return value
+ # is incremented and compared with the limit. In this case:
+ # "rack::attack:#{Time.now.to_i/1.second}:load_url_title/req/ip:#{req.ip}"
+ #
+ # If falsy, the cache key is neither incremented nor checked.
+
+ req.ip if req.path == 'hacks/load_url_title'
+ end
+
+ self.throttled_response = lambda do |env|
+ now = Time.zone.now
+ match_data = env['rack.attack.match_data']
+ period = match_data[:period]
+ limit = match_data[:limit]
+
+ headers = {
+ 'X-RateLimit-Limit' => limit.to_s,
+ 'X-RateLimit-Remaining' => '0',
+ 'X-RateLimit-Reset' => (now + (period - now.to_i % period)).to_s
+ }
+
+ [429, headers, ['']]
+ end
+ end
+end
diff --git a/config/initializers/version.rb b/config/initializers/version.rb
index cccd43bf..a1e20598 100644
--- a/config/initializers/version.rb
+++ b/config/initializers/version.rb
@@ -1,4 +1,4 @@
# frozen_string_literal: true
-METAMAPS_VERSION = '3.0.1'
+METAMAPS_VERSION = '3.2'
METAMAPS_BUILD = `git log -1 --pretty=%H`.chomp[0..11].freeze
METAMAPS_LAST_UPDATED = `git log -1 --pretty='%ad'`.split(' ').values_at(1, 2, 4).join(' ').freeze
diff --git a/config/initializers/warden_hooks.rb b/config/initializers/warden_hooks.rb
index da983955..a0d8fd88 100644
--- a/config/initializers/warden_hooks.rb
+++ b/config/initializers/warden_hooks.rb
@@ -1,9 +1,10 @@
-Warden::Manager.after_set_user do |user,auth,opts|
+# frozen_string_literal: true
+Warden::Manager.after_set_user do |user, auth, opts|
scope = opts[:scope]
auth.cookies.signed["#{scope}.id"] = user.id
auth.cookies.signed["#{scope}.expires_at"] = 30.minutes.from_now
end
-Warden::Manager.before_logout do |user, auth, opts|
+Warden::Manager.before_logout do |_user, auth, opts|
scope = opts[:scope]
auth.cookies.signed["#{scope}.id"] = nil
auth.cookies.signed["#{scope}.expires_at"] = nil
diff --git a/config/routes.rb b/config/routes.rb
index 8fe56fd6..5abf5668 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -14,6 +14,7 @@ Metamaps::Application.routes.draw do
get 'starred'
get 'mapper/:id', action: 'mapper'
end
+ get :explore, to: redirect('/')
resources :maps, except: [:index, :edit] do
member do
diff --git a/db/migrate/20170122201451_create_attachments.rb b/db/migrate/20170122201451_create_attachments.rb
new file mode 100644
index 00000000..a798fcfd
--- /dev/null
+++ b/db/migrate/20170122201451_create_attachments.rb
@@ -0,0 +1,12 @@
+class CreateAttachments < ActiveRecord::Migration[5.0]
+ def change
+ create_table :attachments do |t|
+ t.references :attachable, polymorphic: true
+ t.attachment :file
+ t.timestamps
+ end
+
+ remove_attachment :topics, :image
+ remove_attachment :topics, :audio
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 7d146be5..a14b6715 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20161218183817) do
+ActiveRecord::Schema.define(version: 20170122201451) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -26,6 +26,18 @@ ActiveRecord::Schema.define(version: 20161218183817) do
t.index ["user_id"], name: "index_access_requests_on_user_id", using: :btree
end
+ create_table "attachments", force: :cascade do |t|
+ t.string "attachable_type"
+ t.integer "attachable_id"
+ t.string "file_file_name"
+ t.string "file_content_type"
+ t.integer "file_file_size"
+ t.datetime "file_updated_at"
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.index ["attachable_type", "attachable_id"], name: "index_attachments_on_attachable_type_and_attachable_id", using: :btree
+ end
+
create_table "delayed_jobs", force: :cascade do |t|
t.integer "priority", default: 0, null: false
t.integer "attempts", default: 0, null: false
@@ -269,17 +281,9 @@ ActiveRecord::Schema.define(version: 20161218183817) do
t.text "link"
t.integer "user_id"
t.integer "metacode_id"
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
t.text "permission"
- t.string "image_file_name", limit: 255
- t.string "image_content_type", limit: 255
- t.integer "image_file_size"
- t.datetime "image_updated_at"
- t.string "audio_file_name", limit: 255
- t.string "audio_content_type", limit: 255
- t.integer "audio_file_size"
- t.datetime "audio_updated_at"
t.integer "defer_to_map_id"
t.index ["metacode_id"], name: "index_topics_on_metacode_id", using: :btree
t.index ["user_id"], name: "index_topics_on_user_id", using: :btree
diff --git a/db/seeds.rb b/db/seeds.rb
index 9f586b9d..f919629f 100644
--- a/db/seeds.rb
+++ b/db/seeds.rb
@@ -38,7 +38,7 @@ Metacode.create(name: 'Process',
manual_icon: 'https://s3.amazonaws.com/metamaps-assets/metacodes/blueprint/96px/bp_process.png',
color: '#BDB25E')
-Metacode.create(name: 'Future',
+Metacode.create(name: 'Future Dev',
manual_icon: 'https://s3.amazonaws.com/metamaps-assets/metacodes/blueprint/96px/bp_futuredev.png',
color: '#25A17F')
@@ -70,7 +70,7 @@ Metacode.create(name: 'Need',
manual_icon: 'https://s3.amazonaws.com/metamaps-assets/metacodes/blueprint/96px/bp_need.png',
color: '#D2A7D4')
-Metacode.create(name: 'Open',
+Metacode.create(name: 'Open Issue',
manual_icon: 'https://s3.amazonaws.com/metamaps-assets/metacodes/blueprint/96px/bp_openissue.png',
color: '#9BBF71')
@@ -142,7 +142,7 @@ Metacode.create(name: 'Aim',
manual_icon: 'https://s3.amazonaws.com/metamaps-assets/metacodes/generics/96px/gen_aim.png',
color: '#B0B0B0')
-Metacode.create(name: 'Good',
+Metacode.create(name: 'Good Practice',
manual_icon: 'https://s3.amazonaws.com/metamaps-assets/metacodes/generics/96px/gen_goodpractice.png',
color: '#BD9E86')
@@ -198,6 +198,10 @@ Metacode.create(name: 'Status',
manual_icon: 'https://s3.amazonaws.com/metamaps-assets/metacodes/generics/96px/gen_status.png',
color: '#EFA7C0')
+Metacode.create(name: 'Story',
+ manual_icon: 'https://s3.amazonaws.com/metamaps-assets/metacodes/generics/96px/gen_story.png',
+ color: '#A7A2DC')
+
Metacode.create(name: 'Tool',
manual_icon: 'https://s3.amazonaws.com/metamaps-assets/metacodes/generics/96px/gen_tool.png',
color: '#828282')
diff --git a/doc/api/traits/searchable.raml b/doc/api/traits/searchable.raml
index fb7700a9..83cfbfb4 100644
--- a/doc/api/traits/searchable.raml
+++ b/doc/api/traits/searchable.raml
@@ -4,3 +4,8 @@ queryParameters:
Search text columns for this string. A query of "example" will be passed to SQL as LIKE %example%. The searchable columns are:
<< searchFields >>
required: false
type: string
+ searchfields:
+ description: |
+ A comma-seperated list of columns to search. For instance, to search a topic's name and description (but not link field) for the string "cognition", you could use `?q=cognition&searchfields=name,desc`.
+ required: false
+ type: string
diff --git a/doc/production/first-deploy.md b/doc/production/first-deploy.md
index 994e68c2..44b6b659 100644
--- a/doc/production/first-deploy.md
+++ b/doc/production/first-deploy.md
@@ -14,12 +14,28 @@
#### Setup Postgres
- sudo apt-get install postgresql-9.4 #specify version!!
+ sudo apt-get install postgresql-9.4
+ # make sure you have development headers for postgres. The package name might be different on your distribution.
+ sudo apt-get install libpq-dev
sudo -u postgres psql
postgres=# CREATE USER metamaps WITH PASSWORD 'mycoolpassword' CREATEDB;
- postgres=# CREATE DATABASE metamap002_production OWNER metamaps;
+ postgres=# CREATE DATABASE metamaps_production OWNER metamaps;
postgres=# \q
+On some deploys, we have had problems with unicode encoding when trying to run `db:setup`. Running the commands in this Github gist resolved the issue: https://gist.github.com/amolkhanorkar/8706915. Try this link if you have problems
+
+#### Install Node for javascript building
+
+ # this first line lets us use up-to-date versions of node.js
+ # instead of the old versions in the Ubuntu repositories
+ curl -sL https://deb.nodesource.com/setup_7.x | sudo -E bash -
+ sudo apt-get install nodejs
+ sudo ln -s /usr/bin/nodejs /usr/bin/node
+
+### Install redis server for action cable
+
+ sudo apt-get install redis-server
+
#### Install system-wide rvm:
sudo gpg --keyserver hkp://keys.gnupg.net \
@@ -38,8 +54,15 @@
rvm user gemsets
git clone https://github.com/metamaps/metamaps \
--branch instance/mycoolinstance
- rvm install $(cat metamaps/.ruby-version) #ensure ruby is installed
- cd metamaps
+ cat metamaps/.ruby-version
+
+The last line tells you what version of ruby you need to install. For example, at the time of writing the version is 2.3.0. As your normal sudo-enabled user, run
+
+ sudo rvm install 2.3.0
+
+Now switch back to the metamaps user and continue
+
+ cd /home/metamaps/metamaps
gem install bundler
RAILS_ENV=production bundle install
@@ -60,16 +83,11 @@ Run this in the metamaps directory, still as metamaps:
# create, load schema, seed
bundle exec rails db:setup
-#### Install node & ES6 modules
-
- sudo aptitude install nodejs npm
- sudo ln -s /usr/bin/nodejs /usr/bin/node
- npm install
-
#### Precompile assets
-This step depends on running npm install first; assets:precompile calls `npm install` and `bin/build-apidocs.sh`, both of which require node_modules to be installed. We suggest you run the commands separately this time to better catch any errors.
+Note that `rails assets:precompile` will normally call `npm install` and `bin/build-apidocs.sh` as part of its process. Both of these latter commands require `npm install` to be run first. We suggest you run all five commands separately this time (like below) to better catch any errors. In the future, you won't need to run the second and third commands separately.
+ npm install
npm run build
bin/build-apidocs.sh
bundle exec rails assets:precompile
@@ -93,17 +111,18 @@ server to see what problems show up:
#### Realtime server:
sudo npm install -g forever
- (crontab -u metamaps -l 2>/dev/null; echo "@reboot env NODE_REALTIME_PORT=5000 $(which forever) --append -l /home/metamaps/logs/forever.realtime.log start /home/metamaps/metamaps/realtime/realtime-server.js") | crontab -u metamaps -
+ (sudo crontab -u metamaps -l 2>/dev/null; echo "@reboot NODE_REALTIME_PORT=5000 /usr/bin/forever --minUptime 1000 --spinSleepTime 1000 --append -l /home/metamaps/logs/forever.realtime.log -c /home/metamaps/metamaps/node_modules/.bin/babel-node --workingDir /home/metamaps/metamaps start /home/metamaps/metamaps/realtime/realtime-server.js") | sudo crontab -u metamaps
mkdir -p /home/metamaps/logs
- env NODE_REALTIME_PORT=5000 forever --append \
+ /usr/bin/forever --minUptime 1000 --spinSleepTime 1000 \
+ --append -l /home/metamaps/logs/forever.realtime.log \
-c /home/metamaps/metamaps/node_modules/.bin/babel-node \
- -l /home/metamaps/logs/forever.realtime.log \
+ --workingDir /home/metamaps/metamaps \
start /home/metamaps/metamaps/realtime/realtime-server.js
#### Upstart service for delayed_worker:
-Put the following code into `/etc/init/metamaps_delayed_worker.conf`:
+If your system uses upstart for init scripts, put the following code into `/etc/init/metamaps_delayed_job.conf`:
description "Delayed Jobs Worker for Metamaps"
@@ -128,3 +147,30 @@ Then start the service and check the last ten lines of the log file to make sure
sudo service metamaps_delayed_job start
tail /var/log/upstart/metamaps_delayed_job.log
+
+#### Systemd service for delayed_worker:
+
+If your system uses systemd for init scripts, ptu the following code into `/etc/systemd/system/metamaps_delayed_job.service`:
+
+ [Unit]
+ Description=metamaps delayed job service
+ After=network-online.target
+
+ [Service]
+ ExecStart=/usr/local/rvm/gems/ruby-2.3.0@metamaps/bin/bundle exec rails jobs:work
+ WorkingDirectory=/home/metamaps/metamaps
+ Restart=always
+ User=metamaps
+ Group=metamaps
+ Environment=HOME=/home/metamaps
+ Environment=PATH="/usr/local/rvm/gems/ruby-2.3.0@metamaps/bin:/usr/local/rvm/gems/ruby-2.3.0@global/bin:/usr/local/rvm/rubies/ruby-2.3.0/bin:/usr/local/rvm/bin:/usr/local/bin:/usr/bin:/bin"
+ Environment=GEM_PATH="/usr/local/rvm/gems/ruby-2.3.0@metamaps:/usr/local/rvm/gems/ruby-2.3.0@global"
+ Environment=RAILS_ENV="production"
+
+ [Install]
+ WantedBy=multi-user.target
+
+Then start the service and check the last ten lines of the log file to make sure it's running OK:
+
+ sudo systemctl start metamaps_delayed_job
+ # ??? how the heck do you check systemd logs??
diff --git a/frontend/src/Metamaps/Cable.js b/frontend/src/Metamaps/Cable.js
index ffeb9ffb..1fcc441e 100644
--- a/frontend/src/Metamaps/Cable.js
+++ b/frontend/src/Metamaps/Cable.js
@@ -1,5 +1,7 @@
/* global $, ActionCable */
+import { indexOf } from 'lodash'
+
import Active from './Active'
import Control from './Control'
import Create from './Create'
@@ -110,7 +112,7 @@ const Cable = {
if (edge.getData('mappings').length - 1 === 0) {
Control.hideEdge(edge)
}
-
+
var index = indexOf(edge.getData('synapses'), synapse)
edge.getData('mappings').splice(index, 1)
edge.getData('synapses').splice(index, 1)
@@ -128,19 +130,20 @@ const Cable = {
// containing only the information we need to determine whether the active mapper
// can view this topic, then if we determine it can, we make a call for the full model
const t = new DataModel.Topic(event.topic)
-
- // refactor the heck outta this, its adding wicked wait time
- var topic, mapping, mapper, cancel
- function waitThenRenderTopic() {
- if (topic && mapping && mapper) {
- Topic.renderTopic(mapping, topic, true)
- Engine.runLayout()
- } else if (!cancel) {
- setTimeout(waitThenRenderTopic, 10)
- }
- }
if (t.authorizeToShow(m) && !DataModel.Topics.get(event.topic.id)) {
+ // refactor the heck outta this, its adding wicked wait time
+ var topic, mapping, mapper, cancel
+
+ const waitThenRenderTopic = () => {
+ if (topic && mapping && mapper) {
+ Topic.renderTopic(mapping, topic, true)
+ Engine.runLayout()
+ } else if (!cancel) {
+ setTimeout(waitThenRenderTopic, 10)
+ }
+ }
+
mapper = DataModel.Mappers.get(event.topic.user_id)
if (mapper === undefined) {
Mapper.get(event.topic.user_id, function(m) {
diff --git a/frontend/src/Metamaps/Control.js b/frontend/src/Metamaps/Control.js
index 4caa5ccc..e5c0c92d 100644
--- a/frontend/src/Metamaps/Control.js
+++ b/frontend/src/Metamaps/Control.js
@@ -1,5 +1,3 @@
-/* global $ */
-
import _ from 'lodash'
import outdent from 'outdent'
@@ -8,7 +6,6 @@ import DataModel from './DataModel'
import Engine from './Engine'
import Filter from './Filter'
import GlobalUI from './GlobalUI'
-import JIT from './JIT'
import Mouse from './Mouse'
import Selected from './Selected'
import Settings from './Settings'
@@ -99,7 +96,6 @@ const Control = {
var permToDelete = Active.Mapper.id === topic.get('user_id') || Active.Mapper.get('admin')
if (permToDelete) {
- var mappableid = topic.id
var mapping = node.getData('mapping')
topic.destroy()
DataModel.Mappings.remove(mapping)
@@ -149,7 +145,6 @@ const Control = {
}
var topic = node.getData('topic')
- var mappableid = topic.id
var mapping = node.getData('mapping')
mapping.destroy()
DataModel.Topics.remove(topic)
@@ -268,7 +263,6 @@ const Control = {
if (edge.getData('synapses').length - 1 === 0) {
Control.hideEdge(edge)
}
- var mappableid = synapse.id
synapse.destroy()
// the server will destroy the mapping, we just need to remove it here
@@ -319,7 +313,6 @@ const Control = {
var synapse = edge.getData('synapses')[index]
var mapping = edge.getData('mappings')[index]
- var mappableid = synapse.id
mapping.destroy()
DataModel.Synapses.remove(synapse)
diff --git a/frontend/src/Metamaps/Create.js b/frontend/src/Metamaps/Create.js
index 4e210c41..3b4c6e34 100644
--- a/frontend/src/Metamaps/Create.js
+++ b/frontend/src/Metamaps/Create.js
@@ -335,18 +335,18 @@ console.log(codesToSwitchToIds)
},
source: synapseBloodhound
},
- {
- name: 'existing_synapses',
- limit: 50,
- display: function(s) { return s.label },
- templates: {
- suggestion: function(s) {
- return Hogan.compile($('#synapseAutocompleteTemplate').html()).render(s)
- },
- header: '
- You can import topics and synapses by uploading a spreadsheet here.
- The file should be in comma-separated format (when you save, change the
- filetype from .xls to .csv).
-
-
-
You can choose which columns to include in your data. Topics must have a name field. Synapses must have Topic 1 and Topic 2.
-
-
* There are many valid import formats. Try exporting a map to see what columns you can include in your import data. You can also copy-paste from Excel to import, or import JSON.
-
* If you are importing a list of links, you can use a Link column in place of the Name column.